summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Styk <mastyk@redhat.com>2019-04-11 16:15:49 +0200
committerMartin Styk <mastyk@redhat.com>2019-04-30 10:07:17 +0000
commit3677fd33166e6ed3eb886d4bd6df3b4008f88bbb (patch)
tree661e08457227b164ffa27ee1723a47ae91a556a9
parentc4a029887858f4970cb287c2550972f05896a5a8 (diff)
Enable Python 3 in Beaker
Intention of this patch is to provide beaker packages in Python 3 OS distributions (e.g. RHEL 8). Currently, we have to provide beaker-common and beaker-client package in python 3 variant. Following things we're done to support this: 1. SPEC file updated to build only client and common RPM for F30+ and RHEL8+. 2. Updated Makefiles to be able use Python 2 and Python 3 3. Updated setup.py files to be able use Python 2 and Python 3 4. Updated common + client code base with syntax supported by Python 2 and Python 3 5. Clean up of unused imports/variables/files 6. Created XMLRPC Python 3 implementation - Common package Change-Id: I8ae3ad8b2317fe7ab787b164cb1120d8982956a3 Signed-off-by: Martin Styk <mastyk@redhat.com>
-rw-r--r--Client/Makefile13
-rwxr-xr-xClient/run-tests.sh11
-rw-r--r--Client/setup.py38
-rw-r--r--Client/src/bkr/client/__init__.py401
-rw-r--r--Client/src/bkr/client/command.py161
-rw-r--r--Client/src/bkr/client/commands/cmd_distro_trees_list.py37
-rw-r--r--Client/src/bkr/client/commands/cmd_distro_trees_verify.py17
-rw-r--r--Client/src/bkr/client/commands/cmd_distros_edit_version.py18
-rw-r--r--Client/src/bkr/client/commands/cmd_distros_list.py28
-rw-r--r--Client/src/bkr/client/commands/cmd_distros_tag.py17
-rw-r--r--Client/src/bkr/client/commands/cmd_distros_untag.py17
-rw-r--r--Client/src/bkr/client/commands/cmd_group_create.py7
-rw-r--r--Client/src/bkr/client/commands/cmd_group_list.py13
-rw-r--r--Client/src/bkr/client/commands/cmd_group_members.py20
-rw-r--r--Client/src/bkr/client/commands/cmd_group_modify.py18
-rw-r--r--Client/src/bkr/client/commands/cmd_harness_test.py54
-rw-r--r--Client/src/bkr/client/commands/cmd_job_cancel.py8
-rw-r--r--Client/src/bkr/client/commands/cmd_job_clone.py48
-rw-r--r--Client/src/bkr/client/commands/cmd_job_comment.py6
-rw-r--r--Client/src/bkr/client/commands/cmd_job_delete.py41
-rw-r--r--Client/src/bkr/client/commands/cmd_job_list.py55
-rw-r--r--Client/src/bkr/client/commands/cmd_job_logs.py16
-rw-r--r--Client/src/bkr/client/commands/cmd_job_modify.py46
-rw-r--r--Client/src/bkr/client/commands/cmd_job_results.py30
-rw-r--r--Client/src/bkr/client/commands/cmd_job_submit.py56
-rw-r--r--Client/src/bkr/client/commands/cmd_job_watch.py8
-rw-r--r--Client/src/bkr/client/commands/cmd_labcontroller_create.py5
-rw-r--r--Client/src/bkr/client/commands/cmd_labcontroller_list.py13
-rw-r--r--Client/src/bkr/client/commands/cmd_labcontroller_modify.py12
-rw-r--r--Client/src/bkr/client/commands/cmd_loan_grant.py10
-rw-r--r--Client/src/bkr/client/commands/cmd_loan_return.py10
-rw-r--r--Client/src/bkr/client/commands/cmd_machine_test.py57
-rw-r--r--Client/src/bkr/client/commands/cmd_policy_grant.py49
-rw-r--r--Client/src/bkr/client/commands/cmd_policy_list.py24
-rw-r--r--Client/src/bkr/client/commands/cmd_policy_revoke.py49
-rw-r--r--Client/src/bkr/client/commands/cmd_pool_add.py11
-rw-r--r--Client/src/bkr/client/commands/cmd_pool_create.py6
-rw-r--r--Client/src/bkr/client/commands/cmd_pool_delete.py10
-rw-r--r--Client/src/bkr/client/commands/cmd_pool_list.py18
-rw-r--r--Client/src/bkr/client/commands/cmd_pool_modify.py5
-rw-r--r--Client/src/bkr/client/commands/cmd_pool_remove.py10
-rw-r--r--Client/src/bkr/client/commands/cmd_pool_systems.py12
-rw-r--r--Client/src/bkr/client/commands/cmd_remove_account.py6
-rw-r--r--Client/src/bkr/client/commands/cmd_system_create.py57
-rw-r--r--Client/src/bkr/client/commands/cmd_system_delete.py9
-rw-r--r--Client/src/bkr/client/commands/cmd_system_details.py16
-rw-r--r--Client/src/bkr/client/commands/cmd_system_list.py93
-rw-r--r--Client/src/bkr/client/commands/cmd_system_modify.py26
-rw-r--r--Client/src/bkr/client/commands/cmd_system_power.py21
-rw-r--r--Client/src/bkr/client/commands/cmd_system_provision.py27
-rw-r--r--Client/src/bkr/client/commands/cmd_system_release.py10
-rw-r--r--Client/src/bkr/client/commands/cmd_system_reserve.py5
-rw-r--r--Client/src/bkr/client/commands/cmd_system_status.py20
-rw-r--r--Client/src/bkr/client/commands/cmd_task_add.py25
-rw-r--r--Client/src/bkr/client/commands/cmd_task_details.py18
-rw-r--r--Client/src/bkr/client/commands/cmd_task_list.py25
-rw-r--r--Client/src/bkr/client/commands/cmd_update_inventory.py24
-rw-r--r--Client/src/bkr/client/commands/cmd_update_prefs.py6
-rw-r--r--Client/src/bkr/client/commands/cmd_user_modify.py18
-rw-r--r--Client/src/bkr/client/commands/cmd_watchdog_extend.py17
-rw-r--r--Client/src/bkr/client/commands/cmd_watchdog_show.py9
-rw-r--r--Client/src/bkr/client/commands/cmd_watchdogs_extend.py11
-rw-r--r--Client/src/bkr/client/commands/cmd_whoami.py6
-rw-r--r--Client/src/bkr/client/commands/cmd_workflow_installer_test.py119
-rw-r--r--Client/src/bkr/client/commands/cmd_workflow_simple.py85
-rw-r--r--Client/src/bkr/client/commands/cmd_workflow_xslt.py688
-rw-r--r--Client/src/bkr/client/convert.py105
-rwxr-xr-xClient/src/bkr/client/main.py76
-rw-r--r--Client/src/bkr/client/task_watcher.py48
-rw-r--r--Client/src/bkr/client/tests/test_wizard.py28
-rwxr-xr-xClient/src/bkr/client/wizard.py254
-rw-r--r--Common/Makefile13
-rw-r--r--Common/bkr/common/helpers.py48
-rw-r--r--Common/bkr/common/hub.py33
-rw-r--r--Common/bkr/common/pyconfig.py95
-rw-r--r--Common/bkr/common/test_xmlrpc.py44
-rw-r--r--Common/bkr/common/xmlrpc2.py (renamed from Common/bkr/common/xmlrpc.py)261
-rw-r--r--Common/bkr/common/xmlrpc3.py489
-rw-r--r--Common/bkr/log.py5
-rwxr-xr-xCommon/bkr/timeout_xmlrpclib.py42
-rwxr-xr-xCommon/run-tests.sh11
-rw-r--r--Common/setup.py8
-rw-r--r--IntegrationTests/src/bkr/inttest/server/selenium/__init__.py14
-rw-r--r--LabController/src/bkr/labcontroller/proxy.py9
-rw-r--r--Makefile7
-rw-r--r--beaker.spec138
-rw-r--r--documentation/Makefile22
-rw-r--r--documentation/conf.py34
88 files changed, 2869 insertions, 1741 deletions
diff --git a/Client/Makefile b/Client/Makefile
index 2e319f2..557d145 100644
--- a/Client/Makefile
+++ b/Client/Makefile
@@ -4,18 +4,25 @@
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
+# Use Python 2 if BKR_PY3 is not defined
+ifeq ($(BKR_PY3),)
+ BKR_PY3 :=0
+endif
+
+COMMAND:= $(shell if [[ $(BKR_PY3) == 0 ]]; then echo "python2"; else echo "python3"; fi)
+
.PHONY: build
build:
env PYTHONPATH=../Common:src:$${PYTHONPATH:+:$$PYTHONPATH} \
- python2 setup.py build
+ $(COMMAND) setup.py build
.PHONY: install
install:
- python2 setup.py install -O1 --skip-build --root $(DESTDIR)
+ $(COMMAND) setup.py install -O1 --skip-build --root $(DESTDIR)
.PHONY: clean
clean:
- python2 setup.py clean
+ $(COMMAND) setup.py clean
rm -rf build
.PHONY: check
diff --git a/Client/run-tests.sh b/Client/run-tests.sh
index b381ef4..28eaf64 100755
--- a/Client/run-tests.sh
+++ b/Client/run-tests.sh
@@ -2,5 +2,14 @@
set -x
+# Use Python 2 version if BKR_PY3 is not defined
+if [[ -z ${BKR_PY3} ]]; then
+ nose_command="nosetests";
+elif [[ ${BKR_PY3} == 1 ]]; then
+ nose_command="nosetests-3";
+else
+ nose_command="nosetests";
+fi
+
env PYTHONPATH=../Client/src:../Common${PYTHONPATH:+:$PYTHONPATH} \
- nosetests ${*:--v --traverse-namespace bkr.client.tests}
+ $nose_command ${*:--v --traverse-namespace bkr.client.tests}
diff --git a/Client/setup.py b/Client/setup.py
index 1f1385e..7b1abec 100644
--- a/Client/setup.py
+++ b/Client/setup.py
@@ -1,16 +1,25 @@
-import sys
-import commands
-from glob import glob
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
from setuptools import setup, find_packages
-from distutils.command.build import build as _build
+
+try:
+ from commands import getstatusoutput
+except ImportError:
+ from subprocess import getstatusoutput
+
def bash_completion_dir():
- status, output = commands.getstatusoutput('pkg-config --variable completionsdir bash-completion')
+ status, output = getstatusoutput(
+ 'pkg-config --variable completionsdir bash-completion')
if status or not output:
return '/etc/bash_completion.d'
return output.strip()
+
setup(
name='beaker-client',
version='26.4',
@@ -22,32 +31,35 @@ setup(
install_requires=[
'beaker-common',
'lxml',
+ 'six',
'requests',
'PrettyTable',
'Jinja2',
],
packages=find_packages('src'),
- package_dir = {'':'src'},
+ package_dir={'': 'src'},
- package_data = {
+ package_data={
'bkr.client': ['host-filters/*']
- },
- namespace_packages = ['bkr'],
+ },
+ namespace_packages=['bkr'],
- data_files = [
+ data_files=[
('/etc/beaker', []),
(bash_completion_dir(), ['bash-completion/bkr']),
],
- classifiers = [
+ classifiers=[
'Development Status :: 5 - Production/Stable',
'Operating System :: POSIX :: Linux',
- 'Programming Language :: Python :: 2.6',
+ 'Programming Language :: Python :: 2.7',
+ 'Programming Language :: Python :: 3.6',
+ 'Programming Language :: Python :: 3.7',
'License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)',
],
- entry_points = {
+ entry_points={
'console_scripts': (
'bkr = bkr.client.main:main',
'beaker-wizard = bkr.client.wizard:main',
diff --git a/Client/src/bkr/client/__init__.py b/Client/src/bkr/client/__init__.py
index 6eb4397..95e1910 100644
--- a/Client/src/bkr/client/__init__.py
+++ b/Client/src/bkr/client/__init__.py
@@ -5,16 +5,20 @@
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
+import glob
import json
-import xml.dom.minidom
-import re, errno, sys, os
-from urlparse import urljoin
import optparse
+import os
+import re
+import sys
+import xml.dom.minidom
from optparse import OptionGroup
+
+import pkg_resources
+from six.moves.urllib_parse import urljoin
+
from bkr.client.command import Command
from bkr.common.pyconfig import PyConfigParser
-import glob
-import pkg_resources
user_config_file = os.environ.get("BEAKER_CLIENT_CONF", None)
if not user_config_file:
@@ -24,7 +28,8 @@ if not user_config_file:
user_config_file = user_conf
elif os.path.exists(old_conf):
user_config_file = old_conf
- sys.stderr.write("%s is deprecated for config, please use %s instead\n" % (old_conf, user_conf))
+ sys.stderr.write(
+ "%s is deprecated for config, please use %s instead\n" % (old_conf, user_conf))
else:
pass
@@ -38,17 +43,18 @@ if system_config_file:
if user_config_file:
conf.load_from_file(user_config_file)
-
_host_filter_presets = None
+
+
def host_filter_presets():
global _host_filter_presets
if _host_filter_presets is not None:
return _host_filter_presets
_host_filter_presets = {}
- config_files = \
- sorted(glob.glob(pkg_resources.resource_filename('bkr.client', 'host-filters/*.conf'))) + \
- sorted(glob.glob('/etc/beaker/host-filters/*.conf'))
+ config_files = (
+ sorted(glob.glob(pkg_resources.resource_filename('bkr.client', 'host-filters/*.conf')))
+ + sorted(glob.glob('/etc/beaker/host-filters/*.conf')))
user_config_file = os.path.expanduser('~/.beaker_client/host-filter')
if os.path.exists(user_config_file):
config_files.append(user_config_file)
@@ -64,6 +70,7 @@ def host_filter_presets():
raise SystemExit(1)
return _host_filter_presets
+
class BeakerCommand(Command):
enabled = False
requires_login = True
@@ -75,7 +82,7 @@ class BeakerCommand(Command):
self.conf['SSL_VERIFY'] = False
proxy_user = kwargs.get('proxy_user')
self.container.set_hub(username, password, auto_login=self.requires_login,
- proxy_user=proxy_user)
+ proxy_user=proxy_user)
def requests_session(self):
try:
@@ -91,17 +98,21 @@ class BeakerCommand(Command):
ssl_verify = self.conf.get('SSL_VERIFY', True)
# HUB_URL will have no trailing slash in the config
base_url = self.conf['HUB_URL'] + '/'
+
class BeakerClientRequestsSession(requests.Session):
"""
Custom requests.Session class with a few conveniences for bkr client code.
"""
+
def __init__(self):
- super(BeakerClientRequestsSession, self).__init__() #pylint: disable=bad-super-call
+ super(BeakerClientRequestsSession,
+ self).__init__() # pylint: disable=bad-super-call
self.cookies = cookies
if not ssl_verify:
self.verify = False
elif ca_cert:
self.verify = ca_cert
+
def request(self, method, url, **kwargs):
# callers can pass in a relative URL and we will figure it out for them
url = urljoin(base_url, url)
@@ -109,44 +120,51 @@ class BeakerCommand(Command):
if 'json' in kwargs:
kwargs['data'] = json.dumps(kwargs.pop('json'))
kwargs.setdefault('headers', {}).update({'Content-Type': 'application/json'})
- return super(BeakerClientRequestsSession, self).request(method, url, **kwargs) #pylint: disable=bad-super-call
+ return super(BeakerClientRequestsSession, self).request(
+ method, url, **kwargs) # pylint: disable=bad-super-call
+
return BeakerClientRequestsSession()
- t_id_types = dict(T = 'RecipeTask',
- TR = 'RecipeTaskResult',
- R = 'Recipe',
- RS = 'RecipeSet',
- J = 'Job')
+ t_id_types = dict(T='RecipeTask',
+ TR='RecipeTaskResult',
+ R='Recipe',
+ RS='RecipeSet',
+ J='Job')
def check_taskspec_args(self, args, permitted_types=None):
- # The server is the one that actually parses these, but we can check
- # a few things on the client side first to catch errors early and give
+ # The server is the one that actually parses these, but we can check
+ # a few things on the client side first to catch errors early and give
# the user better error messages.
for task in args:
if ':' not in task:
self.parser.error('Invalid taskspec %r: '
- 'see "Specifying tasks" in bkr(1)' % task)
+ 'see "Specifying tasks" in bkr(1)' % task)
type, id = task.split(':', 1)
if type not in self.t_id_types:
self.parser.error('Unrecognised type %s in taskspec %r: '
- 'see "Specifying tasks" in bkr(1)' % (type, task))
+ 'see "Specifying tasks" in bkr(1)' % (type, task))
if permitted_types is not None and type not in permitted_types:
self.parser.error('Taskspec type must be one of [%s]'
- % ', '.join(permitted_types))
+ % ', '.join(permitted_types))
+
def prettyxml(option, opt_str, value, parser):
# prettyxml implies debug as well.
parser.values.prettyxml = True
parser.values.debug = True
-def generateKickstart(template_file):
- abs_path = os.path.abspath(template_file)
+
+def generate_kickstart(template_file):
+ abs_path = os.path.abspath(template_file)
return open(abs_path, 'r').read()
-def generateKernelOptions(template_file):
- abs_path = os.path.abspath(template_file)
+generateKickstart = generate_kickstart
+
+
+def generate_kernel_options(template_file):
+ abs_path = os.path.abspath(template_file)
lines = []
# look for ## kernel_options: line
@@ -159,6 +177,10 @@ def generateKernelOptions(template_file):
return " ".join(lines)
+
+generateKernelOptions = generate_kernel_options
+
+
class BeakerWorkflow(BeakerCommand):
doc = xml.dom.minidom.Document()
@@ -184,7 +206,7 @@ class BeakerWorkflow(BeakerCommand):
)
self.parser.add_option(
"--pretty-xml", "--prettyxml",
- action="callback",
+ action="callback",
callback=prettyxml,
default=False,
help="Pretty-print generated job XML with indentation",
@@ -210,7 +232,7 @@ class BeakerWorkflow(BeakerCommand):
)
distro_options = OptionGroup(self.parser,
- 'Options for selecting distro tree(s)')
+ 'Options for selecting distro tree(s)')
distro_options.add_option(
"--family",
help="Use latest distro of this family for job",
@@ -239,7 +261,7 @@ class BeakerWorkflow(BeakerCommand):
self.parser.add_option_group(distro_options)
system_options = OptionGroup(self.parser,
- 'Options for selecting system(s)')
+ 'Options for selecting system(s)')
system_options.add_option(
"--machine", metavar="FQDN",
help="Require this machine for job",
@@ -332,18 +354,18 @@ class BeakerWorkflow(BeakerCommand):
)
# for compat only
task_options.add_option("--type", action="append",
- help=optparse.SUPPRESS_HELP)
+ help=optparse.SUPPRESS_HELP)
self.parser.add_option_group(task_options)
job_options = OptionGroup(self.parser, 'Options for job configuration')
job_options.add_option(
- '--job-owner', metavar='USERNAME',
- help='Submit job on behalf of USERNAME '
- '(submitting user must be a submission delegate for job owner)',
+ '--job-owner', metavar='USERNAME',
+ help='Submit job on behalf of USERNAME '
+ '(submitting user must be a submission delegate for job owner)',
)
job_options.add_option(
- "--job-group", metavar='GROUPNAME',
- help="Associate a group to this job"
+ "--job-group", metavar='GROUPNAME',
+ help="Associate a group to this job"
)
job_options.add_option(
"--whiteboard",
@@ -443,9 +465,9 @@ class BeakerWorkflow(BeakerCommand):
)
# for compat only
installation_options.add_option("--kernel_options",
- help=optparse.SUPPRESS_HELP)
+ help=optparse.SUPPRESS_HELP)
installation_options.add_option("--kernel_options_post",
- help=optparse.SUPPRESS_HELP)
+ help=optparse.SUPPRESS_HELP)
self.parser.add_option_group(installation_options)
multihost_options = OptionGroup(self.parser, 'Options for multi-host testing')
@@ -463,17 +485,17 @@ class BeakerWorkflow(BeakerCommand):
)
self.parser.add_option_group(multihost_options)
- def getArches(self, *args, **kwargs):
+ def get_arches(self, *args, **kwargs):
"""
- Get all arches that apply to either this distro, or the distro which
+ Get all arches that apply to either this distro, or the distro which
will be selected by the given family and tag.
"""
- distro = kwargs.get("distro", None)
- family = kwargs.get("family", None)
- tags = kwargs.get("tag", None)
+ distro = kwargs.get("distro", None)
+ family = kwargs.get("family", None)
+ tags = kwargs.get("tag", None)
- if not hasattr(self,'hub'):
+ if not hasattr(self, 'hub'):
self.set_hub(**kwargs)
if distro:
@@ -481,35 +503,53 @@ class BeakerWorkflow(BeakerCommand):
else:
return self.hub.distros.get_arch(dict(osmajor=family, tags=tags))
- def getOsMajors(self, *args, **kwargs):
- """ Get all OsMajors, optionally filter by tag """
+ getArches = get_arches
+
+ def get_os_majors(self, *args, **kwargs):
+ """
+ Get all OsMajors, optionally filter by tag
+ """
+
tags = kwargs.get("tag", [])
- if not hasattr(self,'hub'):
+ if not hasattr(self, 'hub'):
self.set_hub(**kwargs)
return self.hub.distros.get_osmajors(tags)
- def getSystemOsMajorArches(self, *args, **kwargs):
- """ Get all OsMajors/arches that apply to this system, optionally filter by tag """
+ getOsMajors = get_os_majors
+
+ def get_system_os_major_arches(self, *args, **kwargs):
+ """
+ Get all OsMajors/arches that apply to this system, optionally filter by tag
+ """
+
fqdn = kwargs.get("machine", '')
tags = kwargs.get("tag", [])
- if not hasattr(self,'hub'):
+ if not hasattr(self, 'hub'):
self.set_hub(**kwargs)
return self.hub.systems.get_osmajor_arches(fqdn, tags)
- def getFamily(self, *args, **kwargs):
- """ Get the family/osmajor for a particular distro """
- distro = kwargs.get("distro", None)
- family = kwargs.get("family", None)
+ getSystemOsMajorArches = get_system_os_major_arches
+
+ def get_family(self, *args, **kwargs):
+ """
+ Get the family / OS major for a particular distro
+ """
+ distro = kwargs.get("distro", None)
+ family = kwargs.get("family", None)
if family:
return family
- if not hasattr(self,'hub'):
+ if not hasattr(self, 'hub'):
self.set_hub(**kwargs)
return self.hub.distros.get_osmajor(distro)
-
- def getTaskNamesFromFile(self, kwargs):
- """ get list of task(s) from a file """
+
+ getFamily = get_family
+
+ def get_task_names_from_file(self, kwargs):
+ """
+ Get list of task(s) from a file
+ """
task_names = []
tasklist = kwargs.get('taskfile')
@@ -524,16 +564,20 @@ class BeakerWorkflow(BeakerCommand):
task_names.append(line.rstrip())
return task_names
- def getTasks(self, *args, **kwargs):
- """ get all requested tasks """
+ getTaskNamesFromFile = get_task_names_from_file
+
+ def get_tasks(self, *args, **kwargs):
+ """
+ Get all requested tasks
+ """
- types = kwargs.get("type", None)
+ types = kwargs.get("type", None)
packages = kwargs.get("package", None)
self.n_clients = kwargs.get("clients", 0)
self.n_servers = kwargs.get("servers", 0)
quiet = kwargs.get("quiet", False)
- if not hasattr(self,'hub'):
+ if not hasattr(self, 'hub'):
self.set_hub(**kwargs)
# We only want valid tasks
@@ -555,8 +599,7 @@ class BeakerWorkflow(BeakerCommand):
if task:
tasks.append(task)
elif not quiet:
- print >>sys.stderr, 'WARNING: task %s not applicable ' \
- 'for distro, ignoring' % name
+ sys.stderr.write('WARNING: task %s not applicable for distro, ignoring\n' % name)
if self.n_clients or self.n_servers:
self.multi_host = True
@@ -574,17 +617,19 @@ class BeakerWorkflow(BeakerCommand):
if self.multi_host:
for t in ntasks[:]:
if t not in multihost_tasks:
- #FIXME add debug print here
+ # FIXME add debug print here
ntasks.remove(t)
else:
for t in ntasks[:]:
if t in multihost_tasks:
- #FIXME add debug print here
+ # FIXME add debug print here
ntasks.remove(t)
tasks.extend(ntasks)
return tasks
- def getInstallTaskName(self, *args, **kwargs):
+ getTasks = get_tasks
+
+ def get_install_task_name(self, *args, **kwargs):
"""
Returns the name of the task which is injected at the start of the recipe.
Its job is to check for any problems in the installation.
@@ -613,25 +658,36 @@ class BeakerWorkflow(BeakerCommand):
return '/distribution/install'
return '/distribution/check-install'
- def processTemplate(self, recipeTemplate,
- requestedTasks,
- taskParams=[],
- distroRequires=None,
- hostRequires=None,
- role='STANDALONE',
- arch=None,
- whiteboard=None,
- install=None,
- reserve=None,
- reserve_duration=None,
- **kwargs):
- """ add tasks and additional requires to our template """
+ getInstallTaskName = get_install_task_name
+
+ def process_template(self, recipeTemplate,
+ requestedTasks,
+ taskParams=None,
+ distroRequires=None,
+ hostRequires=None,
+ role='STANDALONE',
+ arch=None,
+ whiteboard=None,
+ install=None,
+ reserve=None,
+ reserve_duration=None,
+ **kwargs):
+ """
+ Add tasks and additional requires to our template
+ """
+
+ if taskParams is None:
+ taskParams = []
+
actualTasks = []
+
for task in requestedTasks:
if arch not in task['arches']:
actualTasks.append(task['name'])
+
# Don't create empty recipes
if actualTasks or reserve or kwargs.get('allow_empty_recipe', False):
+
# Copy basic requirements
recipe = recipeTemplate.clone()
if whiteboard:
@@ -648,8 +704,8 @@ class BeakerWorkflow(BeakerCommand):
recipe.addTask(install_task_name)
if install:
paramnode = self.doc.createElement('param')
- paramnode.setAttribute('name' , 'PKGARGNAME')
- paramnode.setAttribute('value' , ' '.join(install))
+ paramnode.setAttribute('name', 'PKGARGNAME')
+ paramnode.setAttribute('value', ' '.join(install))
recipe.addTask('/distribution/pkginstall', paramNodes=[paramnode])
if kwargs.get("ndump"):
recipe.addTask('/kernel/networking/ndnc')
@@ -659,6 +715,7 @@ class BeakerWorkflow(BeakerCommand):
recipe.addTask(task, role=role, taskParams=taskParams)
if reserve:
recipe.addReservesys(duration=reserve_duration)
+
# process kickstart template if given
ksTemplate = kwargs.get("kickstart", None)
if ksTemplate:
@@ -672,13 +729,17 @@ class BeakerWorkflow(BeakerCommand):
recipe = None
return recipe
+ processTemplate = process_template
+
+
class BeakerJobTemplateError(ValueError):
"""
- Raised to indicate that something invalid or impossible has been requested
+ Raised to indicate that something invalid or impossible has been requested
while a BeakerJob template was being used to generate a job definition.
"""
pass
+
class BeakerBase(object):
doc = xml.dom.minidom.Document()
@@ -695,11 +756,12 @@ class BeakerBase(object):
myxml = self.node.toxml()
return myxml
+
class BeakerJob(BeakerBase):
def __init__(self, *args, **kwargs):
self.node = self.doc.createElement('job')
whiteboard = self.doc.createElement('whiteboard')
- whiteboard.appendChild(self.doc.createTextNode(kwargs.get('whiteboard','')))
+ whiteboard.appendChild(self.doc.createTextNode(kwargs.get('whiteboard', '')))
if kwargs.get('cc'):
notify = self.doc.createElement('notify')
for cc in kwargs.get('cc'):
@@ -717,8 +779,10 @@ class BeakerJob(BeakerBase):
if kwargs.get('job_owner'):
self.node.setAttribute('user', kwargs.get('job_owner'))
- def addRecipeSet(self, recipeSet=None):
- """ properly add a recipeSet to this job """
+ def add_recipe_set(self, recipeSet=None):
+ """
+ Properly add a recipeSet to this job
+ """
if recipeSet:
if isinstance(recipeSet, BeakerRecipeSet):
node = recipeSet.node
@@ -729,8 +793,12 @@ class BeakerJob(BeakerBase):
if len(node.getElementsByTagName('recipe')) > 0:
self.node.appendChild(node.cloneNode(True))
- def addRecipe(self, recipe=None):
- """ properly add a recipe to this job """
+ addRecipeSet = add_recipe_set
+
+ def add_recipe(self, recipe=None):
+ """
+ Properly add a recipe to this job
+ """
if recipe:
if isinstance(recipe, BeakerRecipe):
node = recipe.node
@@ -743,13 +811,18 @@ class BeakerJob(BeakerBase):
recipeSet.appendChild(node.cloneNode(True))
self.node.appendChild(recipeSet)
+ addRecipe = add_recipe
+
+
class BeakerRecipeSet(BeakerBase):
def __init__(self, *args, **kwargs):
self.node = self.doc.createElement('recipeSet')
self.node.setAttribute('priority', kwargs.get('priority', ''))
- def addRecipe(self, recipe=None):
- """ properly add a recipe to this recipeSet """
+ def add_recipe(self, recipe=None):
+ """
+ Properly add a recipe to this recipeSet
+ """
if recipe:
if isinstance(recipe, BeakerRecipe):
node = recipe.node
@@ -760,9 +833,12 @@ class BeakerRecipeSet(BeakerBase):
if len(node.getElementsByTagName('task')) > 0:
self.node.appendChild(node.cloneNode(True))
+ addRecipe = add_recipe
+
+
class BeakerRecipeBase(BeakerBase):
def __init__(self, *args, **kwargs):
- self.node.setAttribute('whiteboard','')
+ self.node.setAttribute('whiteboard', '')
andDistroRequires = self.doc.createElement('and')
partitions = self.doc.createElement('partitions')
@@ -776,7 +852,9 @@ class BeakerRecipeBase(BeakerBase):
self.node.appendChild(partitions)
def _addBaseHostRequires(self, **kwargs):
- """ Add hostRequires """
+ """
+ Add hostRequires
+ """
machine = kwargs.get("machine", None)
force = kwargs.get('ignore_system_status', False)
@@ -788,7 +866,7 @@ class BeakerRecipeBase(BeakerBase):
if machine and force:
# if machine is specified, emit a warning message that any
# other host selection criteria is ignored
- for opt in ['hostrequire', 'keyvalue', 'random', 'systype',
+ for opt in ['hostrequire', 'keyvalue', 'random', 'systype',
'host_filter']:
if kwargs.get(opt, None):
sys.stderr.write('Warning: Ignoring --%s'
@@ -808,9 +886,10 @@ class BeakerRecipeBase(BeakerBase):
self.addHostRequires(systemType)
p2 = re.compile(r'\s*([\!=<>]+|&gt;|&lt;|(?<=\s)like(?=\s))\s*')
for keyvalue in keyvalues:
- splitkeyvalue = p2.split(keyvalue,3)
+ splitkeyvalue = p2.split(keyvalue, 3)
if len(splitkeyvalue) != 3:
- raise BeakerJobTemplateError('--keyvalue option must be in the form "KEY OPERATOR VALUE"')
+ raise BeakerJobTemplateError(
+ '--keyvalue option must be in the form "KEY OPERATOR VALUE"')
key, op, value = splitkeyvalue
mykeyvalue = self.doc.createElement('key_value')
mykeyvalue.setAttribute('key', '%s' % key)
@@ -821,9 +900,10 @@ class BeakerRecipeBase(BeakerBase):
if require.lstrip().startswith('<'):
myrequire = xml.dom.minidom.parseString(require).documentElement
else:
- splitrequire = p2.split(require,3)
+ splitrequire = p2.split(require, 3)
if len(splitrequire) != 3:
- raise BeakerJobTemplateError('--hostrequire option must be in the form "TAG OPERATOR VALUE"')
+ raise BeakerJobTemplateError(
+ '--hostrequire option must be in the form "TAG OPERATOR VALUE"')
key, op, value = splitrequire
myrequire = self.doc.createElement('%s' % key)
myrequire.setAttribute('op', '%s' % op)
@@ -833,8 +913,7 @@ class BeakerRecipeBase(BeakerBase):
self.addAutopick(random)
if host_filter:
_host_filter_presets = host_filter_presets()
- host_filter_expanded = _host_filter_presets.get \
- (host_filter, None)
+ host_filter_expanded = _host_filter_presets.get(host_filter, None)
if host_filter_expanded:
self.addHostRequires(xml.dom.minidom.parseString
(host_filter_expanded).documentElement)
@@ -842,8 +921,10 @@ class BeakerRecipeBase(BeakerBase):
sys.stderr.write('Pre-defined host-filter does not exist: %s\n' % host_filter)
sys.exit(1)
- def addBaseRequires(self, *args, **kwargs):
- """ Add base requires """
+ def add_base_requires(self, *args, **kwargs):
+ """
+ Add base requires
+ """
self._addBaseHostRequires(**kwargs)
distro = kwargs.get("distro", None)
@@ -906,20 +987,24 @@ class BeakerRecipeBase(BeakerBase):
if ignore_panic:
self.add_ignore_panic()
- def addRepo(self, node):
+ addBaseRequires = add_base_requires
+
+ def add_repo(self, node):
self.repos.appendChild(node.cloneNode(True))
- def addPostRepo(self,repourl_lst):
- """ function to add repos only in %post section of kickstart
+ addRepo = add_repo
+
+ def add_post_repo(self, repourl_lst):
+ """
+ Function to add repos only in %post section of kickstart
- addRepo() function does add the repos to be available during the
+ add_repo() function does add the repos to be available during the
installation time whereas this function appends the yum repo config
files in the %post section of the kickstart so that they are ONLY
available after the installation.
"""
post_repo_config = ""
for i, repoloc in enumerate(repourl_lst):
-
post_repo_config += '''
cat << EOF >/etc/yum.repos.d/beaker-postrepo%(i)s.repo
[beaker-postrepo%(i)s]
@@ -936,8 +1021,12 @@ EOF
ks_append.appendChild(self.doc.createCDATASection(post_repo_config))
self.ks_appends.appendChild(ks_append.cloneNode(True))
- def addHostRequires(self, nodes):
- """ Accepts either xml, dom.Element or a list of dom.Elements """
+ addPostRepo = add_post_repo
+
+ def add_host_requires(self, nodes):
+ """
+ Accepts either xml, dom.Element or a list of dom.Elements
+ """
if isinstance(nodes, str):
parse = xml.dom.minidom.parseString(nodes.strip())
nodes = []
@@ -948,10 +1037,14 @@ EOF
if isinstance(nodes, list):
for node in nodes:
if isinstance(node, xml.dom.minidom.Element):
- self.andHostRequires.appendChild(node.cloneNode(True))
+ self.and_host_requires.appendChild(node.cloneNode(True))
+
+ addHostRequires = add_host_requires
- def addDistroRequires(self, nodes):
- """ Accepts either xml, dom.Element or a list of dom.Elements """
+ def add_distro_requires(self, nodes):
+ """
+ Accepts either xml, dom.Element or a list of dom.Elements
+ """
if isinstance(nodes, str):
parse = xml.dom.minidom.parseString(nodes.strip())
nodes = []
@@ -962,9 +1055,17 @@ EOF
if isinstance(nodes, list):
for node in nodes:
if isinstance(node, xml.dom.minidom.Element):
- self.andDistroRequires.appendChild(node.cloneNode(True))
+ self.and_distro_requires.appendChild(node.cloneNode(True))
+
+ addDistroRequires = add_distro_requires
+
+ def add_task(self, task, role='STANDALONE', paramNodes=None, taskParams=None):
+
+ if taskParams is None:
+ taskParams = []
+ if paramNodes is None:
+ paramNodes = []
- def addTask(self, task, role='STANDALONE', paramNodes=[], taskParams=[]):
recipeTask = self.doc.createElement('task')
recipeTask.setAttribute('name', '%s' % task)
recipeTask.setAttribute('role', '%s' % role)
@@ -973,20 +1074,25 @@ EOF
params.appendChild(param)
for taskParam in taskParams:
param = self.doc.createElement('param')
- param.setAttribute('name' , taskParam.split('=',1)[0])
- param.setAttribute('value' , taskParam.split('=',1)[1])
+ param.setAttribute('name', taskParam.split('=', 1)[0])
+ param.setAttribute('value', taskParam.split('=', 1)[1])
params.appendChild(param)
recipeTask.appendChild(params)
self.node.appendChild(recipeTask)
- def addReservesys(self, duration=None):
+ addTask = add_task
+
+ def add_reservesys(self, duration=None):
reservesys = self.doc.createElement('reservesys')
if duration:
reservesys.setAttribute('duration', duration)
self.node.appendChild(reservesys)
- def addPartition(self,name=None, type=None, fs=None, size=None):
- """ add a partition node
+ addReservesys = add_reservesys
+
+ def add_partition(self, name=None, type=None, fs=None, size=None):
+ """
+ Add a partition node
"""
if name:
partition = self.doc.createElement('partition')
@@ -1003,23 +1109,30 @@ EOF
partition.setAttribute('fs', str(fs))
self.partitions.appendChild(partition)
- def addKickstart(self, kickstart):
+ addPartition = add_partition
+
+ def add_kickstart(self, kickstart):
recipeKickstart = self.doc.createElement('kickstart')
recipeKickstart.appendChild(self.doc.createCDATASection(kickstart))
self.node.appendChild(recipeKickstart)
- def addAutopick(self, random):
+ addKickstart = add_kickstart
+
+ def add_autopick(self, random):
recipeAutopick = self.doc.createElement('autopick')
- recipeAutopick.setAttribute('random', unicode(random).lower())
+ random = u'{}'.format(random).lower()
+ recipeAutopick.setAttribute('random', random)
self.node.appendChild(recipeAutopick)
+ addAutopick = add_autopick
+
def add_ignore_panic(self):
recipeIgnorePanic = self.doc.createElement('watchdog')
recipeIgnorePanic.setAttribute('panic', 'ignore')
self.node.appendChild(recipeIgnorePanic)
def set_ks_meta(self, value):
- return self.node.setAttribute('ks_meta', value)
+ self.node.setAttribute('ks_meta', value)
def get_ks_meta(self):
return self.node.getAttribute('ks_meta')
@@ -1027,7 +1140,7 @@ EOF
ks_meta = property(get_ks_meta, set_ks_meta)
def set_kernel_options(self, value):
- return self.node.setAttribute('kernel_options', value)
+ self.node.setAttribute('kernel_options', value)
def get_kernel_options(self):
return self.node.getAttribute('kernel_options')
@@ -1035,7 +1148,7 @@ EOF
kernel_options = property(get_kernel_options, set_kernel_options)
def set_kernel_options_post(self, value):
- return self.node.setAttribute('kernel_options_post', value)
+ self.node.setAttribute('kernel_options_post', value)
def get_kernel_options_post(self):
return self.node.getAttribute('kernel_options_post')
@@ -1043,33 +1156,35 @@ EOF
kernel_options_post = property(get_kernel_options_post, set_kernel_options_post)
def set_whiteboard(self, value):
- return self.node.setAttribute('whiteboard', value)
+ self.node.setAttribute('whiteboard', value)
def get_whiteboard(self):
return self.node.getAttribute('whiteboard')
whiteboard = property(get_whiteboard, set_whiteboard)
- def get_andDistroRequires(self):
- return self.node\
- .getElementsByTagName('distroRequires')[0]\
- .getElementsByTagName('and')[0]
- andDistroRequires = property(get_andDistroRequires)
+ def get_and_distro_requires(self):
+ return self.node.getElementsByTagName('distroRequires')[0].getElementsByTagName('and')[0]
+
+ and_distro_requires = andDistroRequires = property(get_and_distro_requires)
- def get_andHostRequires(self):
+ def get_and_host_requires(self):
hostRequires = self.node.getElementsByTagName('hostRequires')[0]
if not hostRequires.getElementsByTagName('and'):
andHostRequires = self.doc.createElement('and')
hostRequires.appendChild(andHostRequires)
return hostRequires.getElementsByTagName('and')[0]
- andHostRequires = property(get_andHostRequires)
+
+ and_host_requires = andHostRequires = property(get_and_host_requires)
def get_repos(self):
return self.node.getElementsByTagName('repos')[0]
+
repos = property(get_repos)
def get_partitions(self):
return self.node.getElementsByTagName('partitions')[0]
+
partitions = property(get_partitions)
@property
@@ -1081,29 +1196,32 @@ EOF
self.node.appendChild(ks_appends)
return ks_appends
+
class BeakerRecipe(BeakerRecipeBase):
def __init__(self, *args, **kwargs):
self.node = self.doc.createElement('recipe')
- super(BeakerRecipe,self).__init__(*args, **kwargs)
+ super(BeakerRecipe, self).__init__(*args, **kwargs)
- def addGuestRecipe(self, guestrecipe):
+ def add_guest_recipe(self, guestrecipe):
""" properly add a guest recipe to this recipe """
if isinstance(guestrecipe, BeakerGuestRecipe):
self.node.appendChild(guestrecipe.node.cloneNode(True))
- elif isinstance(guestrecipe, xml.dom.minidom.Element) and \
- guestrecipe.tagName == 'guestrecipe':
+ elif (isinstance(guestrecipe, xml.dom.minidom.Element)
+ and guestrecipe.tagName == 'guestrecipe'):
self.node.appendChild(guestrecipe.cloneNode(True))
else:
- #FIXME raise error here.
- sys.stderr.write("invalid object\n")
+ raise BeakerJobTemplateError(u'Invalid guest recipe')
+
+ addGuestRecipe = add_guest_recipe
+
class BeakerGuestRecipe(BeakerRecipeBase):
def __init__(self, *args, **kwargs):
self.node = self.doc.createElement('guestrecipe')
- super(BeakerGuestRecipe,self).__init__(*args, **kwargs)
+ super(BeakerGuestRecipe, self).__init__(*args, **kwargs)
def set_guestargs(self, value):
- return self.node.setAttribute('guestargs', value)
+ self.node.setAttribute('guestargs', value)
def get_guestargs(self):
return self.node.getAttribute('guestargs')
@@ -1111,10 +1229,9 @@ class BeakerGuestRecipe(BeakerRecipeBase):
guestargs = property(get_guestargs, set_guestargs)
def set_guestname(self, value):
- return self.node.setAttribute('guestname', value)
+ self.node.setAttribute('guestname', value)
def get_guestname(self):
return self.node.getAttribute('guestname')
guestname = property(get_guestname, set_guestname)
-
diff --git a/Client/src/bkr/client/command.py b/Client/src/bkr/client/command.py
index 05a7953..6381598 100644
--- a/Client/src/bkr/client/command.py
+++ b/Client/src/bkr/client/command.py
@@ -1,4 +1,3 @@
-
# -*- coding: utf-8 -*-
# This program is free software; you can redistribute it and/or modify
@@ -6,29 +5,34 @@
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
-# This is a copy and paste of various parts of kobo 0.4.2-1 needed to support the
-# creation of kobo commands
-
-import sys
-import os
import optparse
+import os
+import sys
from optparse import Option
-from xmlrpclib import Fault
-from bkr.common.pyconfig import PyConfigParser
+
+import six
+
from bkr.common.hub import HubProxy
+from bkr.common.pyconfig import PyConfigParser
+
def username_prompt(prompt=None, default_value=None):
- """Ask for a username."""
+ """
+ Ask for a username.
+ """
+
if default_value is not None:
return default_value
prompt = prompt or "Enter your username: "
- print >>sys.stderr, prompt,
+ sys.stderr.write(prompt)
return sys.stdin.readline()
def password_prompt(prompt=None, default_value=None):
- """Ask for a password."""
+ """
+ Ask for a password.
+ """
import getpass
if default_value is not None:
@@ -45,14 +49,16 @@ def password_prompt(prompt=None, default_value=None):
def yes_no_prompt(prompt, default_value=None):
- """Give a yes/no (y/n) question."""
+ """
+ Give a yes/no (y/n) question.
+ """
if default_value is not None:
if default_value not in ("Y", "N"):
raise ValueError("Invalid default value: %s" % default_value)
default_value = default_value.upper()
prompt = "%s [%s/%s]: " % (prompt, ("y", "Y")[default_value == "Y"], ("n", "N")[default_value == "N"])
- print >>sys.stderr, prompt,
+ sys.stderr.write(prompt)
while True:
user_input = sys.stdin.readline().strip().upper()
@@ -64,10 +70,13 @@ def yes_no_prompt(prompt, default_value=None):
if user_input == "N":
return False
+
def are_you_sure_prompt(prompt=None):
- """Give a yes/no (y/n) question."""
+ """
+ Give a yes/no (y/n) question.
+ """
prompt = prompt or "Are you sure? Enter 'YES' to continue: "
- print >>sys.stderr, prompt,
+ sys.stderr.write(prompt)
user_input = sys.stdin.readline().strip()
if user_input == "YES":
@@ -86,13 +95,16 @@ class Plugin(object):
def __getattr__(self, name):
"""
Get missing attribute from a container.
- This is quite hackish but it allows to define settings and methods per container.
+ This is quite hackish but it allows to define settings
+ and methods per container.
"""
return getattr(self.container, name)
class Command(Plugin):
- """An abstract class representing a command for CommandOptionParser."""
+ """
+ An abstract class representing a command for CommandOptionParser.
+ """
enabled = False
admin = False
@@ -107,25 +119,31 @@ class Command(Plugin):
self.parser = parser
def options(self):
- """Add options to self.parser."""
+ """
+ Add options to self.parser.
+ """
pass
def run(self, *args, **kwargs):
- """Run a command. Arguments contain parsed options."""
+ """
+ Run a command. Arguments contain parsed options.
+ """
raise NotImplementedError()
class PluginContainer(object):
- """A plugin container.
+ """
+ A plugin container.
Usage: Inherit PluginContainer and register plugins to the new class.
+
"""
def __getitem__(self, name):
return self._get_plugin(name)
def __iter__(self):
- return self.plugins.iterkeys()
+ return six.iterkeys(self.plugins)
@classmethod
def normalize_name(cls, name):
@@ -133,12 +151,16 @@ class PluginContainer(object):
@classmethod
def _get_plugins(cls):
- """Return dictionary of registered plugins."""
+ """
+ Return dictionary of registered plugins.
+ """
result = {}
- parent_plugins = cls._get_parent_plugins(cls.normalize_name).items() #pylint: disable=no-member
- class_plugins = getattr(cls, "_class_plugins", {}).items()
- for name, plugin_class in parent_plugins + class_plugins:
+ parent_plugins = cls._get_parent_plugins(cls.normalize_name) # pylint: disable=no-member
+ class_plugins = getattr(cls, "_class_plugins", {})
+ d = parent_plugins.copy()
+ d.update(class_plugins)
+ for name, plugin_class in d.items():
result[name] = plugin_class
return result
@@ -155,17 +177,20 @@ class PluginContainer(object):
continue
# read inherited plugins first (conflicts are resolved recursively)
- plugins = parent._get_parent_plugins(normalize_function) #pylint: disable=no-member
+ plugins = parent._get_parent_plugins(normalize_function) # pylint: disable=no-member
# read class plugins, override inherited on name conflicts
if hasattr(parent, "_class_plugins"):
- for plugin_class in parent._class_plugins.values(): #pylint: disable=no-member
+ for plugin_class in parent._class_plugins.values(): # pylint: disable=no-member
normalized_name = normalize_function(plugin_class.__name__)
plugins[normalized_name] = plugin_class
- for name, value in plugins.iteritems():
+ for name, value in six.iteritems(plugins):
if result.get(name, value) != value:
- raise RuntimeError("Cannot register plugin '%s'. Another plugin with the same normalized name (%s) is already in the container." % (str(value), normalized_name))
+ raise RuntimeError(
+ "Cannot register plugin '%s'. "
+ "Another plugin with the same normalized name (%s) "
+ "is already in the container." % (str(value), normalized_name))
result.update(plugins)
@@ -178,7 +203,9 @@ class PluginContainer(object):
return self._plugins
def _get_plugin(self, name):
- """Return a plugin or raise KeyError."""
+ """
+ Return a plugin or raise KeyError.
+ """
normalized_name = self.normalize_name(name)
if normalized_name not in self.plugins:
@@ -191,7 +218,9 @@ class PluginContainer(object):
@classmethod
def register_plugin(cls, plugin, name=None):
- """Register a new plugin. Return normalized plugin name."""
+ """
+ Register a new plugin. Return normalized plugin name.
+ """
if cls is PluginContainer:
raise TypeError("Can't register plugin to the PluginContainer base class.")
@@ -209,7 +238,9 @@ class PluginContainer(object):
@classmethod
def register_module(cls, module, prefix=None, skip_broken=False):
- """Register all plugins in a module's sub-modules.
+ """
+
+ Register all plugins in a module's sub-modules.
@param module: a python module that contains plugin sub-modules
@type module: module
@@ -238,7 +269,8 @@ class PluginContainer(object):
__import__(module.__name__, {}, {}, [mod])
except:
import sys
- print >> sys.stderr, "WARNING: Skipping broken plugin module: %s.%s" % (module.__name__, mod)
+ sys.stderr.write("WARNING: Skipping broken plugin module: %s.%s"
+ % (module.__name__, mod))
module_list.remove(mod)
else:
__import__(module.__name__, {}, {}, module_list)
@@ -259,11 +291,15 @@ class BeakerClientConfigurationError(ValueError):
class CommandContainer(PluginContainer):
- """Container for Command classes."""
+ """
+ Container for Command classes.
+ """
@classmethod
def normalize_name(cls, name):
- """Replace some characters in command names."""
+ """
+ Replace some characters in command names.
+ """
return name.lower().replace('_', '-').replace(' ', '-')
@@ -286,26 +322,28 @@ class ClientCommandContainer(CommandContainer):
cacert = self.conf.get('CA_CERT')
if cacert and not os.path.exists(cacert):
- raise BeakerClientConfigurationError('CA_CERT configuration points to non-existing file: %s' % cacert)
+ raise BeakerClientConfigurationError(
+ 'CA_CERT configuration points to non-existing file: %s' % cacert)
self.hub = HubProxy(conf=self.conf, auto_login=auto_login)
class CommandOptionParser(optparse.OptionParser):
"""Enhanced OptionParser with plugin support."""
+
def __init__(self,
- usage=None,
- option_list=None,
- option_class=Option,
- version=None,
- conflict_handler="error",
- description=None,
- formatter=None,
- add_help_option=True,
- prog=None,
- command_container=None,
- default_command="help",
- add_username_password_options=False):
+ usage=None,
+ option_list=None,
+ option_class=Option,
+ version=None,
+ conflict_handler="error",
+ description=None,
+ formatter=None,
+ add_help_option=True,
+ prog=None,
+ command_container=None,
+ default_command="help",
+ add_username_password_options=False):
usage = usage or "%prog <command> [args] [--help]"
self.container = command_container
@@ -313,7 +351,9 @@ class CommandOptionParser(optparse.OptionParser):
self.command = None
formatter = formatter or optparse.IndentedHelpFormatter(max_help_position=33)
- optparse.OptionParser.__init__(self, usage, option_list, option_class, version, conflict_handler, description, formatter, add_help_option, prog)
+ optparse.OptionParser.__init__(self, usage, option_list, option_class, version,
+ conflict_handler, description, formatter, add_help_option,
+ prog)
if add_username_password_options:
option_list = [
@@ -334,7 +374,7 @@ class CommandOptionParser(optparse.OptionParser):
commands = []
admin_commands = []
- for name, plugin in sorted(self.container.plugins.iteritems()):
+ for name, plugin in sorted(six.iteritems(self.container.plugins)):
if getattr(plugin, 'hidden', False):
continue
is_admin = getattr(plugin, "admin", False)
@@ -356,9 +396,10 @@ class CommandOptionParser(optparse.OptionParser):
return "\n".join(commands + admin_commands)
def parse_args(self, args=None, values=None):
- """return (command_instance, opts, args)"""
+ """
+ Return (command_instance, opts, args)
+ """
args = self._get_args(args)
- command = None
if len(args) > 0 and not args[0].startswith("-"):
command = args[0]
@@ -376,16 +417,21 @@ class CommandOptionParser(optparse.OptionParser):
self.command = cmd.normalized_name
cmd.options()
cmd_opts, cmd_args = optparse.OptionParser.parse_args(self, args, values)
- return (cmd, cmd_opts, cmd_args)
+ return cmd, cmd_opts, cmd_args
def run(self, args=None, values=None):
- """parse arguments and run a command"""
+ """
+ Parse arguments and run a command
+ """
cmd, cmd_opts, cmd_args = self.parse_args(args, values)
cmd_kwargs = cmd_opts.__dict__
cmd.run(*cmd_args, **cmd_kwargs)
+
class Help(Command):
- """show this help message and exit"""
+ """
+ Show this help message and exit
+ """
enabled = True
def options(self):
@@ -396,7 +442,9 @@ class Help(Command):
class Help_Admin(Command):
- """show help message about administrative commands and exit"""
+ """
+ Show help message about administrative commands and exit
+ """
enabled = True
def options(self):
@@ -408,5 +456,6 @@ class Help_Admin(Command):
def run(self, *args, **kwargs):
self.parser.print_help(admin=True)
+
CommandContainer.register_plugin(Help)
CommandContainer.register_plugin(Help_Admin)
diff --git a/Client/src/bkr/client/commands/cmd_distro_trees_list.py b/Client/src/bkr/client/commands/cmd_distro_trees_list.py
index 6471406..b4dac32 100644
--- a/Client/src/bkr/client/commands/cmd_distro_trees_list.py
+++ b/Client/src/bkr/client/commands/cmd_distro_trees_list.py
@@ -50,7 +50,7 @@ Options
.. option:: --family <family>
Limit to distros of the given family (major version), for example
- ``RedHatEnterpriseLinuxServer5``.
+ ``RedHatEnterpriseLinuxServer8``.
.. option:: --arch <arch>
@@ -93,9 +93,9 @@ the exit status will be 1.
Examples
--------
-List details of all RHEL5.6 Server nightly trees from a particular date::
+List details of all RHEL 8.0.0 nightly trees from a particular date::
- bkr distro-trees-list --name "RHEL5.6-Server-20101110%"
+ bkr distro-trees-list --name "RHEL-8.0.0-20190410%"
See also
--------
@@ -103,13 +103,18 @@ See also
:manpage:`bkr(1)`, :manpage:`bkr-distros-list(1)`
"""
+from __future__ import print_function
-import sys
import json
+import sys
+
from bkr.client import BeakerCommand
+
class Distro_Trees_List(BeakerCommand):
- """List distro trees"""
+ """
+ List distro trees
+ """
enabled = True
requires_login = False
@@ -174,7 +179,6 @@ class Distro_Trees_List(BeakerCommand):
help='filter by XML criteria, as in <distroRequires/>',
)
-
def run(self, *args, **kwargs):
filter = dict( limit = kwargs.pop("limit", None),
name = kwargs.pop("name", None),
@@ -192,20 +196,21 @@ class Distro_Trees_List(BeakerCommand):
self.set_hub(**kwargs)
distro_trees = self.hub.distrotrees.filter(filter)
if format == 'json':
- print json.dumps(distro_trees, indent=4)
+ print(json.dumps(distro_trees, indent=4))
elif format == 'tabular':
if distro_trees:
- print "-"*70
+ print("-" * 70)
for dt in distro_trees:
- print " ID: %s" % dt['distro_tree_id']
- print " Name: %-34.34s Arch: %s" % (dt['distro_name'], dt['arch'])
- print "OSVersion: %-34.34s Variant: %s" % (dt['distro_osversion'], dt['variant'])
- print " Tags: %s" % ", ".join(dt['distro_tags'])
- print
- print " Lab controller/URLs:"
+ print(" ID: %s" % dt['distro_tree_id'])
+ print(" Name: %-34.34s Arch: %s" % (dt['distro_name'], dt['arch']))
+ print("OSVersion: %-34.34s Variant: %s" % (
+ dt['distro_osversion'], dt['variant']))
+ print(" Tags: %s" % ", ".join(dt['distro_tags']))
+ print()
+ print(" Lab controller/URLs:")
for labc, url in dt['available']:
- print " %-32s: %s" % (labc, url)
- print "-"*70
+ print(" %-32s: %s" % (labc, url))
+ print("-" * 70)
else:
sys.stderr.write("Nothing Matches\n")
if not distro_trees:
diff --git a/Client/src/bkr/client/commands/cmd_distro_trees_verify.py b/Client/src/bkr/client/commands/cmd_distro_trees_verify.py
index 30afab6..83280ed 100644
--- a/Client/src/bkr/client/commands/cmd_distro_trees_verify.py
+++ b/Client/src/bkr/client/commands/cmd_distro_trees_verify.py
@@ -44,7 +44,7 @@ Options
.. option:: --family <family>
Limit to distros of the given family (major version), for example
- ``RedHatEnterpriseLinuxServer5``.
+ ``RedHatEnterpriseLinuxServer8``.
.. option:: --arch <arch>
@@ -79,13 +79,17 @@ See also
:manpage:`bkr-distro-trees-list(1)`, :manpage:`bkr(1)`
"""
+from __future__ import print_function
import sys
+
from bkr.client import BeakerCommand
class Distro_Trees_Verify(BeakerCommand):
- """Verify distro trees"""
+ """
+ Verify distro trees
+ """
enabled = True
requires_login = False
@@ -128,7 +132,6 @@ class Distro_Trees_Verify(BeakerCommand):
help="filter by arch",
)
-
def run(self, *args, **kwargs):
onlybroken = kwargs.pop("broken", False)
filter = dict( limit = kwargs.pop("limit", None),
@@ -147,11 +150,11 @@ class Distro_Trees_Verify(BeakerCommand):
available_lcs = set(lc for lc, url in tree['available'])
broken = lab_controllers.difference(available_lcs)
if not onlybroken or broken:
- print '%s %s %s %s Tags:%s' % (tree['distro_tree_id'],
- tree['distro_name'], tree['variant'] or '',
- tree['arch'], ','.join(tree['distro_tags']))
+ print('%s %s %s %s Tags:%s' % (tree['distro_tree_id'],
+ tree['distro_name'], tree['variant'] or '',
+ tree['arch'], ','.join(tree['distro_tags'])))
if broken:
- print "missing from labs %s" % list(broken)
+ print("missing from labs %s" % list(broken))
else:
sys.stderr.write("Nothing Matches\n")
sys.exit(1)
diff --git a/Client/src/bkr/client/commands/cmd_distros_edit_version.py b/Client/src/bkr/client/commands/cmd_distros_edit_version.py
index 30c0ea0..408adab 100644
--- a/Client/src/bkr/client/commands/cmd_distros_edit_version.py
+++ b/Client/src/bkr/client/commands/cmd_distros_edit_version.py
@@ -19,8 +19,8 @@ Synopsis
Description
-----------
-Applies the given version (for example, ``RedHatEnterpriseLinuxServer5.6`` or
-``Fedora14``) to all matching distros in Beaker.
+Applies the given version (for example, ``RedHatEnterpriseLinux8.0`` or
+``Fedora30``) to all matching distros in Beaker.
Options
-------
@@ -43,7 +43,7 @@ Examples
Set the version for all RHEL5.6 Server nightly trees from a particular date::
- bkr distros-edit-version --name "RHEL5.6-Server-20101110%" RedHatEnterpriseLinuxServer5.6
+ bkr distros-edit-version --name "RHEL-8.0.0-20190410%" RedHatEnterpriseLinux8.0
Notes
-----
@@ -56,12 +56,15 @@ See also
:manpage:`bkr(1)`
"""
+from __future__ import print_function
from bkr.client import BeakerCommand
class Distros_Edit_Version(BeakerCommand):
- """Edit distros version"""
+ """
+ Edit distros version
+ """
enabled = True
@@ -74,7 +77,6 @@ class Distros_Edit_Version(BeakerCommand):
help="tag by name, use % for wildcard",
)
-
def run(self, *args, **kwargs):
if len(args) < 1:
self.parser.error("Please specify a version")
@@ -84,7 +86,7 @@ class Distros_Edit_Version(BeakerCommand):
self.set_hub(**kwargs)
distros = self.hub.distros.edit_version(name, version)
- print "Updated the following distros with Version: %s" % version
- print "------------------------------------------------------"
+ print("Updated the following distros with Version: %s" % version)
+ print("------------------------------------------------------")
for distro in distros:
- print distro
+ print(distro)
diff --git a/Client/src/bkr/client/commands/cmd_distros_list.py b/Client/src/bkr/client/commands/cmd_distros_list.py
index b1c2139..502d3e4 100644
--- a/Client/src/bkr/client/commands/cmd_distros_list.py
+++ b/Client/src/bkr/client/commands/cmd_distros_list.py
@@ -67,9 +67,9 @@ the exit status will be 1.
Examples
--------
-List details of all RHEL6 distros with the RELEASED tag::
+List details of all RHEL7 distros with the RELEASED tag::
- bkr distros-list --family RedHatEnterpriseLinux6 --tag RELEASED
+ bkr distros-list --family RedHatEnterpriseLinux7 --tag RELEASED
History
-------
@@ -84,13 +84,18 @@ See also
:manpage:`bkr(1)`, :manpage:`bkr-distro-trees-list(1)`
"""
+from __future__ import print_function
-import sys
import json
+import sys
+
from bkr.client import BeakerCommand
+
class Distros_List(BeakerCommand):
- """list distros"""
+ """
+ List distros
+ """
enabled = True
requires_login = False
@@ -131,7 +136,6 @@ class Distros_List(BeakerCommand):
help="filter by distro id",
)
-
def run(self, *args, **kwargs):
filter = dict( limit = kwargs.pop("limit", None),
name = kwargs.pop("name", None),
@@ -144,16 +148,16 @@ class Distros_List(BeakerCommand):
self.set_hub(**kwargs)
distros = self.hub.distros.filter(filter)
if format == 'json':
- print json.dumps(distros, indent=4)
+ print(json.dumps(distros, indent=4))
elif format == 'tabular':
if distros:
- print "-"*70
+ print("-" * 70)
for distro in distros:
- print " ID: %s" % distro['distro_id']
- print " Name: %s" % distro['distro_name']
- print "OSVersion: %s" % distro['distro_version']
- print " Tags: %s" % ", ".join(distro['distro_tags'])
- print "-"*70
+ print(" ID: %s" % distro['distro_id'])
+ print(" Name: %s" % distro['distro_name'])
+ print("OSVersion: %s" % distro['distro_version'])
+ print(" Tags: %s" % ", ".join(distro['distro_tags']))
+ print("-" * 70)
else:
sys.stderr.write("Nothing Matches\n")
if not distros:
diff --git a/Client/src/bkr/client/commands/cmd_distros_tag.py b/Client/src/bkr/client/commands/cmd_distros_tag.py
index e3f9c7e..97ee667 100644
--- a/Client/src/bkr/client/commands/cmd_distros_tag.py
+++ b/Client/src/bkr/client/commands/cmd_distros_tag.py
@@ -41,9 +41,9 @@ Non-zero on error, otherwise zero.
Examples
--------
-Tags all RHEL5.6 Server nightly trees from a particular date with the "INSTALLS" tag::
+Tags all RHEL8.0.0 nightly trees from a particular date with the "INSTALLS" tag::
- bkr distros-tag --name RHEL5.6-Server-20101110% INSTALLS
+ bkr distros-tag --name RHEL-8.0.0-20190410% INSTALLS
Notes
-----
@@ -56,15 +56,17 @@ See also
:manpage:`bkr-distros-untag(1)`, :manpage:`bkr(1)`
"""
+from __future__ import print_function
from bkr.client import BeakerCommand
class Distros_Tag(BeakerCommand):
- """tag distros"""
+ """
+ Tag distros
+ """
enabled = True
-
def options(self):
self.parser.usage = "%%prog %s [options] <tag>" % self.normalized_name
@@ -74,7 +76,6 @@ class Distros_Tag(BeakerCommand):
help="tag by name, use % for wildcard",
)
-
def run(self, *args, **kwargs):
if len(args) < 1:
self.parser.error("Please specify a tag")
@@ -86,7 +87,7 @@ class Distros_Tag(BeakerCommand):
self.set_hub(**kwargs)
distros = self.hub.distros.tag(name, tag)
- print "Tagged the following distros with tag: %s" % tag
- print "------------------------------------------------------"
+ print("Tagged the following distros with tag: %s" % tag)
+ print("------------------------------------------------------")
for distro in distros:
- print distro
+ print(distro)
diff --git a/Client/src/bkr/client/commands/cmd_distros_untag.py b/Client/src/bkr/client/commands/cmd_distros_untag.py
index 0ce0524..dea024f 100644
--- a/Client/src/bkr/client/commands/cmd_distros_untag.py
+++ b/Client/src/bkr/client/commands/cmd_distros_untag.py
@@ -41,9 +41,9 @@ Non-zero on error, otherwise zero.
Examples
--------
-Removes the "STABLE" tag from all RHEL5.6 Server nightly trees from a particular date::
+Removes the "STABLE" tag from all RHEL 8.0.0 nightly trees from a particular date::
- bkr distros-untag --name RHEL5.6-Server-20101110% STABLE
+ bkr distros-untag --name RHEL-8.0.0-20190410% STABLE
Notes
-----
@@ -56,15 +56,17 @@ See also
:manpage:`bkr-distros-tag(1)`, :manpage:`bkr(1)`
"""
+from __future__ import print_function
from bkr.client import BeakerCommand
class Distros_Untag(BeakerCommand):
- """untag distros"""
+ """
+ Untag distros
+ """
enabled = True
-
def options(self):
self.parser.usage = "%%prog %s [options] <tag>" % self.normalized_name
@@ -79,7 +81,6 @@ class Distros_Untag(BeakerCommand):
help="untag by arch",
)
-
def run(self, *args, **kwargs):
if len(args) < 1:
self.parser.error("Please specify a tag")
@@ -91,7 +92,7 @@ class Distros_Untag(BeakerCommand):
self.set_hub(**kwargs)
distros = self.hub.distros.untag(name, tag)
- print "Removed Tag %s from the following distros:" % tag
- print "------------------------------------------------------"
+ print("Removed Tag %s from the following distros:" % tag)
+ print("------------------------------------------------------")
for distro in distros:
- print distro
+ print(distro)
diff --git a/Client/src/bkr/client/commands/cmd_group_create.py b/Client/src/bkr/client/commands/cmd_group_create.py
index a989b01..ab54946 100644
--- a/Client/src/bkr/client/commands/cmd_group_create.py
+++ b/Client/src/bkr/client/commands/cmd_group_create.py
@@ -61,10 +61,15 @@ See also
:manpage:`bkr(1)`
"""
+from __future__ import print_function
+
from bkr.client import BeakerCommand
+
class Group_Create(BeakerCommand):
- """Create a Group"""
+ """
+ Create a Group
+ """
enabled = True
def options(self):
diff --git a/Client/src/bkr/client/commands/cmd_group_list.py b/Client/src/bkr/client/commands/cmd_group_list.py
index 00e6ea8..05bc13c 100644
--- a/Client/src/bkr/client/commands/cmd_group_list.py
+++ b/Client/src/bkr/client/commands/cmd_group_list.py
@@ -54,11 +54,17 @@ See also
"""
-from bkr.client import BeakerCommand
+from __future__ import print_function
+
import sys
+from bkr.client import BeakerCommand
+
+
class Group_List(BeakerCommand):
- """List groups"""
+ """
+ List groups
+ """
enabled = True
def options(self):
@@ -81,8 +87,7 @@ class Group_List(BeakerCommand):
requests_session = self.requests_session()
- params = {}
- params['page_size'] = limit
+ params = {'page_size': limit}
if owner:
params['q'] = 'owner.user_name:%s' % owner
diff --git a/Client/src/bkr/client/commands/cmd_group_members.py b/Client/src/bkr/client/commands/cmd_group_members.py
index 766459e..2937947 100644
--- a/Client/src/bkr/client/commands/cmd_group_members.py
+++ b/Client/src/bkr/client/commands/cmd_group_members.py
@@ -42,12 +42,19 @@ Non-zero on error, otherwise zero.
"""
-from bkr.client import BeakerCommand
-import urllib
+from __future__ import print_function
+
import json
+from six.moves.urllib import parse
+
+from bkr.client import BeakerCommand
+
+
class Group_Members(BeakerCommand):
- """List group members"""
+ """
+ List group members
+ """
enabled = True
requires_login = False
@@ -72,7 +79,8 @@ class Group_Members(BeakerCommand):
self.set_hub(**kwargs)
requests_session = self.requests_session()
- res = requests_session.get('groups/%s' % urllib.quote(group), headers={'Accept': 'application/json'})
+ res = requests_session.get('groups/%s' % parse.quote(group),
+ headers={'Accept': 'application/json'})
res.raise_for_status()
members = []
@@ -90,7 +98,7 @@ class Group_Members(BeakerCommand):
else:
output_tuple = (m['username'], m['email'], 'Member')
- print '%s %s %s' % output_tuple
+ print('%s %s %s' % output_tuple)
if format == 'json':
- print json.dumps(members)
+ print(json.dumps(members))
diff --git a/Client/src/bkr/client/commands/cmd_group_modify.py b/Client/src/bkr/client/commands/cmd_group_modify.py
index 58ef200..a6e4d59 100644
--- a/Client/src/bkr/client/commands/cmd_group_modify.py
+++ b/Client/src/bkr/client/commands/cmd_group_modify.py
@@ -102,11 +102,15 @@ See also
"""
-import urllib
+from six.moves.urllib import parse
+
from bkr.client import BeakerCommand
+
class Group_Modify(BeakerCommand):
- """Modify an existing Group"""
+ """
+ Modify an existing Group
+ """
enabled = True
def options(self):
@@ -184,24 +188,24 @@ class Group_Modify(BeakerCommand):
requests_session = self.requests_session()
for member in add_member:
- url = 'groups/%s/members/' % urllib.quote(group)
+ url = 'groups/%s/members/' % parse.quote(group)
res = requests_session.post(url, json={'user_name': member})
res.raise_for_status()
for member in remove_member:
- url = 'groups/%s/members/' % urllib.quote(group)
+ url = 'groups/%s/members/' % parse.quote(group)
res = requests_session.delete(url, params={'user_name': member})
res.raise_for_status()
if grant_owner:
for member in grant_owner:
- url = 'groups/%s/owners/' % urllib.quote(group)
+ url = 'groups/%s/owners/' % parse.quote(group)
res = requests_session.post(url, json={'user_name': member})
res.raise_for_status()
if revoke_owner:
for member in revoke_owner:
- url = 'groups/%s/owners/' % urllib.quote(group)
+ url = 'groups/%s/owners/' % parse.quote(group)
res = requests_session.delete(url, params={'user_name': member})
res.raise_for_status()
@@ -215,5 +219,5 @@ class Group_Modify(BeakerCommand):
if password:
group_attrs['root_password'] = password
if group_attrs:
- res = requests_session.patch('groups/%s' % urllib.quote(group), json=group_attrs)
+ res = requests_session.patch('groups/%s' % parse.quote(group), json=group_attrs)
res.raise_for_status()
diff --git a/Client/src/bkr/client/commands/cmd_harness_test.py b/Client/src/bkr/client/commands/cmd_harness_test.py
index fcc4574..0770e21 100644
--- a/Client/src/bkr/client/commands/cmd_harness_test.py
+++ b/Client/src/bkr/client/commands/cmd_harness_test.py
@@ -1,4 +1,3 @@
-
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
@@ -21,7 +20,7 @@ Description
Generates a Beaker job to test that the harness can be installed correctly on
all available combinations of distro family, variant, and arch.
-This is intended to catch misconfigurations and missing/incomplete harness
+This is intended to catch misconfiguration and missing/incomplete harness
repos, not find bugs in the harness.
Options
@@ -51,21 +50,18 @@ See also
:manpage:`bkr(1)`
"""
-try:
- any # builtin in Python 2.5+
-except NameError:
- def any(iterable):
- for element in iterable:
- if element:
- return True
- return False
+from __future__ import print_function
import sys
+
from bkr.client import BeakerWorkflow, BeakerJob, BeakerRecipeSet, BeakerRecipe
from bkr.client.task_watcher import watch_tasks
+
class Harness_Test(BeakerWorkflow):
- """Workflow for testing harness installation"""
+ """
+ Workflow for testing harness installation
+ """
enabled = True
def options(self):
@@ -99,11 +95,11 @@ class Harness_Test(BeakerWorkflow):
kwargs['whiteboard'] = 'Test harness installation'
if not families:
- families = self.getOsMajors(**kwargs)
+ families = self.get_os_majors(**kwargs)
# filter out any junky old distros with no family
families = [f for f in families if f]
- fva = set() # all family-variant-arch combinations
+ fva = set() # all family-variant-arch combinations
for family in families:
dts = self.hub.distrotrees.filter({'family': family})
for dt in dts:
@@ -111,28 +107,28 @@ class Harness_Test(BeakerWorkflow):
# if this family has any variants, discard combinations which have blank variant
if any(f == family and v for f, v, a in fva):
fva.difference_update([(f, v, a) for f, v, a in fva
- if f == family and not v])
+ if f == family and not v])
job = BeakerJob(**kwargs)
for family, variant, arch in sorted(fva):
- requestedTasks = self.getTasks(family=family, **kwargs)
+ requestedTasks = self.get_tasks(family=family, **kwargs)
recipe = BeakerRecipe()
- recipe.addBaseRequires(family=family, variant=variant, arch=arch, **kwargs)
+ recipe.add_base_requires(family=family, variant=variant, arch=arch, **kwargs)
arch_node = self.doc.createElement('distro_arch')
arch_node.setAttribute('op', '=')
arch_node.setAttribute('value', arch)
- recipe = self.processTemplate(recipe, requestedTasks, taskParams=taskParams,
- distroRequires=arch_node, arch=arch, family=family,
- allow_empty_recipe=True, **kwargs)
+ recipe = self.process_template(recipe, requestedTasks, taskParams=taskParams,
+ distroRequires=arch_node, arch=arch, family=family,
+ allow_empty_recipe=True, **kwargs)
recipe.whiteboard = ' '.join([family, variant, arch])
recipeset = BeakerRecipeSet(**kwargs)
- recipeset.addRecipe(recipe)
- job.addRecipeSet(recipeset)
+ recipeset.add_recipe(recipe)
+ job.add_recipe_set(recipeset)
jobxml = job.toxml(**kwargs)
if debug:
- print jobxml
+ print(jobxml)
submitted_jobs = []
failed = False
@@ -140,12 +136,12 @@ class Harness_Test(BeakerWorkflow):
if not dryrun:
try:
submitted_jobs.append(self.hub.jobs.upload(jobxml))
- except Exception, ex:
+ print("Submitted: %s" % submitted_jobs)
+ except (KeyboardInterrupt, SystemError):
+ raise
+ except Exception as ex:
failed = True
- print >>sys.stderr, ex
- if not dryrun:
- print "Submitted: %s" % submitted_jobs
+ sys.stderr.write('Exception: %s\n' % ex)
if wait:
- watch_tasks(self.hub, submitted_jobs)
- if failed:
- sys.exit(1)
+ failed |= watch_tasks(self.hub, submitted_jobs)
+ sys.exit(failed)
diff --git a/Client/src/bkr/client/commands/cmd_job_cancel.py b/Client/src/bkr/client/commands/cmd_job_cancel.py
index c74125a..75921a1 100644
--- a/Client/src/bkr/client/commands/cmd_job_cancel.py
+++ b/Client/src/bkr/client/commands/cmd_job_cancel.py
@@ -59,13 +59,15 @@ See also
:manpage:`bkr(1)`
"""
+from __future__ import print_function
from bkr.client import BeakerCommand
-from optparse import OptionValueError
class Job_Cancel(BeakerCommand):
- """Cancel Jobs/Recipes"""
+ """
+ Cancel Jobs/Recipes
+ """
enabled = True
def options(self):
@@ -88,4 +90,4 @@ class Job_Cancel(BeakerCommand):
self.set_hub(**kwargs)
for task in args:
self.hub.taskactions.stop(task, 'cancel', msg)
- print 'Cancelled %s' % task
+ print('Cancelled %s' % task)
diff --git a/Client/src/bkr/client/commands/cmd_job_clone.py b/Client/src/bkr/client/commands/cmd_job_clone.py
index 203b673..9effc95 100644
--- a/Client/src/bkr/client/commands/cmd_job_clone.py
+++ b/Client/src/bkr/client/commands/cmd_job_clone.py
@@ -83,14 +83,21 @@ See also
:manpage:`bkr(1)`
"""
+from __future__ import print_function
+
import sys
+
import lxml.etree
+import six
+
from bkr.client import BeakerCommand
-from optparse import OptionValueError
from bkr.client.task_watcher import *
+
class Job_Clone(BeakerCommand):
- """Clone Jobs/RecipeSets"""
+ """
+ Clone Jobs/RecipeSets
+ """
enabled = True
def options(self):
@@ -122,9 +129,9 @@ class Job_Clone(BeakerCommand):
help="print the jobxml that it would submit, in pretty format",
)
self.parser.add_option(
- '--job-owner', metavar='USERNAME',
- help='Clone job on behalf of USERNAME '
- '(cloning user must be a submission delegate for job owner)',
+ '--job-owner', metavar='USERNAME',
+ help='Clone job on behalf of USERNAME '
+ '(cloning user must be a submission delegate for job owner)',
)
def run(self, *args, **kwargs):
@@ -156,19 +163,22 @@ class Job_Clone(BeakerCommand):
root.set('user', job_owner)
jobxml = lxml.etree.tostring(root, encoding='utf8')
if xml or pretty:
- print lxml.etree.tostring(lxml.etree.fromstring(jobxml),
- pretty_print=pretty,
- xml_declaration=True,
- encoding='utf8')
+ str_xml = lxml.etree.tostring(lxml.etree.fromstring(jobxml),
+ pretty_print=pretty,
+ xml_declaration=True,
+ encoding='utf8')
+ if six.PY3:
+ str_xml = str_xml.decode('utf-8')
+ print(str_xml)
if not dryrun:
- submitted_jobs.append(self.hub.jobs.upload(jobxml))
- except Exception, ex:
- failed = True
+ submitted_jobs.append(self.hub.jobs.upload(
+ jobxml.decode('utf-8') if six.PY3 else jobxml))
+ print("Submitted: %s" % submitted_jobs)
+ except (KeyboardInterrupt, SystemError):
raise
- print >>sys.stderr, ex
- if not dryrun:
- print "Submitted: %s" % submitted_jobs
- if wait:
- watch_tasks(self.hub, submitted_jobs)
- if failed:
- sys.exit(1)
+ except Exception as ex:
+ failed = True
+ sys.stderr.write('Exception: %s\n' % ex)
+ if not dryrun and wait:
+ failed |= watch_tasks(self.hub, submitted_jobs)
+ sys.exit(failed)
diff --git a/Client/src/bkr/client/commands/cmd_job_comment.py b/Client/src/bkr/client/commands/cmd_job_comment.py
index 6217df3..b6c79fa 100644
--- a/Client/src/bkr/client/commands/cmd_job_comment.py
+++ b/Client/src/bkr/client/commands/cmd_job_comment.py
@@ -56,10 +56,12 @@ See also
"""
from bkr.client import BeakerCommand
-from optparse import OptionValueError
+
class Job_Comment(BeakerCommand):
- """Comment on RecipeSet/RecipeTask/RecipeTaskResult"""
+ """
+ Comment on RecipeSet/RecipeTask/RecipeTaskResult
+ """
enabled = True
def options(self):
diff --git a/Client/src/bkr/client/commands/cmd_job_delete.py b/Client/src/bkr/client/commands/cmd_job_delete.py
index 79c02df..c8a3aeb 100644
--- a/Client/src/bkr/client/commands/cmd_job_delete.py
+++ b/Client/src/bkr/client/commands/cmd_job_delete.py
@@ -1,4 +1,3 @@
-
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
@@ -82,11 +81,15 @@ See also
:manpage:`bkr(1)`
"""
+from __future__ import print_function
+
from bkr.client import BeakerCommand
-from optparse import OptionValueError
+
class Job_Delete(BeakerCommand):
- """Delete Jobs in Beaker """
+ """
+ Delete Jobs in Beaker
+ """
enabled = True
def options(self):
@@ -97,7 +100,6 @@ class Job_Delete(BeakerCommand):
help="Family for which the Job is run against"
)
-
self.parser.add_option(
"-t",
"--tag",
@@ -148,13 +150,13 @@ class Job_Delete(BeakerCommand):
)
"""
- def run(self,*args, **kwargs):
- tag = kwargs.pop('tag',None)
+ def run(self, *args, **kwargs):
+ tag = kwargs.pop('tag', None)
product = kwargs.pop('product', None)
complete_days = kwargs.pop('completeDays', None)
- family = kwargs.pop('family',None)
- dryrun = kwargs.pop('dryrun',None)
- #FIXME This is only useful for admins, will enable when we have the admin delete fucntionality
+ family = kwargs.pop('family', None)
+ dryrun = kwargs.pop('dryrun', None)
+ # FIXME This is only useful for admins, will enable when we have the admin delete fucntionality
"""
if user_deleted is True:
if complete_days or tag or family or product or len(args) > 0:
@@ -164,11 +166,22 @@ class Job_Delete(BeakerCommand):
if complete_days is not None and complete_days < 1:
self.parser.error('Please pass a positive integer to completeDays')
- if len(args) < 1 and tag is None and complete_days is None and family is None and product is None:
- self.parser.error('Please specify either a job, recipeset, tag, family, product or complete days')
+ if (len(args) < 1
+ and tag is None
+ and complete_days is None
+ and family is None
+ and product is None
+ ):
+ self.parser.error('Please specify either a job, recipeset, tag, family, product or '
+ 'complete days')
if len(args) > 0:
- if tag is not None or complete_days is not None or family is not None or product is not None:
- self.parser.error('Please either delete by job or tag/complete/family/product, not by both')
+ if (tag is not None
+ or complete_days is not None
+ or family is not None
+ or product is not None
+ ):
+ self.parser.error('Please either delete by job or tag/complete/family/product, '
+ 'not by both')
self.check_taskspec_args(args, permitted_types=['J'])
self.set_hub(**kwargs)
@@ -176,4 +189,4 @@ class Job_Delete(BeakerCommand):
if args:
for job in args:
jobs.append(job)
- print self.hub.jobs.delete_jobs(jobs,tag,complete_days,family,dryrun, product)
+ print(self.hub.jobs.delete_jobs(jobs, tag, complete_days, family, dryrun, product))
diff --git a/Client/src/bkr/client/commands/cmd_job_list.py b/Client/src/bkr/client/commands/cmd_job_list.py
index edfd4d5..7ef186f 100644
--- a/Client/src/bkr/client/commands/cmd_job_list.py
+++ b/Client/src/bkr/client/commands/cmd_job_list.py
@@ -1,4 +1,3 @@
-
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
@@ -114,12 +113,17 @@ See also
:manpage:`bkr(1)`
"""
-from bkr.client import BeakerCommand
+from __future__ import print_function
+
import json
-from optparse import OptionValueError
+
+from bkr.client import BeakerCommand
+
class Job_List(BeakerCommand):
- """List Beaker jobs """
+ """
+ List Beaker jobs
+ """
enabled = True
requires_login = False
@@ -209,7 +213,7 @@ class Job_List(BeakerCommand):
help='Results display format: list, json [default: %default]',
)
- def run(self,*args, **kwargs):
+ def run(self, *args, **kwargs):
family = kwargs.pop('family', None)
tags = kwargs.pop('tag', None)
product = kwargs.pop('product', None)
@@ -226,18 +230,25 @@ class Job_List(BeakerCommand):
minid = kwargs.pop('min_id', None)
maxid = kwargs.pop('max_id', None)
if minid or maxid:
- if minid and minid<=0 or maxid and maxid <= 0:
+ if minid and minid <= 0 or maxid and maxid <= 0:
self.parser.error('Please specify a non zero positive Job ID')
if minid and maxid:
- if maxid < minid:
+ if maxid < minid:
self.parser.error('Max Job ID should be greater than or equal to min Job ID')
if complete_days is not None and complete_days < 1:
self.parser.error('Please pass a positive integer to completeDays')
- if complete_days is None and tags is None and family is None and product is None\
- and owner is None and mine is None and whiteboard is None:
- self.parser.error('Please pass either the completeDays time delta, a tag, product, family, or owner')
+ if (complete_days is None
+ and tags is None
+ and family is None
+ and product is None
+ and owner is None
+ and mine is None
+ and whiteboard is None
+ ):
+ self.parser.error('Please pass either the completeDays time delta, a tag'
+ ', product, family, or owner')
if args:
self.parser.error('This command does not accept any arguments')
@@ -254,20 +265,20 @@ class Job_List(BeakerCommand):
if mine:
self.hub._login()
jobs = self.hub.jobs.filter(dict(tags=tags,
- daysComplete=complete_days,
- family=family,
- product=product,
- owner=owner,
- whiteboard=whiteboard,
- mine=mine,
- minid=minid,
- maxid=maxid,
- limit=limit,
- is_finished=is_finished))
+ daysComplete=complete_days,
+ family=family,
+ product=product,
+ owner=owner,
+ whiteboard=whiteboard,
+ mine=mine,
+ minid=minid,
+ maxid=maxid,
+ limit=limit,
+ is_finished=is_finished))
if format == 'list':
for job_id in jobs:
- print job_id
+ print(job_id)
if format == 'json':
- print json.dumps(jobs)
+ print(json.dumps(jobs))
diff --git a/Client/src/bkr/client/commands/cmd_job_logs.py b/Client/src/bkr/client/commands/cmd_job_logs.py
index 4e4abe4..25b6b26 100644
--- a/Client/src/bkr/client/commands/cmd_job_logs.py
+++ b/Client/src/bkr/client/commands/cmd_job_logs.py
@@ -56,17 +56,21 @@ See also
:manpage:`bkr(1)`
"""
-import sys
+from __future__ import print_function
+
from bkr.client import BeakerCommand
+
class Job_Logs(BeakerCommand):
- """Print URLs of recipe log files"""
+ """
+ Print URLs of recipe log files
+ """
enabled = True
requires_login = False
def options(self):
self.parser.add_option('--size', action='store_true',
- help='Print file size alongside each log file')
+ help='Print file size alongside each log file')
self.parser.usage = "%%prog %s [options] <taskspec>..." % self.normalized_name
def _log_size(self, url):
@@ -74,7 +78,7 @@ class Job_Logs(BeakerCommand):
if response.status_code in (404, 410):
return '<missing>'
elif response.status_code >= 400:
- return '<error:%s>' % (response.status_code)
+ return '<error:%s>' % response.status_code
try:
return '%6d' % int(response.headers['Content-Length'])
except ValueError:
@@ -89,6 +93,6 @@ class Job_Logs(BeakerCommand):
logfiles = self.hub.taskactions.files(task)
for log in logfiles:
if kwargs.get('size'):
- print self._log_size(log['url']), log['url']
+ print(self._log_size(log['url']), log['url'])
else:
- print log['url']
+ print(log['url'])
diff --git a/Client/src/bkr/client/commands/cmd_job_modify.py b/Client/src/bkr/client/commands/cmd_job_modify.py
index 9a229e9..7c4c424 100644
--- a/Client/src/bkr/client/commands/cmd_job_modify.py
+++ b/Client/src/bkr/client/commands/cmd_job_modify.py
@@ -40,7 +40,7 @@ Options
.. option:: --retention-tag <retention_tag>
- Sets the retention tag of a job. Must co-incide with correct product value.
+ Sets the retention tag of a job. Must coincide with correct product value.
Please refer to the job page to see a list of available retention tags.
.. option:: --product <product>
@@ -104,42 +104,50 @@ See also
:manpage:`bkr(1)`
"""
-from bkr.client import BeakerCommand
-from xmlrpclib import Fault
-import requests
-import json
+
+from __future__ import print_function
+
import sys
+import requests
+from six.moves.xmlrpc_client import Fault
+
+from bkr.client import BeakerCommand
+
+
class Job_Modify(BeakerCommand):
- """Modify certain job properties """
+ """
+ Modify certain job properties
+ """
enabled = True
+
def options(self):
self.parser.usage = "%%prog %s [options] <taskspec> ..." % self.normalized_name
self.parser.add_option(
"-r",
"--response",
- help = "Set a job or recipesets response. Valid values are 'ack' or 'nak'",
- )
+ help="Set a job or recipesets response. Valid values are 'ack' or 'nak'",
+ )
self.parser.add_option(
"-t",
"--retention-tag",
- help = "Set a job's retention tag",
- )
+ help="Set a job's retention tag",
+ )
self.parser.add_option(
"-p",
"--product",
- help = "Set a job's product",
- )
+ help="Set a job's product",
+ )
self.parser.add_option(
"--priority",
type='choice',
choices=['Low', 'Medium', 'Normal', 'High', 'Urgent'],
help='Change job priority: Low, Medium, Normal, High, Urgent',
- )
+ )
self.parser.add_option(
'--whiteboard',
help='Set job or recipe whiteboard',
@@ -169,22 +177,22 @@ class Job_Modify(BeakerCommand):
modded.append(taskspec)
if retention_tag or product is not None:
self.hub.jobs.set_retention_product(taskspec, retention_tag,
- product,)
+ product, )
modded.append(taskspec)
if priority:
res = requests_session.patch('recipesets/by-taskspec/%s'
- % taskspec, json={'priority': priority.title()})
+ % taskspec, json={'priority': priority.title()})
res.raise_for_status()
modded.append(taskspec)
if whiteboard:
type, id = taskspec.split(':', 1)
if type == 'J':
res = requests_session.patch('jobs/%s' % id,
- json={'whiteboard': whiteboard})
+ json={'whiteboard': whiteboard})
res.raise_for_status()
elif type == 'R':
res = requests_session.patch('recipes/%s' % id,
- json={'whiteboard': whiteboard})
+ json={'whiteboard': whiteboard})
res.raise_for_status()
modded.append(taskspec)
except (Fault, requests.HTTPError) as e:
@@ -192,9 +200,9 @@ class Job_Modify(BeakerCommand):
error = True
if modded:
modded = set(modded)
- print 'Successfully modified jobs %s' % ' '.join(modded)
+ print('Successfully modified jobs %s' % ' '.join(modded))
else:
- print 'No jobs modified'
+ print('No jobs modified')
if error:
sys.exit(1)
diff --git a/Client/src/bkr/client/commands/cmd_job_results.py b/Client/src/bkr/client/commands/cmd_job_results.py
index 9b08d53..88dd98f 100644
--- a/Client/src/bkr/client/commands/cmd_job_results.py
+++ b/Client/src/bkr/client/commands/cmd_job_results.py
@@ -74,14 +74,18 @@ See also
:manpage:`bkr(1)`
"""
+from __future__ import print_function
+
+import lxml.etree
+import six
from bkr.client import BeakerCommand
-from optparse import OptionValueError
-from bkr.client.task_watcher import *
-from xml.dom.minidom import Document, parseString
+
class Job_Results(BeakerCommand):
- """Get Jobs/Recipes Results"""
+ """
+ Get Jobs/Recipes Results
+ """
enabled = True
requires_login = False
@@ -106,12 +110,11 @@ class Job_Results(BeakerCommand):
help='Omit logs from results XML',
)
-
def run(self, *args, **kwargs):
self.check_taskspec_args(args)
- format = kwargs.pop("format")
- prettyxml = kwargs.pop("prettyxml", None)
+ format = kwargs.pop("format")
+ prettyxml = kwargs.pop("prettyxml", None)
include_logs = kwargs.pop('include_logs')
self.set_hub(**kwargs)
@@ -122,16 +125,19 @@ class Job_Results(BeakerCommand):
# XML is really bytes, the fact that the server is sending the bytes as an
# XML-RPC Unicode string is just a mistake in Beaker's API
myxml = myxml.encode('utf8')
- if prettyxml:
- print parseString(myxml).toprettyxml(encoding='utf8')
- else:
- print myxml
+ str_xml = lxml.etree.tostring(lxml.etree.fromstring(myxml),
+ pretty_print=prettyxml,
+ xml_declaration=prettyxml,
+ encoding='utf-8')
+ if six.PY3: # Decode bytes to string (otherwise print will be broken)
+ str_xml = str_xml.decode('utf-8')
+ print(str_xml)
elif format == 'junit-xml':
type, colon, id = task.partition(':')
if type != 'J':
self.parser.error('JUnit XML format is only available for jobs')
response = requests_session.get('jobs/%s.junit.xml' % id)
response.raise_for_status()
- print response.content
+ print(response.content)
else:
raise RuntimeError('Format %s not implemented' % format)
diff --git a/Client/src/bkr/client/commands/cmd_job_submit.py b/Client/src/bkr/client/commands/cmd_job_submit.py
index cbf3089..100a5aa 100644
--- a/Client/src/bkr/client/commands/cmd_job_submit.py
+++ b/Client/src/bkr/client/commands/cmd_job_submit.py
@@ -100,29 +100,43 @@ See also
:manpage:`bkr(1)`
"""
+from __future__ import print_function
-from bkr.client.task_watcher import *
-from bkr.client.convert import Convert
-from bkr.client import BeakerCommand
-from optparse import OptionValueError
-import lxml.etree
-import pkg_resources
import sys
import xml.dom.minidom
-def combineTag(olddoc, newdoc, tag_name):
+import lxml.etree
+import pkg_resources
+import six
+
+from bkr.client import BeakerCommand
+from bkr.client.convert import Convert
+from bkr.client.task_watcher import *
+
+
+def combine_tag(olddoc, newdoc, tag_name):
# Take the tag from the first olddoc and add it to the newdoc.
tags = olddoc.getElementsByTagName(tag_name)
if tags and not newdoc.getElementsByTagName(tag_name):
newdoc.appendChild(tags[0])
-def combineAttr(olddoc, newdoc, attr_name):
+
+combineTag = combine_tag
+
+
+def combine_attr(olddoc, newdoc, attr_name):
# Take the attr from the first olddoc and set it to the newdoc.
if attr_name not in newdoc._attrs and attr_name in olddoc._attrs:
newdoc.setAttribute(attr_name, olddoc.getAttribute(attr_name))
+
+combineAttr = combine_attr
+
+
class Job_Submit(BeakerCommand):
- """Submit job(s) to scheduler"""
+ """
+ Submit job(s) to scheduler
+ """
enabled = True
def options(self):
@@ -205,7 +219,11 @@ stdin."""
jobxmls = []
for job in jobs:
if job == '-':
- mystring = sys.stdin.read()
+ if six.PY3: # Handle piped non UTF-8 data
+ with open(0, 'rb') as f:
+ mystring = f.read()
+ else:
+ mystring = sys.stdin.read()
else:
mystring = open(job, "r").read()
try:
@@ -223,11 +241,11 @@ stdin."""
combined = xml.dom.minidom.Document().createElement("job")
for jobxml in jobxmls:
doc = xml.dom.minidom.parseString(jobxml)
- combineTag(doc,combined,"whiteboard")
- combineTag(doc,combined,"notify")
- combineAttr(doc.getElementsByTagName("job")[0], combined, "retention_tag")
- combineAttr(doc.getElementsByTagName("job")[0], combined, "product")
- combineAttr(doc.getElementsByTagName("job")[0], combined, "user")
+ combine_tag(doc, combined, "whiteboard")
+ combine_tag(doc, combined, "notify")
+ combine_attr(doc.getElementsByTagName("job")[0], combined, "retention_tag")
+ combine_attr(doc.getElementsByTagName("job")[0], combined, "product")
+ combine_attr(doc.getElementsByTagName("job")[0], combined, "user")
# Add all recipeSet(s) to combined job.
for recipeSet in doc.getElementsByTagName("recipeSet"):
combined.appendChild(recipeSet)
@@ -239,21 +257,21 @@ stdin."""
if convert:
jobxml = Convert.rhts2beaker(jobxml)
if debug or print_xml:
- print jobxml
+ print(jobxml)
try:
job_schema.assertValid(lxml.etree.fromstring(jobxml))
- except Exception, e:
+ except Exception as e:
sys.stderr.write('WARNING: job xml validation failed: %s\n' % e)
if not dryrun:
try:
submitted_jobs.append(self.hub.jobs.upload(jobxml, ignore_missing_tasks))
except (KeyboardInterrupt, SystemExit):
raise
- except Exception, ex:
+ except Exception as ex:
is_failed = True
sys.stderr.write('Exception: %s\n' % ex)
if not dryrun:
- print "Submitted: %s" % submitted_jobs
+ print("Submitted: %s" % submitted_jobs)
if wait:
is_failed |= watch_tasks(self.hub, submitted_jobs)
sys.exit(is_failed)
diff --git a/Client/src/bkr/client/commands/cmd_job_watch.py b/Client/src/bkr/client/commands/cmd_job_watch.py
index 1af3cb7..f2ede9e 100644
--- a/Client/src/bkr/client/commands/cmd_job_watch.py
+++ b/Client/src/bkr/client/commands/cmd_job_watch.py
@@ -63,12 +63,15 @@ See also
"""
import sys
+
from bkr.client import BeakerCommand
-from optparse import OptionValueError
from bkr.client.task_watcher import *
+
class Job_Watch(BeakerCommand):
- """Watch Jobs/Recipes"""
+ """
+ Watch Jobs/Recipes
+ """
enabled = True
requires_login = False
@@ -83,7 +86,6 @@ class Job_Watch(BeakerCommand):
Undetermined behaviour when listening on multiple tasks",
)
-
def run(self, *args, **kwargs):
if not args:
self.parser.error('Please specify one or more tasks')
diff --git a/Client/src/bkr/client/commands/cmd_labcontroller_create.py b/Client/src/bkr/client/commands/cmd_labcontroller_create.py
index 9c5d375..ea12db9 100644
--- a/Client/src/bkr/client/commands/cmd_labcontroller_create.py
+++ b/Client/src/bkr/client/commands/cmd_labcontroller_create.py
@@ -77,10 +77,11 @@ See also
from bkr.client import BeakerCommand
-import json
class LabController_Create(BeakerCommand):
- """Creates a new Lab controller"""
+ """
+ Creates a new Lab controller
+ """
enabled = True
def options(self):
diff --git a/Client/src/bkr/client/commands/cmd_labcontroller_list.py b/Client/src/bkr/client/commands/cmd_labcontroller_list.py
index 2b883a2..672f847 100644
--- a/Client/src/bkr/client/commands/cmd_labcontroller_list.py
+++ b/Client/src/bkr/client/commands/cmd_labcontroller_list.py
@@ -39,24 +39,29 @@ See also
:manpage:`bkr(1)`
"""
+from __future__ import print_function
from bkr.client import BeakerCommand
class LabController_List(BeakerCommand):
- """list labcontrollers"""
+ """
+ List lab controllers
+ """
enabled = True
requires_login = False
def options(self):
self.parser.usage = "%%prog %s" % self.normalized_name
-
def run(self, *args, **kwargs):
self.set_hub(**kwargs)
for lab_controller in self.hub.lab_controllers():
- print lab_controller
+ print(lab_controller)
+
class List_LabControllers(LabController_List):
- """To provide backwards compatibility"""
+ """
+ To provide backwards compatibility
+ """
hidden = True
diff --git a/Client/src/bkr/client/commands/cmd_labcontroller_modify.py b/Client/src/bkr/client/commands/cmd_labcontroller_modify.py
index a12f961..06d5b77 100644
--- a/Client/src/bkr/client/commands/cmd_labcontroller_modify.py
+++ b/Client/src/bkr/client/commands/cmd_labcontroller_modify.py
@@ -89,11 +89,15 @@ See also
"""
-import urllib
+from six.moves.urllib import parse
+
from bkr.client import BeakerCommand
+
class LabController_Modify(BeakerCommand):
- """Modify attributes of an existing lab controller"""
+ """
+ Modify attributes of an existing lab controller
+ """
enabled = True
def options(self):
@@ -133,11 +137,11 @@ class LabController_Modify(BeakerCommand):
if kwargs.pop('create', False):
lc_data['fqdn'] = fqdn
res = requests_session.post('labcontrollers/', json=lc_data)
- # If the lab controller aleady exists, fall back to send a PATCH request.
+ # If the lab controller already exists, fall back to send a PATCH request.
if res.status_code != 409:
update = False
res.raise_for_status()
if update:
- url = 'labcontrollers/%s' % urllib.quote(fqdn, '')
+ url = 'labcontrollers/%s' % parse.quote(fqdn, '')
res = requests_session.patch(url, json=lc_data)
res.raise_for_status()
diff --git a/Client/src/bkr/client/commands/cmd_loan_grant.py b/Client/src/bkr/client/commands/cmd_loan_grant.py
index 0210fb7..5aa91b4 100644
--- a/Client/src/bkr/client/commands/cmd_loan_grant.py
+++ b/Client/src/bkr/client/commands/cmd_loan_grant.py
@@ -68,11 +68,15 @@ See also
:manpage:`bkr(1)`
"""
-import urllib
+from six.moves.urllib import parse
+
from bkr.client import BeakerCommand
+
class Loan_Grant(BeakerCommand):
- """Loan a system to a specific user"""
+ """
+ Loan a system to a specific user
+ """
enabled = True
def options(self):
@@ -97,7 +101,7 @@ class Loan_Grant(BeakerCommand):
comment = kwargs.pop('comment', None)
self.set_hub(**kwargs)
- grant_url = 'systems/%s/loans/' % urllib.quote(fqdn, '')
+ grant_url = 'systems/%s/loans/' % parse.quote(fqdn, '')
data = {'recipient': recipient, 'comment': comment}
requests_session = self.requests_session()
res = requests_session.post(grant_url, json=data)
diff --git a/Client/src/bkr/client/commands/cmd_loan_return.py b/Client/src/bkr/client/commands/cmd_loan_return.py
index 8eb9fbf..a8f736b 100644
--- a/Client/src/bkr/client/commands/cmd_loan_return.py
+++ b/Client/src/bkr/client/commands/cmd_loan_return.py
@@ -53,11 +53,15 @@ See also
:manpage:`bkr(1)`
"""
-import urllib
+from six.moves.urllib import parse
+
from bkr.client import BeakerCommand
+
class Loan_Return(BeakerCommand):
- """Return a previously granted loan"""
+ """
+ Return a previously granted loan
+ """
enabled = True
def options(self):
@@ -69,7 +73,7 @@ class Loan_Return(BeakerCommand):
fqdn = args[0]
self.set_hub(**kwargs)
- update_url = 'systems/%s/loans/+current' % urllib.quote(fqdn, '')
+ update_url = 'systems/%s/loans/+current' % parse.quote(fqdn, '')
requests_session = self.requests_session()
res = requests_session.patch(update_url, json={'finish': 'now'})
res.raise_for_status()
diff --git a/Client/src/bkr/client/commands/cmd_machine_test.py b/Client/src/bkr/client/commands/cmd_machine_test.py
index 45b7ce4..156c74c 100644
--- a/Client/src/bkr/client/commands/cmd_machine_test.py
+++ b/Client/src/bkr/client/commands/cmd_machine_test.py
@@ -64,16 +64,22 @@ See also
:manpage:`bkr(1)`
"""
+from __future__ import print_function
-from bkr.client.task_watcher import *
-from bkr.client import BeakerCommand, BeakerWorkflow, BeakerJob, BeakerRecipeSet, BeakerRecipe
-from optparse import OptionValueError
import sys
-import copy
import xml.dom.minidom
+from bkr.client import BeakerJob
+from bkr.client import BeakerRecipe
+from bkr.client import BeakerRecipeSet
+from bkr.client import BeakerWorkflow
+from bkr.client.task_watcher import *
+
+
class Machine_Test(BeakerWorkflow):
- """Workflow to generate job to test machines"""
+ """
+ Workflow to generate job to test machines
+ """
enabled = True
doc = xml.dom.minidom.Document()
@@ -106,11 +112,11 @@ class Machine_Test(BeakerWorkflow):
def run(self, *args, **kwargs):
self.set_hub(**kwargs)
- debug = kwargs.get("debug", False)
+ debug = kwargs.get("debug", False)
dryrun = kwargs.get("dryrun", False)
wait = kwargs.get("wait", False)
machine = kwargs.get("machine", None)
- families = kwargs.get("family", [])
+ family = kwargs.get("family", [])
taskParams = kwargs.get("taskparam", [])
# Add in Inventory if requested
@@ -124,16 +130,17 @@ class Machine_Test(BeakerWorkflow):
kwargs["whiteboard"] = "Test %s" % machine
# If family is specified on command line just do it.
- if kwargs['family']:
+ if family:
if not kwargs['arches']:
self.parser.error("If family is specified you must specify arches as well")
- families = dict((family, [arch for arch in kwargs['arches']]) for family in kwargs['family'])
+ families = dict(
+ (family, [arch for arch in kwargs['arches']]) for family in family)
else:
- families = self.getSystemOsMajorArches(*args, **kwargs)
+ families = self.get_system_os_major_arches(*args, **kwargs)
# Exit early
if not families:
- print >>sys.stderr, 'Could not find an appropriate distro to provision system with.'
+ sys.stderr.write('Could not find an appropriate distro to provision system with.')
sys.exit(1)
# Create Job
@@ -142,32 +149,32 @@ class Machine_Test(BeakerWorkflow):
for family, arches in families.items():
kwargs['family'] = family
# get all tasks requested
- requestedTasks = self.getTasks(*args, **kwargs)
+ requestedTasks = self.get_tasks(*args, **kwargs)
# If arch is specified on command line limit to just those. (if they match)
if kwargs['arches']:
arches = set(kwargs['arches']).intersection(set(arches))
for arch in arches:
- recipeTemplate = BeakerRecipe()
+ recipeTemplate = BeakerRecipe()
# Add Distro Requirements
temp = dict(kwargs)
temp['family'] = family
- recipeTemplate.addBaseRequires(*args, **temp)
+ recipeTemplate.add_base_requires(*args, **temp)
arch_node = self.doc.createElement('distro_arch')
arch_node.setAttribute('op', '=')
arch_node.setAttribute('value', arch)
- recipeSet = BeakerRecipeSet(**kwargs)
- recipeSet.addRecipe(self.processTemplate(recipeTemplate,
- requestedTasks,
- taskParams=taskParams,
- allow_empty_recipe=True,
- distroRequires=arch_node, **temp))
- job.addRecipeSet(recipeSet)
+ recipe_set = BeakerRecipeSet(**kwargs)
+ recipe_set.add_recipe(self.process_template(recipeTemplate,
+ requestedTasks,
+ taskParams=taskParams,
+ allow_empty_recipe=True,
+ distroRequires=arch_node, **temp))
+ job.add_recipe_set(recipe_set)
# jobxml
jobxml = job.toxml(**kwargs)
if debug:
- print jobxml
+ print(jobxml)
submitted_jobs = []
failed = False
@@ -175,11 +182,11 @@ class Machine_Test(BeakerWorkflow):
if not dryrun:
try:
submitted_jobs.append(self.hub.jobs.upload(jobxml))
- except Exception, ex:
+ except Exception as ex:
failed = True
- print >>sys.stderr, ex
+ sys.stderr.write(ex)
if not dryrun:
- print "Submitted: %s" % submitted_jobs
+ print("Submitted: %s" % submitted_jobs)
if wait:
watch_tasks(self.hub, submitted_jobs)
if failed:
diff --git a/Client/src/bkr/client/commands/cmd_policy_grant.py b/Client/src/bkr/client/commands/cmd_policy_grant.py
index 7073252..3848682 100644
--- a/Client/src/bkr/client/commands/cmd_policy_grant.py
+++ b/Client/src/bkr/client/commands/cmd_policy_grant.py
@@ -1,4 +1,3 @@
-
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
@@ -83,32 +82,36 @@ See also
:manpage:`bkr(1)`, :manpage:`bkr-policy-revoke(1)`
"""
-import urllib
+from six.moves.urllib import parse
+
from bkr.client import BeakerCommand
+
class Policy_Grant(BeakerCommand):
- """Grant permissions in an access policy"""
+ """
+ Grant permissions in an access policy
+ """
enabled = True
def options(self):
self.parser.usage = "%%prog %s <options>" % self.normalized_name
self.parser.add_option('--system', metavar='FQDN',
- help='Modify custom access policy for FQDN')
+ help='Modify custom access policy for FQDN')
self.parser.add_option('--pool', metavar='NAME',
- help='Modify access policy for system pool NAME')
+ help='Modify access policy for system pool NAME')
self.parser.add_option('--permission', metavar='PERMISSION',
- dest='permissions', action='append', default=[],
- help='Grant PERMISSION in policy: '
- 'view, edit_policy, edit_system, loan_any, loan_self, '
- 'control_system, reserve')
+ dest='permissions', action='append', default=[],
+ help='Grant PERMISSION in policy: '
+ 'view, edit_policy, edit_system, loan_any, loan_self, '
+ 'control_system, reserve')
self.parser.add_option('--user', metavar='USERNAME',
- dest='users', action='append', default=[],
- help='Grant permission to USERNAME')
+ dest='users', action='append', default=[],
+ help='Grant permission to USERNAME')
self.parser.add_option('--group', metavar='GROUP',
- dest='groups', action='append', default=[],
- help='Grant permission to GROUP')
+ dest='groups', action='append', default=[],
+ help='Grant permission to GROUP')
self.parser.add_option('--everybody', action='store_true', default=False,
- help='Grant permission to all Beaker users')
+ help='Grant permission to all Beaker users')
def run(self, system=None, pool=None, permissions=None, users=None,
groups=None, everybody=None, *args, **kwargs):
@@ -120,28 +123,28 @@ class Policy_Grant(BeakerCommand):
self.parser.error('Specify at least one permission to grant using --permission')
if not (users or groups or everybody):
self.parser.error('Specify at least one user or group '
- 'to grant permissions to, or --everybody')
+ 'to grant permissions to, or --everybody')
self.set_hub(**kwargs)
requests_session = self.requests_session()
if system:
- rules_url = 'systems/%s/access-policy/rules/' % urllib.quote(system, '')
+ rules_url = 'systems/%s/access-policy/rules/' % parse.quote(system, '')
if pool:
- rules_url = 'pools/%s/access-policy/rules/' % urllib.quote(pool, '')
+ rules_url = 'pools/%s/access-policy/rules/' % parse.quote(pool, '')
for permission in permissions:
for user in users:
res = requests_session.post(rules_url,
- json=dict(permission=permission, user=user,
- group=None, everybody=False))
+ json=dict(permission=permission, user=user,
+ group=None, everybody=False))
res.raise_for_status()
for group in groups:
res = requests_session.post(rules_url,
- json=dict(permission=permission, group=group,
- user=None, everybody=False))
+ json=dict(permission=permission, group=group,
+ user=None, everybody=False))
res.raise_for_status()
if everybody:
res = requests_session.post(rules_url,
- json=dict(permission=permission, user=None, group=None,
- everybody=True))
+ json=dict(permission=permission, user=None, group=None,
+ everybody=True))
res.raise_for_status()
diff --git a/Client/src/bkr/client/commands/cmd_policy_list.py b/Client/src/bkr/client/commands/cmd_policy_list.py
index 72860f2..acca829 100644
--- a/Client/src/bkr/client/commands/cmd_policy_list.py
+++ b/Client/src/bkr/client/commands/cmd_policy_list.py
@@ -82,14 +82,20 @@ See also
:manpage:`bkr(1)`, :manpage:`bkr-policy-list(1)`
"""
-import urllib
-from bkr.client import BeakerCommand
+from __future__ import print_function
+
import json
+
from prettytable import PrettyTable
+from six.moves.urllib import parse
+
+from bkr.client import BeakerCommand
class Policy_List(BeakerCommand):
- """Retrieves policy list"""
+ """
+ Retrieves policy list
+ """
enabled = True
def options(self):
@@ -122,8 +128,8 @@ class Policy_List(BeakerCommand):
rules_group = kwargs.get('group', None)
# only one or none of the filtering criteria must be specified
- if len(filter(lambda x: x,
- [rules_mine, rules_user, rules_group])) > 1:
+ if len(list(filter(lambda x: x,
+ [rules_mine, rules_user, rules_group]))) > 1:
self.parser.error('Only one filtering criteria allowed')
# build the query string for filtering, if any
@@ -139,14 +145,14 @@ class Policy_List(BeakerCommand):
requests_session = self.requests_session()
if kwargs.get('custom', False):
- rules_url = 'systems/%s/access-policy' % urllib.quote(fqdn, '')
+ rules_url = 'systems/%s/access-policy' % parse.quote(fqdn, '')
else:
- rules_url = 'systems/%s/active-access-policy/' % urllib.quote(fqdn, '')
+ rules_url = 'systems/%s/active-access-policy/' % parse.quote(fqdn, '')
res = requests_session.get(rules_url, params=query_string)
res.raise_for_status()
if kwargs['format'] == 'json':
- print res.text
+ print(res.text)
else:
policy_dict = json.loads(res.text)
# setup table
@@ -156,4 +162,4 @@ class Policy_List(BeakerCommand):
table.add_row([col if col else 'X' for col in [rule['permission'],
rule['user'], rule['group'],
everybody_humanreadble]])
- print table.get_string(sortby='Permission')
+ print(table.get_string(sortby='Permission'))
diff --git a/Client/src/bkr/client/commands/cmd_policy_revoke.py b/Client/src/bkr/client/commands/cmd_policy_revoke.py
index 335b84e..1cb63d0 100644
--- a/Client/src/bkr/client/commands/cmd_policy_revoke.py
+++ b/Client/src/bkr/client/commands/cmd_policy_revoke.py
@@ -1,4 +1,3 @@
-
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
@@ -88,32 +87,36 @@ See also
:manpage:`bkr(1)`, :manpage:`bkr-policy-grant(1)`
"""
-import urllib
+from six.moves.urllib import parse
+
from bkr.client import BeakerCommand
+
class Policy_Revoke(BeakerCommand):
- """Revoke permissions in an access policy"""
+ """
+ Revoke permissions in an access policy
+ """
enabled = True
def options(self):
self.parser.usage = "%%prog %s <options>" % self.normalized_name
self.parser.add_option('--system', metavar='FQDN',
- help='Modify custom access policy for FQDN')
+ help='Modify custom access policy for FQDN')
self.parser.add_option('--pool', metavar='NAME',
- help='Modify access policy for system pool NAME')
+ help='Modify access policy for system pool NAME')
self.parser.add_option('--permission', metavar='PERMISSION',
- dest='permissions', action='append', default=[],
- help='Revoke PERMISSION in policy: '
- 'view, edit_policy, edit_system, loan_any, loan_self, '
- 'control_system, reserve')
+ dest='permissions', action='append', default=[],
+ help='Revoke PERMISSION in policy: '
+ 'view, edit_policy, edit_system, loan_any, loan_self, '
+ 'control_system, reserve')
self.parser.add_option('--user', metavar='USERNAME',
- dest='users', action='append', default=[],
- help='Revoke permission for USERNAME')
+ dest='users', action='append', default=[],
+ help='Revoke permission for USERNAME')
self.parser.add_option('--group', metavar='GROUP',
- dest='groups', action='append', default=[],
- help='Revoke permission for GROUP')
+ dest='groups', action='append', default=[],
+ help='Revoke permission for GROUP')
self.parser.add_option('--everybody', action='store_true', default=False,
- help='Revoke permission granted to all Beaker users')
+ help='Revoke permission granted to all Beaker users')
def run(self, system=None, pool=None, permissions=None, users=None,
groups=None, everybody=None, *args, **kwargs):
@@ -125,27 +128,27 @@ class Policy_Revoke(BeakerCommand):
self.parser.error('Specify at least one permission to revoke using --permission')
if not (users or groups or everybody):
self.parser.error('Specify at least one user or group '
- 'to revoke permissions for, or --everybody')
+ 'to revoke permissions for, or --everybody')
self.set_hub(**kwargs)
requests_session = self.requests_session()
if system:
- rules_url = 'systems/%s/access-policy/rules/' % urllib.quote(system, '')
+ rules_url = 'systems/%s/access-policy/rules/' % parse.quote(system, '')
if pool:
- rules_url = 'pools/%s/access-policy/rules/' % urllib.quote(pool, '')
+ rules_url = 'pools/%s/access-policy/rules/' % parse.quote(pool, '')
if users:
res = requests_session.delete(rules_url,
- params=[('permission', p) for p in permissions]
- + [('user', u) for u in users])
+ params=[('permission', p) for p in permissions]
+ + [('user', u) for u in users])
res.raise_for_status()
if groups:
res = requests_session.delete(rules_url,
- params=[('permission', p) for p in permissions]
- + [('group', g) for g in groups])
+ params=[('permission', p) for p in permissions]
+ + [('group', g) for g in groups])
res.raise_for_status()
if everybody:
res = requests_session.delete(rules_url,
- params=[('permission', p) for p in permissions]
- + [('everybody', '1')])
+ params=[('permission', p) for p in permissions]
+ + [('everybody', '1')])
res.raise_for_status()
diff --git a/Client/src/bkr/client/commands/cmd_pool_add.py b/Client/src/bkr/client/commands/cmd_pool_add.py
index adef108..649add1 100644
--- a/Client/src/bkr/client/commands/cmd_pool_add.py
+++ b/Client/src/bkr/client/commands/cmd_pool_add.py
@@ -65,10 +65,15 @@ See also
"""
-import urllib
+from six.moves.urllib import parse
+
from bkr.client import BeakerCommand
+
+
class Pool_Add(BeakerCommand):
- """Adds systems to an existing system pool"""
+ """
+ Adds systems to an existing system pool
+ """
enabled = True
def options(self):
@@ -95,5 +100,5 @@ class Pool_Add(BeakerCommand):
for s in systems:
res = requests_session.post('pools/%s/systems/' %
- urllib.quote(pool), json={'fqdn':s})
+ parse.quote(pool), json={'fqdn':s})
res.raise_for_status()
diff --git a/Client/src/bkr/client/commands/cmd_pool_create.py b/Client/src/bkr/client/commands/cmd_pool_create.py
index 9791568..ae85754 100644
--- a/Client/src/bkr/client/commands/cmd_pool_create.py
+++ b/Client/src/bkr/client/commands/cmd_pool_create.py
@@ -75,10 +75,12 @@ See also
from bkr.client import BeakerCommand
-import json
+
class Pool_Create(BeakerCommand):
- """Creates a system pool"""
+ """
+ Creates a system pool
+ """
enabled = True
def options(self):
diff --git a/Client/src/bkr/client/commands/cmd_pool_delete.py b/Client/src/bkr/client/commands/cmd_pool_delete.py
index 9e833b5..8136d15 100644
--- a/Client/src/bkr/client/commands/cmd_pool_delete.py
+++ b/Client/src/bkr/client/commands/cmd_pool_delete.py
@@ -49,11 +49,15 @@ See also
"""
+from six.moves.urllib import parse
+
from bkr.client import BeakerCommand
-import urllib
+
class Pool_Delete(BeakerCommand):
- """Deletes a system pool"""
+ """
+ Deletes a system pool
+ """
enabled = True
def options(self):
@@ -66,5 +70,5 @@ class Pool_Delete(BeakerCommand):
self.set_hub(**kwargs)
requests_session = self.requests_session()
for pool in args:
- res = requests_session.delete('pools/%s/' % urllib.quote(pool, ''))
+ res = requests_session.delete('pools/%s/' % parse.quote(pool, ''))
res.raise_for_status()
diff --git a/Client/src/bkr/client/commands/cmd_pool_list.py b/Client/src/bkr/client/commands/cmd_pool_list.py
index 21f2057..b58e84c 100644
--- a/Client/src/bkr/client/commands/cmd_pool_list.py
+++ b/Client/src/bkr/client/commands/cmd_pool_list.py
@@ -58,12 +58,17 @@ See also
"""
-from bkr.client import BeakerCommand
+from __future__ import print_function
+
import sys
+from bkr.client import BeakerCommand
+
class Pool_List(BeakerCommand):
- """List pools"""
+ """
+ List pools
+ """
enabled = True
def options(self):
@@ -84,21 +89,22 @@ class Pool_List(BeakerCommand):
owning_group = kwargs.get('owning_group', None)
limit = kwargs.get('limit')
- if len(filter(None, [owner, owning_group])) > 1:
+ if len(list(filter(None, [owner, owning_group]))) > 1:
self.parser.error('Only one of --owner or --owning-group may be specified')
self.set_hub(**kwargs)
requests_session = self.requests_session()
- params = {}
- params['page_size'] = limit
+ params = {'page_size': limit}
if owner:
params['q'] = 'owner.user_name:%s' % owner
elif owning_group:
params['q'] = 'owner.group_name:%s' % owning_group
- response = requests_session.get('pools/', params=params, headers={'Accept': 'application/json'})
+ response = requests_session.get('pools/',
+ params=params,
+ headers={'Accept': 'application/json'})
response.raise_for_status()
attributes = response.json()
pools = attributes['entries']
diff --git a/Client/src/bkr/client/commands/cmd_pool_modify.py b/Client/src/bkr/client/commands/cmd_pool_modify.py
index c184366..701d90e 100644
--- a/Client/src/bkr/client/commands/cmd_pool_modify.py
+++ b/Client/src/bkr/client/commands/cmd_pool_modify.py
@@ -73,8 +73,11 @@ See also
from bkr.client import BeakerCommand
+
class Pool_Modify(BeakerCommand):
- """Modify attributes of an existing system pool"""
+ """
+ Modify attributes of an existing system pool
+ """
enabled = True
def options(self):
diff --git a/Client/src/bkr/client/commands/cmd_pool_remove.py b/Client/src/bkr/client/commands/cmd_pool_remove.py
index 5cc1daf..221a9e8 100644
--- a/Client/src/bkr/client/commands/cmd_pool_remove.py
+++ b/Client/src/bkr/client/commands/cmd_pool_remove.py
@@ -65,11 +65,15 @@ See also
"""
-import urllib
+from six.moves.urllib import parse
+
from bkr.client import BeakerCommand
+
class Pool_Remove(BeakerCommand):
- """Removes systems from a system pool"""
+ """
+ Removes systems from a system pool
+ """
enabled = True
def options(self):
@@ -95,5 +99,5 @@ class Pool_Remove(BeakerCommand):
requests_session = self.requests_session()
for s in systems:
res = requests_session.delete('pools/%s/systems/?fqdn=%s' %
- (urllib.quote(pool), urllib.quote(s)))
+ (parse.quote(pool), parse.quote(s)))
res.raise_for_status()
diff --git a/Client/src/bkr/client/commands/cmd_pool_systems.py b/Client/src/bkr/client/commands/cmd_pool_systems.py
index 6eb74c5..521878e 100644
--- a/Client/src/bkr/client/commands/cmd_pool_systems.py
+++ b/Client/src/bkr/client/commands/cmd_pool_systems.py
@@ -51,11 +51,17 @@ See also
"""
-from bkr.client import BeakerCommand
+from __future__ import print_function
+
import json
+from bkr.client import BeakerCommand
+
+
class Pool_Systems(BeakerCommand):
- """List systems in a pool"""
+ """
+ List systems in a pool
+ """
enabled = True
def options(self):
@@ -84,7 +90,7 @@ class Pool_Systems(BeakerCommand):
systems = attributes['systems']
if kwargs['format'] == 'json':
- print json.dumps(systems)
+ print(json.dumps(systems))
else:
for system in systems:
print(system)
diff --git a/Client/src/bkr/client/commands/cmd_remove_account.py b/Client/src/bkr/client/commands/cmd_remove_account.py
index 76b8321..d6c2909 100644
--- a/Client/src/bkr/client/commands/cmd_remove_account.py
+++ b/Client/src/bkr/client/commands/cmd_remove_account.py
@@ -61,10 +61,14 @@ See also
:manpage:`bkr(1)`
"""
+
from bkr.client import BeakerCommand
+
class Remove_Account(BeakerCommand):
- """Remove user accounts"""
+ """
+ Remove user accounts
+ """
enabled=True
diff --git a/Client/src/bkr/client/commands/cmd_system_create.py b/Client/src/bkr/client/commands/cmd_system_create.py
index ba5fc03..51c0f93 100644
--- a/Client/src/bkr/client/commands/cmd_system_create.py
+++ b/Client/src/bkr/client/commands/cmd_system_create.py
@@ -1,4 +1,3 @@
-
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
@@ -112,59 +111,61 @@ See also
"""
-
from bkr.client import BeakerCommand
-import json
+
class System_Create(BeakerCommand):
- """Creates a system"""
+ """
+ Creates a system
+ """
enabled = True
def options(self):
self.parser.usage = "%%prog %s <options> <fqdn>" % self.normalized_name
- self.parser.add_option('--lab-controller', metavar='FQDN',
- help='Attach the lab controller FQDN to the system')
+ self.parser.add_option('--lab-controller', metavar='FQDN',
+ help='Attach the lab controller FQDN to the system')
self.parser.add_option(
"--arch",
action='append',
default=[],
help="Architecture supported by the system",
- )
+ )
self.parser.add_option('--location',
- help='Physical location of the system')
+ help='Physical location of the system')
self.parser.add_option('--power-type', metavar='TYPE',
- help='Remote power control type')
- self.parser.add_option('--power-address', metavar='ADDRESS',
- help='Address passed to the power control script')
+ help='Remote power control type')
+ self.parser.add_option('--power-address', metavar='ADDRESS',
+ help='Address passed to the power control script')
self.parser.add_option('--power-user', metavar='USERNAME',
- help='Username passed to the power control script')
+ help='Username passed to the power control script')
self.parser.add_option('--power-password', metavar='PASSWORD',
- help='Password passed to the power control script')
+ help='Password passed to the power control script')
self.parser.add_option('--power-id',
- help='Unique identifier passed to the power control script')
+ help='Unique identifier passed to the power control script')
self.parser.add_option('--power-quiescent-period', metavar='SECONDS',
- help='Quiescent period for power control')
+ help='Quiescent period for power control')
self.parser.add_option('--release-action', type='choice',
- choices=['PowerOff', 'LeaveOn', 'ReProvision'],
- help='Action to take whenever a reservation for this system is returned')
- self.parser.add_option('--reprovision-distro-tree', metavar='ID',
- help='Distro tree to be installed when the release action is ReProvision')
+ choices=['PowerOff', 'LeaveOn', 'ReProvision'],
+ help='Action to take whenever a reservation for this system is '
+ 'returned')
+ self.parser.add_option('--reprovision-distro-tree', metavar='ID',
+ help='Distro tree to be installed when the release action is '
+ 'ReProvision')
self.parser.add_option('--condition', type='choice',
- choices=['Automated', 'Manual', 'Broken', 'Removed'],
- help='System Condition: Automated, Manual, Broken, Removed')
+ choices=['Automated', 'Manual', 'Broken', 'Removed'],
+ help='System Condition: Automated, Manual, Broken, Removed')
self.parser.add_option('--host-hypervisor', metavar='TYPE',
- dest='hypervisor',
- help='Type of hypervisor which this system is hosted on')
+ dest='hypervisor',
+ help='Type of hypervisor which this system is hosted on')
def run(self, *args, **kwargs):
if len(args) != 1:
self.parser.error('Exactly one system fqdn must be given')
- system_attrs = {}
- system_attrs['fqdn'] = args[0]
- attrs = ['location', 'power_type','power_address', 'power_user',
- 'power_password', 'power_id', 'power_quiescent_period',
- 'release_action', 'hypervisor']
+ system_attrs = {'fqdn': args[0]}
+ attrs = ['location', 'power_type', 'power_address', 'power_user',
+ 'power_password', 'power_id', 'power_quiescent_period',
+ 'release_action', 'hypervisor']
for attr in attrs:
value = kwargs.pop(attr, None)
if value:
diff --git a/Client/src/bkr/client/commands/cmd_system_delete.py b/Client/src/bkr/client/commands/cmd_system_delete.py
index 63db9a9..19d2034 100644
--- a/Client/src/bkr/client/commands/cmd_system_delete.py
+++ b/Client/src/bkr/client/commands/cmd_system_delete.py
@@ -45,10 +45,15 @@ See also
:manpage:`bkr(1)`
"""
+from __future__ import print_function
+
from bkr.client import BeakerCommand
+
class System_Delete(BeakerCommand):
- """Delete a system"""
+ """
+ Delete a system
+ """
enabled = True
def options(self):
@@ -60,4 +65,4 @@ class System_Delete(BeakerCommand):
fqdn = args[0]
self.set_hub(**kwargs)
- print self.hub.systems.delete(fqdn)
+ print(self.hub.systems.delete(fqdn))
diff --git a/Client/src/bkr/client/commands/cmd_system_details.py b/Client/src/bkr/client/commands/cmd_system_details.py
index b7b44c4..9255da0 100644
--- a/Client/src/bkr/client/commands/cmd_system_details.py
+++ b/Client/src/bkr/client/commands/cmd_system_details.py
@@ -47,13 +47,17 @@ See also
:manpage:`bkr-system-list(1)`, :manpage:`bkr(1)`
"""
-import sys
-import urllib
-import urllib2
+from __future__ import print_function
+
+from six.moves.urllib import parse
+
from bkr.client import BeakerCommand
+
class System_Details(BeakerCommand):
- """Export RDF/XML description of a system"""
+ """
+ Export RDF/XML description of a system
+ """
enabled = True
def options(self):
@@ -64,7 +68,7 @@ class System_Details(BeakerCommand):
self.parser.error('Exactly one system fqdn must be given')
fqdn = args[0]
- system_url = 'view/%s?tg_format=rdfxml' % urllib.quote(fqdn, '')
+ system_url = 'view/%s?tg_format=rdfxml' % parse.quote(fqdn, '')
# This will log us in using XML-RPC
self.set_hub(**kwargs)
@@ -72,4 +76,4 @@ class System_Details(BeakerCommand):
session = self.requests_session()
response = session.get(system_url)
response.raise_for_status()
- print response.text
+ print(response.text)
diff --git a/Client/src/bkr/client/commands/cmd_system_list.py b/Client/src/bkr/client/commands/cmd_system_list.py
index 6b4e137..cc7194b 100644
--- a/Client/src/bkr/client/commands/cmd_system_list.py
+++ b/Client/src/bkr/client/commands/cmd_system_list.py
@@ -1,4 +1,3 @@
-
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
@@ -135,15 +134,22 @@ See also
:manpage:`bkr(1)`
"""
-import sys
+from __future__ import print_function
+
import optparse
-import urllib
-import urllib2
+import sys
+
import lxml.etree
+import six
+from six.moves.urllib import parse
+
from bkr.client import BeakerCommand, host_filter_presets
+
class System_List(BeakerCommand):
- """List systems"""
+ """
+ List systems
+ """
enabled = True
search = dict()
@@ -159,54 +165,54 @@ class System_List(BeakerCommand):
def options(self):
self.parser.usage = "%%prog %s [options]" % self.normalized_name
self.parser.add_option('--available', action='store_const',
- const='available', dest='feed',
- help='Only include systems available to be used by this user')
+ const='available', dest='feed',
+ help='Only include systems available to be used by this user')
self.parser.add_option('--free', action='store_const',
- const='free', dest='feed',
- help='Only include systems available '
- 'to this user and not currently being used')
+ const='free', dest='feed',
+ help='Only include systems available '
+ 'to this user and not currently being used')
self.parser.add_option('--removed', action='store_const',
- const='removed', dest='feed',
- help='Only include systems which have been removed')
+ const='removed', dest='feed',
+ help='Only include systems which have been removed')
self.parser.add_option('--mine', action='store_const',
- const='mine', dest='feed',
- help='Only include systems owned by this user')
+ const='mine', dest='feed',
+ help='Only include systems owned by this user')
self.parser_add_option('--type', metavar='TYPE', table='System/Type',
- help='Only include systems of TYPE')
+ help='Only include systems of TYPE')
self.parser_add_option('--status', metavar='STATUS', table='System/Status',
- help='Only include systems with STATUS')
+ help='Only include systems with STATUS')
self.parser_add_option('--pool', metavar='POOL', table='System/Pools',
- help='Only include systems in POOL')
+ help='Only include systems in POOL')
self.parser_add_option('--group', metavar='GROUP', table='System/Pools',
- help=optparse.SUPPRESS_HELP)
+ help=optparse.SUPPRESS_HELP)
self.parser_add_option('--arch', metavar='ARCH', table='System/Arch',
- help='Only include systems with ARCH')
+ help='Only include systems with ARCH')
self.parser_add_option('--dev-vendor-id', metavar='VENDOR-ID',
- table='Devices/Vendor_id',
- help='only include systems with a device that has VENDOR-ID')
+ table='Devices/Vendor_id',
+ help='only include systems with a device that has VENDOR-ID')
self.parser_add_option('--dev-device-id', metavar='DEVICE-ID',
- table='Devices/Device_id',
- help='only include systems with a device that has DEVICE-ID')
+ table='Devices/Device_id',
+ help='only include systems with a device that has DEVICE-ID')
self.parser_add_option('--dev-sub-vendor-id', metavar='SUBVENDOR-ID',
- table='Devices/Subsys_vendor_id',
- help='only include systems with a device that has SUBVENDOR-ID')
+ table='Devices/Subsys_vendor_id',
+ help='only include systems with a device that has SUBVENDOR-ID')
self.parser_add_option('--dev-sub-device-id', metavar='SUBDEVICE-ID',
- table='Devices/Subsys_device_id',
- help='only include systems with a device that has SUBDEVICE-ID')
+ table='Devices/Subsys_device_id',
+ help='only include systems with a device that has SUBDEVICE-ID')
self.parser_add_option('--dev-driver', metavar='DRIVER',
- table='Devices/Driver',
- help='only include systems with a device that has DRIVER')
+ table='Devices/Driver',
+ help='only include systems with a device that has DRIVER')
self.parser_add_option('--dev-description', metavar='DESCRIPTION',
- table='Devices/Description',
- help='only include systems with a device that has DESCRIPTION')
+ table='Devices/Description',
+ help='only include systems with a device that has DESCRIPTION')
self.parser.add_option('--xml-filter', metavar='XML',
- action='append', default=[],
- help='only include systems matching the given XML filter, '
- 'as in <hostRequires/>')
+ action='append', default=[],
+ help='only include systems matching the given XML filter, '
+ 'as in <hostRequires/>')
self.parser.add_option('--host-filter', metavar='NAME',
- action='append', default=[],
- help='Only include systems matching pre-defined host filter, '
- 'as in bkr workflow-* --host-filter')
+ action='append', default=[],
+ help='Only include systems matching pre-defined host filter, '
+ 'as in bkr workflow-* --host-filter')
self.parser.set_defaults(feed='')
def run(self, *args, **kwargs):
@@ -217,12 +223,12 @@ class System_List(BeakerCommand):
('tg_format', 'atom'),
('list_tgp_limit', 0),
]
- for i, x in enumerate(self.search.iteritems()):
+ for i, x in enumerate(six.iteritems(self.search)):
if kwargs[x[0]]:
qs_args.extend([
('systemsearch-%d.table' % i, x[1]),
('systemsearch-%d.operation' % i, 'is'),
- ('systemsearch-%d.value' % i, kwargs[x[0]])
+ ('systemsearch-%d.value' % i, kwargs[x[0]])
])
xmlsearch = ''.join(kwargs['xml_filter'])
for filter_name in kwargs['host_filter']:
@@ -234,7 +240,7 @@ class System_List(BeakerCommand):
if xmlsearch:
qs_args.append(('xmlsearch', xmlsearch))
- feed_url = '%s?%s' % (kwargs['feed'], urllib.urlencode(qs_args))
+ feed_url = '%s?%s' % (kwargs['feed'], parse.urlencode(qs_args))
# This will log us in using XML-RPC
self.set_hub(**kwargs)
@@ -249,8 +255,11 @@ class System_List(BeakerCommand):
sys.stderr.write('Nothing Matches\n')
sys.exit(1)
for title in titles:
- print title.text.strip()
+ print(title.text.strip())
+
class List_Systems(System_List):
- """To provide backwards compatibility"""
+ """
+ To provide backwards compatibility
+ """
hidden = True
diff --git a/Client/src/bkr/client/commands/cmd_system_modify.py b/Client/src/bkr/client/commands/cmd_system_modify.py
index a68cf60..34612ff 100644
--- a/Client/src/bkr/client/commands/cmd_system_modify.py
+++ b/Client/src/bkr/client/commands/cmd_system_modify.py
@@ -1,4 +1,3 @@
-
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
@@ -82,27 +81,32 @@ See also
:manpage:`bkr(1)`
"""
-import urllib
+from six.moves.urllib import parse
+
from bkr.client import BeakerCommand
+
class System_Modify(BeakerCommand):
- """Modify Beaker system attributes"""
+ """
+ Modify Beaker system attributes
+ """
enabled = True
def options(self):
self.parser.usage = "%%prog %s [options] <fqdn> .." % self.normalized_name
self.parser.add_option('--owner', metavar='USERNAME',
- help='Change owner to USERNAME')
+ help='Change owner to USERNAME')
self.parser.add_option('--condition', type='choice',
- choices=['Automated', 'Manual', 'Broken', 'Removed'],
- help='Change condition: Automated, Manual, Broken, Removed')
+ choices=['Automated', 'Manual', 'Broken', 'Removed'],
+ help='Change condition: Automated, Manual, Broken, Removed')
self.parser.add_option('--host-hypervisor', metavar='TYPE',
- help='Change host hypervisor to TYPE')
+ help='Change host hypervisor to TYPE')
self.parser.add_option('--pool-policy', metavar='POOL',
- help='Change active access policy to the access policy of POOL')
+ help='Change active access policy to the access policy of POOL')
self.parser.add_option('--use-custom-policy', action='store_true', default=None,
- help="Change active access policy to the system's custom access policy")
+ help="Change active access policy to the "
+ "system's custom access policy")
def run(self, *args, **kwargs):
owner = kwargs.pop('owner')
@@ -117,7 +121,7 @@ class System_Modify(BeakerCommand):
self.parser.error('Specify one or more system FQDNs to modify')
if not any(option is not None for option in
- [owner, condition, host_hypervisor, pool, custom_policy]):
+ [owner, condition, host_hypervisor, pool, custom_policy]):
self.parser.error('At least one option is required, specifying what to change')
if pool and custom_policy:
self.parser.error('Only one of --pool-policy or'
@@ -138,6 +142,6 @@ class System_Modify(BeakerCommand):
requests_session = self.requests_session()
for fqdn in args:
- system_update_url = 'systems/%s/' % urllib.quote(fqdn, '')
+ system_update_url = 'systems/%s/' % parse.quote(fqdn, '')
res = requests_session.patch(system_update_url, json=system_attr)
res.raise_for_status()
diff --git a/Client/src/bkr/client/commands/cmd_system_power.py b/Client/src/bkr/client/commands/cmd_system_power.py
index 47b0000..7cacbaf 100644
--- a/Client/src/bkr/client/commands/cmd_system_power.py
+++ b/Client/src/bkr/client/commands/cmd_system_power.py
@@ -1,4 +1,3 @@
-
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
@@ -67,8 +66,11 @@ See also
from bkr.client import BeakerCommand
+
class System_Power(BeakerCommand):
- """Control power for a system"""
+ """
+ Control power for a system
+ """
enabled = True
valid_actions = ('on', 'off', 'interrupt', 'reboot', 'none')
@@ -76,13 +78,14 @@ class System_Power(BeakerCommand):
def options(self):
self.parser.usage = "%%prog %s [options] <fqdn>" % self.normalized_name
self.parser.add_option('--action', metavar='ACTION',
- help='Perform ACTION (on, off, interrupt, or reboot) [default: %default]')
+ help='Perform ACTION (on, off, interrupt, or reboot) '
+ '[default: %default]')
self.parser.add_option('--clear-netboot', action='store_true',
- help="Clear system's netboot configuration "
- "before performing action")
+ help="Clear system's netboot configuration "
+ "before performing action")
self.parser.add_option('--force', action='store_true',
- help='Perform action even if system is '
- 'currently in use by another user')
+ help='Perform action even if system is '
+ 'currently in use by another user')
self.parser.set_defaults(action='reboot', force=False)
def run(self, *args, **kwargs):
@@ -92,13 +95,13 @@ class System_Power(BeakerCommand):
if kwargs['action'] not in self.valid_actions:
self.parser.error('Power action must be one of: %r'
- % (self.valid_actions,))
+ % (self.valid_actions,))
json_data = {
'only_if_current_user_matches': True
}
if kwargs['force']:
- json_data['only_if_current_user_matches'] = False
+ json_data['only_if_current_user_matches'] = False
self.set_hub(**kwargs)
requests_session = self.requests_session()
actions = []
diff --git a/Client/src/bkr/client/commands/cmd_system_provision.py b/Client/src/bkr/client/commands/cmd_system_provision.py
index 2b497ea..61dc0f8 100644
--- a/Client/src/bkr/client/commands/cmd_system_provision.py
+++ b/Client/src/bkr/client/commands/cmd_system_provision.py
@@ -1,4 +1,3 @@
-
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
@@ -113,30 +112,34 @@ See also
:manpage:`bkr-workflow-simple(1)`, :manpage:`bkr-system-power(1)`, :manpage:`bkr(1)`
"""
-import sys
import optparse
+import sys
+
from bkr.client import BeakerCommand
+
class System_Provision(BeakerCommand):
- """Provision a system with a distro"""
+ """
+ Provision a system with a distro
+ """
enabled = True
def options(self):
self.parser.usage = "%%prog %s [options] <fqdn>" % self.normalized_name
self.parser.add_option('--distro-tree', metavar='ID',
- help='Provision distro tree identified by ID')
+ help='Provision distro tree identified by ID')
self.parser.add_option('--distro', help=optparse.SUPPRESS_HELP)
self.parser.add_option('--ks-meta', metavar='OPTS',
- help='Pass OPTS as kickstart metadata')
+ help='Pass OPTS as kickstart metadata')
self.parser.add_option('--kernel-options', metavar='OPTS',
- help='Pass OPTS as kernel options during installation')
+ help='Pass OPTS as kernel options during installation')
self.parser.add_option('--kernel-options-post', metavar='OPTS',
- help='Pass OPTS as kernel options after installation')
+ help='Pass OPTS as kernel options after installation')
self.parser.add_option('--kickstart', metavar='FILENAME',
- help='Read complete kickstart from FILENAME (- for stdin)')
+ help='Read complete kickstart from FILENAME (- for stdin)')
self.parser.add_option('--no-reboot',
- action='store_false', dest='reboot',
- help="Configure installation but don't reboot system")
+ action='store_false', dest='reboot',
+ help="Configure installation but don't reboot system")
self.parser.set_defaults(reboot=True)
def run(self, *args, **kwargs):
@@ -160,5 +163,5 @@ class System_Provision(BeakerCommand):
self.set_hub(**kwargs)
self.hub.systems.provision(fqdn, kwargs['distro_tree'],
- kwargs['ks_meta'], kwargs['kernel_options'],
- kwargs['kernel_options_post'], kickstart, kwargs['reboot'])
+ kwargs['ks_meta'], kwargs['kernel_options'],
+ kwargs['kernel_options_post'], kickstart, kwargs['reboot'])
diff --git a/Client/src/bkr/client/commands/cmd_system_release.py b/Client/src/bkr/client/commands/cmd_system_release.py
index 9ce99e3..5532824 100644
--- a/Client/src/bkr/client/commands/cmd_system_release.py
+++ b/Client/src/bkr/client/commands/cmd_system_release.py
@@ -57,11 +57,15 @@ See also
:manpage:`bkr(1)`
"""
-import urllib
+from six.moves.urllib import parse
+
from bkr.client import BeakerCommand
+
class System_Release(BeakerCommand):
- """Release a reserved system"""
+ """
+ Release a reserved system
+ """
enabled = True
def options(self):
@@ -70,7 +74,7 @@ class System_Release(BeakerCommand):
def run(self, *args, **kwargs):
self.set_hub(**kwargs)
for fqdn in args:
- update_url = 'systems/%s/reservations/+current' % urllib.quote(fqdn, '')
+ update_url = 'systems/%s/reservations/+current' % parse.quote(fqdn, '')
requests_session = self.requests_session()
res = requests_session.patch(update_url, json={'finish_time': 'now'})
res.raise_for_status()
diff --git a/Client/src/bkr/client/commands/cmd_system_reserve.py b/Client/src/bkr/client/commands/cmd_system_reserve.py
index cb9b3fd..27b38f0 100644
--- a/Client/src/bkr/client/commands/cmd_system_reserve.py
+++ b/Client/src/bkr/client/commands/cmd_system_reserve.py
@@ -57,8 +57,11 @@ See also
from bkr.client import BeakerCommand
+
class System_Reserve(BeakerCommand):
- """Reserve a system for manual usage"""
+ """
+ Reserve a system for manual usage
+ """
enabled = True
def options(self):
diff --git a/Client/src/bkr/client/commands/cmd_system_status.py b/Client/src/bkr/client/commands/cmd_system_status.py
index 6775ecb..9bd75a6 100644
--- a/Client/src/bkr/client/commands/cmd_system_status.py
+++ b/Client/src/bkr/client/commands/cmd_system_status.py
@@ -51,13 +51,17 @@ See also
:manpage:`bkr(1)`
"""
-import urllib
-from bkr.client import BeakerCommand
+from __future__ import print_function
+
from json import loads
-class System_Status(BeakerCommand):
+from six.moves.urllib import parse
- enabled=True
+from bkr.client import BeakerCommand
+
+
+class System_Status(BeakerCommand):
+ enabled = True
def options(self):
self.parser.usage = "%%prog %s <options> <fqdn>" % self.normalized_name
@@ -66,7 +70,7 @@ class System_Status(BeakerCommand):
choices=['tabular', 'json'],
default='tabular',
help='Display results in FORMAT: '
- 'tabular, json [default: %default]')
+ 'tabular, json [default: %default]')
def run(self, *args, **kwargs):
if len(args) != 1:
@@ -75,11 +79,11 @@ class System_Status(BeakerCommand):
format = kwargs.get('format')
self.set_hub(**kwargs)
requests_session = self.requests_session()
- status_url = 'systems/%s/status' % urllib.quote(fqdn, '')
+ status_url = 'systems/%s/status' % parse.quote(fqdn, '')
res = requests_session.get(status_url)
res.raise_for_status()
if format == 'json':
- print res.text
+ print(res.text)
else:
system_status = loads(res.text)
condition = system_status.get('condition')
@@ -112,4 +116,4 @@ class System_Status(BeakerCommand):
msg.append('%4sComment: %s' % ('', loan_comment))
else:
msg.append('Current loan: %s' % None)
- print '\n'.join(msg)
+ print('\n'.join(msg))
diff --git a/Client/src/bkr/client/commands/cmd_task_add.py b/Client/src/bkr/client/commands/cmd_task_add.py
index 7bbe094..f40e0f9 100644
--- a/Client/src/bkr/client/commands/cmd_task_add.py
+++ b/Client/src/bkr/client/commands/cmd_task_add.py
@@ -51,22 +51,25 @@ See also
:manpage:`bkr(1)`
"""
+from __future__ import print_function
-from bkr.client.task_watcher import *
-from bkr.client import BeakerCommand
-from optparse import OptionValueError
-import sys
import os.path
-import xmlrpclib
+import sys
+
+from six.moves import xmlrpc_client
+
+from bkr.client import BeakerCommand
+
class Task_Add(BeakerCommand):
- """Add/Update task to scheduler"""
+ """
+ Add/Update task to scheduler
+ """
enabled = True
def options(self):
self.parser.usage = "%%prog %s [options] <taskrpm>..." % self.normalized_name
-
def run(self, *args, **kwargs):
tasks = args
@@ -74,13 +77,13 @@ class Task_Add(BeakerCommand):
failed = False
for task in tasks:
task_name = os.path.basename(task)
- task_binary = xmlrpclib.Binary(open(task, "r").read())
- print task_name
+ task_binary = xmlrpc_client.Binary(open(task, "rb").read())
+ print(task_name)
try:
- print self.hub.tasks.upload(task_name, task_binary)
+ print(self.hub.tasks.upload(task_name, task_binary))
except (KeyboardInterrupt, SystemExit):
raise
- except Exception, ex:
+ except Exception as ex:
failed = True
sys.stderr.write('Exception: %s\n' % ex)
diff --git a/Client/src/bkr/client/commands/cmd_task_details.py b/Client/src/bkr/client/commands/cmd_task_details.py
index cb03a8f..40b0bc1 100644
--- a/Client/src/bkr/client/commands/cmd_task_details.py
+++ b/Client/src/bkr/client/commands/cmd_task_details.py
@@ -52,18 +52,15 @@ See also
:manpage:`bkr(1)`
"""
+from __future__ import print_function
-from bkr.client.task_watcher import *
from bkr.client import BeakerCommand
-from optparse import OptionValueError
-import sys
-import os.path
-import xmlrpclib
-from xml.dom.minidom import Document, parseString
class Task_Details(BeakerCommand):
- """Show details about Task"""
+ """
+ Show details about Task
+ """
enabled = True
def options(self):
@@ -89,7 +86,6 @@ class Task_Details(BeakerCommand):
)
def run(self, *args, **kwargs):
- filter = dict()
xml = kwargs.pop("xml")
prettyxml = kwargs.pop("prettyxml")
valid = True
@@ -99,8 +95,8 @@ class Task_Details(BeakerCommand):
self.set_hub(**kwargs)
for task in args:
if xml:
- print "%s\n%s" % (task, self.hub.tasks.to_xml(task, prettyxml, valid))
+ print("%s\n%s" % (task, self.hub.tasks.to_xml(task, prettyxml, valid)))
elif prettyxml:
- print "%s\n%s" % (task, self.hub.tasks.to_xml(task, prettyxml, valid))
+ print("%s\n%s" % (task, self.hub.tasks.to_xml(task, prettyxml, valid)))
else:
- print "%s %s" % (task, self.hub.tasks.to_dict(task,valid))
+ print("%s %s" % (task, self.hub.tasks.to_dict(task,valid)))
diff --git a/Client/src/bkr/client/commands/cmd_task_list.py b/Client/src/bkr/client/commands/cmd_task_list.py
index 70eae71..4fc0b22 100644
--- a/Client/src/bkr/client/commands/cmd_task_list.py
+++ b/Client/src/bkr/client/commands/cmd_task_list.py
@@ -98,17 +98,18 @@ See also
:manpage:`bkr(1)`
"""
+from __future__ import print_function
-from bkr.client.task_watcher import *
-from bkr.client import BeakerCommand
-from optparse import OptionValueError
import sys
-import os.path
-import xmlrpclib
-from xml.dom.minidom import Document, parseString
+from xml.dom.minidom import Document
+
+from bkr.client import BeakerCommand
+
class Task_List(BeakerCommand):
- """List tasks in Beaker's task library"""
+ """
+ List tasks in Beaker's task library
+ """
enabled = True
def options(self):
@@ -153,13 +154,13 @@ class Task_List(BeakerCommand):
"destructive and unmarked tasks)"),
)
-
def run(self, *args, **kwargs):
filter = dict()
filter['types'] = kwargs.pop("type", None)
filter['packages'] = kwargs.pop("package", None)
filter['distro_name'] = kwargs.pop("distro", None)
filter['valid'] = True
+
# Make sure they didn't specify both destructive and non_destructive.
if not kwargs.get("destructive") or not kwargs.get("non_destructive"):
if kwargs.get("destructive", None):
@@ -174,9 +175,9 @@ class Task_List(BeakerCommand):
xmlparams = doc.createElement('params')
for param in params:
try:
- (key, value) = param.split('=',1)
+ (key, value) = param.split('=', 1)
except ValueError:
- print "Params must be KEY=VALUE %s is not" % param
+ print("Params must be KEY=VALUE %s is not" % param)
sys.exit(1)
xmlparam = doc.createElement('param')
xmlparam.setAttribute('name', '%s' % key)
@@ -187,6 +188,6 @@ class Task_List(BeakerCommand):
xmltask = doc.createElement('task')
xmltask.setAttribute('name', task_dict['name'])
xmltask.appendChild(xmlparams)
- print xmltask.toprettyxml()
+ print(xmltask.toprettyxml())
else:
- print task_dict['name']
+ print(task_dict['name'])
diff --git a/Client/src/bkr/client/commands/cmd_update_inventory.py b/Client/src/bkr/client/commands/cmd_update_inventory.py
index e028442..2f29f31 100644
--- a/Client/src/bkr/client/commands/cmd_update_inventory.py
+++ b/Client/src/bkr/client/commands/cmd_update_inventory.py
@@ -86,16 +86,24 @@ See also
:manpage:`bkr(1)`, :manpage:`bkr-machine-test(1)`,
"""
-import sys
+from __future__ import print_function
+
import cgi
+import sys
from xml.dom.minidom import parseString
+
from requests.exceptions import HTTPError
+
from bkr.client import BeakerCommand
from bkr.client.task_watcher import watch_tasks
+
class Update_Inventory(BeakerCommand):
- """Submits a Inventory job"""
+ """
+ Submits a Inventory job
+ """
enabled = True
+
def options(self):
self.parser.usage = "%%prog %s <fqdn>.." % self.normalized_name
self.parser.add_option(
@@ -139,11 +147,11 @@ class Update_Inventory(BeakerCommand):
failed = False
for fqdn in args:
res = requests_session.post('jobs/+inventory',
- json={'fqdn':fqdn,
- 'dryrun':dryrun})
+ json={'fqdn': fqdn,
+ 'dryrun': dryrun})
try:
res.raise_for_status()
- except HTTPError, e:
+ except HTTPError as e:
sys.stderr.write('HTTP error: %s, %s\n' % (fqdn, e))
content_type, _ = cgi.parse_header(e.response.headers.get(
'Content-Type', ''))
@@ -155,13 +163,13 @@ class Update_Inventory(BeakerCommand):
else:
res_data = res.json()
if xml:
- print res_data['job_xml']
+ print(res_data['job_xml'])
if prettyxml:
- print parseString(res_data['job_xml']).toprettyxml(encoding='utf8')
+ print(parseString(res_data['job_xml']).toprettyxml(encoding='utf8'))
if not dryrun:
submitted_jobs.append(res_data['job_id'])
if not dryrun:
- print "Submitted: %s" % submitted_jobs
+ print("Submitted: %s" % submitted_jobs)
if wait:
failed |= watch_tasks(self.hub, submitted_jobs)
sys.exit(failed)
diff --git a/Client/src/bkr/client/commands/cmd_update_prefs.py b/Client/src/bkr/client/commands/cmd_update_prefs.py
index b97e1ae..73cf53f 100644
--- a/Client/src/bkr/client/commands/cmd_update_prefs.py
+++ b/Client/src/bkr/client/commands/cmd_update_prefs.py
@@ -52,10 +52,12 @@ See also
from bkr.client import BeakerCommand
-import sys
+
class Update_Prefs(BeakerCommand):
- """Update user preferences"""
+ """
+ Update user preferences
+ """
enabled = True
def options(self):
diff --git a/Client/src/bkr/client/commands/cmd_user_modify.py b/Client/src/bkr/client/commands/cmd_user_modify.py
index ace48a4..8b29da0 100644
--- a/Client/src/bkr/client/commands/cmd_user_modify.py
+++ b/Client/src/bkr/client/commands/cmd_user_modify.py
@@ -1,4 +1,3 @@
-
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
@@ -61,15 +60,20 @@ See also
:manpage:`bkr(1)`
"""
-from bkr.client import BeakerCommand
-from xmlrpclib import Fault
+
+from __future__ import print_function
+
from sys import exit
+from bkr.client import BeakerCommand
+
class User_Modify(BeakerCommand):
- """Modify certain user properties"""
+ """
+ Modify certain user properties
+ """
- enabled=True
+ enabled = True
def options(self):
self.parser.usage = "%%prog %s [options]" % self.normalized_name
@@ -92,9 +96,9 @@ class User_Modify(BeakerCommand):
if delegate_to_remove:
self.hub.prefs. \
remove_submission_delegate_by_name(delegate_to_remove)
- print 'Removed submission delegate %s' % delegate_to_remove
+ print('Removed submission delegate %s' % delegate_to_remove)
if delegate_to_add:
self.hub.prefs. \
add_submission_delegate_by_name(delegate_to_add)
- print 'Added submission delegate %s' % delegate_to_add
+ print('Added submission delegate %s' % delegate_to_add)
exit(0)
diff --git a/Client/src/bkr/client/commands/cmd_watchdog_extend.py b/Client/src/bkr/client/commands/cmd_watchdog_extend.py
index 7e30747..5307317 100644
--- a/Client/src/bkr/client/commands/cmd_watchdog_extend.py
+++ b/Client/src/bkr/client/commands/cmd_watchdog_extend.py
@@ -62,12 +62,15 @@ See also
:manpage:`bkr(1)`
"""
-import urllib
+from six.moves.urllib import parse
+
from bkr.client import BeakerCommand
-from optparse import OptionValueError
+
class Watchdog_Extend(BeakerCommand):
- """Extend Recipe's Watchdog"""
+ """
+ Extend Recipe's Watchdog
+ """
enabled = True
def options(self):
@@ -78,11 +81,11 @@ class Watchdog_Extend(BeakerCommand):
)
self.parser.usage = "%%prog %s [options] [<taskspec> | <fqdn>]..." % self.normalized_name
-
def run(self, *args, **kwargs):
extend_by = kwargs.pop("by", None)
if not args:
- self.parser.error('Please either specify one or more <taskspec> arguments or system FQDNs')
+ self.parser.error(
+ 'Please either specify one or more <taskspec> arguments or system FQDNs')
taskspecs = []
systems = []
for arg in args:
@@ -99,10 +102,10 @@ class Watchdog_Extend(BeakerCommand):
requests_session = self.requests_session()
for s in systems:
res = requests_session.post('recipes/by-fqdn/%s/watchdog' %
- urllib.quote(s, ''), json={'kill_time': extend_by})
+ parse.quote(s, ''), json={'kill_time': extend_by})
res.raise_for_status()
for t in taskspecs:
res = requests_session.post('recipes/by-taskspec/%s/watchdog' %
- urllib.quote(t), json={'kill_time': extend_by})
+ parse.quote(t), json={'kill_time': extend_by})
res.raise_for_status()
diff --git a/Client/src/bkr/client/commands/cmd_watchdog_show.py b/Client/src/bkr/client/commands/cmd_watchdog_show.py
index 98dc251..a795f87 100644
--- a/Client/src/bkr/client/commands/cmd_watchdog_show.py
+++ b/Client/src/bkr/client/commands/cmd_watchdog_show.py
@@ -51,12 +51,15 @@ See also
:manpage:`bkr(1)`
"""
+from __future__ import print_function
+
from bkr.client import BeakerCommand
-from optparse import OptionValueError
class Watchdog_Show(BeakerCommand):
- """Display Task's Watchdog"""
+ """
+ Display Task's Watchdog
+ """
enabled = True
requires_login = False
@@ -68,5 +71,5 @@ class Watchdog_Show(BeakerCommand):
self.set_hub(**kwargs)
for task_id in args:
seconds_left = self.hub.recipes.tasks.watchdog(task_id)
- print "%s: %s" % (task_id, seconds_left or 'N/A')
+ print("%s: %s" % (task_id, seconds_left or 'N/A'))
diff --git a/Client/src/bkr/client/commands/cmd_watchdogs_extend.py b/Client/src/bkr/client/commands/cmd_watchdogs_extend.py
index 6e39171..6e7f2a0 100644
--- a/Client/src/bkr/client/commands/cmd_watchdogs_extend.py
+++ b/Client/src/bkr/client/commands/cmd_watchdogs_extend.py
@@ -49,11 +49,15 @@ See also
:manpage:`bkr(1)`
"""
+from __future__ import print_function
+
from bkr.client import BeakerCommand
-from optparse import OptionValueError
+
class Watchdogs_Extend(BeakerCommand):
- """Extend the Watchdog for all Tasks"""
+ """
+ Extend the Watchdog for all Tasks
+ """
enabled = True
def options(self):
@@ -65,9 +69,8 @@ class Watchdogs_Extend(BeakerCommand):
self.parser.usage = "%%prog %s [options]" % self.normalized_name
-
def run(self, *args, **kwargs):
extend_by = kwargs.pop("by", None)
self.set_hub(**kwargs)
- print self.hub.watchdogs.extend(extend_by)
+ print(self.hub.watchdogs.extend(extend_by))
diff --git a/Client/src/bkr/client/commands/cmd_whoami.py b/Client/src/bkr/client/commands/cmd_whoami.py
index c612389..e4e7f9f 100644
--- a/Client/src/bkr/client/commands/cmd_whoami.py
+++ b/Client/src/bkr/client/commands/cmd_whoami.py
@@ -39,8 +39,10 @@ See also
:manpage:`bkr(1)`
"""
+from __future__ import print_function
import json
+
from bkr.client import BeakerCommand
@@ -48,11 +50,9 @@ class WhoAmI(BeakerCommand):
"""Who Am I"""
enabled = True
-
def options(self):
self.parser.usage = "%%prog %s" % self.normalized_name
-
def run(self, *args, **kwargs):
self.set_hub(**kwargs)
requests_session = self.requests_session()
@@ -66,4 +66,4 @@ class WhoAmI(BeakerCommand):
}
if attributes.get('proxied_by_user'):
result['proxied_by_username'] = attributes['proxied_by_user']['user_name']
- print json.dumps(result)
+ print(json.dumps(result))
diff --git a/Client/src/bkr/client/commands/cmd_workflow_installer_test.py b/Client/src/bkr/client/commands/cmd_workflow_installer_test.py
index 65ac7dd..f671c5c 100644
--- a/Client/src/bkr/client/commands/cmd_workflow_installer_test.py
+++ b/Client/src/bkr/client/commands/cmd_workflow_installer_test.py
@@ -12,7 +12,7 @@ Synopsis
--------
:program:`bkr workflow-installer-test` [*workflow options*] [*options*]
-| [:option:`--template` <kickstart_template>]
+| [:option:`--template` <kickstart_template>]
Description
-----------
@@ -65,20 +65,28 @@ See also
:manpage:`bkr(1)`
"""
+from __future__ import print_function
+
import os
-import sys
import string
+import sys
import xml.dom.minidom
+
+from jinja2 import Environment
+from jinja2 import FileSystemLoader
from jinja2.ext import Extension
-from optparse import OptionValueError
-from jinja2 import Environment, FileSystemLoader
-from bkr.client import BeakerCommand, BeakerWorkflow, BeakerJob, BeakerRecipeSet, BeakerRecipe
+
+from bkr.client import BeakerJob
+from bkr.client import BeakerRecipe
+from bkr.client import BeakerRecipeSet
+from bkr.client import BeakerWorkflow
+
class SkipBlockExtension(Extension):
"""
- Jinja2 extenstion to skip template blocks.
- To use add it to Environment and set
- env.skip_blocks to a list of block names to skip.
+ Jinja2 extension to skip template blocks.
+ To use add it to Environment and set
+ env.skip_blocks to a list of block names to skip.
"""
def __init__(self, environment):
@@ -91,48 +99,47 @@ class SkipBlockExtension(Extension):
in_endblock = False
for token in stream:
- if (token.type == 'block_begin'):
- if (stream.current.value == 'block'):
+ if token.type == 'block_begin':
+ if stream.current.value == 'block':
block_level += 1
- if (stream.look().value in self.environment.skip_blocks):
+ if stream.look().value in self.environment.skip_blocks:
skip_level = block_level
- if (token.value == 'endblock' ):
+ if token.value == 'endblock':
in_endblock = True
if skip_level == 0:
yield token
- if (token.type == 'block_end'):
+ if token.type == 'block_end':
if in_endblock:
in_endblock = False
block_level -= 1
- if skip_level == block_level+1:
+ if skip_level == block_level + 1:
skip_level = 0
-
def generateKickstart(template_file, args):
"""
Generate ks.cfg from template while skipping
blocks named 'kernel_options'.
"""
- abs_path = os.path.abspath(template_file)
- dir_name = os.path.dirname(abs_path)
+ abs_path = os.path.abspath(template_file)
+ dir_name = os.path.dirname(abs_path)
base_name = os.path.basename(abs_path)
env = Environment(
- loader=FileSystemLoader(dir_name),
- extensions = [SkipBlockExtension],
- cache_size = 0,
- line_comment_prefix = '##',
- )
- env.skip_blocks.append('kernel_options') #pylint: disable=no-member
+ loader=FileSystemLoader(dir_name),
+ extensions=[SkipBlockExtension],
+ cache_size=0,
+ line_comment_prefix='##',
+ )
+ env.skip_blocks.append('kernel_options') # pylint: disable=no-member
template = env.get_template(base_name)
- return "\n"+template.render(args)
+ return "\n" + template.render(args)
def generateKernelOptions(template_file, args):
@@ -141,19 +148,19 @@ def generateKernelOptions(template_file, args):
'kernel_options' block if it exists.
"""
- abs_path = os.path.abspath(template_file)
- dir_name = os.path.dirname(abs_path)
+ abs_path = os.path.abspath(template_file)
+ dir_name = os.path.dirname(abs_path)
base_name = os.path.basename(abs_path)
env = Environment(
- loader=FileSystemLoader(dir_name),
- cache_size = 0,
- line_comment_prefix = '##',
- )
+ loader=FileSystemLoader(dir_name),
+ cache_size=0,
+ line_comment_prefix='##',
+ )
template = env.get_template(base_name)
lines = []
- if template.blocks.has_key('kernel_options'):
+ if 'kernel_options' in template.blocks:
# there is a {% block kernel_options %} defined
for line in template.blocks['kernel_options'](template.new_context(args)):
lines.append(line.strip())
@@ -173,7 +180,9 @@ def generateKernelOptions(template_file, args):
class Workflow_Installer_Test(BeakerWorkflow):
- """Workflow which generates kickstart configuration based on Jinja2 templates"""
+ """
+ Workflow which generates kickstart configuration based on Jinja2 templates
+ """
enabled = True
doc = xml.dom.minidom.Document()
@@ -184,7 +193,7 @@ class Workflow_Installer_Test(BeakerWorkflow):
"--template",
default="",
help="Kickstart template to use for installation"
- )
+ )
self.parser.usage = "%%prog %s [options]" % self.normalized_name
def _get_os_major_version(self, os_major):
@@ -194,9 +203,10 @@ class Workflow_Installer_Test(BeakerWorkflow):
return os_major
def run(self, *args, **kwargs):
- sys.stderr.write('workflow-installer-test is deprecated, use --kickstart with workflow-simple or any other workflow command')
+ sys.stderr.write('workflow-installer-test is deprecated, use '
+ '--kickstart with workflow-simple or any other workflow command')
- debug = kwargs.get("debug", False)
+ debug = kwargs.get("debug", False)
dryrun = kwargs.get("dryrun", False)
family = kwargs.get("family", None)
distro = kwargs.get("distro", None)
@@ -219,21 +229,20 @@ class Workflow_Installer_Test(BeakerWorkflow):
if family:
kwargs['os_major'] = int(self._get_os_major_version(family))
- else: # get family data based on distro
- if not hasattr(self,'hub'):
+ else: # get family data based on distro
+ if not hasattr(self, 'hub'):
self.set_hub(**kwargs)
# this will return info about all arches and variants
# but the family string is the same so break after the first iteration
for distro in self.hub.distrotrees.filter({'name': distro, 'family': family}):
- family = distro["distro_osmajor"] # e.g. RedHatEnterpriseLinux6
- os_version = distro["distro_osversion"] # e.g. RedHatEnterpriseLinux6.3
+ family = distro["distro_osmajor"] # e.g. RedHatEnterpriseLinux6
+ os_version = distro["distro_osversion"] # e.g. RedHatEnterpriseLinux6.3
kwargs["os_major"] = int(self._get_os_major_version(family))
kwargs["family"] = family
kwargs["os_minor"] = int(os_version.replace(family, "").replace(".", ""))
break
-
# Add kickstart and kernel options
ks_args = {}
@@ -250,7 +259,7 @@ class Workflow_Installer_Test(BeakerWorkflow):
ks_args[name.upper()] = value
ks_args[name] = value
except:
- print >>sys.stderr, "Every task param has to have a value."
+ sys.stderr.write("Every task param has to have a value.")
sys.exit(1)
if kwargs['kernel_options'] is None:
@@ -286,29 +295,29 @@ class Workflow_Installer_Test(BeakerWorkflow):
recipeSet = BeakerRecipeSet(**kwargs)
if self.multi_host:
for i in range(self.n_servers):
- recipeSet.addRecipe(self.processTemplate(recipeTemplate,
+ recipeSet.addRecipe(self.processTemplate(recipeTemplate,
requestedTasks,
taskParams=taskParams,
- distroRequires=arch_node,
+ distroRequires=arch_node,
role='SERVERS',
arch=arch,
**kwargs))
for i in range(self.n_clients):
- recipeSet.addRecipe(self.processTemplate(recipeTemplate,
+ recipeSet.addRecipe(self.processTemplate(recipeTemplate,
requestedTasks,
taskParams=taskParams,
- distroRequires=arch_node,
+ distroRequires=arch_node,
role='CLIENTS',
arch=arch,
**kwargs))
job.addRecipeSet(recipeSet)
else:
recipe = self.processTemplate(recipeTemplate,
- requestedTasks,
- taskParams=taskParams,
- distroRequires=arch_node,
- arch=arch,
- **kwargs)
+ requestedTasks,
+ taskParams=taskParams,
+ distroRequires=arch_node,
+ arch=arch,
+ **kwargs)
recipeSet.addRecipe(recipe)
job.addRecipeSet(recipeSet)
@@ -316,15 +325,15 @@ class Workflow_Installer_Test(BeakerWorkflow):
jobxml = job.toxml(**kwargs)
if debug:
- print jobxml
+ print(jobxml)
if not dryrun:
- if not hasattr(self,'hub'):
+ if not hasattr(self, 'hub'):
self.set_hub(**kwargs)
try:
job = self.hub.jobs.upload(jobxml)
- except Exception, ex:
- print >>sys.stderr, unicode(ex)
+ except Exception as ex:
+ sys.stderr.write(ex)
sys.exit(1)
else:
- print "Submitted: %s" % job
+ print("Submitted: %s" % job)
diff --git a/Client/src/bkr/client/commands/cmd_workflow_simple.py b/Client/src/bkr/client/commands/cmd_workflow_simple.py
index 0e944d1..a6edeba 100644
--- a/Client/src/bkr/client/commands/cmd_workflow_simple.py
+++ b/Client/src/bkr/client/commands/cmd_workflow_simple.py
@@ -54,16 +54,24 @@ See also
:manpage:`bkr(1)`
"""
+from __future__ import print_function
-from bkr.client.task_watcher import *
-from bkr.client import BeakerCommand, BeakerWorkflow, BeakerJob, BeakerRecipeSet, BeakerRecipe
-import xmlrpclib
-from optparse import OptionValueError
import sys
import xml.dom.minidom
+from six.moves.xmlrpc_client import Fault
+
+from bkr.client import BeakerJob
+from bkr.client import BeakerRecipe
+from bkr.client import BeakerRecipeSet
+from bkr.client import BeakerWorkflow
+from bkr.client.task_watcher import *
+
+
class Workflow_Simple(BeakerWorkflow):
- """Simple workflow to generate job to scheduler"""
+ """
+ Simple workflow to generate job to scheduler
+ """
enabled = True
doc = xml.dom.minidom.Document()
@@ -94,15 +102,15 @@ class Workflow_Simple(BeakerWorkflow):
if not arches:
# Get default arches that apply for this distro/family
- arches = self.getArches(*args, **kwargs)
+ arches = self.get_arches(*args, **kwargs)
# get all tasks requested
try:
- requestedTasks = self.getTasks(*args, **kwargs)
- except xmlrpclib.Fault:
- requestedTasks = None
+ requested_tasks = self.get_tasks(*args, **kwargs)
+ except Fault:
+ requested_tasks = None
- if not requestedTasks:
+ if not requested_tasks:
sys.stderr.write("No tasks match the specified option(s)\n")
sys.exit(1)
@@ -110,61 +118,58 @@ class Workflow_Simple(BeakerWorkflow):
job = BeakerJob(*args, **kwargs)
# Create Base Recipe
- recipeTemplate = BeakerRecipe()
+ recipe_template = BeakerRecipe()
# Add Distro Requirements
- recipeTemplate.addBaseRequires(*args, **kwargs)
+ recipe_template.add_base_requires(*args, **kwargs)
# Add Host Requirements
-
-
for arch in arches:
arch_node = self.doc.createElement('distro_arch')
arch_node.setAttribute('op', '=')
arch_node.setAttribute('value', arch)
- recipeSet = BeakerRecipeSet(**kwargs)
+ recipe_set = BeakerRecipeSet(**kwargs)
if self.multi_host:
for i in range(self.n_servers):
- recipeSet.addRecipe(self.processTemplate(recipeTemplate,
- requestedTasks,
- taskParams=taskParams,
- distroRequires=arch_node,
- role='SERVERS',
- arch=arch,
- **kwargs))
+ recipe_set.add_recipe(self.process_template(recipe_template,
+ requested_tasks,
+ taskParams=taskParams,
+ distroRequires=arch_node,
+ role='SERVERS',
+ arch=arch,
+ **kwargs))
for i in range(self.n_clients):
- recipeSet.addRecipe(self.processTemplate(recipeTemplate,
- requestedTasks,
- taskParams=taskParams,
- distroRequires=arch_node,
- role='CLIENTS',
- arch=arch,
- **kwargs))
+ recipe_set.add_recipe(self.process_template(recipe_template,
+ requested_tasks,
+ taskParams=taskParams,
+ distroRequires=arch_node,
+ role='CLIENTS',
+ arch=arch,
+ **kwargs))
else:
- recipeSet.addRecipe(self.processTemplate(recipeTemplate,
- requestedTasks,
- taskParams=taskParams,
- distroRequires=arch_node,
- arch=arch,
- **kwargs))
- job.addRecipeSet(recipeSet)
+ recipe_set.add_recipe(self.process_template(recipe_template,
+ requested_tasks,
+ taskParams=taskParams,
+ distroRequires=arch_node,
+ arch=arch,
+ **kwargs))
+ job.add_recipe_set(recipe_set)
# jobxml
jobxml = job.toxml(**kwargs)
if debug:
- print jobxml
+ print(jobxml)
submitted_jobs = []
is_failed = False
-
if not dryrun:
try:
submitted_jobs.append(self.hub.jobs.upload(jobxml))
- print "Submitted: %s" % submitted_jobs
+ print("Submitted: %s" % submitted_jobs)
except (KeyboardInterrupt, SystemExit):
raise
- except Exception, ex:
+ except Exception as ex:
is_failed = True
sys.stderr.write('Exception: %s\n' % ex)
if wait:
diff --git a/Client/src/bkr/client/commands/cmd_workflow_xslt.py b/Client/src/bkr/client/commands/cmd_workflow_xslt.py
index 26b59f4..9ea2302 100644
--- a/Client/src/bkr/client/commands/cmd_workflow_xslt.py
+++ b/Client/src/bkr/client/commands/cmd_workflow_xslt.py
@@ -5,27 +5,44 @@
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
-from bkr.client.task_watcher import *
-from bkr.client import BeakerCommand
+import os
+import re
+import sys
from optparse import OptionValueError, OptionGroup
-import ConfigParser
-import sys, os, re
-import libxml2, libxslt
+
+from lxml import etree
+from six.moves import configparser
+
+from bkr.client import BeakerCommand
+from bkr.client.task_watcher import *
+
+
+def get_node_text(node):
+ """
+ Always return string representation for node text
+ """
+ if not node.text:
+ return ''
+ return node.text
class JobArguments(object):
- "Internal object for the JobConfiguration class - A simple argument container"
+ """
+ Internal object for the JobConfiguration class - A simple argument container
+ """
def __init__(self):
- "Constructor"
self.arguments = {}
self.current = None
self.current_node = None
self.re_tag = re.compile('(.*)(\[@(.*)=(.*)\])')
- def AddArgument(self, name, argtype, tagname, tagvaltype, tagvalname, tagnamelmnt,
- value, optional):
- "Registers a new argument with a unique name"
+ def add_argument(self, name, argtype, tagname, tagvaltype, tagvalname,
+ tagnamelmnt, value, optional):
+ """
+ Registers a new argument with a unique name
+ """
+
self.arguments[name] = {'argtype': argtype,
'optional': optional,
'tagname': tagname,
@@ -35,117 +52,133 @@ class JobArguments(object):
'value': value,
'processed': False}
+ def get_argument_keys(self):
+ """
+ Returns all the unique argument keys
+ """
- def GetArgumentKeys(self):
- "Returns all the unique argument keys"
return self.arguments.keys()
-
- def GetNextArgumentOnTag(self, tagname):
- """Returns an argument setup for a specific XML tag which has
-not yet been processed. When all arguments for the given
-tagname are processed, None will be returned"""
+ def get_next_argument_on_tag(self, tag_name):
+ """
+ Returns an argument setup for a specific XML tag which has
+ not yet been processed. When all arguments for the given
+ tag_name are processed, None will be returned
+ """
for key in self.arguments.keys():
- if self.arguments[key]['processed'] is False and self.arguments[key]['tagname'] == tagname:
+ if (self.arguments[key]['processed'] is False
+ and self.arguments[key]['tagname'] == tag_name):
self.current = key
return self.arguments[key]
return None
-
- def CreateTag(self, key):
- """Creates an libxml2.xmlNode for a given argument key. It
-returns the tag name and the created xmlNode."""
+ def create_tag(self, key):
+ """
+ Creates an etree.Element for a given argument key.
+ It returns the tag name and the created Element.
+ """
tagname = self.arguments[key]['tagname']
rxp = self.re_tag.match(tagname)
if rxp:
res = rxp.groups()
if len(res) != 4:
- raise Exception("Error in <tag/> - value '%s' is not parseable" % tagname)
+ raise Exception("Error in <tag/> - value '%s' is not parsable" % tagname)
- tagnode = libxml2.newNode(res[0])
- tagnode.newProp(res[2].strip("\"'"), res[3].strip("\"'"))
+ tagnode = etree.Element(res[0])
+ tagnode.set(res[2].strip("\"'"), res[3].strip("\"'"))
else:
- tagnode = libxml2.newNode(tagname)
-
- self.current_node = tagnode;
- return (tagname, tagnode)
+ tagnode = etree.Element(tagname)
+ self.current_node = tagnode
+ return tagname, tagnode
- def CreateChildTag(self, key):
- """Create a child node for a node created by CreateTag(), used by lists"""
+ def create_child_tag(self, key):
+ """
+ Create a child node for a node created by create_tag(), used by lists
+ """
tagname = self.arguments[key]['tagname_childs']
- tagnode = libxml2.newNode(tagname)
- self.current_node.addChild(tagnode)
- return tagnode
-
-
- def IsValid(self, key):
- "Returns True if the argument has a value if it is not an optional argument"
- return not (self.arguments[key]['optional'] is False and self.arguments[key]['value'] is None)
+ tagnode = etree.Element(tagname)
+ self.current_node.append(tagnode)
+ return tagnode
- def IsProcessed(self, key):
- "Returns True if the argument has been processed"
+ def is_valid(self, key):
+ """
+ Returns True if the argument has a value if it is not an optional argument
+ """
+ return not (self.arguments[key]['optional'] is False
+ and self.arguments[key]['value'] is None)
+
+ def is_processed(self, key):
+ """
+ Returns True if the argument has been processed
+ """
return self.arguments[key]['processed']
-
- def SetValue(self, key, value):
- "Sets a new value for an argument"
+ def set_value(self, key, value):
+ """
+ Sets a new value for an argument
+ """
self.arguments[key]['value'] = value
-
- def SetProcessed(self):
- "Marks the argument returned by GetNextArgumentOnTag() as processed"
+ def set_processed(self):
+ """
+ Marks the argument returned by get_next_argument_on_tag() as processed
+ """
self.arguments[self.current]['processed'] = True
-
- def PrintArguments(self):
- "Dumps the registered arguments which are set to stdout"
- print ' Job arguments:'
+ def print_arguments(self):
+ """
+ Dumps the registered arguments which are set to stdout
+ """
+ print(' Job arguments:')
for key in self.arguments.keys():
if self.arguments[key]['value'] is not None:
- print ' - %s: %s' % (key, self.arguments[key]['value'])
-
+ print(' - %s: %s' % (key, self.arguments[key]['value']))
class JobConfig(object):
- "Class which parses the job configuration XML and validates the script arguments parameters"
+ """
+ Class which parses the job configuration XML and validates the script arguments parameters
+ """
def __init__(self, argpars, jobdefaults):
- "Constructor - Needs a optparse.OptionParser object and a JobDefaults object"
-
- self.argpars = argpars
- self.grparser = OptionGroup(argpars, 'XSLT Job Config Specific Options',
- 'These options are specific to and defined in '\
- 'the Job Configuration XML')
- self.defaults = jobdefaults
-
- self.jobargs = JobArguments()
- self.internalxml = None
- self.beakerxml = None
- self.whiteboard = None
- self.savexml = None
- self.saveintxml = None
- self.xslt = None
- self.xsltfile = None
- self.xslt_nodes = None
+ """
+ Needs a optparse.OptionParser object and a JobDefaults object
+ """
+
+ self.argpars = argpars
+ self.grparser = OptionGroup(argpars, 'XSLT Job Config Specific Options',
+ 'These options are specific to and defined in '
+ 'the Job Configuration XML')
+ self.defaults = jobdefaults
+
+ self.jobargs = JobArguments()
+ self.internalxml = None
+ self.beakerxml = None
+ self.whiteboard = None
+ self.savexml = None
+ self.saveintxml = None
+ self.xslt = None
+ self.xsltfile = None
+ self.xslt_nodes = None
self.xslt_override = None
- self.xslt_name = None
+ self.xslt_name = None
self.__jobxml_parsed = False
# Add global job options
globgrp = OptionGroup(argpars, 'Global XSLT Workflow Options',
- 'Generic options for the parser and Beaker XML generator')
+ 'Generic options for the parser and Beaker XML generator')
globgrp.add_option('--xslt-override', None, dest='xslt_override',
type='string', default=None, metavar='FILENAME', action='store',
help='Override the XSLT file to use')
globgrp.add_option('-X', '--xslt-name', dest='xslt_name',
type='string', default=None, metavar='NAME', action='store',
help='Use another named XSLT template defined in the job XML')
- globgrp.add_option('-W','--whiteboard', dest='whiteboard',
+ globgrp.add_option('-W', '--whiteboard', dest='whiteboard',
type='string', default=None, metavar='TEXT', action='store',
help='Whiteboard text for the job')
globgrp.add_option('--save-xml', None, dest='savexml',
@@ -156,9 +189,10 @@ class JobConfig(object):
help='Save raw submission XML to FILENAME')
argpars.add_option_group(globgrp)
-
- def parserCB_ParseJobXML(self, option, opt_str, value, parser):
- "Callback function used by optparse for parsing the given job XML file"
+ def parse_job_xml_callback(self, option, opt_str, value, parser):
+ """
+ Callback function used by optparse for parsing the given job XML file
+ """
# Check that not more --job-xml arguments are used
if parser.values.jobxml is not None:
@@ -169,41 +203,43 @@ class JobConfig(object):
# Parse the Job XML
try:
- self.ParseJobXML(parser, value)
+ self.parse_job_xml(parser, value)
setattr(parser.values, option.dest, value)
- self.defaults.parserCBhelper_jobxml_parsed()
- except Exception, e:
+ self.defaults.job_xml_parsed_callback()
+ except Exception as e:
# Re-throw the exception as optparse.OptionValueError instead
raise OptionValueError(str(e))
-
- def ParseJobXML(self, parser, jobxml):
- "Parses the given --job-xml file and extends the group option parser with the configured arguments"
+ def parse_job_xml(self, parser, jobxml):
+ """
+ Parses the given --job-xml file and extends the group
+ option parser with the configured arguments
+ """
if jobxml is None:
raise Exception('No Job XML file given')
try:
- jobcfg = libxml2.parseFile(jobxml)
- except Exception, e:
+ job_cfg = etree.parse(jobxml)
+ except Exception as e:
raise Exception('Failed to parse Job XML (%s): %s' % (jobxml, str(e)))
# Extract job config name and XSLT file to use
- xp = jobcfg.xpathNewContext()
try:
- self.name = xp.xpathEval('/jobConfig/name')[0].get_content()
- self.xslt_nodes = xp.xpathEval('/jobConfig/xslt')
- if self.xslt_nodes is None or len(self.xslt_nodes) < 1:
+ self.name = get_node_text(job_cfg.xpath('/jobConfig/name')[0])
+ self.xslt_nodes = job_cfg.xpath('/jobConfig/xslt')
+ if len(self.xslt_nodes) < 1:
raise Exception('Job XML (%s) is missing <xslt/> tag(s)' % jobxml)
except IndexError:
+ # TODO : This raise can be removed (possibly)
+ # missing <xslt> is raised before this line
raise Exception('Job XML (%s) is missing <name/> and/or <xslt/> tags' % jobxml)
# Parse /jobConfig/arguments/arg tags
- arguments = {}
- jobdefaults = self.defaults.GetJobDefaults(jobxml)
+ jobdefaults = self.defaults.get_job_defaults(jobxml)
- for xnode in xp.xpathEval('/jobConfig/arguments/arg'):
- if xnode.name != 'arg' or xnode.prop('section') != 'recipe':
+ for xnode in job_cfg.xpath('/jobConfig/arguments/arg'):
+ if xnode.attrib.get('section') != 'recipe':
continue
# Extract information from the <arg section='recipe' [type='xxxx'] [optional='1']/> tag
@@ -216,68 +252,70 @@ class JobConfig(object):
# - optional: Will mark the argument as optional if set to 1. If not optional
# the option parser will complain about missing options to the command line
#
- argtype = xnode.prop('type') or 'string'
- argoptional = xnode.prop('optional') == '1'
- argaction = (argtype == 'bool') and 'store_true' or 'store'
- arglong = None
- argshort = None
- argmetavar = None
- argdefault = None
- tagname = None
- tagvaltype = None
+ argtype = xnode.attrib.get('type') or 'string'
+ argoptional = xnode.attrib.get('optional') == '1'
+ argaction = (argtype == 'bool') and 'store_true' or 'store'
+ arglong = None
+ argshort = None
+ argmetavar = None
+ argdefault = None
+ tagname = None
+ tagvaltype = None
tagnamelmnt = None
- descr = None
+ descr = None
# Parse the <arg/> children nodes
- node = xnode.children.next
- while node:
- if node.name == 'text':
+ for node in xnode.getchildren():
+ if node.tag == 'text':
# We ignore pure text nodes - should not be processed here
- node = node.next
continue
- elif node.name == 'name':
+ elif node.tag == 'name':
# <name short='{short arg}'/> tag
# - Defines the long option name in the text node and the short option
# is defined via the 'short' attribute.
- arglong = node.get_content().strip()
- if node.prop('short'):
- argshort = node.prop('short').strip()
- elif node.name == 'tag':
+ arglong = get_node_text(node).strip()
+ if node.attrib.get('short'):
+ argshort = node.attrib['short'].strip()
+ elif node.tag == 'tag':
# <tag type='{string, attribute}' [attrname='attribute name']/>
# - Defines the XML tag name for the internal XML this option will set. If
# type is 'string' the given tag content value will be used for as the
# tag name. If type is 'attribute' it will be added as an XML tag attribute
# to the tag defined by the (text node) value. The name of the attribute
# variable is set by a required 'attrname' attribute in the <tag/>.
- tagname = node.get_content().strip()
- tagvaltype = node.hasProp('type') and node.prop('type').strip() or None
- tagvalname = node.hasProp('attrname') and node.prop('attrname').strip() or None
+ tagname = get_node_text(node).strip()
+ tagvaltype = ('type' in node.attrib and node.attrib['type'].strip() or None)
+ tagvalname = ('attrname' in node.attrib
+ and node.attrib['attrname'].strip() or None)
tagnamelmnt = None
if tagvaltype == "list":
- tagnamelmnt = node.hasProp('element_tag') and node.prop('element_tag') or 'value'
- elif node.name == 'description':
+ tagnamelmnt = ('element_tag' in node.attrib
+ and node.attrib['element_tag'] or 'value')
+ elif node.tag == 'description':
# <description/>
# - Describes the option in plain English, used by the --help screen
- descr = ' '.join([s.strip() for s in node.get_content().expandtabs(1).split(' ') if len(s.strip()) > 0])
- elif node.name == 'metavar':
+ node_text = get_node_text(node)
+ descr = ' '.join([s.strip()
+ for s in node_text.expandtabs(1).split(' ')
+ if len(s.strip()) > 0])
+ elif node.tag == 'metavar':
# <metavar/>
# - Defines a illustrative option value, used by the --help screen
- argmetavar = node.get_content().strip()
+ argmetavar = get_node_text(node).strip()
- elif node.name == 'default':
+ elif node.tag == 'default':
# <default/>
# - Will give a default value to the option if the option is not given
# via the command line
- argdefault = node.get_content().strip()
+ argdefault = get_node_text(node).strip()
- node = node.next
# Some quick sanity checks on important tags
if arglong is None or len(arglong) < 1:
raise Exception('The <name/> tag must be present with a value.')
# We need tagname and tag-value-type
- if tagname is None or tagvaltype is None:
+ if tagname is None or tagvaltype is None:
raise Exception("The <tag/> tag on '%s' must be present with a "
"value and must have a 'type' attribute" % arglong)
@@ -288,10 +326,10 @@ class JobConfig(object):
# If 'attribute' we need an attribute name too
if tagvaltype == 'attribute' and tagvalname is None:
- raise Exception("The <tag/> on '%s' is missing a 'name' attribute" % arglong);
+ raise Exception("The <tag/> on '%s' is missing a 'name' attribute" % arglong)
# If we have another default value from the bks-defaults file, use that instead
- if jobdefaults.has_key(arglong):
+ if arglong in jobdefaults:
argdefault = jobdefaults[arglong]
# If argument type is 'bool', remove type flag
@@ -308,7 +346,8 @@ class JobConfig(object):
metavar=argmetavar,
action=argaction,
help='%s%s (default: %s)' %
- (argoptional and 'Optional, ' or 'Required, ', descr, argdefault)
+ (argoptional and 'Optional, ' or 'Required, ', descr,
+ argdefault)
)
else:
self.grparser.add_option('--%s' % arglong,
@@ -318,68 +357,73 @@ class JobConfig(object):
metavar=argmetavar,
action=argaction,
help='%s%s (default: %s)' %
- (argoptional and 'Optional, ' or 'Required, ', descr, argdefault)
+ (argoptional and 'Optional, ' or 'Required, ', descr,
+ argdefault)
)
# Save some important information about this option
- self.jobargs.AddArgument(arglong, argtype, tagname, tagvaltype, tagvalname, tagnamelmnt,
- argdefault, argoptional)
+ self.jobargs.add_argument(arglong, argtype, tagname, tagvaltype, tagvalname, tagnamelmnt,
+ argdefault, argoptional)
parser.add_option_group(self.grparser)
self.__jobxml_parsed = True
- del jobcfg
- # EOFNC: def ParseJobXML()
+ del job_cfg
+ # EOFNC: def ParseJobXML()
- def ValidateArguments(self, kwargs):
- "Validates command line arguments, and checks if all required arguments are set"
-
+ def validate_arguments(self, kwargs):
+ """
+ Validates command line arguments,
+ and checks if all required arguments are set
+ """
if self.__jobxml_parsed is False:
self.argpars.error('Missing --job-xml <filename>. See --help for more information.')
# Validate the input from the command line
# against what the XML jobConfig defines
- for optkey in self.jobargs.GetArgumentKeys():
+ for optkey in self.jobargs.get_argument_keys():
try:
optval = kwargs.get(optkey, None)
- except AttributeError, e:
+ except AttributeError as e:
raise e
- # optval = None
if optval is None:
- if not self.jobargs.IsValid(optkey):
- raise Exception('Missing required argument: --%s' %optkey)
+ if not self.jobargs.is_valid(optkey):
+ raise Exception('Missing required argument: --%s' % optkey)
else:
- self.jobargs.SetValue(optkey, optval)
+ self.jobargs.set_value(optkey, optval)
# Save global arguments we need
- self.whiteboard = kwargs.get('whiteboard', None)
- self.savexml = kwargs.get('savexml', None)
- self.saveintxml = kwargs.get('saveintxml', None)
+ self.whiteboard = kwargs.get('whiteboard', None)
+ self.savexml = kwargs.get('savexml', None)
+ self.saveintxml = kwargs.get('saveintxml', None)
self.xslt_override = kwargs.get('xslt_override', None)
- self.xslt_name = kwargs.get('xslt_name', None)
+ self.xslt_name = kwargs.get('xslt_name', None)
- def LoadXSLT(self):
- "Loads the XSLT file configured in the Job XML or overridden by the command line ."
+ def load_xslt(self):
+ """
+ Loads the XSLT file configured in the Job XML or overridden by the command line.
+ """
no_error_print = False
try:
if self.xslt_override is None:
# Which XSLT file should we load?
+ # TODO: Possibly whole if can be removed and values sanitized
if len(self.xslt_nodes) > 1:
for n in self.xslt_nodes:
- if self.xslt_name is None and n.hasProp('name') is None:
+ if self.xslt_name is None and n.attrib.get('name') is None:
# If no named XSLT template is given, grab the <xslt/>
# without a name attribute
- self.xsltfile = n.get_content()
+ self.xsltfile = get_node_text(n)
break
- elif self.xslt_name is not None and n.hasProp('name') is not None:
+ elif self.xslt_name is not None and n.attrib.get('name') is not None:
# If looking for a named XSLT template
if n.prop('name') == self.xslt_name:
- self.xsltfile = n.get_content()
+ self.xsltfile = get_node_text(n)
break
else:
- self.xsltfile = self.xslt_nodes[0].get_content()
+ self.xsltfile = get_node_text(self.xslt_nodes[0])
else:
self.xsltfile = self.xslt_override
self.xslt_name = None
@@ -393,18 +437,20 @@ class JobConfig(object):
raise Exception('No XSLT file is configured')
# Do the loading and XSLT document parsing
- xsltdoc = libxml2.parseFile(self.xsltfile)
- self.xslt = libxslt.parseStylesheetDoc(xsltdoc)
+ style_doc = etree.parse(self.xsltfile)
+ self.xslt = etree.XSLT(style_doc)
del self.xslt_nodes
- except Exception, e:
+ except Exception as e:
if not no_error_print:
- print '** ERROR ** Failed to parse the XSLT template: %s'% self.xsltfile
+ print('** ERROR ** Failed to parse the XSLT template: %s' % self.xsltfile)
raise e
-
- def __format_xml_value(self, argtype, argvalue):
- "Private method: Formats an argument value for XML tags according to the argument type"
+ @staticmethod
+ def __format_xml_value(argtype, argvalue):
+ """
+ Formats an argument value for XML tags according to the argument type
+ """
if argtype == 'bool':
# Convert Python True/False value to 'true' or 'false' string
@@ -413,213 +459,251 @@ class JobConfig(object):
# Pass through on all other types, using the native type
return argvalue
-
def __generate_internal_xml(self):
- "Private method: Generates the internal XML needed for XSLT template"
+ """
+ Generates the internal XML needed for XSLT template
+ """
# Generate new XML document and set <submit/> to be the root tag
- xml = libxml2.newDoc('1.0')
- submit_node = libxml2.newNode('submit')
- xml.setRootElement(submit_node)
+ submit_node = etree.Element('submit')
+ tree = etree.ElementTree(submit_node)
# Add white board text if set
- wb_node = libxml2.newNode('whiteboard')
- if self.whiteboard is not None:
- wb_node.addChild(libxml2.newText(self.whiteboard))
- submit_node.addChild(wb_node)
+ wb_node = etree.Element('whiteboard')
+ wb_node.text = self.whiteboard
+ submit_node.append(wb_node)
# Add the recipe node ...
- recipe_node = libxml2.newNode('recipe')
- submit_node.addChild(recipe_node)
+ recipe_node = etree.Element('recipe')
+ submit_node.append(recipe_node)
# ... and add all the defined arguments
- for key in self.jobargs.GetArgumentKeys():
- if self.jobargs.IsProcessed(key):
+ for key in self.jobargs.get_argument_keys():
+ if self.jobargs.is_processed(key):
continue
- (tagname, tagnode) = self.jobargs.CreateTag(key)
+ tag_name, tag_element = self.jobargs.create_tag(key)
- arg = self.jobargs.GetNextArgumentOnTag(tagname)
+ arg = self.jobargs.get_next_argument_on_tag(tag_name)
while arg is not None:
if arg['value']:
- recipe_node.addChild(tagnode)
+ recipe_node.append(tag_element)
if arg['tagvaluetype'] == 'value':
- tagnode.addChild(libxml2.newText(self.__format_xml_value(arg['argtype'], arg['value'])))
+ tag_element.text = self.__format_xml_value(arg['argtype'], arg['value'])
elif arg['tagvaluetype'] == 'attribute':
- tagnode.newProp(arg['tagvaluename'], self.__format_xml_value(arg['argtype'], arg['value']))
+ tag_element.set(arg['tagvaluename'],
+ self.__format_xml_value(arg['argtype'], arg['value']))
elif arg['tagvaluetype'] == "list":
for listval in arg["value"].split(","):
- tagchild_n = self.jobargs.CreateChildTag(key)
- tagchild_n.addChild(libxml2.newText(listval))
+ tagchild_n = self.jobargs.create_child_tag(key)
+ tagchild_n.text = listval
else:
raise Exception("Unknown <tag/> type '%s' found in '%s'"
% (arg['tagvaluetype'], key))
- self.jobargs.SetProcessed()
- arg = self.jobargs.GetNextArgumentOnTag(tagname)
+ self.jobargs.set_processed()
+ arg = self.jobargs.get_next_argument_on_tag(tag_name)
- return xml
+ return tree
+ def generate_xml(self):
+ """
+ Generates the internal submit XML document and parses it through
+ the defined XSLT template.
- def GenerateXML(self):
- """Generates the internal submit XML document and parses it through
-the defined XSLT template. Returns and libxml2.xmlDoc containing the
-result of the XSLT processing"""
+ Returns _XSLTResultTree containing the result of the XSLT processing
+ """
if self.xsltfile is None:
- raise Exception('No XSLT file has been loaded (Have JobConfig::LoadXSLT() been called?)')
+ raise Exception(
+ 'No XSLT file has been loaded (Have JobConfig::LoadXSLT() been called?)')
if self.internalxml is None:
self.internalxml = self.__generate_internal_xml()
- self.beakerxml = self.xslt.applyStylesheet(self.internalxml, None)
+ self.beakerxml = self.xslt(self.internalxml)
return self.beakerxml
-
- def GetBeakerXMLasString(self):
- """Returns the XML result of GenerateXML() as a string"""
+ def get_beaker_xml_string(self):
+ """
+ Returns the XML result of generate_xml() as a string
+ """
if self.beakerxml is None:
- raise Exception('Beaker XML has not been generated yet - hint: JobConfig::GenerateXML()')
- return self.xslt.saveResultToString(self.beakerxml).decode(self.xslt.encoding())
-
-
- def GetBeakerXMLdoc(self):
- """Returns the libxml2.xmlDoc of the XML result from GenerateXML()"""
+ raise Exception(
+ 'Beaker XML has not been generated yet - hint: JobConfig::GenerateXML()')
+ # TODO: str can return invalid XML. Instead of str we should use etree.tostring
+ # Result will be None in case of invalid XML
+ return str(self.beakerxml)
+
+ def get_beaker_xml_doc(self):
+ """
+ Returns the _XSLTResultTree of the XML result from generate_xml()
+ """
if self.beakerxml is None:
- raise Exception('Beaker XML has not been generated yet - hint: JobConfig::GenerateXML()')
+ raise Exception(
+ 'Beaker XML has not been generated yet - hint: JobConfig::GenerateXML()')
return self.beakerxml
-
- def SaveBeakerXML(self, name):
- """Saves the parsed result of GenerateXML() to a given file"""
- self.beakerxml.saveFormatFileEnc(name,'UTF-8', 1)
-
-
- def GetInternalXMLdoc(self):
- """Returns the internal XML document as a libxml2.xmlDoc, used for the
-XSLT parsing. This is most useful for debugging only"""
+ def save_beaker_xml(self, filename):
+ """
+ Saves Beaker tree to a given file
+ """
+ with open(filename, 'wb') as fd:
+ # TODO: Invalid XML can be returned without using etree.tostring
+ self.beakerxml.write_output(fd)
+
+ def save_internal_xml(self, filename):
+ """
+ Saves Internal tree to a given file
+ """
+ self.get_internal_xml_doc() # Make sure Internal XML doc is generated
+ with open(filename, 'wb') as fd:
+ fd.write(etree.tostring(self.internalxml, encoding='utf-8', pretty_print=True))
+
+ def get_internal_xml_doc(self):
+ """
+ Returns the internal XML document as a etree.ElementTree, used for the
+ XSLT parsing. This is most useful for debugging only
+ """
if self.internalxml is None:
self.internalxml = self.__generate_internal_xml()
return self.internalxml
-
- def GetGlobalJobArguments(self):
- """Returns the global command line arguments as an dictionary"""
- return {'beakerxml': self.savexml,
- 'internalxml': self.saveintxml,
- 'whiteboard': self.whiteboard,
- 'xslt_name': self.xslt_name}
-
-
- def GetJobName(self):
- """Returns the job name set in the job configuration XML"""
+ def get_global_job_args(self):
+ """
+ Returns the global command line arguments as an dictionary
+ """
+ return {
+ 'beakerxml': self.savexml,
+ 'internalxml': self.saveintxml,
+ 'whiteboard': self.whiteboard,
+ 'xslt_name': self.xslt_name
+ }
+
+ def get_job_name(self):
+ """
+ Returns the job name set in the job configuration XML
+ """
return self.name
-
- def GetXSLTfilename(self):
- """Returns the XSLT template filename defined in the job configuration XML"""
+ def get_xslt_filename(self):
+ """
+ Returns the XSLT template filename defined in the job configuration XML
+ """
return self.xsltfile
-
- def PrintJobArguments(self):
- """Prints all the job specific arguments to the screen"""
- return self.jobargs.PrintArguments()
-
+ def print_job_args(self):
+ """
+ Prints all the job specific arguments to the screen
+ """
+ return self.jobargs.print_arguments()
-class JobDefaults(ConfigParser.ConfigParser):
- "Configuration file parser for default settings"
+class JobDefaults(configparser.ConfigParser):
+ """
+ Configuration file parser for default settings
+ """
def __init__(self):
self.vars = {'profile': None, 'parsed-files': []}
self.__jobxml_parsed = False
- ConfigParser.ConfigParser.__init__(self)
+ configparser.ConfigParser.__init__(self)
- deffiles=('%s/.beaker-client/bks-defaults' % os.path.expanduser('~'),
- 'bks-defaults'
- )
+ deffiles = ('%s/.beaker-client/bks-defaults' % os.path.expanduser('~'),
+ 'bks-defaults'
+ )
self.read(deffiles)
-
def read(self, fname):
- "A wrapper around ConfigParser.ConfigParser.read()"
+ """
+ Wrapper around ConfigParser.ConfigParser.read()
+ """
- ConfigParser.ConfigParser.read(self, fname)
+ configparser.ConfigParser.read(self, fname)
self.vars['parsed-files'].append(fname)
+ def update_config_callback(self, option, opt_str, value, parser):
+ """
+ Used by the optparse object as a callback function for the --defaults argument
+ """
- def parserCB_UpdateConfig(self, option, opt_str, value, parser):
- "Used by the optparse object as a callback function for the --defaults argument"
if not self.__jobxml_parsed:
try:
self.read(value)
setattr(parser.values, option.dest, value)
- except Exception, e:
+ except Exception as e:
raise OptionValueError('Failed to parse configuration file (%s): %s' % (value, e))
else:
raise OptionValueError('--defaults cannot be used after --job-xml')
-
- def SetProfile(self, profile):
+ def set_profile(self, profile):
if len(profile) > 0:
self.vars['profile'] = profile
else:
raise OptionValueError('--profile is lacking a profile name')
-
- def parserCB_SetProfile(self, option, opt_str, value, parser):
- "Used by the optparse object as a callback function for the --profile argument"
+ def set_profile_callback(self, option, opt_str, value, parser):
+ """
+ Used by the optparse object as a callback function for the --profile argument
+ """
if not self.__jobxml_parsed:
setattr(parser.values, option.dest, value)
- self.SetProfile(value)
+ self.set_profile(value)
else:
raise OptionValueError('--profile cannot be used after --job-xml')
-
- def parserCBhelper_jobxml_parsed(self):
+ def job_xml_parsed_callback(self):
self.__jobxml_parsed = True
+ def get_default_job_xml(self):
+ """
+ Returns the configured default Job XML file if configured, otherwise NULL
+ """
- def GetDefaultJobXML(self):
- "Returns the configured default Job XML file if configured, otherwise NULL"
try:
return self.get('defaults', 'jobxml')
- except ConfigParser.NoOptionError:
+ except configparser.NoOptionError:
# Ignore if the 'jobxml' setting is not found
return None
- except ConfigParser.NoSectionError:
+ except configparser.NoSectionError:
# Ignore if the 'defaults' section is not found
return None
+ def get_job_defaults(self, job_file_name):
+ """
+ Extracts the configured job defaults for the defined Job XML and defaults profile
+ """
+
+ job_defaults = {}
- def GetJobDefaults(self, jobfname):
- "Extracts the configured job defaults for the defined Job XML and defaults profile"
- jobdefaults = {}
try:
- fname = os.path.basename(jobfname)
- section = self.vars['profile'] is not None and '%s:%s' % (fname, self.vars['profile']) or fname
- for d in [{k: v} for k,v in self.items(section)]:
- jobdefaults.update(d)
- except ConfigParser.NoSectionError:
+ file_name = os.path.basename(job_file_name)
+ section = self.vars['profile'] is not None and '%s:%s' % (
+ file_name, self.vars['profile']) or file_name
+
+ for d in [{k: v} for k, v in self.items(section)]:
+ job_defaults.update(d)
+ except configparser.NoSectionError:
if self.vars['profile'] is not None:
# if we have a defaults profile set, complain about the miss
raise OptionValueError("There is no defaults profile '%s' defined for %s" %
- (self.vars['profile'], fname))
+ (self.vars['profile'], file_name))
else:
# if not, we don't care about it
pass
- return jobdefaults
-
+ return job_defaults
class Workflow_XSLT(BeakerCommand):
- """XSLT workflow - Generates beaker jobs based on XSLT templates"""
+ """
+ XSLT workflow - Generates beaker jobs based on XSLT templates
+ """
+
enabled = True
def __init__(self, parser):
- self.__job_def = JobDefaults()
- self.__job_cfg = JobConfig(parser, self.__job_def)
+ self.job_def = JobDefaults()
+ self.job_cfg = JobConfig(parser, self.job_def)
self.parser = parser
-
def options(self):
super(Workflow_XSLT, self).options()
@@ -635,32 +719,34 @@ class Workflow_XSLT(BeakerCommand):
if args.startswith('--job-xml'):
jobxml_cmdline = True
elif args.startswith('--profile='):
- self.__job_def.SetProfile(args.split('=')[1])
+ self.job_def.set_profile(args.split('=')[1])
elif args == '--profile':
grab_profile = True
else:
- self.__job_def.SetProfile(args)
+ self.job_def.set_profile(args)
grab_profile = False
if grab_profile is True:
raise OptionValueError('--profile is lacking a profile name')
-
# If --job-xml is not in the command line, parse the defaults file
if defjobxml is None and not jobxml_cmdline:
- defjobxml = self.__job_def.GetDefaultJobXML()
+ defjobxml = self.job_def.get_default_job_xml()
if defjobxml is not None:
- self.__job_cfg.ParseJobXML(self.parser, defjobxml)
+ self.job_cfg.parse_job_xml(self.parser, defjobxml)
- self.parser.add_option('--defaults', action='callback', callback=self.__job_def.parserCB_UpdateConfig,
+ self.parser.add_option('--defaults', action='callback',
+ callback=self.job_def.update_config_callback,
nargs=1, type='string', metavar='FILENAME',
help='Load an additional defaults configuration')
- self.parser.add_option('--profile', action='callback', callback=self.__job_def.parserCB_SetProfile,
- nargs=1, type='string', metavar='PROFILE-NAME',
- help='Use a different configured defaults profile')
+ self.parser.add_option('--profile', action='callback',
+ callback=self.job_def.set_profile_callback,
+ nargs=1, type='string', metavar='PROFILE-NAME',
+ help='Use a different configured defaults profile')
- self.parser.add_option('--job-xml', action='callback', callback=self.__job_cfg.parserCB_ParseJobXML,
+ self.parser.add_option('--job-xml', action='callback',
+ callback=self.job_cfg.parse_job_xml_callback,
nargs=1, type='string', metavar='FILENAME', dest='jobxml',
help='Job XML file (Default: %s)' % defjobxml)
@@ -672,8 +758,6 @@ class Workflow_XSLT(BeakerCommand):
help='Wait on job completion')
self.parser.usage = '%%prog %s [options]' % self.normalized_name
- # EOFNC: def option()
-
def run(self, *args, **kwargs):
@@ -682,44 +766,44 @@ class Workflow_XSLT(BeakerCommand):
profile = kwargs.get('profile', None)
wait = kwargs.get('wait', None)
- self.__job_cfg.ValidateArguments(kwargs)
+ self.job_cfg.validate_arguments(kwargs)
# Grab some collected info and parse the requested XSLT file
- globalargs = self.__job_cfg.GetGlobalJobArguments()
- self.__job_cfg.LoadXSLT()
+ globalargs = self.job_cfg.get_global_job_args()
+ self.job_cfg.load_xslt()
- print '-' * 75
- print 'Generating Beaker XML'
- print ' Job config: %s' % jobxml
+ print('-' * 75)
+ print('Generating Beaker XML')
+ print(' Job config: %s' % jobxml)
if profile:
- print ' Defaults profile: %s' % profile
+ print(' Defaults profile: %s' % profile)
if globalargs['xslt_name']:
- print ' XSLT name: %s' % globalargs['xslt_name']
- print ' XSLT template: %s' % self.__job_cfg.GetXSLTfilename()
- print ' Job name: %s' % self.__job_cfg.GetJobName()
- print ' Whiteboard: %s' % globalargs['whiteboard']
- self.__job_cfg.PrintJobArguments()
- print '-' * 75
+ print(' XSLT name: %s' % globalargs['xslt_name'])
+ print(' XSLT template: %s' % self.job_cfg.get_xslt_filename())
+ print(' Job name: %s' % self.job_cfg.get_job_name())
+ print(' Whiteboard: %s' % globalargs['whiteboard'])
+ self.job_cfg.print_job_args()
+ print('-' * 75)
# Do we want to save the internal XML used for the XSLT processing?
saved = False
if globalargs['internalxml']:
- self.__job_cfg.GetInternalXMLdoc().saveFormatFileEnc(globalargs['internalxml'], 'UTF-8', 1)
+ self.job_cfg.save_internal_xml(globalargs['internalxml'])
saved = True
# Do the main work - this does the XSLT processing
- self.__job_cfg.GenerateXML()
+ self.job_cfg.generate_xml()
# Fetch the result and do something with it
# Do we want to save the Beaker XML in addition?
if globalargs['beakerxml']:
- self.__job_cfg.SaveBeakerXML(globalargs['beakerxml'])
+ self.job_cfg.save_beaker_xml(globalargs['beakerxml'])
saved = True
# Send the job to Beaker, if it's not a dry-run.
if not dryrun:
- print "** Sending job to Beaker ...",
+ print("** Sending job to Beaker ...", )
sys.stdout.flush()
self.set_hub(**kwargs)
@@ -727,14 +811,14 @@ class Workflow_XSLT(BeakerCommand):
failed = False
try:
- submitted_jobs.append(self.hub.jobs.upload(self.__job_cfg.GetBeakerXMLasString()))
- print "Success"
- except Exception, ex:
- print "FAIL"
+ submitted_jobs.append(self.hub.jobs.upload(self.job_cfg.get_beaker_xml_string()))
+ print("Success")
+ except Exception as ex:
+ print("FAIL")
failed = True
- print ex
+ print(ex)
- print '** Submitted: %s' % submitted_jobs
+ print('** Submitted: %s' % submitted_jobs)
if wait:
watch_tasks(self.hub, submitted_jobs)
if failed:
@@ -742,6 +826,4 @@ class Workflow_XSLT(BeakerCommand):
else:
# If dryrun without saving anything, dump the Beaker XML to stdout
if not saved:
- print self.__job_cfg.GetBeakerXMLasString()
-
-
+ print(self.job_cfg.get_beaker_xml_string())
diff --git a/Client/src/bkr/client/convert.py b/Client/src/bkr/client/convert.py
index 3ad27e1..bd2afb5 100644
--- a/Client/src/bkr/client/convert.py
+++ b/Client/src/bkr/client/convert.py
@@ -1,10 +1,8 @@
-
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
-import sys
import re
import xml.dom.minidom
@@ -13,21 +11,22 @@ __all__ = (
"rhts2beaker",
)
+
def rhts2beaker(jobfile):
convert = Convert(xml.dom.minidom.parseString(jobfile))
return convert.toxml()
-class Convert(object):
+class Convert(object):
doc = xml.dom.minidom.Document()
rhts2beaker = staticmethod(rhts2beaker)
invalidjobtags = ['submitter',
'workflow',
- ]
+ ]
invalidrecipetags = ['yumInstall',
'driverdisk',
- ]
+ ]
def __init__(self, jobxml):
self.counter = 0
@@ -64,19 +63,23 @@ class Convert(object):
return require
def handle_addrepo(self, addrepo):
- """ process repos """
+ """
+ Process repos
+ """
self.counter += 1
repo = self.doc.createElement('repo')
- repo.setAttribute('name','myrepo_%s' % self.counter)
- repo.setAttribute('url','%s' % addrepo)
+ repo.setAttribute('name', 'myrepo_%s' % self.counter)
+ repo.setAttribute('url', '%s' % addrepo)
return repo
-
+
def handle_addpackage(self, addpackage):
- """ process packages """
+ """
+ Process packages
+ """
package = self.doc.createElement('package')
- package.setAttribute('name','%s' % addpackage)
+ package.setAttribute('name', '%s' % addpackage)
return package
-
+
def handle_hostRequires(self, requires):
require = None
requires_search = re.compile(r'([^\s]+)\s+([^\s]+)\s+([^\s]+)')
@@ -104,7 +107,7 @@ class Convert(object):
require.setAttribute('op', '%s' % op)
require.setAttribute('value', '%s' % value)
return require
-
+
def handle_tasks(self, nodes):
for child in nodes.childNodes:
if child.nodeType == child.ELEMENT_NODE:
@@ -112,7 +115,7 @@ class Convert(object):
child.tagName = 'task'
else:
self.handle_tasks(child)
-
+
def handle_partition(self, node):
partition = self.doc.createElement('partition')
for child in node.childNodes:
@@ -139,60 +142,54 @@ class Convert(object):
kernel_options = '%s ' % recipe.getAttribute('kernel_options')
if 'bootargs' in recipe._attrs:
kernel_options = '%s%s' % (kernel_options, recipe.getAttribute('bootargs'))
- recipe.setAttribute('kernel_options',kernel_options)
+ recipe.setAttribute('kernel_options', kernel_options)
recipe.removeAttribute('bootargs')
if 'testrepo' in recipe._attrs:
recipe.removeAttribute('testrepo')
for child in recipe.childNodes:
- if child.nodeType == child.ELEMENT_NODE and \
- child.tagName == 'bootargs':
- del_nodes.append(child)
- kernel_options = '%s%s' % (kernel_options, self.getText(child.childNodes))
- recipe.setAttribute('kernel_options' , kernel_options)
- if child.nodeType == child.ELEMENT_NODE and \
- child.tagName == 'distroRequires':
- del_nodes.append(child)
- require = self.handle_distroRequires(self.getText(child.childNodes))
- if require:
- and_distro.appendChild(require)
-
- if child.nodeType == child.ELEMENT_NODE and \
- child.tagName == 'hostRequires':
- del_nodes.append(child)
- require = self.handle_hostRequires(self.getText(child.childNodes))
- if require:
- and_host.appendChild(require)
-
- if child.nodeType == child.ELEMENT_NODE and \
- child.tagName == 'partition':
- del_nodes.append(child)
- partitions.appendChild(self.handle_partition(child))
-
- if child.nodeType == child.ELEMENT_NODE and \
- child.tagName == 'addrepo':
- del_nodes.append(child)
- repo = self.handle_addrepo(self.getText(child.childNodes))
- repos.appendChild(repo)
-
- if child.nodeType == child.ELEMENT_NODE and \
- child.tagName == 'installPackage':
- del_nodes.append(child)
- package = self.handle_addpackage(self.getText(child.childNodes))
- packages.appendChild(package)
-
+ if child.nodeType == child.ELEMENT_NODE and child.tagName == 'bootargs':
+ del_nodes.append(child)
+ kernel_options = '%s%s' % (kernel_options, self.getText(child.childNodes))
+ recipe.setAttribute('kernel_options', kernel_options)
+ if child.nodeType == child.ELEMENT_NODE and child.tagName == 'distroRequires':
+ del_nodes.append(child)
+ require = self.handle_distroRequires(self.getText(child.childNodes))
+ if require:
+ and_distro.appendChild(require)
+
+ if child.nodeType == child.ELEMENT_NODE and child.tagName == 'hostRequires':
+ del_nodes.append(child)
+ require = self.handle_hostRequires(self.getText(child.childNodes))
+ if require:
+ and_host.appendChild(require)
+
+ if child.nodeType == child.ELEMENT_NODE and child.tagName == 'partition':
+ del_nodes.append(child)
+ partitions.appendChild(self.handle_partition(child))
+
+ if child.nodeType == child.ELEMENT_NODE and child.tagName == 'addrepo':
+ del_nodes.append(child)
+ repo = self.handle_addrepo(self.getText(child.childNodes))
+ repos.appendChild(repo)
+
+ if child.nodeType == child.ELEMENT_NODE and child.tagName == 'installPackage':
+ del_nodes.append(child)
+ package = self.handle_addpackage(self.getText(child.childNodes))
+ packages.appendChild(package)
+
distro = self.doc.createElement('distroRequires')
distro.appendChild(and_distro)
host = self.doc.createElement('hostRequires')
host.appendChild(and_host)
-
+
for child in del_nodes:
- recipe.removeChild(child)
+ recipe.removeChild(child)
recipe.appendChild(packages)
recipe.appendChild(repos)
recipe.appendChild(distro)
recipe.appendChild(host)
recipe.appendChild(partitions)
-
+
def handle_invalid(self, nodes, invalids):
for invalid in invalids:
for node in nodes:
diff --git a/Client/src/bkr/client/main.py b/Client/src/bkr/client/main.py
index 1b9089a..faca452 100755
--- a/Client/src/bkr/client/main.py
+++ b/Client/src/bkr/client/main.py
@@ -1,4 +1,3 @@
-
# -*- coding: utf-8 -*-
# This program is free software; you can redistribute it and/or modify
@@ -6,21 +5,25 @@
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
-import os
-import sys
+import cgi
import errno
-import signal
-import pkg_resources
import logging
-from optparse import Option, IndentedHelpFormatter, SUPPRESS_HELP
-import xmlrpclib
-import cgi
+import signal
+import sys
+from optparse import IndentedHelpFormatter
+from optparse import Option
+from optparse import SUPPRESS_HELP
+
import gssapi
-from bkr.client.command import CommandOptionParser, ClientCommandContainer, BeakerClientConfigurationError
+import pkg_resources
+from six.moves.xmlrpc_client import Fault
+
+from bkr.client.command import BeakerClientConfigurationError
+from bkr.client.command import ClientCommandContainer
+from bkr.client.command import CommandOptionParser
from bkr.common import __version__
from bkr.log import log_to_stream
-
__all__ = (
"main",
)
@@ -30,68 +33,71 @@ class BeakerCommandContainer(ClientCommandContainer):
@classmethod
def register_all(cls):
- # Load all modules in the bkr.client.commands package as commands, for
- # backwards compatibility with older packages that just drop their files
+ # Load all modules in the bkr.client.commands package as commands, for
+ # backwards compatibility with older packages that just drop their files
# into the bkr.client.commands package.
import bkr.client.commands
cls.register_module(bkr.client.commands, prefix='cmd_')
- # Load subcommands from setuptools entry points in the bkr.client.commands
- # group. This is the new, preferred way for other packages to provide their
+ # Load subcommands from setuptools entry points in the bkr.client.commands
+ # group. This is the new, preferred way for other packages to provide their
# own bkr subcommands.
for entrypoint in pkg_resources.iter_entry_points('bkr.client.commands'):
cls.register_plugin(entrypoint.load(), name=entrypoint.name)
+
class BeakerOptionParser(CommandOptionParser):
standard_option_list = [
Option('--hub', metavar='URL',
- help='Connect to Beaker server at URL (overrides config file)'),
+ help='Connect to Beaker server at URL (overrides config file)'),
Option('--username',
- help='Use USERNAME for password authentication (overrides config file)'),
+ help='Use USERNAME for password authentication (overrides config file)'),
Option('--password',
- help='Use PASSWORD for password authentication (overrides config file)'),
+ help='Use PASSWORD for password authentication (overrides config file)'),
Option('--insecure', action='store_true',
- help='Skip SSL certificate validity checks'),
+ help='Skip SSL certificate validity checks'),
Option('--proxy-user',
- help=SUPPRESS_HELP),
+ help=SUPPRESS_HELP),
]
+
BeakerCommandContainer.register_all()
-from bkr.client import conf, BeakerJobTemplateError
+from bkr.client import BeakerJobTemplateError
+from bkr.client import conf
+
def warn_on_version_mismatch(response):
if 'X-Beaker-Version' not in response.headers:
sys.stderr.write('WARNING: client version is %s '
- 'but server version is < 24.0\n'
- % __version__)
+ 'but server version is < 24.0\n'
+ % __version__)
else:
server_version = response.headers['X-Beaker-Version']
server_major = server_version.split('.', 1)[0]
client_major = __version__.split('.', 1)[0]
if server_major != client_major:
sys.stderr.write('WARNING: client version is %s '
- 'but server version is %s\n'
- % (__version__, server_version))
+ 'but server version is %s\n'
+ % (__version__, server_version))
+
def main():
log_to_stream(sys.stderr, level=logging.WARNING)
- global conf
command_container = BeakerCommandContainer(conf=conf)
formatter = IndentedHelpFormatter(max_help_position=60, width=120)
parser = BeakerOptionParser(version=__version__,
- conflict_handler='resolve',
- command_container=command_container,
- default_command="help", formatter=formatter)
+ conflict_handler='resolve',
+ command_container=command_container,
+ default_command="help", formatter=formatter)
# This is parser.run(), but with more sensible error handling
cmd, cmd_opts, cmd_args = parser.parse_args()
if not cmd_opts.hub and not conf:
sys.stderr.write("Configuration file not found. Please create an /etc/beaker/client.conf "
- "or ~/.beaker_client/config configuration file.\n")
+ "or ~/.beaker_client/config configuration file.\n")
return 1
-
# Need to deal with the possibility that requests is not importable...
try:
import requests
@@ -117,20 +123,20 @@ def main():
return 1
else:
raise
- except xmlrpclib.Fault, e:
+ except Fault as e:
sys.stderr.write('XML-RPC fault: %s\n' % e.faultString)
return 1
- except maybe_http_error, e:
+ except maybe_http_error as e:
warn_on_version_mismatch(e.response)
sys.stderr.write('HTTP error: %s\n' % e)
content_type, _ = cgi.parse_header(e.response.headers.get('Content-Type', ''))
if content_type == 'text/plain':
- sys.stderr.write(e.response.content.rstrip('\n') + '\n')
+ sys.stderr.write(e.response.content.decode('utf-8').rstrip('\n') + '\n')
return 1
- except BeakerJobTemplateError, e:
+ except BeakerJobTemplateError as e:
sys.stderr.write('%s\n' % e)
return 1
- except BeakerClientConfigurationError, e:
+ except BeakerClientConfigurationError as e:
sys.stderr.write('%s\n' % e)
return 1
except IOError as e:
diff --git a/Client/src/bkr/client/task_watcher.py b/Client/src/bkr/client/task_watcher.py
index d743b00..78be2fc 100644
--- a/Client/src/bkr/client/task_watcher.py
+++ b/Client/src/bkr/client/task_watcher.py
@@ -5,37 +5,46 @@
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
+from __future__ import print_function
+
import sys
-import time
+import six
+import time
__all__ = (
"TaskWatcher",
"watch_tasks"
)
+
def display_tasklist_status(task_list):
state_dict = {}
for task in task_list:
- for state, value in task.get_state_dict().iteritems():
+ for state, value in six.iteritems(task.get_state_dict()):
state_dict.setdefault(state, 0)
state_dict[state] += value
- print "--> " + " ".join(( "%s: %s" % (key, state_dict[key]) for key in sorted(state_dict) )) + " [total: %s]" % sum(state_dict.values())
+ print("--> " + " ".join(("%s: %s" % (key, state_dict[key])
+ for key in sorted(state_dict)))
+ + " [total: %s]" % sum(state_dict.values()))
+
def watch_tasks(hub, task_id_list, indentation_level=0, sleep_time=30, task_url=None):
- """Watch the task statuses until they finish."""
+ """
+ Watch the task statuses until they finish.
+ """
if not task_id_list:
return
watcher = TaskWatcher()
is_failed = False
try:
- print "Watching tasks (this may be safely interrupted)..."
+ print("Watching tasks (this may be safely interrupted)...")
for task_id in sorted(task_id_list):
watcher.task_list.append(Task(hub, task_id, indentation_level))
# print task url if task_url is set or TASK_URL exists in config file
task_url = task_url or hub._conf.get("TASK_URL", None)
if task_url is not None:
- print "Task url: %s" % (task_url % task_id)
+ print("Task url: %s" % (task_url % task_id))
while True:
all_done = True
changed = False
@@ -49,15 +58,15 @@ def watch_tasks(hub, task_id_list, indentation_level=0, sleep_time=30, task_url=
break
time.sleep(sleep_time)
except KeyboardInterrupt:
- running_task_list = [ t.task_id for t in watcher.task_list if not watcher.is_finished(t) ]
+ running_task_list = [t.task_id for t in watcher.task_list if not watcher.is_finished(t)]
if running_task_list:
- print "Tasks still running: %s" % running_task_list
+ print("Tasks still running: %s" % running_task_list)
# Don't report pass on jobs still running.
is_failed = True
return is_failed
-class TaskWatcher(object):
+class TaskWatcher(object):
display_tasklist_status = staticmethod(display_tasklist_status)
def __init__(self):
@@ -70,7 +79,7 @@ class TaskWatcher(object):
return False
result = task.task_info.get("is_finished", False)
- for subtask in self.subtask_dict.itervalues():
+ for subtask in six.itervalues(self.subtask_dict):
result &= subtask.is_finished()
return result
@@ -80,7 +89,7 @@ class TaskWatcher(object):
return False
result = task.task_info.get("is_failed", False)
- for subtask in self.subtask_dict.itervalues():
+ for subtask in six.itervalues(self.subtask_dict):
result |= subtask.is_failed()
return result
@@ -93,7 +102,7 @@ class TaskWatcher(object):
task.task_info = task.hub.taskactions.task_info(task.task_id, False)
if task.task_info is None:
- print "No such task id: %s" % task.task_id
+ print("No such task id: %s" % task.task_id)
sys.exit(1)
changed = False
@@ -102,11 +111,12 @@ class TaskWatcher(object):
# compare and note status changes
laststate = last["state"]
if laststate != state:
- print "%s: %s -> %s" % (task, task.display_state(last), task.display_state(task.task_info))
+ print("%s: %s -> %s" % (task, task.display_state(last),
+ task.display_state(task.task_info)))
changed = True
else:
# first time we're seeing this task, so just show the current state
- print "%s: %s" % (task, task.display_state(task.task_info))
+ print("%s: %s" % (task, task.display_state(task.task_info)))
changed = True
# update all subtasks
@@ -136,16 +146,12 @@ class Task(object):
return False
return self.task_info.get("is_failed", False)
-
def display_state(self, task_info):
worker = task_info.get("worker")
if worker is not None:
return "%s (%s)" % (task_info["state_label"], worker["name"])
return "%s" % task_info["state_label"]
-
-
-
def get_state_dict(self):
state_dict = {}
if self.task_info is not None:
@@ -153,11 +159,9 @@ class Task(object):
state_dict.setdefault(state, 0)
state_dict[state] += 1
- for subtask in self.subtask_dict.itervalues():
- for state, value in subtask.get_state_dict().iteritems():
+ for subtask in six.itervalues(self.subtask_dict):
+ for state, value in six.iteritems(subtask.get_state_dict()):
state_dict.setdefault(state, 0)
state_dict[state] += value
return state_dict
-
-
diff --git a/Client/src/bkr/client/tests/test_wizard.py b/Client/src/bkr/client/tests/test_wizard.py
index e190012..3dbb593 100644
--- a/Client/src/bkr/client/tests/test_wizard.py
+++ b/Client/src/bkr/client/tests/test_wizard.py
@@ -1,26 +1,28 @@
-
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
-import StringIO
-from datetime import date
import unittest2 as unittest
+from datetime import date
from mock import Mock, patch
+from six.moves import StringIO
+
from bkr.client import wizard
EXPECTED_GPL_HEADER = ("Copyright (c) %s %s.\n" %
- (date.today().year, wizard.LICENSE_ORGANISATION))
+ (date.today().year, wizard.LICENSE_ORGANISATION))
EXPECTED_OTHER_HEADER = ("Copyright (c) %s %s. All rights reserved.\n" %
- (date.today().year, wizard.LICENSE_ORGANISATION))
+ (date.today().year, wizard.LICENSE_ORGANISATION))
+
class ShellEscapingTest(unittest.TestCase):
def test_it(self):
self.assertEquals(wizard.shellEscaped(r'a " ` $ ! \ z'),
- r'a \" \` \$ \! \\ z')
+ r'a \" \` \$ \! \\ z')
+
class LicenseTests(unittest.TestCase):
@@ -89,7 +91,7 @@ class ArchitecturesTest(unittest.TestCase):
def test_contain_aarch64(self):
self.assertIn('aarch64', self.archs.list)
- #https://bugzilla.redhat.com/show_bug.cgi?id=1120487
+ # https://bugzilla.redhat.com/show_bug.cgi?id=1120487
def test_contain_ppc64le(self):
self.assertIn('ppc64le', self.archs.list)
@@ -104,12 +106,12 @@ class BugsTest(unittest.TestCase):
bug_id=887866,
)
options = wizard.Options(['beaker-wizard', 'CVE-2012-5660'],
- load_user_prefs=False)
+ load_user_prefs=False)
bugs = wizard.Bugs(options)
bugs.bug = bzbug
self.assertEquals(
- 'abrt: Race condition in abrt-action-install-debuginfo',
- bugs.getSummary())
+ 'abrt: Race condition in abrt-action-install-debuginfo',
+ bugs.getSummary())
class DescTest(unittest.TestCase):
@@ -118,7 +120,7 @@ class DescTest(unittest.TestCase):
def test_shell_metachars_escaped_in_makefile(self):
options = wizard.Options(['beaker-wizard', '--yes'], load_user_prefs=False)
desc = wizard.Desc(options, suggest="Test for BZ#1234567 "
- "(I ran `rm -rf ~` and everything's gone suddenly)")
+ "(I ran `rm -rf ~` and everything's gone suddenly)")
self.assertEquals(
"""\n \t@echo "Description: Test for BZ#1234567 (I ran \`rm -rf ~\` and everything's gone suddenly)" >> $(METADATA)""",
desc.formatMakefileLine())
@@ -133,12 +135,12 @@ class ReleasesTest(unittest.TestCase):
# https://bugzilla.redhat.com/show_bug.cgi?id=1131429
def test_default_excludes_rhel4_rhel5(self):
self.assertEqual(self.releases.data,
- ['-RHEL4', '-RHELClient5', '-RHELServer5'])
+ ['-RHEL4', '-RHELClient5', '-RHELServer5'])
class TypeTest(unittest.TestCase):
- @patch('sys.stdout', new_callable=StringIO.StringIO)
+ @patch('sys.stdout', new_callable=StringIO)
def test_prints_full_heading(self, cpt_stdout):
type = wizard.Type()
type.heading()
diff --git a/Client/src/bkr/client/wizard.py b/Client/src/bkr/client/wizard.py
index a4fca16..856b467 100755
--- a/Client/src/bkr/client/wizard.py
+++ b/Client/src/bkr/client/wizard.py
@@ -4,6 +4,11 @@
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
+from __future__ import division
+from __future__ import print_function
+
+import math
+
DESCRIPTION = """Beaker Wizard is a tool which can transform that
"create all the necessary files with correct names, values, and paths"
boring phase of every test creation into one-line joy. For power
@@ -339,7 +344,7 @@ import re
import os
# Version
-WizardVersion = "2.3.0"
+WizardVersion = "2.3.1"
# Regular expressions
RegExpPackage = re.compile("^(?![._+-])[.a-zA-Z0-9_+-]+(?<![._-])$")
@@ -369,10 +374,17 @@ SuggestedTestTypes = """Regression Performance Stress Certification
KernelReporting Sanity Library""".split()
# Guesses
-GuessAuthorLogin = pwd.getpwuid(os.getuid())[0].decode(sys.getfilesystemencoding())
+pwd_uinfo = pwd.getpwuid(os.getuid())
+try:
+ GuessAuthorLogin = pwd_uinfo.pw_name.decode(sys.getfilesystemencoding())
+except AttributeError:
+ GuessAuthorLogin = pwd_uinfo.pw_name
+try:
+ GuessAuthorName = pwd_uinfo.pw_gecos.decode(sys.getfilesystemencoding())
+except AttributeError:
+ GuessAuthorName = pwd_uinfo.pw_gecos
GuessAuthorDomain = re.sub("^.*\.([^.]+\.[^.]+)$", "\\1", os.uname()[1])
GuessAuthorEmail = "%s@%s" % (GuessAuthorLogin, GuessAuthorDomain)
-GuessAuthorName = pwd.getpwuid(os.getuid())[4].decode(sys.getfilesystemencoding())
# Make sure guesses are valid values
if not RegExpEmail.match(GuessAuthorEmail):
@@ -475,24 +487,33 @@ def unique(seq):
dictionary = {}
for i in seq:
dictionary[i] = 1
- return dictionary.keys()
+ return list(dictionary.keys())
def hr(width = 70):
""" Return simple ascii horizontal rule """
if width < 2: return ""
return "# " + (width - 2) * "~"
-def comment(text, width = 70, comment = "#", top = True, bottom = True, padding = 3):
- """ Create nicely formated comment """
+
+def comment(text, width=70, comment="#", top=True, bottom=True, padding=3):
+ """
+ Create nicely formatted comment
+ """
result = ""
+
# top hrule & padding
- if width and top: result += hr(width) + "\n"
- result += int(padding/3) * (comment + "\n")
+ if width and top:
+ result += hr(width) + "\n"
+ result += int(math.floor(padding/3)) * (comment + "\n")
+
# prepend lines with the comment char and padding
result += re.compile("^(?!#)", re.M).sub(comment + padding * " ", text)
+
# bottom padding & hrule
- result += int(padding/3) * ("\n" + comment)
- if width and bottom: result += "\n" + hr(width)
+ result += int(math.floor(padding/3)) * ("\n" + comment)
+ if width and bottom:
+ result += "\n" + hr(width)
+
# remove any trailing spaces
result = re.compile("\s+$", re.M).sub("", result)
return result
@@ -563,8 +584,8 @@ def addToGit(path):
stdout=subprocess.PIPE, stderr=subprocess.PIPE,)
out, err = process.communicate()
if process.wait():
- print "Sorry, failed to add %s to git :-(" % path
- print out, err
+ print("Sorry, failed to add %s to git :-(" % path)
+ print(out, err)
sys.exit(1)
except OSError:
print("Unable to run %s, is %s installed?"
@@ -606,7 +627,7 @@ class Preferences:
exec("self.%s = findNode(self.author, '%s')" % (node, node))
# if the node cannot be found get the default from template
if not eval("self." + node):
- print "Could not find <%s> in preferences, using default" % node
+ print("Could not find <%s> in preferences, using default" % node)
exec("self.%s = findNode(self.template, '%s').cloneNode(True)"
% (node, node))
exec("self.author.appendChild(self.%s)" % node)
@@ -617,19 +638,21 @@ class Preferences:
exec("self.%s = findNode(self.test, '%s')" % (node, node))
# if the node cannot be found get the default from template
if not eval("self." + node):
- print "Could not find <%s> in preferences, using default" % node
+ print("Could not find <%s> in preferences, using default" % node)
exec("self.%s = findNode(self.template, '%s').cloneNode(True)" % (node, node))
exec("self.test.appendChild(self.%s)" % node)
def load(self):
- """ Load user preferences (or set to defaults) """
+ """
+ Load user preferences (or set to defaults)
+ """
preferences_file = os.environ.get("BEAKER_WIZARD_CONF", PreferencesFile)
try:
self.xml = parse(preferences_file)
except:
if os.path.exists(preferences_file):
- print "I'm sorry, the preferences file seems broken.\n" \
- "Did you do something ugly to %s?" % preferences_file
+ print("I'm sorry, the preferences file seems broken.\n" \
+ "Did you do something ugly to %s?" % preferences_file)
sleep(3)
else:
self.firstRun = True
@@ -639,14 +662,16 @@ class Preferences:
try:
self.parse()
except:
- print "Failed to parse %s, falling to defaults." % preferences_file
+ print("Failed to parse %s, falling to defaults." % preferences_file)
sleep(3)
self.xml = self.template
self.parse()
- def update(self, author, email, confirm, type, namespace, \
- time, priority, confidential, destructive, prefix, license, skeleton):
- """ Update preferences with current settings """
+ def update(self, author, email, confirm, type, namespace,
+ time, priority, confidential, destructive, prefix, license, skeleton):
+ """
+ Update preferences with current settings
+ """
setNode(self.name, author)
setNode(self.email, email)
setNode(self.confirm, confirm)
@@ -665,22 +690,22 @@ class Preferences:
# try to create directory
try:
os.makedirs(PreferencesDir)
- except OSError, e:
+ except OSError as e:
if e.errno == 17:
pass
else:
- print "Cannot create preferences directory %s :-(" % PreferencesDir
+ print("Cannot create preferences directory %s :-(" % PreferencesDir)
return
# try to write the file
try:
file = open(PreferencesFile, "w")
except:
- print "Cannot write to %s" % PreferencesFile
+ print("Cannot write to %s" % PreferencesFile)
else:
file.write((self.xml.toxml() + "\n").encode("utf-8"))
file.close()
- print "Preferences saved to %s" % PreferencesFile
+ print("Preferences saved to %s" % PreferencesFile)
sleep(1)
def getAuthor(self): return getNode(self.name)
@@ -713,23 +738,27 @@ class Help:
if options:
# display expert usage page only
if options.expert():
- print self.expert();
+ print(self.expert())
sys.exit(0)
# show version info
elif options.ver():
- print self.version();
+ print(self.version())
sys.exit(0)
- def usage(self):
+ @staticmethod
+ def usage():
return "beaker-wizard [options] [TESTNAME] [BUG/CVE...] or beaker-wizard Makefile"
- def version(self):
+ @staticmethod
+ def version():
return "beaker-wizard %s" % WizardVersion
- def description(self):
+ @staticmethod
+ def description():
return DESCRIPTION
- def expert(self):
+ @staticmethod
+ def expert():
os.execv('/usr/bin/man', ['man', 'beaker-wizard'])
sys.exit(1)
@@ -745,10 +774,9 @@ class Makefile:
self.path = options.arg[0]
try:
# open and read the whole content into self.text
- print "Reading the Makefile..."
- file = open(self.path)
- self.text = "".join(file.readlines())
- file.close()
+ print("Reading the Makefile...")
+ with open(self.path) as fd:
+ self.text = ''.join(fd.readlines())
# substitute the old style $TEST sub-variables if present
for var in "TOPLEVEL_NAMESPACE PACKAGE_NAME RELATIVE_PATH".split():
@@ -756,18 +784,18 @@ class Makefile:
if m: self.text = re.sub("\$\(%s\)" % var, m.group(1), self.text)
# locate the metadata section
- print "Inspecting the metadata section..."
+ print("Inspecting the metadata section...")
m = RegExpMetadata.search(self.text)
self.metadata = m.group(1)
# parse the $TEST and $TESTVERSION
- print "Checking for the full test name and version..."
+ print("Checking for the full test name and version...")
m = RegExpTest.search(self.text)
options.arg = [m.group(1)]
m = RegExpVersion.search(self.text)
options.opt.version = m.group(1)
except:
- print "Failed to parse the original Makefile"
+ print("Failed to parse the original Makefile")
sys.exit(6)
# disable test name prefixing and set confirm to nothing
@@ -786,7 +814,7 @@ class Makefile:
}
# parse info from metadata line by line
- print "Parsing the individual metadata..."
+ print("Parsing the individual metadata...")
for line in self.metadata.split("\n"):
m = re.search("echo\s+[\"'](\w+):\s*(.*)[\"']", line)
# skip non-@echo lines
@@ -825,7 +853,7 @@ class Makefile:
options.arg.extend(options.opt.bug)
# success
- print "Makefile successfully parsed."
+ print("Makefile successfully parsed.")
def save(self, test, version, content):
# possibly update the $TEST and $TESTVERSION
@@ -846,10 +874,10 @@ class Makefile:
file.write(self.text.encode("utf-8"))
file.close()
except:
- print "Cannot write to %s" % self.path
+ print("Cannot write to %s" % self.path)
sys.exit(3)
else:
- print "Makefile successfully written"
+ print("Makefile successfully written")
class Options:
@@ -1018,7 +1046,11 @@ class Options:
# convert all args to unicode
uniarg = []
for arg in argv[1:]:
- uniarg.append(unicode(arg, "utf-8"))
+ try:
+ uarg = unicode(arg, 'utf-8')
+ except NameError:
+ uarg = arg
+ uniarg.append(uarg)
# and parse it!
(self.opt, self.arg) = parser.parse_args(uniarg)
@@ -1067,16 +1099,16 @@ class Options:
try:
from bugzilla import Bugzilla
except:
- print "Sorry, the bugzilla interface is not available right now, try:\n" \
- " yum install python-bugzilla\n" \
- "Use 'bugzilla login' command if you wish to access restricted bugs."
+ print("Sorry, the bugzilla interface is not available right now, try:\n"
+ " yum install python-bugzilla\n"
+ "Use 'bugzilla login' command if you wish to access restricted bugs.")
sys.exit(8)
else:
try:
- print "Contacting bugzilla..."
+ print("Contacting bugzilla...")
self.bugzilla = Bugzilla(url=BugzillaXmlrpc)
except:
- print "Cannot connect to bugzilla, check your net connection."
+ print("Cannot connect to bugzilla, check your net connection.")
sys.exit(9)
# command-line-only option interface
@@ -1181,17 +1213,19 @@ class Inquisitor:
self.data = re.sub("\s+", " ", self.data)
def read(self):
- """ Read an answer from user """
+ """
+ Read an answer from user
+ """
try:
- answer = unicode(sys.stdin.readline().strip(), "utf-8")
+ answer = u'{}'.format(sys.stdin.readline().strip())
except KeyboardInterrupt:
- print "\nOk, finishing for now. See you later ;-)"
+ print("\nOk, finishing for now. See you later ;-)")
sys.exit(4)
# if just enter pressed, we leave self.data as it is (confirmed)
if answer != "":
# append the data if the answer starts with a "+"
m = re.search("^\+\s*(.*)", answer)
- if m and type(self.data) is list:
+ if m and isinstance(self.data, list):
self.data.append(m.group(1))
else:
self.data = answer
@@ -1199,7 +1233,7 @@ class Inquisitor:
def heading(self):
""" Display nice heading with question """
- print "\n" + self.question + "\n" + 77 * "~";
+ print("\n" + self.question + "\n" + 77 * "~")
def value(self):
""" Return current value """
@@ -1223,11 +1257,11 @@ class Inquisitor:
def describe(self):
if self.description is not None:
- print wrapText(self.description)
+ print(wrapText(self.description))
def format(self, data = None):
""" Display in a nicely indented style """
- print self.name.rjust(ReviewWidth), ":", (data or self.show())
+ print(self.name.rjust(ReviewWidth), ":", (data or self.show()))
def formatMakefileLine(self, name = None, value = None):
""" Format testinfo line for Makefile inclusion """
@@ -1312,7 +1346,8 @@ class SingleChoice(Inquisitor):
def heading(self):
Inquisitor.heading(self)
- if self.list: print wrapText("Possible values: " + ", ".join(self.list))
+ if self.list:
+ print(wrapText("Possible values: " + ", ".join(self.list)))
class YesNo(SingleChoice):
@@ -1531,8 +1566,11 @@ DEFINED_LICENSES = {
"other" : PROPRIETARY_LICENSE_TEMPLATE,
}
+
class License(Inquisitor):
- """ License to be included in test files """
+ """
+ License to be included in test files
+ """
def init(self):
self.name = "License"
@@ -1543,17 +1581,20 @@ class License(Inquisitor):
self.licenses = DEFINED_LICENSES
def get(self):
- """ Return license corresponding to user choice """
- if self.data != "other" and self.data in self.licenses.keys():
+ """
+ Return license corresponding to user choice
+ """
+
+ if self.data != "other" and self.data in self.licenses:
return dedentText(self.licenses[self.data])
else:
license = self.options.pref.getLicenseContent(self.data)
- if license: # user defined license from preferences
- return dedentText(self.licenses["other"] % (
- license,), count = 12)
- else: # anything else
- return dedentText(self.licenses["other"] % (
- "PROVIDE YOUR LICENSE TEXT HERE.",))
+ if license: # user defined license from preferences
+ return dedentText(self.licenses["other"]
+ % (license,), count=12)
+ else: # anything else
+ return dedentText(self.licenses["other"]
+ % ("PROVIDE YOUR LICENSE TEXT HERE.",))
class Time(Inquisitor):
@@ -1725,8 +1766,8 @@ class Type(Inquisitor):
def heading(self):
Inquisitor.heading(self)
- print wrapText("Recommended values: " + ", ".join(sorted(self.dirs)))
- print wrapText("Possible values: " + ", ".join(self.list))
+ print(wrapText("Recommended values: " + ", ".join(sorted(self.dirs))))
+ print(wrapText("Possible values: " + ", ".join(self.list)))
def propose(self):
""" Try to find nearest match in the list"""
@@ -1845,17 +1886,17 @@ class Bugs(MultipleChoice):
bugid = self.data[0]
# contact bugzilla and try to fetch the details
try:
- print "Fetching details for", self.showItem(self.data[0])
+ print("Fetching details for", self.showItem(self.data[0]))
self.bug = self.options.bugzilla.getbug(bugid,
include_fields=['id', 'alias', 'component', 'summary',
'attachments'])
- except Exception, e:
+ except Exception as e:
if re.search('not authorized to access', str(e)):
- print "Sorry, %s has a restricted access.\n"\
- "Use 'bugzilla login' command to set up cookies "\
- "then try again." % self.showItem(self.data[0])
+ print("Sorry, %s has a restricted access.\n"
+ "Use 'bugzilla login' command to set up cookies "
+ "then try again." % self.showItem(self.data[0]))
else:
- print "Sorry, could not get details for %s\n%s" % (bugid, e)
+ print("Sorry, could not get details for %s\n%s" % (bugid, e))
sleep(3)
return
# successfully fetched
@@ -2006,7 +2047,7 @@ class Reproducers(MultipleChoice):
# Provide "None" as a possible choice for attachment download
self.list.append("None")
- print "Examining attachments for possible reproducers"
+ print("Examining attachments for possible reproducers")
for attachment in self.bug.attachments:
# skip obsolete and patch attachments
is_patch = attachment.get("is_patch", attachment.get("ispatch"))
@@ -2020,10 +2061,10 @@ class Reproducers(MultipleChoice):
RegExpReproducer.search(filename):
self.data.append(filename)
self.pref.append(filename)
- print "Adding",
+ print("Adding"),
else:
- print "Skipping",
- print "%s (%s)" % (filename, attachment['description'])
+ print("Skipping",)
+ print("%s (%s)" % (filename, attachment['description']))
sleep(1)
def download(self, path):
@@ -2036,7 +2077,7 @@ class Reproducers(MultipleChoice):
is_obsolete = attachment.get(
"is_obsolete", attachment.get("isobsolete"))
if attachment_filename in self.data and is_obsolete == 0:
- print "Attachment", attachment_filename,
+ print("Attachment", attachment_filename,)
try:
dirfiles = os.listdir(path)
filename = path + "/" + attachment_filename
@@ -2045,12 +2086,11 @@ class Reproducers(MultipleChoice):
# rename the attachment if it has the same name as one
# of the files in the current directory
if attachment_filename in dirfiles:
- print "- file already exists in {0}/".format(path)
+ print("- file already exists in {0}/".format(path))
new_name = ""
while new_name == "":
- print "Choose a new filename for the attachment: ",
- new_name = unicode(
- sys.stdin.readline().strip(), "utf-8")
+ print("Choose a new filename for the attachment: ",)
+ new_name = u'{}'.format(sys.stdin.readline().strip())
filename = path + "/" + new_name
local = open(filename, 'w')
@@ -2065,11 +2105,11 @@ class Reproducers(MultipleChoice):
else:
addedToGit = ""
except:
- print "download failed"
- print "python-bugzilla-0.5 or higher required"
+ print("download failed")
+ print("python-bugzilla-0.5 or higher required")
sys.exit(5)
else:
- print "downloaded" + addedToGit
+ print("downloaded" + addedToGit)
class RunFor(MultipleChoice):
@@ -2626,7 +2666,7 @@ class Skeleton(SingleChoice):
rhtsrequires = skeleton.getAttribute("rhtsrequires")
return rhtsrequires
except:
- print "getRhtsRequires exception"
+ print("getRhtsRequires exception")
def getMakefile(self, type, testname, version, author, reproducers, meta):
""" Return Makefile skeleton """
@@ -2727,13 +2767,13 @@ class Test(SingleChoice):
# possibly print first time welcome message
if self.options.pref.firstRun:
- print dedentText("""
+ print(dedentText("""
Welcome to The Beaker Wizard!
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
It seems, you're running the beaker-wizard for the first time.
I'll try to be a little bit more verbose. Should you need
any help in the future, just try using the "?" character.
- """, count = 16)
+ """, count = 16))
# gather all test data
self.testname = Name(self.options)
@@ -2777,21 +2817,21 @@ class Test(SingleChoice):
def format(self):
""" Format all test fields into nice table """
- print
- print self.fullPath()
- print
+ print()
+ print(self.fullPath())
+ print()
self.namespace.format()
self.package.format()
self.type.format()
self.path.format()
self.testname.format()
self.desc.format()
- print
+ print()
self.testname.bugs.format()
if not self.options.makefile: # skip in makefile edit mode
self.testname.prefix.format()
self.testname.bugs.reproducers.format()
- print
+ print()
self.runfor.format()
self.requires.format()
self.rhtsrequires.format()
@@ -2799,17 +2839,17 @@ class Test(SingleChoice):
self.releases.format()
self.version.format()
self.time.format()
- print
+ print()
self.priority.format()
self.license.format()
self.confidential.format()
self.destructive.format()
- print
+ print()
if not self.options.makefile:
self.skeleton.format() # irrelevant in makefile edit mode
self.author.format()
self.email.format()
- print
+ print()
def heading(self):
SingleChoice.heading(self)
@@ -2821,7 +2861,7 @@ class Test(SingleChoice):
# quit
if re.match("q|exit", self.data, re.I):
- print "Ok, quitting for now. See you later ;-)"
+ print("Ok, quitting for now. See you later ;-)")
sys.exit(0)
# no (seems the user is beginner -> turn on verbosity)
elif re.match("no?$", self.data, re.I):
@@ -2956,12 +2996,12 @@ class Test(SingleChoice):
if os.path.exists(fullpath):
sys.stdout.write(fullpath + " already exists, ")
if self.options.force():
- print "force on -> overwriting"
+ print("force on -> overwriting")
else:
sys.stdout.write("overwrite? [y/n] ")
- answer = unicode(sys.stdin.readline(), "utf-8")
+ answer = u'{}'.format(sys.stdin.readline())
if not re.match("y", answer, re.I):
- print "Ok skipping. Next time use -f if you want to overwrite files."
+ print("Ok skipping. Next time use -f if you want to overwrite files.")
return
# let's write it
@@ -2978,10 +3018,10 @@ class Test(SingleChoice):
addToGit(fullpath)
addedToGit = ", added to git"
except IOError:
- print "Cannot write to %s" % fullpath
+ print("Cannot write to %s" % fullpath)
sys.exit(3)
else:
- print "File", fullpath, "written" + addedToGit
+ print("File", fullpath, "written" + addedToGit)
def create(self):
""" Create all necessary test files """
@@ -3009,13 +3049,13 @@ class Test(SingleChoice):
# otherwise attempt to create the whole hiearchy
else:
os.makedirs(path)
- except OSError, e:
- print "Bad, cannot create test directory %s :-(" % path
+ except OSError:
+ print("Bad, cannot create test directory %s :-(" % path)
sys.exit(1)
except AlreadyExists:
- print "Well, directory %s already exists, let's see..." % path
+ print("Well, directory %s already exists, let's see..." % path)
else:
- print "Directory %s created%s" % (path, addedToGit)
+ print("Directory %s created %s" % (path, addedToGit))
# if test type is Library, create lib.sh and don't include PURPOSE
if self.type.value() == "Library":
@@ -3040,7 +3080,7 @@ class Test(SingleChoice):
comment(self.formatHeader("runtest.sh")) + "\n" +
comment(self.license.get(), top = False) + "\n" +
self.skeleton.getRuntest(self),
- mode=0755
+ mode=0o755
)
# Makefile
diff --git a/Common/Makefile b/Common/Makefile
index 8151772..0e6ff29 100644
--- a/Common/Makefile
+++ b/Common/Makefile
@@ -4,16 +4,23 @@
# the Free Software # Foundation, either version 2 of the License, or
# (at your option) any later version.
+# Use Python 2 if BKR_PY3 is not defined
+ifeq ($(BKR_PY3),)
+ BKR_PY3 :=0
+endif
+
+COMMAND:= $(shell if [[ $(BKR_PY3) == 0 ]]; then echo "python2"; else echo "python3"; fi)
+
.PHONY: build
build:
- python2 setup.py build
+ $(COMMAND) setup.py build
# Workaround for https://bitbucket.org/pypa/setuptools/issue/2/
# See adjacent setup.py for more details
echo bkr > build/namespace_packages.txt
.PHONY: install
install: build
- python2 setup.py install -O1 --skip-build --root $(DESTDIR)
+ $(COMMAND) setup.py install -O1 --skip-build --root $(DESTDIR)
# Workaround for https://bitbucket.org/pypa/setuptools/issue/2/
# See adjacent setup.py for more details
install -m0644 build/namespace_packages.txt \
@@ -21,7 +28,7 @@ install: build
.PHONY: clean
clean:
- python2 setup.py clean
+ $(COMMAND) setup.py clean
rm -rf build
.PHONY: check
diff --git a/Common/bkr/common/helpers.py b/Common/bkr/common/helpers.py
index 2adb9c6..f6c5c70 100644
--- a/Common/bkr/common/helpers.py
+++ b/Common/bkr/common/helpers.py
@@ -4,17 +4,19 @@
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
+from __future__ import division
+
from threading import Thread, Event
-import Queue, copy
from logging import getLogger
-from contextlib import contextmanager
import tempfile
import os
import fcntl
import errno
+from six.moves import queue
log = getLogger(__name__)
+
class _QueueAccess:
def __init__(self, in_q, out_q):
@@ -23,17 +25,21 @@ class _QueueAccess:
class BkrThreadPool:
+
_qpool = {}
_tpool = {}
+ def __init__(self):
+ pass
+
@classmethod
def create_and_run(cls, name, target_f, target_args, num=30, *args, **kw):
if name in cls._qpool:
raise Exception('%s has already been initialised in the BkrThreadPool' % name)
- out_q = Queue.Queue()
- in_q = Queue.Queue()
- cls._qpool[name] = _QueueAccess(in_q, out_q)
+ out_q = queue.Queue()
+ in_q = queue.Queue()
+ cls._qpool[name] = _QueueAccess(in_q, out_q)
cls._tpool[name] = []
for i in range(num):
t = Thread(target=target_f, args=target_args)
@@ -56,8 +62,14 @@ class BkrThreadPool:
class RepeatTimer(Thread):
- def __init__(self, interval, function, stop_on_exception=True, args=[], kwargs={}):
+ def __init__(self, interval, function, stop_on_exception=True, args=None, kwargs=None):
Thread.__init__(self)
+
+ if kwargs is None:
+ kwargs = {}
+ if args is None:
+ args = []
+
self.interval = interval
self.function = function
self.args = args
@@ -79,11 +91,11 @@ class RepeatTimer(Thread):
if not self.finished.is_set():
try:
self.function(*self.args, **self.kwargs)
- except Exception, e:
+ except Exception as e:
if self.stop_on_exception:
self.finished.clear()
raise
- # XXX Not strictly for auth'ing, think of something better
+ # TODO: Not strictly for auth'ing, think of something better
log.exception('Login Fail')
self.finished.clear()
@@ -91,24 +103,29 @@ class RepeatTimer(Thread):
class SensitiveUnicode(unicode):
def __repr__(self):
return '<repr blocked>'
+
def encode(self, *args, **kwargs):
return SensitiveStr(super(SensitiveUnicode, self).encode(*args, **kwargs))
+
class SensitiveStr(str):
def __repr__(self):
return '<repr blocked>'
+
def decode(self, *args, **kwargs):
return SensitiveUnicode(super(SensitiveStr, self).decode(*args, **kwargs))
+
# Would be nice if Python did this for us: http://bugs.python.org/issue8604
class AtomicFileReplacement(object):
- """Replace a file atomically
+ """
+ Replace a file atomically
Easiest usage is as a context manager, but create_temp, destroy_temp
and replace_dest can also be called directly if needed
"""
- def __init__(self, dest_path, mode=0644):
+ def __init__(self, dest_path, mode=0o644):
self.dest_path = dest_path
self.mode = mode
self._temp_info = None
@@ -168,6 +185,7 @@ class AtomicFileReplacement(object):
# Backwards compatibility alias
atomically_replaced_file = AtomicFileReplacement
+
def atomic_link(source, dest):
temp_path = tempfile.mktemp(prefix=os.path.basename(dest),
dir=os.path.dirname(dest))
@@ -183,6 +201,7 @@ def atomic_link(source, dest):
# Now re-raise the original exception
raise
+
def atomic_symlink(source, dest):
temp_path = tempfile.mktemp(prefix=os.path.basename(dest),
dir=os.path.dirname(dest))
@@ -198,6 +217,7 @@ def atomic_symlink(source, dest):
# Now re-raise the original exception
raise
+
def makedirs_ignore(path, mode):
"""
Creates the given directory (and any parents), but succeeds if it already
@@ -205,10 +225,11 @@ def makedirs_ignore(path, mode):
"""
try:
os.makedirs(path, mode)
- except OSError, e:
+ except OSError as e:
if e.errno != errno.EEXIST:
raise
+
def siphon(src, dest):
while True:
chunk = src.read(4096)
@@ -216,16 +237,18 @@ def siphon(src, dest):
break
dest.write(chunk)
+
def unlink_ignore(path):
"""
Unlinks the given path, but succeeds if it doesn't exist.
"""
try:
os.unlink(path)
- except OSError, e:
+ except OSError as e:
if e.errno != errno.ENOENT:
raise
+
class Flock(object):
"""
Context manager which locks the given path using flock(2).
@@ -246,6 +269,7 @@ class Flock(object):
os.close(self.fd)
del self.fd
+
# This is a method on timedelta in Python 2.7+
def total_seconds(td):
"""
diff --git a/Common/bkr/common/hub.py b/Common/bkr/common/hub.py
index dbc25a3..288d534 100644
--- a/Common/bkr/common/hub.py
+++ b/Common/bkr/common/hub.py
@@ -5,23 +5,31 @@
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
-# Mostly copy and pasted from kobo.client.__init__ (version 0.4.2-1)
-# Removed upload_task_log() and upload_file()
import os
import base64
import ssl
-import xmlrpclib
-import urlparse
+import six
+from six.moves import xmlrpc_client
+from six.moves import urllib_parse as urlparse
import gssapi
from bkr.common.pyconfig import PyConfigParser, ImproperlyConfigured
-from bkr.common.xmlrpc import CookieTransport, SafeCookieTransport, \
- retry_request_decorator
+if six.PY2:
+ from bkr.common.xmlrpc2 import CookieTransport, SafeCookieTransport, retry_request_decorator
+if six.PY3:
+ from bkr.common.xmlrpc3 import CookieTransport, SafeCookieTransport, retry_request_decorator
+
class AuthenticationError(Exception):
- """Authentication failed for some reason."""
+ """
+ Authentication failed for some reason.
+ """
pass
+
+
class HubProxy(object):
- """A Hub client (thin ServerProxy wrapper)."""
+ """
+ A Hub client (thin ServerProxy wrapper).
+ """
def __init__(self, conf, client_type=None, logger=None, transport=None,
auto_login=True, timeout=120, **kwargs):
@@ -67,7 +75,7 @@ class HubProxy(object):
TransportClass = retry_request_decorator(CookieTransport)
self._transport = TransportClass(**transport_args)
- self._hub = xmlrpclib.ServerProxy(
+ self._hub = xmlrpc_client.ServerProxy(
"%s/%s/" % (self._hub_url, self._client_type),
allow_none=True, transport=self._transport,
verbose=self._conf.get("DEBUG_XMLRPC"))
@@ -103,7 +111,7 @@ class HubProxy(object):
self._logged_in = True
except KeyboardInterrupt:
raise
- except Exception, ex:
+ except Exception as ex:
self._logger and self._logger.error("Failed to create new session: %s" % ex)
raise
else:
@@ -185,5 +193,8 @@ class HubProxy(object):
ex.args = (ex.message, )
raise ex
req_enc = base64.encodestring(res.token)
-
+ try:
+ req_enc = str(req_enc, 'utf-8') # bytes to string
+ except TypeError:
+ pass
self._hub.auth.login_krbv(req_enc, proxyuser)
diff --git a/Common/bkr/common/pyconfig.py b/Common/bkr/common/pyconfig.py
index 9239874..ae0068b 100644
--- a/Common/bkr/common/pyconfig.py
+++ b/Common/bkr/common/pyconfig.py
@@ -26,20 +26,29 @@ PyConfigParser accepts following python-like syntax:
- from <file_without_suffix> import *
- from <file_without_suffix> import var1, var2
"""
+
+from __future__ import print_function
+
import os
import fnmatch
import itertools
import keyword
import token
import tokenize
-from cStringIO import StringIO
+import six
+from six.moves import StringIO
class ImproperlyConfigured(Exception):
- """Program is improperly configured."""
+ """
+ Program is improperly configured.
+ """
pass
def get_dict_value(dictionary, key):
- """Return a value from a dictionary, if not found, use 'glob' keys (*, ? metachars), then use default value with '*' key."""
+ """
+ Return a value from a dictionary, if not found,
+ use 'glob' keys (*, ? metachars), then use default value with '*' key.
+ """
if dictionary is None:
return None
@@ -51,7 +60,7 @@ def get_dict_value(dictionary, key):
except KeyError:
if isinstance(key, str):
matches = []
- for pattern in dictionary.iterkeys():
+ for pattern in six.iterkeys(dictionary):
if pattern == '*' or not isinstance(pattern, str):
# exclude '*', because it would match every time
continue
@@ -67,7 +76,9 @@ def get_dict_value(dictionary, key):
class PyConfigParser(dict):
- """Python-like syntax config parser."""
+ """
+ Python-like syntax config parser.
+ """
get_dict_value = staticmethod(get_dict_value)
@@ -81,6 +92,7 @@ class PyConfigParser(dict):
self._config_file_suffix = config_file_suffix
self._debug = debug
self._open_file = None
+ self._tokens = None
def __getitem__(self, name):
if name.startswith("_"):
@@ -93,7 +105,9 @@ class PyConfigParser(dict):
return dict.__setitem__(self, name, value)
def load_from_file(self, file_name):
- """Load data data from a file."""
+ """
+ Load data data from a file.
+ """
fo = open(file_name, "r")
data = fo.read()
fo.close()
@@ -101,23 +115,31 @@ class PyConfigParser(dict):
self.load_from_string(data)
def load_from_string(self, input_string):
- """Load data from a string."""
+ """
+ Load data from a string.
+ """
if input_string:
self._tokens = tokenize.generate_tokens(StringIO(input_string).readline)
for key, value in self._parse():
self[key] = value
def load_from_dict(self, input_dict):
- """Load data from a dictionary."""
+ """
+ Load data from a dictionary.
+ """
if input_dict is not None:
self.update(input_dict)
def load_from_conf(self, conf):
- """Load data from another config."""
+ """
+ Load data from another config.
+ """
self.load_from_dict(conf)
def _parse(self):
- """Parse config file and store results to this object."""
+ """
+ Parse config file and store results to this object.
+ """
while True:
self._get_token()
@@ -141,7 +163,8 @@ class PyConfigParser(dict):
yield key, value
def _assert_token(self, *args):
- """Check if token has proper name and value.
+ """
+ Check if token has proper name and value.
*args are tuples (name, value), (name, )
"""
@@ -153,12 +176,14 @@ class PyConfigParser(dict):
raise SyntaxError("Invalid syntax: file: %s, begin: %s, end: %s, text: %s" % (self._open_file, self._tok_begin, self._tok_end, self._tok_line))
def _get_token(self, skip_newline=True):
- """Get a new token from token generator."""
- self._tok_number, self._tok_value, self._tok_begin, self._tok_end, self._tok_line = self._tokens.next()
+ """
+ Get a new token from token generator.
+ """
+ self._tok_number, self._tok_value, self._tok_begin, self._tok_end, self._tok_line = next(self._tokens)
self._tok_name = token.tok_name.get(self._tok_number, None)
if self._debug:
- print "%2s %16s %s" % (self._tok_number, self._tok_name, self._tok_value.strip())
+ print("%2s %16s %s" % (self._tok_number, self._tok_name, self._tok_value.strip()))
# skip some tokens
if self._tok_name in ["COMMENT", "INDENT", "DEDENT"]:
@@ -168,7 +193,9 @@ class PyConfigParser(dict):
self._get_token()
def _get_NAME(self):
- """Return a NAME token value."""
+ """
+ Return a NAME token value.
+ """
if self._tok_value == "False":
return False
@@ -182,13 +209,16 @@ class PyConfigParser(dict):
return self[self._tok_value]
def _get_STRING(self):
- """Return a STRING token value."""
+ """
+ Return a STRING token value.
+ """
+
# remove apostrophes or quotation marks
result = self._tok_value[1:-1]
# look at next token if "%s" follows the string
self._tokens, tmp = itertools.tee(self._tokens)
- if tmp.next()[1:2] != ("%", ):
+ if next(tmp)[1:2] != ("%", ):
# just a regular string
return result
@@ -199,7 +229,9 @@ class PyConfigParser(dict):
return result % values
def _get_NUMBER(self, negative=False):
- """Return a NUMER token value."""
+ """
+ Return a NUMBER token value.
+ """
if self._tok_value.find(".") != -1:
result = float(self._tok_value)
else:
@@ -210,7 +242,9 @@ class PyConfigParser(dict):
return result
def _get_value(self, get_next=True, basic_types_only=False):
- """Get a value (number, string, other variable value, ...)."""
+ """
+ Get a value (number, string, other variable value, ...).
+ """
if get_next:
self._get_token()
@@ -237,7 +271,10 @@ class PyConfigParser(dict):
self._assert_token(("FOO", ))
def _get_from_import(self):
- """Parse 'from <config> import <variables/*>' and import <config> data to this object."""
+ """
+ Parse 'from <config> import <variables/*>'
+ and import <config> data to this object.
+ """
file_name = ""
while True:
@@ -271,12 +308,16 @@ class PyConfigParser(dict):
raise KeyError("Can't import %s from %s." % (key, file_name))
def _skip_commas(self, skip_newline=True):
- """Skip OP tokens which contain commas."""
+ """
+ Skip OP tokens which contain commas.
+ """
while (self._tok_name, self._tok_value) == ("OP", ","):
self._get_token(skip_newline)
def _get_dict(self):
- """Get a dictionary content."""
+ """
+ Get a dictionary content.
+ """
result = {}
while True:
self._get_token()
@@ -296,7 +337,9 @@ class PyConfigParser(dict):
return result
def _get_list(self):
- """Get a list content."""
+ """
+ Get a list content.
+ """
result = []
while True:
self._get_token()
@@ -311,7 +354,9 @@ class PyConfigParser(dict):
return result
def _get_tuple(self):
- """Get a tuple content."""
+ """
+ Get a tuple content.
+ """
result = []
while True:
self._get_token()
@@ -331,5 +376,5 @@ if "PROJECT_CONFIG_FILE" in os.environ:
try:
settings = PyConfigParser()
settings.load_from_file(os.environ.get("PROJECT_CONFIG_FILE"))
- except Exception, ex:
+ except Exception as ex:
raise ImproperlyConfigured("Could not load config file: %s" % ex)
diff --git a/Common/bkr/common/test_xmlrpc.py b/Common/bkr/common/test_xmlrpc.py
index 20eaac4..7919d12 100644
--- a/Common/bkr/common/test_xmlrpc.py
+++ b/Common/bkr/common/test_xmlrpc.py
@@ -4,27 +4,39 @@
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
-import unittest
import socket
-import bkr.common.xmlrpc
+import unittest
+
+import six
+
+if six.PY2:
+ from bkr.common import xmlrpc2 as xmlrpc_interface
+if six.PY3:
+ from bkr.common import xmlrpc3 as xmlrpc_interface
+
class DummyTransport:
- """Helper to test retry_request_decorator"""
+ """
+ Helper to test retry_request_decorator
+ """
def __init__(self):
self.request_count = 0
self.close_count = 0
def request(self, hostname, failures=0):
- self.request_count += 1
- if self.request_count <= failures:
- raise socket.error
- return self.request_count
+ self.request_count += 1
+ if self.request_count <= failures:
+ raise socket.error
+ return self.request_count
def close(self):
- self.close_count += 1
+ self.close_count += 1
+
class DummyLogger:
- """Helper to test retry_request_decorator"""
+ """
+ Helper to test retry_request_decorator
+ """
def __init__(self):
self.messages = []
@@ -36,14 +48,14 @@ class RetryTransportMixin:
DEFAULT_RETRY_COUNT = 5
def make_transport(self):
- return bkr.common.xmlrpc.retry_request_decorator(DummyTransport)(
+ return xmlrpc_interface.retry_request_decorator(DummyTransport)(
retry_timeout=0.001)
class RetryTransportTestCase(RetryTransportMixin, unittest.TestCase):
# Check with the standard library logger in place
def test_default_retry_settings(self):
- transport = bkr.common.xmlrpc.retry_request_decorator(DummyTransport)()
+ transport = xmlrpc_interface.retry_request_decorator(DummyTransport)()
self.assertEqual(transport.retry_count, self.DEFAULT_RETRY_COUNT)
self.assertEqual(transport.retry_timeout, 30)
@@ -65,11 +77,11 @@ class RetryTransportTestCase(RetryTransportMixin, unittest.TestCase):
class RetryTransportLoggingTestCase(RetryTransportMixin, unittest.TestCase):
# Check we're logging the right things
def setUp(self):
- self.orig_logger = bkr.common.xmlrpc.logger
- self.logger = bkr.common.xmlrpc.logger = DummyLogger()
+ self.orig_logger = xmlrpc_interface.logger
+ self.logger = xmlrpc_interface.logger = DummyLogger()
def tearDown(self):
- bkr.common.xmlrpc.logger = self.orig_logger
+ xmlrpc_interface.logger = self.orig_logger
def test_immediate_success(self):
transport = self.make_transport()
@@ -83,7 +95,7 @@ class RetryTransportLoggingTestCase(RetryTransportMixin, unittest.TestCase):
transport = self.make_transport()
self.assertEqual(transport.request("dummy", failures=2), 3)
max_retries = self.DEFAULT_RETRY_COUNT
- expected = range(max_retries, max_retries-2, -1)
+ expected = list(range(max_retries, max_retries-2, -1))
self.assertEqual(self.get_retry_counts(), expected)
self.assertTrue(self.logger.messages[0][2]["exc_info"])
@@ -91,5 +103,5 @@ class RetryTransportLoggingTestCase(RetryTransportMixin, unittest.TestCase):
transport = self.make_transport()
self.assertRaises(socket.error, transport.request, "dummy",
failures = self.DEFAULT_RETRY_COUNT + 1)
- expected = range(self.DEFAULT_RETRY_COUNT, 0, -1)
+ expected = list(range(self.DEFAULT_RETRY_COUNT, 0, -1))
self.assertEqual(self.get_retry_counts(), expected)
diff --git a/Common/bkr/common/xmlrpc.py b/Common/bkr/common/xmlrpc2.py
index 2f65c27..50cc3ea 100644
--- a/Common/bkr/common/xmlrpc.py
+++ b/Common/bkr/common/xmlrpc2.py
@@ -5,14 +5,12 @@
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
-# This is a cut and paste from kobo.xmlrpc, version 0.4.2-1
import base64
import cookielib
import httplib
import os
import socket
import ssl
-import sys
import threading
import logging
import time
@@ -31,6 +29,10 @@ logger = logging.getLogger(__name__)
class TimeoutHTTPConnection(httplib.HTTPConnection):
+
+ def set_timeout(self, value):
+ setattr(self, '_timeout', value)
+
def connect(self):
httplib.HTTPConnection.connect(self)
timeout = getattr(self, "_timeout", 0)
@@ -74,11 +76,12 @@ class TimeoutHTTPProxyConnection(TimeoutHTTPConnection):
self.putheader("Proxy-Authorization", "Basic %s" % enc_userpass)
def set_host_and_port(self, host, port):
- if hasattr(self, "_set_hostport"): # Python < 2.7.7
- self._set_hostport(host, port) #pylint: disable=no-member
+ if hasattr(self, "_set_hostport"): # Python < 2.7.7
+ self._set_hostport(host, port) # pylint: disable=no-member
else:
(self.host, self.port) = self._get_hostport(host, port)
+
class TimeoutHTTPSProxyConnection(TimeoutHTTPProxyConnection):
default_port = httplib.HTTPSConnection.default_port
@@ -117,65 +120,28 @@ class TimeoutHTTPSProxyConnection(TimeoutHTTPProxyConnection):
return TimeoutHTTPConnection.putrequest(self, method, url)
-class TimeoutHTTP(httplib.HTTP):
- _connection_class = TimeoutHTTPConnection
-
- def set_timeout(self, timeout):
- self._conn._timeout = timeout
-
-
-class TimeoutProxyHTTP(TimeoutHTTP):
- _connection_class = TimeoutHTTPProxyConnection
-
- def __init__(self, host='', proxy='', port=None, strict=None,
- proxy_user=None, proxy_password=None):
- if port == 0:
- port = None
- self._setup(self._connection_class(host, proxy, port=port,
- strict=strict,
- proxy_user=proxy_user,
- proxy_password=proxy_password))
-
- def _setup(self, conn):
- httplib.HTTP._setup(self, conn)
- # XXX: Hack for python >= 2.7 where a _single_request method is used
- # and the method needs a connection object with .getresponse() method
- self.getresponse = conn.getresponse
+class TimeoutHTTPSConnection(httplib.HTTPSConnection):
+ def set_timeout(self, value):
+ setattr(self, '_timeout', value)
-class TimeoutHTTPSConnection(httplib.HTTPSConnection):
def connect(self):
- timeout = getattr(self, "_timeout", 0)
+ timeout = getattr(self, '_timeout', 0)
if timeout:
self.timeout = timeout
httplib.HTTPSConnection.connect(self)
-class TimeoutHTTPS(httplib.HTTPS):
- _connection_class = TimeoutHTTPSConnection
-
- def set_timeout(self, timeout):
- self._conn._timeout = timeout
-
-
-class TimeoutProxyHTTPS(TimeoutHTTPS):
+class TimeoutProxyHTTPS(TimeoutHTTPSProxyConnection):
_connection_class = TimeoutHTTPSProxyConnection
- def __init__(self, host='', proxy='', port=None, strict=None,
- proxy_user=None, proxy_password=None, cert_file=None,
- key_file=None):
+ def __init__(self, host='', proxy='', port=None, proxy_user=None,
+ proxy_password=None, cert_file=None, key_file=None, **kwargs):
+
if port == 0:
port = None
- self._setup(self._connection_class(host, proxy, port=port,
- strict=strict, proxy_user=proxy_user,
- proxy_password=proxy_password, cert_file=cert_file,
- key_file=key_file))
-
- def _setup(self, conn):
- httplib.HTTP._setup(self, conn)
- # XXX: Hack for python >= 2.7 where a _single_request method is used
- # and the method needs a connection object with .getresponse() method
- self.getresponse = conn.getresponse
+ TimeoutHTTPSProxyConnection.__init__(self, host, proxy, port, proxy_user, proxy_password,
+ cert_file, key_file, **kwargs)
class CookieResponse(object):
@@ -195,9 +161,10 @@ class CookieTransport(xmlrpclib.Transport):
USAGE:
>>> import xmlrpclib
- from kr.common.xmlrpc import CookieTransport
- client = xmlrpclib.ServerProxy("http://<server>/xmlrpc", transport=CookieTransport())
- # for https:// connections use SafeCookieTransport() instead.
+ >>> from bkr.common.xmlrpc import CookieTransport
+ >>> client = xmlrpclib.ServerProxy("http://<server>/xmlrpc", transport=CookieTransport())
+
+ For https:// connections use SafeCookieTransport() instead.
"""
_use_datetime = False # fix for python 2.5+
@@ -209,8 +176,7 @@ class CookieTransport(xmlrpclib.Transport):
self.proxy_config = self._get_proxy(**kwargs)
self.no_proxy = os.environ.get("no_proxy", "").lower().split(',')
- if hasattr(xmlrpclib.Transport, "__init__"):
- xmlrpclib.Transport.__init__(self, *args, **kwargs)
+ xmlrpclib.Transport.__init__(self, *args, **kwargs)
self.cookiejar = cookiejar or cookielib.CookieJar()
@@ -221,7 +187,9 @@ class CookieTransport(xmlrpclib.Transport):
self.cookiejar.load(self.cookiejar.filename)
def _get_proxy(self, **kwargs):
- """Return dict with appropriate proxy settings"""
+ """
+ Return dict with appropriate proxy settings
+ """
proxy = None
proxy_user = None
proxy_password = None
@@ -270,8 +238,9 @@ class CookieTransport(xmlrpclib.Transport):
return proxy_settings
def make_connection(self, host):
+
host.lower()
- host_ = host # Host with(out) port
+
if ':' in host:
# Remove port from the host
host_ = host.split(':')[0]
@@ -279,41 +248,28 @@ class CookieTransport(xmlrpclib.Transport):
host_ = "%s:%s" % (host, TimeoutHTTPProxyConnection.default_port)
if self.proxy_config["proxy"] and host not in self.no_proxy and host_ not in self.no_proxy:
- if sys.version_info[:2] < (2, 7):
- host, extra_headers, x509 = self.get_host_info(host)
- conn = TimeoutProxyHTTP(host, **self.proxy_config)
- conn.set_timeout(self.timeout)
- return conn
- else:
- CONNECTION_LOCK.acquire()
- host, extra_headers, x509 = self.get_host_info(host)
- conn = TimeoutProxyHTTPS(host, **self.proxy_config)
- conn.set_timeout(self.timeout)
- CONNECTION_LOCK.release()
- return conn
-
- if sys.version_info[:2] < (2, 7):
- host, extra_headers, x509 = self.get_host_info(host)
- conn = TimeoutHTTP(host)
- conn.set_timeout(self.timeout)
- return conn
- else:
CONNECTION_LOCK.acquire()
- self._connection = (None, None) # this disables connection caching which causes a race condition when running in threads
- conn = xmlrpclib.Transport.make_connection(self, host)
+ host, _, _ = self.get_host_info(host)
+ conn = TimeoutProxyHTTPS(host, **self.proxy_config)
+ conn.set_timeout(self.timeout)
CONNECTION_LOCK.release()
- if self.timeout:
- conn.timeout = self.timeout
return conn
- def send_request(self, connection, handler, request_body):
- return xmlrpclib.Transport.send_request(self, connection, handler, request_body)
+ CONNECTION_LOCK.acquire()
+ # this disables connection caching which causes a race condition when running in threads
+ self._connection = (None, None)
+ conn = xmlrpclib.Transport.make_connection(self, host)
+ CONNECTION_LOCK.release()
+
+ if self.timeout:
+ conn.timeout = self.timeout
- def send_host(self, connection, host):
- return xmlrpclib.Transport.send_host(self, connection, host)
+ return conn
def send_cookies(self, connection, cookie_request):
- """Add cookies to the header."""
+ """
+ Add cookies to the header.
+ """
self.cookiejar.add_cookie_header(cookie_request)
for header, value in cookie_request.header_items():
@@ -326,7 +282,8 @@ class CookieTransport(xmlrpclib.Transport):
if hasattr(self.cookiejar, "save"):
self.cookiejar.save(self.cookiejar.filename)
- def _kerberos_client_request(self, host, handler, errcode, errmsg, headers):
+ @staticmethod
+ def _kerberos_client_request(host, handler, errcode, errmsg, headers):
"""Kerberos auth - create a client request string"""
# check if "Negotiate" challenge is present in headers
@@ -350,7 +307,8 @@ class CookieTransport(xmlrpclib.Transport):
return vc, kerberos.authGSSClientResponse(vc)
- def _kerberos_verify_response(self, vc, host, handler, errcode, errmsg, headers):
+ @staticmethod
+ def _kerberos_verify_response(vc, host, handler, headers):
"""Kerberos auth - verify client identity"""
# verify that headers contain WWW-Authenticate header
auth_header = headers.get("WWW-Authenticate", None)
@@ -373,7 +331,7 @@ class CookieTransport(xmlrpclib.Transport):
if rc == -1:
errcode = 401
raise xmlrpclib.ProtocolError(host + handler, errcode, errmsg, headers)
- except kerberos.GSSError, ex:
+ except kerberos.GSSError as ex:
errcode = 401
errmsg += ": %s/%s" % (ex[0][0], ex[1][0])
raise xmlrpclib.ProtocolError(host + handler, errcode, errmsg, headers)
@@ -385,48 +343,7 @@ class CookieTransport(xmlrpclib.Transport):
errmsg = "KERBEROS: Could not clean-up GSSAPI: %s/%s" % (ex[0][0], ex[1][0])
raise xmlrpclib.ProtocolError(host + handler, errcode, errmsg, headers)
- def _request(self, host, handler, request_body, verbose=0):
- """Send a HTTP request."""
- h = self.make_connection(host)
- self.verbose = verbose
- if verbose:
- h.set_debuglevel(1)
-
- request_url = "%s://%s/" % (self.scheme, host)
- cookie_request = urllib2.Request(request_url)
-
- self.send_request(h, handler, request_body)
- self.send_host(h, host)
- self.send_cookies(h, cookie_request)
- self.send_user_agent(h)
- self.send_content(h, request_body)
- errcode, errmsg, headers = h.getreply()
-
- if errcode == 401 and USE_KERBEROS:
- vc, challenge = self._kerberos_client_request(host, handler, errcode, errmsg, headers)
- # retry the original request & add the Authorization header:
- self.send_request(h, handler, request_body)
- self.send_host(h, host)
- h.putheader("Authorization", "Negotiate %s" % challenge)
- self.send_cookies(h, cookie_request)
- self.send_user_agent(h)
- self.send_content(h, request_body)
- errcode, errmsg, headers = h.getreply()
- self._kerberos_verify_response(vc, host, handler, errcode, errmsg, headers)
-
- elif errcode != 200:
- raise xmlrpclib.ProtocolError(host + handler, errcode, errmsg, headers)
-
- self._save_cookies(headers, cookie_request)
-
- try:
- sock = h._conn.sock
- except AttributeError:
- sock = None
-
- return self.parse_response(h.getfile())
-
- def _single_request(self, host, handler, request_body, verbose=0):
+ def single_request(self, host, handler, request_body, verbose=0):
# issue XML-RPC request
request_url = "%s://%s/" % (self.scheme, host)
@@ -461,7 +378,7 @@ class CookieTransport(xmlrpclib.Transport):
self.send_content(h, request_body)
self._extra_headers = []
response = h.getresponse(buffering=True)
- self._kerberos_verify_response(vc, host, handler, response.status, response.reason, response.msg)
+ self._kerberos_verify_response(vc, host, handler, response.msg)
if response.status == 200:
self.verbose = verbose
@@ -477,17 +394,10 @@ class CookieTransport(xmlrpclib.Transport):
raise
# discard any response data and raise exception
- if (response.getheader("content-length", 0)):
+ if response.getheader("content-length", 0):
response.read()
raise xmlrpclib.ProtocolError(host + handler, response.status, response.reason, response.msg)
- # override the appropriate request method
- if hasattr(xmlrpclib.Transport, "single_request"):
- # python 2.7+
- single_request = _single_request
- else:
- # python 2.6-
- request = _request
class SafeCookieTransport(xmlrpclib.SafeTransport, CookieTransport):
@@ -496,11 +406,19 @@ class SafeCookieTransport(xmlrpclib.SafeTransport, CookieTransport):
USAGE: see CookieTransport
"""
+
scheme = "https"
+ def __init__(self, *args, **kwargs):
+ # SafeTransport.__init__ does this but we can't call that because we
+ # have an inheritance diamond and these are old-style classes...
+ self.context = kwargs.pop('context', None)
+ CookieTransport.__init__(self, *args, **kwargs)
+
def make_connection(self, host):
+
host.lower()
- host_ = host # Host with(out) port
+
if ':' in host:
# Remove port from the host
host_ = host.split(':')[0]
@@ -508,53 +426,28 @@ class SafeCookieTransport(xmlrpclib.SafeTransport, CookieTransport):
host_ = "%s:%s" % (host, TimeoutHTTPSProxyConnection.default_port)
if self.proxy_config["proxy"] and host not in self.no_proxy and host_ not in self.no_proxy:
- if sys.version_info[:2] < (2, 7):
- host, extra_headers, x509 = self.get_host_info(host)
- conn = TimeoutProxyHTTPS(host, **self.proxy_config)
- conn.set_timeout(self.timeout)
- return conn
- else:
- CONNECTION_LOCK.acquire()
- host, extra_headers, x509 = self.get_host_info(host)
- conn = TimeoutProxyHTTPS(host, **self.proxy_config)
- conn.set_timeout(self.timeout)
- CONNECTION_LOCK.release()
- return conn
-
- if sys.version_info[:2] < (2, 7):
- host, extra_headers, x509 = self.get_host_info(host)
- conn = TimeoutHTTPS(host, None, **(x509 or {}))
+ CONNECTION_LOCK.acquire()
+ host, _, _ = self.get_host_info(host)
+ conn = TimeoutProxyHTTPS(host, **self.proxy_config)
conn.set_timeout(self.timeout)
- return conn
- else:
- conn = xmlrpclib.SafeTransport.make_connection(self, host)
- if self.timeout:
- conn.timeout = self.timeout
- return conn
-
- # override the appropriate request method
- if hasattr(xmlrpclib.Transport, "single_request"):
- # python 2.7+
- single_request = CookieTransport._single_request
- else:
- # python 2.6-
- request = CookieTransport._request
+ CONNECTION_LOCK.release()
- def __init__(self, *args, **kwargs):
- # SafeTransport.__init__ does this but we can't call that because we
- # have an inheritance diamond and these are old-style classes...
- self.context = kwargs.pop('context', None)
- CookieTransport.__init__(self, *args, **kwargs)
+ return conn
- def send_request(self, connection, handler, request_body):
- return xmlrpclib.SafeTransport.send_request(self, connection, handler, request_body)
+ CONNECTION_LOCK.acquire()
+ self._connection = (None, None)
+ conn = xmlrpclib.SafeTransport.make_connection(self, host)
+ if self.timeout:
+ conn.timeout = self.timeout
+ CONNECTION_LOCK.release()
- def send_host(self, connection, host):
- return xmlrpclib.SafeTransport.send_host(self, connection, host)
+ return conn
def retry_request_decorator(transport_class):
- """Use this class decorator on a Transport to retry requests which failed on socket errors."""
+ """
+ Use this class decorator on a Transport to retry requests which failed on socket errors.
+ """
class RetryTransportClass(transport_class):
def __init__(self, *args, **kwargs):
self.retry_count = kwargs.pop("retry_count", 5)
@@ -566,19 +459,19 @@ def retry_request_decorator(transport_class):
if self.retry_count == 0:
return transport_class.request(self, *args, **kwargs)
- for i in xrange(self.retry_count + 1):
+ for i in range(self.retry_count + 1):
try:
result = transport_class.request(self, *args, **kwargs)
return result
except KeyboardInterrupt:
raise
- except (socket.error, socket.herror, socket.gaierror, socket.timeout), ex:
+ except (socket.error, socket.herror, socket.gaierror, socket.timeout) as ex:
if hasattr(self, 'close'):
self.close()
if i >= self.retry_count:
raise
retries_left = self.retry_count - i
- retries = (retries_left == 1 and "retry" or "retries") # 1 retry left / X retries left
+ retries = (retries_left == 1 and "retry" or "retries") # 1 retry left / X retries left
logger.warning("XML-RPC connection to %s failed: %s, %d %s left",
args[0], " ".join(ex.args[1:]), retries_left, retries,
exc_info=True)
diff --git a/Common/bkr/common/xmlrpc3.py b/Common/bkr/common/xmlrpc3.py
new file mode 100644
index 0000000..869c98e
--- /dev/null
+++ b/Common/bkr/common/xmlrpc3.py
@@ -0,0 +1,489 @@
+# -*- coding: utf-8 -*-
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+import base64
+import os
+import socket
+import ssl
+import threading
+import logging
+import time
+# Make pylint2 happy
+import six.moves.http_client as httplib
+import six.moves.http_cookiejar as cookielib
+import six.moves.xmlrpc_client as xmlrpclib
+from six.moves.urllib_parse import urlparse
+from six.moves.urllib import request
+
+try:
+ import kerberos
+
+ USE_KERBEROS = True
+except ImportError:
+ USE_KERBEROS = False
+CONNECTION_LOCK = threading.Lock()
+
+logger = logging.getLogger(__name__)
+
+
+class TimeoutHTTPConnection(httplib.HTTPConnection):
+
+ def set_timeout(self, value):
+ setattr(self, '_timeout', value)
+
+ def connect(self):
+ httplib.HTTPConnection.connect(self)
+ timeout = getattr(self, "_timeout", 0)
+ if timeout:
+ self.sock.settimeout(timeout)
+
+
+class TimeoutHTTPProxyConnection(TimeoutHTTPConnection):
+ default_port = httplib.HTTPConnection.default_port
+
+ def __init__(self, host, proxy, port=None, proxy_user=None, proxy_password=None, **kwargs):
+ TimeoutHTTPConnection.__init__(self, proxy, **kwargs)
+ self.proxy, self.proxy_port = self.host, self.port
+ self.set_host_and_port(host, port)
+ self.real_host, self.real_port = self.host, self.port
+ self.proxy_user = proxy_user
+ self.proxy_password = proxy_password
+
+ def connect(self):
+ # Connect to the proxy
+ self.set_host_and_port(self.proxy, self.proxy_port)
+ httplib.HTTPConnection.connect(self)
+ self.set_host_and_port(self.real_host, self.real_port)
+ timeout = getattr(self, "_timeout", 0)
+ if timeout:
+ self.sock.settimeout(timeout)
+
+ def putrequest(self, method, url, skip_host=0, skip_accept_encoding=0):
+ host = self.real_host
+ if self.default_port != self.real_port:
+ host = host + ':' + str(self.real_port)
+ url = "http://%s%s" % (host, url)
+ httplib.HTTPConnection.putrequest(self, method, url)
+ self._add_auth_proxy_header()
+
+ def _add_auth_proxy_header(self):
+ if not self.proxy_user:
+ return
+ userpass = "%s:%s" % (self.proxy_user, self.proxy_password)
+ enc_userpass = base64.encodebytes(userpass).strip() # pylint: disable=no-member
+ self.putheader("Proxy-Authorization", "Basic %s" % enc_userpass)
+
+ def set_host_and_port(self, host, port):
+ if hasattr(self, "_set_hostport"): # Python < 2.7.7
+ self._set_hostport(host, port) # pylint: disable=no-member
+ else:
+ (self.host, self.port) = self._get_hostport(host, port)
+
+
+class TimeoutHTTPSProxyConnection(TimeoutHTTPProxyConnection):
+ default_port = httplib.HTTPSConnection.default_port
+
+ def __init__(self, host, proxy, port=None, proxy_user=None,
+ proxy_password=None, cert_file=None, key_file=None, **kwargs):
+ TimeoutHTTPProxyConnection.__init__(self, host, proxy, port,
+ proxy_user, proxy_password, **kwargs)
+ self.cert_file = cert_file
+ self.key_file = key_file
+ self.connect()
+
+ def connect(self):
+ TimeoutHTTPProxyConnection.connect(self)
+ host = "%s:%s" % (self.real_host, self.real_port)
+ TimeoutHTTPConnection.putrequest(self, "CONNECT", host)
+ self._add_auth_proxy_header()
+ TimeoutHTTPConnection.endheaders(self)
+
+ class MyHTTPSResponse(httplib.HTTPResponse):
+ def begin(self):
+ httplib.HTTPResponse.begin(self)
+ self.will_close = 0
+
+ response_class = self.response_class
+ self.response_class = MyHTTPSResponse
+ response = httplib.HTTPConnection.getresponse(self)
+ self.response_class = response_class
+ response.close()
+ if response.status != 200:
+ self.close()
+ raise socket.error(1001, response.status, response.msg)
+
+ self.sock = ssl.wrap_socket(self.sock, keyfile=self.key_file, certfile=self.cert_file)
+
+ def putrequest(self, method, url, skip_host=0, skip_accept_encoding=0):
+ return TimeoutHTTPConnection.putrequest(self, method, url)
+
+
+class TimeoutHTTPSConnection(httplib.HTTPSConnection):
+
+ def set_timeout(self, value):
+ setattr(self, '_timeout', value)
+
+ def connect(self):
+ timeout = getattr(self, '_timeout', 0)
+ if timeout:
+ self.timeout = timeout
+ httplib.HTTPSConnection.connect(self)
+
+
+class TimeoutProxyHTTPS(TimeoutHTTPSProxyConnection):
+ _connection_class = TimeoutHTTPSProxyConnection
+
+ def __init__(self, host='', proxy='', port=None, proxy_user=None,
+ proxy_password=None, cert_file=None, key_file=None, **kwargs):
+ if port == 0:
+ port = None
+ TimeoutHTTPSProxyConnection.__init__(self, host, proxy, port, proxy_user, proxy_password,
+ cert_file, key_file, **kwargs)
+
+
+class CookieResponse(object):
+ """
+ Fake response class for cookie extraction.
+ """
+
+ def __init__(self, headers):
+ self.headers = headers
+
+ def info(self):
+ """
+ Pass response headers to cookie jar.
+ """
+ return self.headers
+
+
+class CookieTransport(xmlrpclib.Transport):
+ """
+ Cookie enabled XML-RPC transport.
+
+ USAGE:
+ >>> import xmlrpc.client
+ >>> from bkr.common.xmlrpc import CookieTransport
+ >>> client = xmlrpc.client.ServerProxy("http://<server>/xmlrpc", transport=CookieTransport())
+
+ For https:// connections use SafeCookieTransport() instead.
+ """
+
+ scheme = "http"
+
+ def __init__(self, *args, **kwargs):
+ cookiejar = kwargs.pop("cookiejar", None)
+ self.timeout = kwargs.pop("timeout", 0)
+ self.proxy_config = self._get_proxy(**kwargs)
+ self.no_proxy = os.environ.get("no_proxy", "").lower().split(',')
+ self.verbose = 0
+ self.cookie_request = None
+
+ xmlrpclib.Transport.__init__(self, *args, **kwargs)
+
+ self.cookiejar = cookiejar or cookielib.CookieJar()
+
+ if hasattr(self.cookiejar, "load"):
+ if not os.path.exists(self.cookiejar.filename):
+ if hasattr(self.cookiejar, "save"):
+ self.cookiejar.save(self.cookiejar.filename)
+ self.cookiejar.load(self.cookiejar.filename)
+
+ def _get_proxy(self, **kwargs):
+ """
+ Return dict with appropriate proxy settings
+ """
+ proxy = None
+ proxy_user = None
+ proxy_password = None
+
+ if kwargs.get("proxy", None):
+ # Use proxy from __init__ params
+ proxy = kwargs["proxy"]
+ if kwargs.get("proxy_user", None):
+ proxy_user = kwargs["proxy_user"]
+ if kwargs.get("proxy_password", None):
+ proxy_password = kwargs["proxy_user"]
+ else:
+ # Try to get proxy settings from environmental vars
+ if self.scheme == "http" and os.environ.get("http_proxy", None):
+ proxy = os.environ["http_proxy"]
+ elif self.scheme == "https" and os.environ.get("https_proxy", None):
+ proxy = os.environ["https_proxy"]
+
+ if proxy:
+ # Parse proxy address
+ # e.g. http://user:password@proxy.company.com:8001/foo/bar
+
+ # Get raw location without path
+ location = urlparse(proxy)[1]
+ if not location:
+ # proxy probably doesn't have a protocol in prefix
+ location = urlparse("http://%s" % proxy)[1]
+
+ # Parse out username and password if present
+ if '@' in location:
+ userpas, location = location.split('@', 1)
+ if userpas and location and not proxy_user:
+ # Set proxy user only if proxy_user is not set yet
+ proxy_user = userpas
+ if ':' in userpas:
+ proxy_user, proxy_password = userpas.split(':', 1)
+
+ proxy = location
+
+ proxy_settings = {
+ "proxy": proxy,
+ "proxy_user": proxy_user,
+ "proxy_password": proxy_password,
+ }
+
+ return proxy_settings
+
+ def make_connection(self, host):
+
+ host.lower()
+
+ if ':' in host:
+ # Remove port from the host
+ host_ = host.split(':')[0]
+ else:
+ host_ = "%s:%s" % (host, TimeoutHTTPProxyConnection.default_port)
+
+ if self.proxy_config["proxy"] and host not in self.no_proxy and host_ not in self.no_proxy:
+ CONNECTION_LOCK.acquire()
+ host, _, _ = self.get_host_info(host)
+ conn = TimeoutProxyHTTPS(host, **self.proxy_config)
+ conn.set_timeout(self.timeout)
+ CONNECTION_LOCK.release()
+ return conn
+
+ CONNECTION_LOCK.acquire()
+ # this disables connection caching which causes a race condition when running in threads
+ self._connection = (None, None)
+ conn = xmlrpclib.Transport.make_connection(self, host)
+ CONNECTION_LOCK.release()
+
+ if self.timeout:
+ conn.timeout = self.timeout
+
+ return conn
+
+ def send_cookies(self, connection):
+ """
+ Add cookies to the header.
+ """
+ self.cookiejar.add_cookie_header(self.cookie_request)
+
+ for header, value in self.cookie_request.header_items():
+ if header.startswith("Cookie"):
+ connection.putheader(header, value)
+
+ def send_headers(self, connection, headers):
+
+ for key, val in headers:
+ connection.putheader(key, val)
+
+ self.send_cookies(connection)
+
+ def _save_cookies(self, headers, cookie_request):
+ cookie_response = CookieResponse(headers)
+ self.cookiejar.extract_cookies(cookie_response, cookie_request)
+ if hasattr(self.cookiejar, "save"):
+ self.cookiejar.save(self.cookiejar.filename)
+
+ @staticmethod
+ def _kerberos_client_request(host, handler, errcode, errmsg, headers):
+ """Kerberos auth - create a client request string"""
+
+ # check if "Negotiate" challenge is present in headers
+ negotiate = [i.lower() for i in headers.get("WWW-Authenticate", "").split(", ")]
+ if "negotiate" not in negotiate:
+ # negotiate not supported, raise 401 error
+ raise xmlrpclib.ProtocolError(host + handler, errcode, errmsg, headers)
+
+ # initialize GSSAPI
+ service = "HTTP@%s" % host
+ rc, vc = kerberos.authGSSClientInit(service)
+ if rc != 1:
+ errmsg = "KERBEROS: Could not initialize GSSAPI"
+ raise xmlrpclib.ProtocolError(host + handler, errcode, errmsg, headers)
+
+ # do a client step
+ rc = kerberos.authGSSClientStep(vc, "")
+ if rc != 0:
+ errmsg = "KERBEROS: Client step failed"
+ raise xmlrpclib.ProtocolError(host + handler, errcode, errmsg, headers)
+
+ return vc, kerberos.authGSSClientResponse(vc)
+
+ @staticmethod
+ def _kerberos_verify_response(vc, host, handler, headers):
+ """Kerberos auth - verify client identity"""
+ # verify that headers contain WWW-Authenticate header
+ auth_header = headers.get("WWW-Authenticate", None)
+ if auth_header is None:
+ errcode = 401
+ errmsg = "KERBEROS: No WWW-Authenticate header in second HTTP response"
+ raise xmlrpclib.ProtocolError(host + handler, errcode, errmsg, headers)
+
+ # verify that WWW-Authenticate contains Negotiate
+ splits = auth_header.split(" ", 1)
+ if (len(splits) != 2) or (splits[0].lower() != "negotiate"):
+ errcode = 401
+ errmsg = "KERBEROS: Incorrect WWW-Authenticate header in second HTTP response: %s" % auth_header
+ raise xmlrpclib.ProtocolError(host + handler, errcode, errmsg, headers)
+
+ # do another client step to verify response from server
+ errmsg = "KERBEROS: Could not verify server WWW-Authenticate header in second HTTP response"
+ try:
+ rc = kerberos.authGSSClientStep(vc, splits[1])
+ if rc == -1:
+ errcode = 401
+ raise xmlrpclib.ProtocolError(host + handler, errcode, errmsg, headers)
+ except kerberos.GSSError as ex:
+ errcode = 401
+ errmsg += ": %s/%s" % (ex[0][0], ex[1][0])
+ raise xmlrpclib.ProtocolError(host + handler, errcode, errmsg, headers)
+
+ # cleanup
+ rc = kerberos.authGSSClientClean(vc)
+ if rc != 1:
+ errcode = 401
+ errmsg = "KERBEROS: Could not clean-up GSSAPI: %s/%s" % (ex[0][0], ex[1][0])
+ raise xmlrpclib.ProtocolError(host + handler, errcode, errmsg, headers)
+
+ def single_request(self, host, handler, request_body, verbose=0):
+ # issue XML-RPC request
+
+ request_url = "%s://%s/" % (self.scheme, host)
+ self.cookie_request = request.Request(request_url)
+ self.verbose = verbose
+
+ try:
+
+ http_con = self.send_request(host, handler, request_body, False) # pylint: disable=too-many-function-args
+ self._extra_headers = []
+ response = http_con.getresponse()
+
+ if response.status == 401 and USE_KERBEROS:
+ vc, challenge = self._kerberos_client_request(host, handler, response.status,
+ response.reason, response.msg)
+
+ # discard any response data
+ if response.getheader("content-length", 0):
+ response.read()
+
+ # retry the original request & add the Authorization header:
+ self._extra_headers = [("Authorization", "Negotiate %s" % challenge)]
+ http_con = self.send_request(host, handler, request_body, verbose) # pylint: disable=too-many-function-args
+ self._extra_headers = []
+
+ response = http_con.getresponse()
+ self._kerberos_verify_response(vc, host, handler, response.msg)
+
+ if response.status == 200:
+ self.verbose = verbose
+ self._save_cookies(response.msg, self.cookie_request)
+ return self.parse_response(response)
+ except xmlrpclib.Fault:
+ raise
+ except Exception:
+ # All unexpected errors leave connection in
+ # a strange state, so we clear it.
+ if hasattr(self, 'close'):
+ self.close()
+ raise
+
+ # discard any response data and raise exception
+ if response.getheader("content-length", 0):
+ response.read()
+ raise xmlrpclib.ProtocolError(host + handler, response.status, response.reason,
+ response.msg)
+
+
+class SafeCookieTransport(xmlrpclib.SafeTransport, CookieTransport):
+ """
+ Cookie enabled XML-RPC transport over HTTPS.
+
+ USAGE: see CookieTransport
+ """
+
+ scheme = "https"
+
+ def __init__(self, *args, **kwargs):
+ # SafeTransport.__init__ does this but we can't call that because we
+ # have an inheritance diamond and these are old-style classes...
+ self.context = kwargs.pop('context', None)
+ CookieTransport.__init__(self, *args, **kwargs)
+
+ def make_connection(self, host):
+
+ host.lower()
+
+ if ':' in host:
+ # Remove port from the host
+ host_ = host.split(':')[0]
+ else:
+ host_ = "%s:%s" % (host, TimeoutHTTPSProxyConnection.default_port)
+
+ if self.proxy_config["proxy"] and host not in self.no_proxy and host_ not in self.no_proxy:
+ CONNECTION_LOCK.acquire()
+ host, _, _ = self.get_host_info(host)
+ conn = TimeoutProxyHTTPS(host, **self.proxy_config)
+ conn.set_timeout(self.timeout)
+ CONNECTION_LOCK.release()
+
+ return conn
+
+ CONNECTION_LOCK.acquire()
+ self._connection = (None, None)
+ conn = xmlrpclib.SafeTransport.make_connection(self, host)
+ if self.timeout:
+ conn.timeout = self.timeout
+ CONNECTION_LOCK.release()
+
+ return conn
+
+
+def retry_request_decorator(transport_class):
+ """
+ Use this class decorator on a Transport
+ to retry requests which failed on socket errors.
+ """
+
+ class RetryTransportClass(transport_class):
+ def __init__(self, *args, **kwargs):
+ self.retry_count = kwargs.pop("retry_count", 5)
+ self.retry_timeout = kwargs.pop("retry_timeout", 30)
+ transport_class.__init__(self, *args, **kwargs)
+
+ def request(self, *args, **kwargs):
+ if self.retry_count == 0:
+ return transport_class.request(self, *args, **kwargs)
+
+ for i in list(range(self.retry_count + 1)):
+ try:
+ result = transport_class.request(self, *args, **kwargs)
+ return result
+ except KeyboardInterrupt:
+ raise
+ except (socket.error, socket.herror, socket.gaierror, socket.timeout) as ex:
+ if hasattr(self, 'close'):
+ self.close()
+ if i >= self.retry_count:
+ raise
+ retries_left = self.retry_count - i
+ # 1 retry left / X retries left
+ retries = (retries_left == 1 and "retry" or "retries")
+ logger.warning("XML-RPC connection to %s failed: %s, %d %s left",
+ args[0], " ".join(ex.args[1:]), retries_left, retries,
+ exc_info=True)
+ time.sleep(self.retry_timeout)
+
+ RetryTransportClass.__name__ = transport_class.__name__
+ RetryTransportClass.__doc__ = transport_class.__name__
+ return RetryTransportClass
diff --git a/Common/bkr/log.py b/Common/bkr/log.py
index c5e6750..ccddcfe 100644
--- a/Common/bkr/log.py
+++ b/Common/bkr/log.py
@@ -5,11 +5,10 @@
# the Free Software # Foundation, either version 2 of the License, or
# (at your option) any later version.
-import os
import syslog
-import codecs
import logging
+
def log_to_stream(stream, level=logging.WARNING):
"""
Configures the logging module to send messages to the given stream (for
@@ -23,6 +22,7 @@ def log_to_stream(stream, level=logging.WARNING):
stream_handler.setFormatter(logging.Formatter('%(asctime)s %(name)s %(levelname)s %(message)s'))
logging.getLogger().handlers = [stream_handler]
+
def log_to_syslog(program_name, facility=syslog.LOG_DAEMON):
syslog.openlog(program_name, syslog.LOG_PID, facility)
syslog_handler = SysLogHandler()
@@ -30,6 +30,7 @@ def log_to_syslog(program_name, facility=syslog.LOG_DAEMON):
syslog_handler.setFormatter(logging.Formatter('%(name)s %(levelname)s %(message)s'))
logging.getLogger().handlers = [syslog_handler]
+
# Like logging.handlers.SysLogHandler, but uses libc syslog(3) and splits
# multi-line messages into separate log entries.
class SysLogHandler(logging.Handler):
diff --git a/Common/bkr/timeout_xmlrpclib.py b/Common/bkr/timeout_xmlrpclib.py
deleted file mode 100755
index b52f3f1..0000000
--- a/Common/bkr/timeout_xmlrpclib.py
+++ /dev/null
@@ -1,42 +0,0 @@
-
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software # Foundation, either version 2 of the License, or
-# (at your option) any later version.
-
-import xmlrpclib
-import httplib
-
-def ServerProxy(url, *args, **kwargs):
- t = TimeoutTransport()
- t.timeout = kwargs.get('timeout', 40)
- if 'timeout' in kwargs:
- del kwargs['timeout']
- kwargs['transport'] = t
- serverproxy = xmlrpclib.ServerProxy(url, *args, **kwargs)
- return serverproxy
-
-Server = ServerProxy
-
-class TimeoutTransport(xmlrpclib.Transport):
-
- def make_connection(self, host):
- conn = TimeoutHTTP(host)
- conn.set_timeout(self.timeout)
- return conn
-
-class TimeoutHTTPConnection(httplib.HTTPConnection):
-
- def connect(self):
- httplib.HTTPConnection.connect(self)
- # check whether socket timeout support is available (Python >= 2.3)
- try:
- self.sock.settimeout(self.timeout)
- except AttributeError:
- pass
-
-class TimeoutHTTP(httplib.HTTP):
- _connection_class = TimeoutHTTPConnection
-
- def set_timeout(self, timeout):
- self._conn.timeout = timeout
diff --git a/Common/run-tests.sh b/Common/run-tests.sh
index 2c93d4b..aaa259a 100755
--- a/Common/run-tests.sh
+++ b/Common/run-tests.sh
@@ -2,5 +2,14 @@
set -x
+# Use Python 2 version if BKR_PY3 is not defined
+if [[ -z ${BKR_PY3} ]]; then
+ nose_command="nosetests";
+elif [[ ${BKR_PY3} == 1 ]]; then
+ nose_command="nosetests-3";
+else
+ nose_command="nosetests";
+fi
+
env PYTHONPATH=.${PYTHONPATH:+:$PYTHONPATH} \
- nosetests ${*:--v bkr}
+ $nose_command ${*:--v bkr}
diff --git a/Common/setup.py b/Common/setup.py
index 9e67e61..6a51e5d 100644
--- a/Common/setup.py
+++ b/Common/setup.py
@@ -19,13 +19,15 @@ setup(
url='https://beaker-project.org/',
packages=find_packages('.'),
- package_dir = {'':'.'},
+ package_dir={'': '.'},
package_data={'bkr.common': ['schema/*.rng', 'schema/*.ttl', 'default.conf']},
- classifiers = [
+ classifiers=[
'Development Status :: 5 - Production/Stable',
'Operating System :: POSIX :: Linux',
- 'Programming Language :: Python :: 2.6',
+ 'Programming Language :: Python :: 2.7',
+ 'Programming Language :: Python :: 3.6',
+ 'Programming Language :: Python :: 3.7',
'License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)',
],
)
diff --git a/IntegrationTests/src/bkr/inttest/server/selenium/__init__.py b/IntegrationTests/src/bkr/inttest/server/selenium/__init__.py
index daf8aab..78a1cb5 100644
--- a/IntegrationTests/src/bkr/inttest/server/selenium/__init__.py
+++ b/IntegrationTests/src/bkr/inttest/server/selenium/__init__.py
@@ -4,22 +4,18 @@
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
-import sys
import os
-import re
import time
import logging
-import subprocess
from selenium import webdriver
from selenium.common.exceptions import ErrorInResponseException
import xmlrpclib
from urlparse import urljoin
-from bkr.common.xmlrpc import CookieTransport, SafeCookieTransport
-from datetime import datetime
-from bkr.inttest import data_setup, get_server_base, Process, DatabaseTestCase
-from bkr.inttest.assertions import wait_for_condition
-from bkr.server.bexceptions import BX
-from time import sleep
+from bkr.common.xmlrpc2 import CookieTransport
+from bkr.common.xmlrpc2 import SafeCookieTransport
+from bkr.inttest import get_server_base
+from bkr.inttest import Process
+from bkr.inttest import DatabaseTestCase
import pkg_resources
pkg_resources.require('selenium >= 2.0b2')
diff --git a/LabController/src/bkr/labcontroller/proxy.py b/LabController/src/bkr/labcontroller/proxy.py
index ea0eb0a..af41b21 100644
--- a/LabController/src/bkr/labcontroller/proxy.py
+++ b/LabController/src/bkr/labcontroller/proxy.py
@@ -8,24 +8,17 @@ import errno
import os
import sys
import logging
-import signal
import time
-import datetime
import base64
import lxml.etree
-import glob
import re
import json
import shutil
import tempfile
import xmlrpclib
-import socket
import subprocess
import pkg_resources
import shlex
-from cStringIO import StringIO
-from socket import gethostname
-from threading import Thread, Event
from xml.sax.saxutils import escape as xml_escape, quoteattr as xml_quoteattr
from werkzeug.wrappers import Response
from werkzeug.exceptions import BadRequest, NotAcceptable, NotFound, \
@@ -34,10 +27,8 @@ from werkzeug.utils import redirect
from werkzeug.http import parse_content_range_header
from werkzeug.wsgi import wrap_file
from bkr.common.hub import HubProxy
-from bkr.common.xmlrpc import CookieTransport, SafeCookieTransport
from bkr.labcontroller.config import get_conf
from bkr.labcontroller.log_storage import LogStorage
-import utils
try:
#pylint: disable=E0611
from subprocess import check_output
diff --git a/Makefile b/Makefile
index 4297c8d..47c5e90 100644
--- a/Makefile
+++ b/Makefile
@@ -4,9 +4,14 @@
# the Free Software # Foundation, either version 2 of the License, or
# (at your option) any later version.
+# Python 2 default
+ifeq ($(BKR_PY3),)
+ BKR_PY3 :=0
+endif
+
DEPCMD := $(shell if [ -f /usr/bin/dnf ]; then echo "dnf builddep"; else echo "yum-builddep"; fi)
+SUBDIRS := $(shell if [[ $(BKR_PY3) == 0 ]]; then echo "Common Client documentation Server LabController IntegrationTests"; else echo "Common Client documentation"; fi)
-SUBDIRS := Common Client documentation Server LabController IntegrationTests
.PHONY: build
build:
diff --git a/beaker.spec b/beaker.spec
index 1502801..7ff90b6 100644
--- a/beaker.spec
+++ b/beaker.spec
@@ -1,6 +1,12 @@
%if 0%{?rhel} && 0%{?rhel} <= 6
%{!?__python2: %global __python2 /usr/bin/python2}
-%{!?python2_sitelib: %global python2_sitelib %(%{__python2} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")}
+%{!?python2_sitelib: %global python2_sitelib %(%{__python2} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")}
+%endif
+
+%if 0%{?fedora} >= 30 || 0%{?rhel} >= 8
+%bcond_without python3
+%else
+%bcond_with python3
%endif
%global _lc_services beaker-proxy beaker-provision beaker-watchdog beaker-transfer
@@ -10,9 +16,9 @@
%bcond_with systemd
%endif
-# This will not necessarily match the RPM Version if the real version number is
-# not representable in RPM. For example, a release candidate might be 0.15.0rc1
-# but that is not usable for the RPM Version because it sorts higher than
+# This will not necessarily match the RPM Version if the real version number is
+# not representable in RPM. For example, a release candidate might be 0.15.0rc1
+# but that is not usable for the RPM Version because it sorts higher than
# 0.15.0, so the RPM will have Version 0.15.0 and Release 0.rc1 in that case.
%global upstream_version 26.4
@@ -46,7 +52,18 @@ Source15: https://github.com/jsmreese/moment-duration-format/archive/8d0bf
BuildArch: noarch
BuildRequires: make
-%if 0%{?fedora} >= 29 || 0%{?rhel} >= 8
+%if %{with python3}
+BuildRequires: python3-setuptools
+BuildRequires: python3-nose
+BuildRequires: python3-unittest2
+BuildRequires: python3-mock
+BuildRequires: python3-setuptools
+BuildRequires: python3-devel
+BuildRequires: python3-docutils
+BuildRequires: python3-sphinx
+BuildRequires: python3-sphinxcontrib-httpdomain
+%else
+%if 0%{?fedora} == 29
BuildRequires: python2-setuptools
BuildRequires: python2-nose
BuildRequires: python2-unittest2
@@ -71,7 +88,7 @@ BuildRequires: python-sphinx >= 1.0
%endif
BuildRequires: python-sphinxcontrib-httpdomain
%endif
-
+%endif
%package common
Summary: Common components for Beaker packages
@@ -79,7 +96,6 @@ Group: Applications/Internet
Provides: %{name} = %{version}-%{release}
Obsoletes: %{name} < 0.17.0-1
-
%package client
Summary: Command-line client for interacting with Beaker
Group: Applications/Internet
@@ -88,17 +104,30 @@ Requires: %{name}-common = %{version}-%{release}
%if 0%{?fedora} || 0%{?rhel} >= 7
BuildRequires: pkgconfig(bash-completion)
%endif
-%if 0%{?fedora} >= 29 || 0%{?rhel} >= 8
+%if %{with python3}
+# These client dependencies are needed in build because of sphinx
+BuildRequires: python3-gssapi
+BuildRequires: python3-lxml
+BuildRequires: python3-prettytable
+BuildRequires: python3-libxml2
+Requires: python3-setuptools
+Requires: python3-gssapi
+Requires: python3-lxml
+Requires: python3-requests
+Requires: python3-libxml2
+Requires: python3-prettytable
+Requires: python3-jinja2
+%else
+%if 0%{?fedora} == 29
# These client dependencies are needed in build because of sphinx
BuildRequires: python2-gssapi
BuildRequires: python2-lxml
-BuildRequires: python2-libxslt
BuildRequires: python2-prettytable
+BuildRequires: python2-libxml2
Requires: python2-setuptools
Requires: python2-gssapi
Requires: python2-lxml
Requires: python2-requests
-Requires: python2-libxslt
Requires: python2-libxml2
Requires: python2-prettytable
Requires: python2-jinja2
@@ -118,9 +147,11 @@ Requires: libxml2-python
Requires: python-prettytable
Requires: python-jinja2
%endif
+%endif
# beaker-wizard was moved from rhts-devel to here in 4.52
Conflicts: rhts-devel < 4.52
+%if %{without python3}
%package server
Summary: Beaker scheduler and web interface
Group: Applications/Internet
@@ -148,7 +179,7 @@ Requires: yum-utils
Requires: nodejs-less >= 1.7
Requires: /usr/bin/cssmin
Requires: /usr/bin/uglifyjs
-%if 0%{?fedora} >= 29 || 0%{?rhel} >= 8
+%if 0%{?fedora} == 29
BuildRequires: python2-requests
BuildRequires: TurboGears
BuildRequires: python2-turbojson
@@ -247,8 +278,9 @@ Requires(post): systemd
Requires(pre): systemd
Requires(postun): systemd
%endif
+%endif
-
+%if %{without python3}
%package integration-tests
Summary: Integration tests for Beaker
Group: Applications/Internet
@@ -261,7 +293,7 @@ Requires: firefox
Requires: lsof
Requires: openldap-servers
Requires: nss_wrapper
-%if 0%{?fedora} >= 29 || 0%{?rhel} >= 8
+%if 0%{?fedora} == 29
Requires: python2-nose
Requires: python2-selenium
Requires: python2-requests
@@ -286,8 +318,9 @@ Requires: python-mock
Requires: python-cssselect
%endif
%endif
+%endif
-
+%if %{without python3}
%package lab-controller
Summary: Daemons for controlling a Beaker lab
Group: Applications/Internet
@@ -302,7 +335,7 @@ Requires: ipmitool
Requires: wsmancli
Requires: telnet
Requires: sudo
-%if 0%{?fedora} >= 29 || 0%{?rhel} >= 8
+%if 0%{?fedora} == 29
# These LC dependencies are needed in build due to tests
BuildRequires: python2-lxml
BuildRequires: python2-gevent
@@ -335,39 +368,48 @@ Requires(post): systemd
Requires(pre): systemd
Requires(postun): systemd
%endif
+%endif
+%if %{without python3}
%package lab-controller-addDistro
Summary: Optional hooks for distro import on Beaker lab controllers
Group: Applications/Internet
Requires: %{name}-common = %{version}-%{release}
Requires: %{name}-lab-controller = %{version}-%{release}
Requires: %{name}-client = %{version}-%{release}
+%endif
+
%description
-Beaker is a full stack software and hardware integration testing system, with
+Beaker is a full stack software and hardware integration testing system, with
the ability to manage a globally distributed network of test labs.
%description common
Python modules which are used by other Beaker packages.
%description client
-The bkr client is a command-line tool for interacting with Beaker servers. You
+The bkr client is a command-line tool for interacting with Beaker servers. You
can use it to submit Beaker jobs, fetch results, and perform many other tasks.
+%if %{without python3}
%description server
-This package provides the central server components for Beaker, which
+This package provides the central server components for Beaker, which
consist of:
-* a Python web application, providing services to remote lab controllers as
- well as a web interface for Beaker users;
-* the beakerd scheduling daemon, which schedules recipes on systems; and
+* a Python web application, providing services to remote lab controllers as
+ well as a web interface for Beaker users;
+* the beakerd scheduling daemon, which schedules recipes on systems; and
* command-line tools for managing a Beaker installation.
+%endif
+%if %{without python3}
%description integration-tests
-This package contains integration tests for Beaker, which require a running
+This package contains integration tests for Beaker, which require a running
database and Beaker server.
+%endif
+%if %{without python3}
%description lab-controller
-The lab controller daemons connect to a central Beaker server in order to
+The lab controller daemons connect to a central Beaker server in order to
manage a local lab of test systems.
The daemons and associated lab controller tools:
@@ -375,11 +417,14 @@ The daemons and associated lab controller tools:
* control power for test systems
* collect logs and results from test runs
* track distros available from the lab's local mirror
+%endif
+%if %{without python3}
%description lab-controller-addDistro
-addDistro.sh can be called after distros have been imported into Beaker. You
-can install this on your lab controller to automatically launch jobs against
+addDistro.sh can be called after distros have been imported into Beaker. You
+can install this on your lab controller to automatically launch jobs against
newly imported distros.
+%endif
%prep
%setup -q -n %{name}-%{upstream_version}
@@ -400,18 +445,24 @@ tar -C Server/assets/marked --strip-components=1 -xzf %{SOURCE14}
tar -C Server/assets/moment-duration-format --strip-components=1 -xzf %{SOURCE15}
%build
+export BKR_PY3=%{with python3}
make
%install
+export BKR_PY3=%{with python3}
DESTDIR=%{buildroot} make install
-# Newer RPM fails if site.less doesn't exist, even though it's marked %%ghost
+%if %{without python3}
+# Newer RPM fails if site.less doesn't exist, even though it's marked %%ghost
# and therefore is not included in the RPM. Seems like an RPM bug...
ln -s /dev/null %{buildroot}%{_datadir}/bkr/server/assets/site.less
+%endif
%check
+export BKR_PY3=%{with python3}
make check
+%if %{without python3}
%post server
%if %{with systemd}
%systemd_post beakerd.service
@@ -428,7 +479,9 @@ chmod go-w %{_localstatedir}/log/%{name}/*.log >/dev/null 2>&1 || :
if [ ! -f %{_datadir}/bkr/server/assets/site.less ] ; then
ln -s /dev/null %{_datadir}/bkr/server/assets/site.less || :
fi
+%endif
+%if %{without python3}
%post lab-controller
%if %{with systemd}
%systemd_post %{_lc_services}
@@ -443,7 +496,9 @@ chown root:root %{_localstatedir}/log/%{name}/*.log >/dev/null 2>&1 || :
chmod go-w %{_localstatedir}/log/%{name}/*.log >/dev/null 2>&1 || :
# Restart rsyslog so that it notices the config which we ship
/sbin/service rsyslog condrestart >/dev/null 2>&1 || :
+%endif
+%if %{without python3}
%postun server
%if %{with systemd}
%systemd_postun_with_restart beakerd.service
@@ -452,7 +507,9 @@ if [ "$1" -ge "1" ]; then
/sbin/service beakerd condrestart >/dev/null 2>&1 || :
fi
%endif
+%endif
+%if %{without python3}
%postun lab-controller
%if %{with systemd}
%systemd_postun_with_restart %{_lc_services}
@@ -463,7 +520,9 @@ if [ "$1" -ge "1" ]; then
done
fi
%endif
+%endif
+%if %{without python3}
%preun server
%if %{with systemd}
%systemd_preun beakerd.service
@@ -473,7 +532,9 @@ if [ "$1" -eq "0" ]; then
/sbin/chkconfig --del beakerd || :
fi
%endif
+%endif
+%if %{without python3}
%preun lab-controller
%if %{with systemd}
%systemd_preun %{_lc_services}
@@ -486,16 +547,26 @@ if [ "$1" -eq "0" ]; then
fi
rm -rf %{_var}/lib/beaker/osversion_data
%endif
+%endif
%files common
+%if %{with python3}
+%dir %{python3_sitelib}/bkr/
+%{python3_sitelib}/bkr/__init__.py*
+%{python3_sitelib}/bkr/common/
+%{python3_sitelib}/bkr/log.py*
+%{python3_sitelib}/bkr/__pycache__/*
+%{python3_sitelib}/beaker_common-*.egg-info/
+%else
%dir %{python2_sitelib}/bkr/
%{python2_sitelib}/bkr/__init__.py*
-%{python2_sitelib}/bkr/timeout_xmlrpclib.py*
%{python2_sitelib}/bkr/common/
%{python2_sitelib}/bkr/log.py*
%{python2_sitelib}/beaker_common-*.egg-info/
+%endif
%doc COPYING
+%if %{without python3}
%files server
%dir %{_sysconfdir}/%{name}
%doc documentation/_build/text/whats-new/
@@ -543,19 +614,28 @@ rm -rf %{_var}/lib/beaker/osversion_data
%attr(-,apache,root) %dir %{_localstatedir}/www/%{name}/rpms
%attr(-,apache,root) %dir %{_localstatedir}/www/%{name}/repos
%attr(-,apache,root) %dir %{_localstatedir}/lib/%{name}
+%endif
+%if %{without python3}
%files integration-tests
%{python2_sitelib}/bkr/inttest/
%{python2_sitelib}/beaker_integration_tests-*-nspkg.pth
%{python2_sitelib}/beaker_integration_tests-*.egg-info/
%{_datadir}/beaker-integration-tests
+%endif
%files client
%dir %{_sysconfdir}/%{name}
%doc Client/client.conf.example
+%if %{with python3}
+%{python3_sitelib}/bkr/client/
+%{python3_sitelib}/beaker_client-*-nspkg.pth
+%{python3_sitelib}/beaker_client-*.egg-info/
+%else
%{python2_sitelib}/bkr/client/
%{python2_sitelib}/beaker_client-*-nspkg.pth
%{python2_sitelib}/beaker_client-*.egg-info/
+%endif
%{_bindir}/beaker-wizard
%{_bindir}/bkr
%{_mandir}/man1/beaker-wizard.1.gz
@@ -567,6 +647,7 @@ rm -rf %{_var}/lib/beaker/osversion_data
%{_sysconfdir}/bash_completion.d
%endif
+%if %{without python3}
%files lab-controller
%dir %{_sysconfdir}/%{name}
%config(noreplace) %{_sysconfdir}/%{name}/labcontroller.conf
@@ -610,9 +691,12 @@ rm -rf %{_var}/lib/beaker/osversion_data
%attr(0440,root,root) %config(noreplace) %{_sysconfdir}/sudoers.d/beaker_proxy_clear_netboot
%config(noreplace) %{_sysconfdir}/rsyslog.d/beaker-lab-controller.conf
%config(noreplace) %{_sysconfdir}/logrotate.d/beaker
+%endif
+%if %{without python3}
%files lab-controller-addDistro
%{_var}/lib/%{name}/addDistro.sh
%{_var}/lib/%{name}/addDistro.d/*
+%endif
%changelog
diff --git a/documentation/Makefile b/documentation/Makefile
index 60cf807..3c4aed4 100644
--- a/documentation/Makefile
+++ b/documentation/Makefile
@@ -1,8 +1,16 @@
# Makefile for Sphinx documentation
#
+# Python 2 default
+ifeq ($(BKR_PY3),)
+ BKR_PY3 :=0
+endif
+
+python_command := $(shell if [[ $(BKR_PY3) == 0 ]]; then echo "python2"; else echo "python3"; fi)
+python_path := $(shell if [[ $(BKR_PY3) == 0 ]]; then echo "../Common:../Server:../Client/src"; else echo "../Common:../Client/src"; fi)
+bkr_path_injected := $(shell if [[ $(BKR_PY3) == 0 ]]; then echo \"../Common/bkr\", \"../Server/bkr\", \"../Client/src/bkr\"; else echo \"../Common/bkr\", \"../Client/src/bkr\"; fi)
SHELL = /bin/bash
-export PYTHONPATH=../Common:../Server:../Client/src
+export PYTHONPATH=$(python_path)
SPHINXBUILD ?= $(firstword $(shell command -v sphinx-1.0-build sphinx-build))
# This Makefile contains some frustrating hacks, centering around the fact that
@@ -27,16 +35,16 @@ SPHINXBUILD ?= $(firstword $(shell command -v sphinx-1.0-build sphinx-build))
# Also see: https://bitbucket.org/pypa/setuptools/issue/6/
SPHINXREQUIRES = "Sphinx >= 1.0",
-ifeq (0,$(shell python2 -c '__requires__ = ["CherryPy < 3.0"]; import pkg_resources' &>/dev/null ; echo $$?))
+ifeq (0,$(shell $(python_command) -c '__requires__ = ["CherryPy < 3.0"]; import pkg_resources' &>/dev/null ; echo $$?))
SPHINXREQUIRES += "CherryPy < 3.0",
endif
-BKR_PATH_INJECTED = "../Common/bkr", "../Server/bkr", "../Client/src/bkr"
+BKR_PATH_INJECTED = $(bkr_path_injected)
# You can set these variables from the command line.
SPHINXOPTS =
-SPHINXBUILD := python2 -c '__requires__ = [$(SPHINXREQUIRES)]; import pkg_resources; \
+SPHINXBUILD := $(python_command) -c '__requires__ = [$(SPHINXREQUIRES)]; import pkg_resources; \
import bkr; bkr.__path__ = [$(BKR_PATH_INJECTED)]; \
- execfile("$(SPHINXBUILD)")'
+ exec(open("$(SPHINXBUILD)").read())'
PAPER =
BUILDDIR = _build
@@ -54,7 +62,9 @@ build: man text
install: man
install -m0755 -d $(DESTDIR)/usr/share/man/man{1,8}
install -m0644 _build/man/*.1 $(DESTDIR)/usr/share/man/man1
- install -m0644 _build/man/*.8 $(DESTDIR)/usr/share/man/man8
+ if [[ ${BKR_PY3} == 0 ]]; then \
+ install -m0644 _build/man/*.8 $(DESTDIR)/usr/share/man/man8; \
+ fi
ln -s bkr-distro-trees-verify.1.gz $(DESTDIR)/usr/share/man/man1/bkr-distros-verify.1.gz
ln -s bkr-system-list.1.gz $(DESTDIR)/usr/share/man/man1/bkr-list-systems.1.gz
ln -s bkr-labcontroller-list $(DESTDIR)/usr/share/man/man1/bkr-list-labcontrollers.1.gz
diff --git a/documentation/conf.py b/documentation/conf.py
index db7ef2d..c98e94e 100644
--- a/documentation/conf.py
+++ b/documentation/conf.py
@@ -1,10 +1,10 @@
-
+import six
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.todo',
'sphinx.ext.extlinks',
'sphinxcontrib.httpdomain', 'sphinxcontrib.autohttp.flask']
master_doc = 'index'
project = u'Beaker'
-copyright = u'2013, Red Hat, Inc'
+copyright = u'2013-2019 Red Hat, Inc.'
try:
import bkr.common
@@ -29,23 +29,28 @@ man_pages = [
[u'David Sommerseth <davids@redhat.com>'], 1),
('man/beaker-wizard', 'beaker-wizard', 'Tool to ease the creation of a new Beaker task',
[u'Petr Splichal <psplicha@redhat.com>'], 1),
- ('admin-guide/man/beaker-create-kickstart', 'beaker-create-kickstart', 'Generate Anaconda kickstarts',
- [u'The Beaker team <beaker-devel@lists.fedorahosted.org>'], 8),
+]
+man_server_pages = [
+ ('admin-guide/man/beaker-create-kickstart',
+ 'beaker-create-kickstart', 'Generate Anaconda kickstarts',
+ [u'The Beaker team <beaker-devel@lists.fedorahosted.org>'], 8),
('admin-guide/man/beaker-create-ipxe-image', 'beaker-create-ipxe-image',
- 'Generate and upload iPXE boot image to Glance',
- [u'The Beaker team <beaker-devel@lists.fedorahosted.org>'], 8),
+ 'Generate and upload iPXE boot image to Glance',
+ [u'The Beaker team <beaker-devel@lists.fedorahosted.org>'], 8),
('admin-guide/man/beaker-import', 'beaker-import', 'Import distros',
- [u'The Beaker team <beaker-devel@lists.fedorahosted.org>'], 8),
+ [u'The Beaker team <beaker-devel@lists.fedorahosted.org>'], 8),
('admin-guide/man/beaker-init', 'beaker-init',
- 'Initialize and populate the Beaker database',
- [u'The Beaker team <beaker-devel@lists.fedorahosted.org>'], 8),
+ 'Initialize and populate the Beaker database',
+ [u'The Beaker team <beaker-devel@lists.fedorahosted.org>'], 8),
('admin-guide/man/beaker-repo-update', 'beaker-repo-update',
- 'Update cached harness packages',
- [u'The Beaker team <beaker-devel@lists.fedorahosted.org>'], 8),
+ 'Update cached harness packages',
+ [u'The Beaker team <beaker-devel@lists.fedorahosted.org>'], 8),
('admin-guide/man/beaker-usage-reminder', 'beaker-usage-reminder',
- 'Send Beaker usage reminder',
- [u'The Beaker team <beaker-devel@lists.fedorahosted.org>'], 8),
+ 'Send Beaker usage reminder',
+ [u'The Beaker team <beaker-devel@lists.fedorahosted.org>'], 8),
]
+if six.PY2: # Server supported only for Python 2
+ man_pages += man_server_pages
man_show_urls = True
latex_documents = [
@@ -62,6 +67,9 @@ extlinks = {
'issue': ('https://bugzilla.redhat.com/show_bug.cgi?id=%s', '#'),
}
+if six.PY3:
+ exclude_patterns = ['server-api/*.rst']
+
# This config is also a Sphinx extension with some Beaker-specific customisations:
import sys