Make JJB python 3 compatible
Convert to use idioms that work for both python 3 and python 2.6+ and ensure that a suitable version of dependencies is included for python 3 compatibility. Update python-jenkins to 0.3.4 as the earliest version that supports python 3 without any known regressions. Add an extra parser check for missing 'command' due to changes in how argparse works under python 3. To access the first element of a dict in both python 2 and 3, 'next(iter(dict.items()))' is used as the standard idiom to replace 'dict.items()[0]' as 'items()' returns an iterator in python 3 which cannot be indexed. Using 'next(iter(..))' allows for both lists and iterators to be passed in without unnecessary conversion of iterators to lists which would be true of 'list(dict.items())[0]'. Original change which was reverted due to breaking use of job-groups is If4b35e2ceee8239379700e22eb79a3eaa04d6f0f. This replaces the previous conversion of 'dict.items()[0]' to 'dict.popitem()', which would result in removing a job-group when first called, thus defeating the benefit of being able to reference the group mulitple times. This usage has been replaced with 'next(iter(dict.items()))' as a non-modifying alternative that still avoids creating unnecessary copies of data while working for all supported versions of python. Change-Id: I37e3b67c043dadddb54e16ee584bde3f79e6a770
This commit is contained in:
parent
87ab085159
commit
64e217f885
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
import errno
|
import errno
|
||||||
import os
|
import os
|
||||||
|
import operator
|
||||||
import sys
|
import sys
|
||||||
import hashlib
|
import hashlib
|
||||||
import yaml
|
import yaml
|
||||||
|
@ -31,8 +32,9 @@ import logging
|
||||||
import copy
|
import copy
|
||||||
import itertools
|
import itertools
|
||||||
import fnmatch
|
import fnmatch
|
||||||
|
import six
|
||||||
from jenkins_jobs.errors import JenkinsJobsException
|
from jenkins_jobs.errors import JenkinsJobsException
|
||||||
import local_yaml
|
import jenkins_jobs.local_yaml as local_yaml
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
MAGIC_MANAGE_STRING = "<!-- Managed by Jenkins Job Builder -->"
|
MAGIC_MANAGE_STRING = "<!-- Managed by Jenkins Job Builder -->"
|
||||||
|
@ -82,7 +84,7 @@ def deep_format(obj, paramdict):
|
||||||
# limitations on the values in paramdict - the post-format result must
|
# limitations on the values in paramdict - the post-format result must
|
||||||
# still be valid YAML (so substituting-in a string containing quotes, for
|
# still be valid YAML (so substituting-in a string containing quotes, for
|
||||||
# example, is problematic).
|
# example, is problematic).
|
||||||
if isinstance(obj, basestring):
|
if hasattr(obj, 'format'):
|
||||||
try:
|
try:
|
||||||
result = re.match('^{obj:(?P<key>\w+)}$', obj)
|
result = re.match('^{obj:(?P<key>\w+)}$', obj)
|
||||||
if result is not None:
|
if result is not None:
|
||||||
|
@ -142,7 +144,7 @@ class YamlParser(object):
|
||||||
" not a {cls}".format(fname=getattr(fp, 'name', fp),
|
" not a {cls}".format(fname=getattr(fp, 'name', fp),
|
||||||
cls=type(data)))
|
cls=type(data)))
|
||||||
for item in data:
|
for item in data:
|
||||||
cls, dfn = item.items()[0]
|
cls, dfn = next(iter(item.items()))
|
||||||
group = self.data.get(cls, {})
|
group = self.data.get(cls, {})
|
||||||
if len(item.items()) > 1:
|
if len(item.items()) > 1:
|
||||||
n = None
|
n = None
|
||||||
|
@ -209,7 +211,7 @@ class YamlParser(object):
|
||||||
for jobspec in project.get('jobs', []):
|
for jobspec in project.get('jobs', []):
|
||||||
if isinstance(jobspec, dict):
|
if isinstance(jobspec, dict):
|
||||||
# Singleton dict containing dict of job-specific params
|
# Singleton dict containing dict of job-specific params
|
||||||
jobname, jobparams = jobspec.items()[0]
|
jobname, jobparams = next(iter(jobspec.items()))
|
||||||
if not isinstance(jobparams, dict):
|
if not isinstance(jobparams, dict):
|
||||||
jobparams = {}
|
jobparams = {}
|
||||||
else:
|
else:
|
||||||
|
@ -225,7 +227,7 @@ class YamlParser(object):
|
||||||
for group_jobspec in group['jobs']:
|
for group_jobspec in group['jobs']:
|
||||||
if isinstance(group_jobspec, dict):
|
if isinstance(group_jobspec, dict):
|
||||||
group_jobname, group_jobparams = \
|
group_jobname, group_jobparams = \
|
||||||
group_jobspec.items()[0]
|
next(iter(group_jobspec.items()))
|
||||||
if not isinstance(group_jobparams, dict):
|
if not isinstance(group_jobparams, dict):
|
||||||
group_jobparams = {}
|
group_jobparams = {}
|
||||||
else:
|
else:
|
||||||
|
@ -275,7 +277,7 @@ class YamlParser(object):
|
||||||
expanded_values = {}
|
expanded_values = {}
|
||||||
for (k, v) in values:
|
for (k, v) in values:
|
||||||
if isinstance(v, dict):
|
if isinstance(v, dict):
|
||||||
inner_key = v.iterkeys().next()
|
inner_key = next(iter(v))
|
||||||
expanded_values[k] = inner_key
|
expanded_values[k] = inner_key
|
||||||
expanded_values.update(v[inner_key])
|
expanded_values.update(v[inner_key])
|
||||||
else:
|
else:
|
||||||
|
@ -295,6 +297,8 @@ class YamlParser(object):
|
||||||
# us guarantee a group of parameters will not be added a
|
# us guarantee a group of parameters will not be added a
|
||||||
# second time.
|
# second time.
|
||||||
uniq = json.dumps(expanded, sort_keys=True)
|
uniq = json.dumps(expanded, sort_keys=True)
|
||||||
|
if six.PY3:
|
||||||
|
uniq = uniq.encode('utf-8')
|
||||||
checksum = hashlib.md5(uniq).hexdigest()
|
checksum = hashlib.md5(uniq).hexdigest()
|
||||||
|
|
||||||
# Lookup the checksum
|
# Lookup the checksum
|
||||||
|
@ -364,7 +368,7 @@ class ModuleRegistry(object):
|
||||||
Mod = entrypoint.load()
|
Mod = entrypoint.load()
|
||||||
mod = Mod(self)
|
mod = Mod(self)
|
||||||
self.modules.append(mod)
|
self.modules.append(mod)
|
||||||
self.modules.sort(lambda a, b: cmp(a.sequence, b.sequence))
|
self.modules.sort(key=operator.attrgetter('sequence'))
|
||||||
if mod.component_type is not None:
|
if mod.component_type is not None:
|
||||||
self.modules_by_component_type[mod.component_type] = mod
|
self.modules_by_component_type[mod.component_type] = mod
|
||||||
|
|
||||||
|
@ -408,7 +412,7 @@ class ModuleRegistry(object):
|
||||||
|
|
||||||
if isinstance(component, dict):
|
if isinstance(component, dict):
|
||||||
# The component is a singleton dictionary of name: dict(args)
|
# The component is a singleton dictionary of name: dict(args)
|
||||||
name, component_data = component.items()[0]
|
name, component_data = next(iter(component.items()))
|
||||||
if template_data:
|
if template_data:
|
||||||
# Template data contains values that should be interpolated
|
# Template data contains values that should be interpolated
|
||||||
# into the component definition
|
# into the component definition
|
||||||
|
@ -629,7 +633,7 @@ class Builder(object):
|
||||||
self.load_files(input_fn)
|
self.load_files(input_fn)
|
||||||
self.parser.generateXML(names)
|
self.parser.generateXML(names)
|
||||||
|
|
||||||
self.parser.jobs.sort(lambda a, b: cmp(a.name, b.name))
|
self.parser.jobs.sort(key=operator.attrgetter('name'))
|
||||||
|
|
||||||
for job in self.parser.jobs:
|
for job in self.parser.jobs:
|
||||||
if names and not matches(job.name, names):
|
if names and not matches(job.name, names):
|
||||||
|
|
|
@ -14,12 +14,11 @@
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import ConfigParser
|
from six.moves import configparser, StringIO
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import sys
|
import sys
|
||||||
import cStringIO
|
|
||||||
|
|
||||||
from jenkins_jobs.builder import Builder
|
from jenkins_jobs.builder import Builder
|
||||||
from jenkins_jobs.errors import JenkinsJobsException
|
from jenkins_jobs.errors import JenkinsJobsException
|
||||||
|
@ -110,6 +109,8 @@ def main(argv=None):
|
||||||
|
|
||||||
parser = create_parser()
|
parser = create_parser()
|
||||||
options = parser.parse_args(argv)
|
options = parser.parse_args(argv)
|
||||||
|
if not options.command:
|
||||||
|
parser.error("Must specify a 'command' to be performed")
|
||||||
if (options.log_level is not None):
|
if (options.log_level is not None):
|
||||||
options.log_level = getattr(logging, options.log_level.upper(),
|
options.log_level = getattr(logging, options.log_level.upper(),
|
||||||
logger.getEffectiveLevel())
|
logger.getEffectiveLevel())
|
||||||
|
@ -130,9 +131,9 @@ def setup_config_settings(options):
|
||||||
'jenkins_jobs.ini')
|
'jenkins_jobs.ini')
|
||||||
if os.path.isfile(localconf):
|
if os.path.isfile(localconf):
|
||||||
conf = localconf
|
conf = localconf
|
||||||
config = ConfigParser.ConfigParser()
|
config = configparser.ConfigParser()
|
||||||
## Load default config always
|
## Load default config always
|
||||||
config.readfp(cStringIO.StringIO(DEFAULT_CONF))
|
config.readfp(StringIO(DEFAULT_CONF))
|
||||||
if os.path.isfile(conf):
|
if os.path.isfile(conf):
|
||||||
logger.debug("Reading config from {0}".format(conf))
|
logger.debug("Reading config from {0}".format(conf))
|
||||||
conffp = open(conf, 'r')
|
conffp = open(conf, 'r')
|
||||||
|
@ -167,11 +168,11 @@ def execute(options, config):
|
||||||
# https://bugs.launchpad.net/openstack-ci/+bug/1259631
|
# https://bugs.launchpad.net/openstack-ci/+bug/1259631
|
||||||
try:
|
try:
|
||||||
user = config.get('jenkins', 'user')
|
user = config.get('jenkins', 'user')
|
||||||
except (TypeError, ConfigParser.NoOptionError):
|
except (TypeError, configparser.NoOptionError):
|
||||||
user = None
|
user = None
|
||||||
try:
|
try:
|
||||||
password = config.get('jenkins', 'password')
|
password = config.get('jenkins', 'password')
|
||||||
except (TypeError, ConfigParser.NoOptionError):
|
except (TypeError, configparser.NoOptionError):
|
||||||
password = None
|
password = None
|
||||||
|
|
||||||
builder = Builder(config.get('jenkins', 'url'),
|
builder = Builder(config.get('jenkins', 'url'),
|
||||||
|
|
|
@ -187,7 +187,7 @@ class LocalLoader(OrderedConstructor, yaml.Loader):
|
||||||
self.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
|
self.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
|
||||||
type(self).construct_yaml_map)
|
type(self).construct_yaml_map)
|
||||||
|
|
||||||
if isinstance(self.stream, file):
|
if hasattr(self.stream, 'name'):
|
||||||
self.search_path.add(os.path.normpath(
|
self.search_path.add(os.path.normpath(
|
||||||
os.path.dirname(self.stream.name)))
|
os.path.dirname(self.stream.name)))
|
||||||
self.search_path.add(os.path.normpath(os.path.curdir))
|
self.search_path.add(os.path.normpath(os.path.curdir))
|
||||||
|
|
|
@ -251,7 +251,7 @@ def ant(parser, xml_parent, data):
|
||||||
if type(data) is str:
|
if type(data) is str:
|
||||||
# Support for short form: -ant: "target"
|
# Support for short form: -ant: "target"
|
||||||
data = {'targets': data}
|
data = {'targets': data}
|
||||||
for setting, value in sorted(data.iteritems()):
|
for setting, value in sorted(data.items()):
|
||||||
if setting == 'targets':
|
if setting == 'targets':
|
||||||
targets = XML.SubElement(ant, 'targets')
|
targets = XML.SubElement(ant, 'targets')
|
||||||
targets.text = value
|
targets.text = value
|
||||||
|
|
|
@ -46,7 +46,7 @@ import xml.etree.ElementTree as XML
|
||||||
import jenkins_jobs.modules.base
|
import jenkins_jobs.modules.base
|
||||||
import jenkins_jobs.errors
|
import jenkins_jobs.errors
|
||||||
import logging
|
import logging
|
||||||
import ConfigParser
|
from six.moves import configparser
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -73,8 +73,8 @@ class HipChat(jenkins_jobs.modules.base.Base):
|
||||||
if self.authToken == '':
|
if self.authToken == '':
|
||||||
raise jenkins_jobs.errors.JenkinsJobsException(
|
raise jenkins_jobs.errors.JenkinsJobsException(
|
||||||
"Hipchat authtoken must not be a blank string")
|
"Hipchat authtoken must not be a blank string")
|
||||||
except (ConfigParser.NoSectionError,
|
except (configparser.NoSectionError,
|
||||||
jenkins_jobs.errors.JenkinsJobsException), e:
|
jenkins_jobs.errors.JenkinsJobsException) as e:
|
||||||
logger.fatal("The configuration file needs a hipchat section" +
|
logger.fatal("The configuration file needs a hipchat section" +
|
||||||
" containing authtoken:\n{0}".format(e))
|
" containing authtoken:\n{0}".format(e))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
|
@ -439,7 +439,7 @@ def cloverphp(parser, xml_parent, data):
|
||||||
|
|
||||||
metrics = data.get('metric-targets', [])
|
metrics = data.get('metric-targets', [])
|
||||||
# list of dicts to dict
|
# list of dicts to dict
|
||||||
metrics = dict(kv for m in metrics for kv in m.iteritems())
|
metrics = dict(kv for m in metrics for kv in m.items())
|
||||||
|
|
||||||
# Populate defaults whenever nothing has been filled by user.
|
# Populate defaults whenever nothing has been filled by user.
|
||||||
for default in default_metrics.keys():
|
for default in default_metrics.keys():
|
||||||
|
@ -887,7 +887,7 @@ def xunit(parser, xml_parent, data):
|
||||||
supported_types = []
|
supported_types = []
|
||||||
|
|
||||||
for configured_type in data['types']:
|
for configured_type in data['types']:
|
||||||
type_name = configured_type.keys()[0]
|
type_name = next(iter(configured_type.keys()))
|
||||||
if type_name not in implemented_types:
|
if type_name not in implemented_types:
|
||||||
logger.warn("Requested xUnit type '%s' is not yet supported",
|
logger.warn("Requested xUnit type '%s' is not yet supported",
|
||||||
type_name)
|
type_name)
|
||||||
|
@ -898,7 +898,7 @@ def xunit(parser, xml_parent, data):
|
||||||
# Generate XML for each of the supported framework types
|
# Generate XML for each of the supported framework types
|
||||||
xmltypes = XML.SubElement(xunit, 'types')
|
xmltypes = XML.SubElement(xunit, 'types')
|
||||||
for supported_type in supported_types:
|
for supported_type in supported_types:
|
||||||
framework_name = supported_type.keys()[0]
|
framework_name = next(iter(supported_type.keys()))
|
||||||
xmlframework = XML.SubElement(xmltypes,
|
xmlframework = XML.SubElement(xmltypes,
|
||||||
types_to_plugin_types[framework_name])
|
types_to_plugin_types[framework_name])
|
||||||
|
|
||||||
|
@ -922,9 +922,10 @@ def xunit(parser, xml_parent, data):
|
||||||
"Unrecognized threshold, should be 'failed' or 'skipped'")
|
"Unrecognized threshold, should be 'failed' or 'skipped'")
|
||||||
continue
|
continue
|
||||||
elname = "org.jenkinsci.plugins.xunit.threshold.%sThreshold" \
|
elname = "org.jenkinsci.plugins.xunit.threshold.%sThreshold" \
|
||||||
% t.keys()[0].title()
|
% next(iter(t.keys())).title()
|
||||||
el = XML.SubElement(xmlthresholds, elname)
|
el = XML.SubElement(xmlthresholds, elname)
|
||||||
for threshold_name, threshold_value in t.values()[0].items():
|
for threshold_name, threshold_value in \
|
||||||
|
next(iter(t.values())).items():
|
||||||
# Normalize and craft the element name for this threshold
|
# Normalize and craft the element name for this threshold
|
||||||
elname = "%sThreshold" % threshold_name.lower().replace(
|
elname = "%sThreshold" % threshold_name.lower().replace(
|
||||||
'new', 'New')
|
'new', 'New')
|
||||||
|
@ -3502,7 +3503,8 @@ def ruby_metrics(parser, xml_parent, data):
|
||||||
XML.SubElement(el, 'metric').text = 'TOTAL_COVERAGE'
|
XML.SubElement(el, 'metric').text = 'TOTAL_COVERAGE'
|
||||||
else:
|
else:
|
||||||
XML.SubElement(el, 'metric').text = 'CODE_COVERAGE'
|
XML.SubElement(el, 'metric').text = 'CODE_COVERAGE'
|
||||||
for threshold_name, threshold_value in t.values()[0].items():
|
for threshold_name, threshold_value in \
|
||||||
|
next(iter(t.values())).items():
|
||||||
elname = threshold_name.lower()
|
elname = threshold_name.lower()
|
||||||
XML.SubElement(el, elname).text = str(threshold_value)
|
XML.SubElement(el, elname).text = str(threshold_value)
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -161,9 +161,9 @@ remoteName/\*')
|
||||||
data['remotes'] = [{data.get('name', 'origin'): data.copy()}]
|
data['remotes'] = [{data.get('name', 'origin'): data.copy()}]
|
||||||
for remoteData in data['remotes']:
|
for remoteData in data['remotes']:
|
||||||
huser = XML.SubElement(user, 'hudson.plugins.git.UserRemoteConfig')
|
huser = XML.SubElement(user, 'hudson.plugins.git.UserRemoteConfig')
|
||||||
remoteName = remoteData.keys()[0]
|
remoteName = next(iter(remoteData.keys()))
|
||||||
XML.SubElement(huser, 'name').text = remoteName
|
XML.SubElement(huser, 'name').text = remoteName
|
||||||
remoteParams = remoteData.values()[0]
|
remoteParams = next(iter(remoteData.values()))
|
||||||
if 'refspec' in remoteParams:
|
if 'refspec' in remoteParams:
|
||||||
refspec = remoteParams['refspec']
|
refspec = remoteParams['refspec']
|
||||||
else:
|
else:
|
||||||
|
@ -368,7 +368,7 @@ def store(parser, xml_parent, data):
|
||||||
pundles = XML.SubElement(scm, 'pundles')
|
pundles = XML.SubElement(scm, 'pundles')
|
||||||
for pundle_spec in pundle_specs:
|
for pundle_spec in pundle_specs:
|
||||||
pundle = XML.SubElement(pundles, '{0}.PundleSpec'.format(namespace))
|
pundle = XML.SubElement(pundles, '{0}.PundleSpec'.format(namespace))
|
||||||
pundle_type = pundle_spec.keys()[0]
|
pundle_type = next(iter(pundle_spec))
|
||||||
pundle_name = pundle_spec[pundle_type]
|
pundle_name = pundle_spec[pundle_type]
|
||||||
if pundle_type not in valid_pundle_types:
|
if pundle_type not in valid_pundle_types:
|
||||||
raise JenkinsJobsException(
|
raise JenkinsJobsException(
|
||||||
|
@ -507,9 +507,9 @@ def tfs(parser, xml_parent, data):
|
||||||
server.
|
server.
|
||||||
:arg str login: The user name that is registered on the server. The user
|
:arg str login: The user name that is registered on the server. The user
|
||||||
name must contain the name and the domain name. Entered as
|
name must contain the name and the domain name. Entered as
|
||||||
domain\\\user or user\@domain (optional).
|
domain\\\\user or user\@domain (optional).
|
||||||
**NOTE**: You must enter in at least two slashes for the
|
**NOTE**: You must enter in at least two slashes for the
|
||||||
domain\\\user format in JJB YAML. It will be rendered normally.
|
domain\\\\user format in JJB YAML. It will be rendered normally.
|
||||||
:arg str use-update: If true, Hudson will not delete the workspace at end
|
:arg str use-update: If true, Hudson will not delete the workspace at end
|
||||||
of each build. This causes the artifacts from the previous build to
|
of each build. This causes the artifacts from the previous build to
|
||||||
remain when a new build starts. (default true)
|
remain when a new build starts. (default true)
|
||||||
|
|
|
@ -91,7 +91,7 @@ def build_gerrit_triggers(xml_parent, data):
|
||||||
'hudsontrigger.events'
|
'hudsontrigger.events'
|
||||||
|
|
||||||
trigger_on_events = XML.SubElement(xml_parent, 'triggerOnEvents')
|
trigger_on_events = XML.SubElement(xml_parent, 'triggerOnEvents')
|
||||||
for config_key, tag_name in available_simple_triggers.iteritems():
|
for config_key, tag_name in available_simple_triggers.items():
|
||||||
if data.get(config_key, False):
|
if data.get(config_key, False):
|
||||||
XML.SubElement(trigger_on_events,
|
XML.SubElement(trigger_on_events,
|
||||||
'%s.%s' % (tag_namespace, tag_name))
|
'%s.%s' % (tag_namespace, tag_name))
|
||||||
|
@ -453,7 +453,7 @@ def pollurl(parser, xml_parent, data):
|
||||||
str(bool(check_content)).lower()
|
str(bool(check_content)).lower()
|
||||||
content_types = XML.SubElement(entry, 'contentTypes')
|
content_types = XML.SubElement(entry, 'contentTypes')
|
||||||
for entry in check_content:
|
for entry in check_content:
|
||||||
type_name = entry.keys()[0]
|
type_name = next(iter(entry.keys()))
|
||||||
if type_name not in valid_content_types:
|
if type_name not in valid_content_types:
|
||||||
raise JenkinsJobsException('check-content must be one of : %s'
|
raise JenkinsJobsException('check-content must be one of : %s'
|
||||||
% ', '.join(valid_content_types.
|
% ', '.join(valid_content_types.
|
||||||
|
|
|
@ -35,6 +35,8 @@ The above URL is the default.
|
||||||
http://ci.openstack.org/zuul/launchers.html#zuul-parameters
|
http://ci.openstack.org/zuul/launchers.html#zuul-parameters
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import itertools
|
||||||
|
|
||||||
|
|
||||||
def zuul():
|
def zuul():
|
||||||
"""yaml: zuul
|
"""yaml: zuul
|
||||||
|
@ -152,8 +154,8 @@ class Zuul(jenkins_jobs.modules.base.Base):
|
||||||
|
|
||||||
def handle_data(self, parser):
|
def handle_data(self, parser):
|
||||||
changed = False
|
changed = False
|
||||||
jobs = (parser.data.get('job', {}).values() +
|
jobs = itertools.chain(parser.data.get('job', {}).values(),
|
||||||
parser.data.get('job-template', {}).values())
|
parser.data.get('job-template', {}).values())
|
||||||
for job in jobs:
|
for job in jobs:
|
||||||
triggers = job.get('triggers')
|
triggers = job.get('triggers')
|
||||||
if not triggers:
|
if not triggers:
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
argparse
|
argparse
|
||||||
ordereddict
|
ordereddict
|
||||||
|
six>=1.5.2
|
||||||
PyYAML
|
PyYAML
|
||||||
python-jenkins
|
python-jenkins>=0.3.4
|
||||||
pbr>=0.8.2,<1.0
|
pbr>=0.8.2,<1.0
|
||||||
|
|
|
@ -26,7 +26,7 @@ import json
|
||||||
import operator
|
import operator
|
||||||
import testtools
|
import testtools
|
||||||
import xml.etree.ElementTree as XML
|
import xml.etree.ElementTree as XML
|
||||||
from ConfigParser import ConfigParser
|
from six.moves import configparser
|
||||||
import jenkins_jobs.local_yaml as yaml
|
import jenkins_jobs.local_yaml as yaml
|
||||||
from jenkins_jobs.builder import XmlJob, YamlParser, ModuleRegistry
|
from jenkins_jobs.builder import XmlJob, YamlParser, ModuleRegistry
|
||||||
from jenkins_jobs.modules import (project_flow,
|
from jenkins_jobs.modules import (project_flow,
|
||||||
|
@ -86,7 +86,7 @@ class BaseTestCase(object):
|
||||||
|
|
||||||
def _read_yaml_content(self):
|
def _read_yaml_content(self):
|
||||||
yaml_filepath = os.path.join(self.fixtures_path, self.in_filename)
|
yaml_filepath = os.path.join(self.fixtures_path, self.in_filename)
|
||||||
with file(yaml_filepath, 'r') as yaml_file:
|
with open(yaml_filepath, 'r') as yaml_file:
|
||||||
yaml_content = yaml.load(yaml_file)
|
yaml_content = yaml.load(yaml_file)
|
||||||
return yaml_content
|
return yaml_content
|
||||||
|
|
||||||
|
@ -118,8 +118,7 @@ class BaseTestCase(object):
|
||||||
pub.gen_xml(parser, xml_project, yaml_content)
|
pub.gen_xml(parser, xml_project, yaml_content)
|
||||||
|
|
||||||
# Prettify generated XML
|
# Prettify generated XML
|
||||||
pretty_xml = unicode(XmlJob(xml_project, 'fixturejob').output(),
|
pretty_xml = XmlJob(xml_project, 'fixturejob').output().decode('utf-8')
|
||||||
'utf-8')
|
|
||||||
|
|
||||||
self.assertThat(
|
self.assertThat(
|
||||||
pretty_xml,
|
pretty_xml,
|
||||||
|
@ -137,7 +136,7 @@ class SingleJobTestCase(BaseTestCase):
|
||||||
yaml_filepath = os.path.join(self.fixtures_path, self.in_filename)
|
yaml_filepath = os.path.join(self.fixtures_path, self.in_filename)
|
||||||
|
|
||||||
if self.conf_filename:
|
if self.conf_filename:
|
||||||
config = ConfigParser()
|
config = configparser.ConfigParser()
|
||||||
conf_filepath = os.path.join(self.fixtures_path,
|
conf_filepath = os.path.join(self.fixtures_path,
|
||||||
self.conf_filename)
|
self.conf_filename)
|
||||||
config.readfp(open(conf_filepath))
|
config.readfp(open(conf_filepath))
|
||||||
|
@ -152,8 +151,8 @@ class SingleJobTestCase(BaseTestCase):
|
||||||
parser.jobs.sort(key=operator.attrgetter('name'))
|
parser.jobs.sort(key=operator.attrgetter('name'))
|
||||||
|
|
||||||
# Prettify generated XML
|
# Prettify generated XML
|
||||||
pretty_xml = unicode("\n".join(job.output() for job in parser.jobs),
|
pretty_xml = u"\n".join(job.output().decode('utf-8')
|
||||||
'utf-8')
|
for job in parser.jobs)
|
||||||
|
|
||||||
self.assertThat(
|
self.assertThat(
|
||||||
pretty_xml,
|
pretty_xml,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import os
|
import os
|
||||||
import ConfigParser
|
from six.moves import configparser, StringIO
|
||||||
import cStringIO
|
import io
|
||||||
import codecs
|
import codecs
|
||||||
import mock
|
import mock
|
||||||
import testtools
|
import testtools
|
||||||
|
@ -22,15 +22,15 @@ class CmdTests(testtools.TestCase):
|
||||||
User passes no args, should fail with SystemExit
|
User passes no args, should fail with SystemExit
|
||||||
"""
|
"""
|
||||||
with mock.patch('sys.stderr'):
|
with mock.patch('sys.stderr'):
|
||||||
self.assertRaises(SystemExit, self.parser.parse_args, [])
|
self.assertRaises(SystemExit, cmd.main, [])
|
||||||
|
|
||||||
def test_non_existing_config_dir(self):
|
def test_non_existing_config_dir(self):
|
||||||
"""
|
"""
|
||||||
Run test mode and pass a non-existing configuration directory
|
Run test mode and pass a non-existing configuration directory
|
||||||
"""
|
"""
|
||||||
args = self.parser.parse_args(['test', 'foo'])
|
args = self.parser.parse_args(['test', 'foo'])
|
||||||
config = ConfigParser.ConfigParser()
|
config = configparser.ConfigParser()
|
||||||
config.readfp(cStringIO.StringIO(cmd.DEFAULT_CONF))
|
config.readfp(StringIO(cmd.DEFAULT_CONF))
|
||||||
self.assertRaises(IOError, cmd.execute, args, config)
|
self.assertRaises(IOError, cmd.execute, args, config)
|
||||||
|
|
||||||
def test_non_existing_config_file(self):
|
def test_non_existing_config_file(self):
|
||||||
|
@ -38,8 +38,8 @@ class CmdTests(testtools.TestCase):
|
||||||
Run test mode and pass a non-existing configuration file
|
Run test mode and pass a non-existing configuration file
|
||||||
"""
|
"""
|
||||||
args = self.parser.parse_args(['test', 'non-existing.yaml'])
|
args = self.parser.parse_args(['test', 'non-existing.yaml'])
|
||||||
config = ConfigParser.ConfigParser()
|
config = configparser.ConfigParser()
|
||||||
config.readfp(cStringIO.StringIO(cmd.DEFAULT_CONF))
|
config.readfp(StringIO(cmd.DEFAULT_CONF))
|
||||||
self.assertRaises(IOError, cmd.execute, args, config)
|
self.assertRaises(IOError, cmd.execute, args, config)
|
||||||
|
|
||||||
def test_non_existing_job(self):
|
def test_non_existing_job(self):
|
||||||
|
@ -52,8 +52,8 @@ class CmdTests(testtools.TestCase):
|
||||||
'cmd-001.yaml'),
|
'cmd-001.yaml'),
|
||||||
'invalid'])
|
'invalid'])
|
||||||
args.output_dir = mock.MagicMock()
|
args.output_dir = mock.MagicMock()
|
||||||
config = ConfigParser.ConfigParser()
|
config = configparser.ConfigParser()
|
||||||
config.readfp(cStringIO.StringIO(cmd.DEFAULT_CONF))
|
config.readfp(StringIO(cmd.DEFAULT_CONF))
|
||||||
cmd.execute(args, config) # probably better to fail here
|
cmd.execute(args, config) # probably better to fail here
|
||||||
|
|
||||||
def test_valid_job(self):
|
def test_valid_job(self):
|
||||||
|
@ -65,8 +65,8 @@ class CmdTests(testtools.TestCase):
|
||||||
'cmd-001.yaml'),
|
'cmd-001.yaml'),
|
||||||
'foo-job'])
|
'foo-job'])
|
||||||
args.output_dir = mock.MagicMock()
|
args.output_dir = mock.MagicMock()
|
||||||
config = ConfigParser.ConfigParser()
|
config = configparser.ConfigParser()
|
||||||
config.readfp(cStringIO.StringIO(cmd.DEFAULT_CONF))
|
config.readfp(StringIO(cmd.DEFAULT_CONF))
|
||||||
cmd.execute(args, config) # probably better to fail here
|
cmd.execute(args, config) # probably better to fail here
|
||||||
|
|
||||||
def test_console_output(self):
|
def test_console_output(self):
|
||||||
|
@ -74,15 +74,14 @@ class CmdTests(testtools.TestCase):
|
||||||
Run test mode and verify that resulting XML gets sent to the console.
|
Run test mode and verify that resulting XML gets sent to the console.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
console_out = cStringIO.StringIO()
|
console_out = io.BytesIO()
|
||||||
with mock.patch('sys.stdout', console_out):
|
with mock.patch('sys.stdout', console_out):
|
||||||
cmd.main(['test', os.path.join(self.fixtures_path,
|
cmd.main(['test', os.path.join(self.fixtures_path,
|
||||||
'cmd-001.yaml')])
|
'cmd-001.yaml')])
|
||||||
xml_content = u"%s" % codecs.open(os.path.join(self.fixtures_path,
|
xml_content = codecs.open(os.path.join(self.fixtures_path,
|
||||||
'cmd-001.xml'),
|
'cmd-001.xml'),
|
||||||
'r',
|
'r', 'utf-8').read()
|
||||||
'utf-8').read()
|
self.assertEqual(console_out.getvalue().decode('utf-8'), xml_content)
|
||||||
self.assertEqual(console_out.getvalue(), xml_content)
|
|
||||||
|
|
||||||
def test_config_with_test(self):
|
def test_config_with_test(self):
|
||||||
"""
|
"""
|
||||||
|
@ -121,8 +120,8 @@ class CmdTests(testtools.TestCase):
|
||||||
|
|
||||||
args = self.parser.parse_args(['test', '-r', '/jjb_configs'])
|
args = self.parser.parse_args(['test', '-r', '/jjb_configs'])
|
||||||
args.output_dir = mock.MagicMock()
|
args.output_dir = mock.MagicMock()
|
||||||
config = ConfigParser.ConfigParser()
|
config = configparser.ConfigParser()
|
||||||
config.readfp(cStringIO.StringIO(cmd.DEFAULT_CONF))
|
config.readfp(StringIO(cmd.DEFAULT_CONF))
|
||||||
cmd.execute(args, config) # probably better to fail here
|
cmd.execute(args, config) # probably better to fail here
|
||||||
|
|
||||||
update_job_mock.assert_called_with(paths, [], output=args.output_dir)
|
update_job_mock.assert_called_with(paths, [], output=args.output_dir)
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<project>
|
||||||
|
<actions/>
|
||||||
|
<description><!-- Managed by Jenkins Job Builder --></description>
|
||||||
|
<keepDependencies>false</keepDependencies>
|
||||||
|
<blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
|
||||||
|
<blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
|
||||||
|
<concurrentBuild>false</concurrentBuild>
|
||||||
|
<canRoam>true</canRoam>
|
||||||
|
<properties/>
|
||||||
|
<scm class="hudson.scm.NullSCM"/>
|
||||||
|
<builders>
|
||||||
|
<hudson.tasks.Shell>
|
||||||
|
<command>#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
print("Doing something cool with python")
|
||||||
|
</command>
|
||||||
|
</hudson.tasks.Shell>
|
||||||
|
</builders>
|
||||||
|
<publishers/>
|
||||||
|
<buildWrappers/>
|
||||||
|
</project>
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<project>
|
||||||
|
<actions/>
|
||||||
|
<description><!-- Managed by Jenkins Job Builder --></description>
|
||||||
|
<keepDependencies>false</keepDependencies>
|
||||||
|
<blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
|
||||||
|
<blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
|
||||||
|
<concurrentBuild>false</concurrentBuild>
|
||||||
|
<canRoam>true</canRoam>
|
||||||
|
<properties/>
|
||||||
|
<scm class="hudson.scm.NullSCM"/>
|
||||||
|
<builders>
|
||||||
|
<hudson.tasks.Shell>
|
||||||
|
<command>#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
print("Doing something cool with python")
|
||||||
|
</command>
|
||||||
|
</hudson.tasks.Shell>
|
||||||
|
</builders>
|
||||||
|
<publishers/>
|
||||||
|
<buildWrappers/>
|
||||||
|
</project>
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<project>
|
||||||
|
<actions/>
|
||||||
|
<description><!-- Managed by Jenkins Job Builder --></description>
|
||||||
|
<keepDependencies>false</keepDependencies>
|
||||||
|
<blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
|
||||||
|
<blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
|
||||||
|
<concurrentBuild>false</concurrentBuild>
|
||||||
|
<canRoam>true</canRoam>
|
||||||
|
<properties/>
|
||||||
|
<scm class="hudson.scm.NullSCM"/>
|
||||||
|
<builders>
|
||||||
|
<hudson.tasks.Shell>
|
||||||
|
<command>#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
print("Doing something else cool with python")
|
||||||
|
</command>
|
||||||
|
</hudson.tasks.Shell>
|
||||||
|
</builders>
|
||||||
|
<publishers/>
|
||||||
|
<buildWrappers/>
|
||||||
|
</project>
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<project>
|
||||||
|
<actions/>
|
||||||
|
<description><!-- Managed by Jenkins Job Builder --></description>
|
||||||
|
<keepDependencies>false</keepDependencies>
|
||||||
|
<blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
|
||||||
|
<blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
|
||||||
|
<concurrentBuild>false</concurrentBuild>
|
||||||
|
<canRoam>true</canRoam>
|
||||||
|
<properties/>
|
||||||
|
<scm class="hudson.scm.NullSCM"/>
|
||||||
|
<builders>
|
||||||
|
<hudson.tasks.Shell>
|
||||||
|
<command>#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
print("Doing something else cool with python")
|
||||||
|
</command>
|
||||||
|
</hudson.tasks.Shell>
|
||||||
|
</builders>
|
||||||
|
<publishers/>
|
||||||
|
<buildWrappers/>
|
||||||
|
</project>
|
|
@ -0,0 +1,34 @@
|
||||||
|
- job-group:
|
||||||
|
name: multiple_jobs
|
||||||
|
jobs:
|
||||||
|
- 'job_one_{version}':
|
||||||
|
- 'job_two_{version}':
|
||||||
|
|
||||||
|
- project:
|
||||||
|
name: multiple_1.2
|
||||||
|
version: 1.2
|
||||||
|
jobs:
|
||||||
|
- multiple_jobs
|
||||||
|
|
||||||
|
- project:
|
||||||
|
name: multiple_1.3
|
||||||
|
version: 1.3
|
||||||
|
jobs:
|
||||||
|
- multiple_jobs
|
||||||
|
|
||||||
|
- job-template:
|
||||||
|
name: 'job_one_{version}'
|
||||||
|
builders:
|
||||||
|
- shell: |
|
||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
print("Doing something cool with python")
|
||||||
|
|
||||||
|
- job-template:
|
||||||
|
name: 'job_two_{version}'
|
||||||
|
builders:
|
||||||
|
- shell: |
|
||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
print("Doing something else cool with python")
|
||||||
|
|
Loading…
Reference in New Issue