diff --git a/jenkins_jobs/cli/entry.py b/jenkins_jobs/cli/entry.py index c243bbeca..fdec54a8f 100644 --- a/jenkins_jobs/cli/entry.py +++ b/jenkins_jobs/cli/entry.py @@ -13,21 +13,20 @@ # License for the specific language governing permissions and limitations # under the License. -import argparse import logging import sys -from stevedore import extension - -import jenkins_jobs.version from jenkins_jobs import cmd +from jenkins_jobs import version +from jenkins_jobs.cli.parser import create_parser +from jenkins_jobs.config import JJBConfig logging.basicConfig(level=logging.INFO) def __version__(): return "Jenkins Job Builder version: %s" % \ - jenkins_jobs.version.version_info.version_string() + version.version_info.version_string() class JenkinsJobs(object): @@ -47,90 +46,27 @@ class JenkinsJobs(object): various command line parameters. """ - def __init__(self, args=None): + def __init__(self, args=None, **kwargs): if args is None: args = [] - parser = self._create_parser() - self._options = parser.parse_args(args) + parser = create_parser() + options = parser.parse_args(args) - if not self._options.command: + self.jjb_config = JJBConfig(arguments=options, **kwargs) + self.jjb_config.do_magical_things() + + if not options.command: parser.error("Must specify a 'command' to be performed") logger = logging.getLogger() - if (self._options.log_level is not None): - self._options.log_level = getattr(logging, - self._options.log_level.upper(), - logger.getEffectiveLevel()) - logger.setLevel(self._options.log_level) - - self._config_file_values = cmd.setup_config_settings(self._options) - - def _create_parser(self): - parser = argparse.ArgumentParser() - parser.add_argument( - '--conf', - dest='conf', - help='''configuration file''') - parser.add_argument( - '-l', - '--log_level', - dest='log_level', - default='info', - help='''log level (default: %(default)s)''') - parser.add_argument( - '--ignore-cache', - action='store_true', - dest='ignore_cache', - default=False, - help='''ignore the cache and update the jobs anyhow (that will only - flush the specified jobs cache)''') - parser.add_argument( - '--flush-cache', - action='store_true', - dest='flush_cache', - default=False, - help='''flush all the cache entries before updating''') - parser.add_argument( - '--version', - dest='version', - action='version', - version=__version__(), - help='''show version''') - parser.add_argument( - '--allow-empty-variables', - action='store_true', - dest='allow_empty_variables', - default=None, - help='''Don\'t fail if any of the variables inside any string are - not defined, replace with empty string instead.''') - parser.add_argument( - '--user', '-u', - help='''The Jenkins user to use for authentication. This overrides - the user specified in the configuration file.''') - parser.add_argument( - '--password', '-p', - help='''Password or API token to use for authenticating towards - Jenkins. This overrides the password specified in the configuration - file.''') - - subparser = parser.add_subparsers( - dest='command', - help='''update, test or delete job''') - - extension_manager = extension.ExtensionManager( - namespace='jjb.cli.subcommands', - invoke_on_load=True, - ) - - def parse_subcommand_args(ext, subparser): - ext.obj.parse_args(subparser) - - extension_manager.map(parse_subcommand_args, subparser) - - return parser + if (options.log_level is not None): + options.log_level = getattr(logging, + options.log_level.upper(), + logger.getEffectiveLevel()) + logger.setLevel(options.log_level) def execute(self): - jenkins_jobs.cmd.execute(self._options, self._config_file_values) + cmd.execute(self.jjb_config) def main(): diff --git a/jenkins_jobs/cli/parser.py b/jenkins_jobs/cli/parser.py new file mode 100644 index 000000000..ca1e009c8 --- /dev/null +++ b/jenkins_jobs/cli/parser.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python +# Copyright (C) 2015 Wayne Warren +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import argparse + +import jenkins_jobs.version + +from stevedore import extension + + +def __version__(): + return "Jenkins Job Builder version: %s" % \ + jenkins_jobs.version.version_info.version_string() + + +def create_parser(): + """ Create an ArgumentParser object usable by JenkinsJobs. + """ + parser = argparse.ArgumentParser() + parser.add_argument( + '--conf', + dest='conf', + help='''configuration file''') + parser.add_argument( + '-l', + '--log_level', + dest='log_level', + default='info', + help='''log level (default: %(default)s)''') + parser.add_argument( + '--ignore-cache', + action='store_true', + dest='ignore_cache', + default=False, + help='''ignore the cache and update the jobs anyhow (that will only + flush the specified jobs cache)''') + parser.add_argument( + '--flush-cache', + action='store_true', + dest='flush_cache', + default=False, + help='''flush all the cache entries before updating''') + parser.add_argument( + '--version', + dest='version', + action='version', + version=__version__(), + help='''show version''') + parser.add_argument( + '--allow-empty-variables', + action='store_true', + dest='allow_empty_variables', + default=None, + help='''Don\'t fail if any of the variables inside any string are + not defined, replace with empty string instead.''') + parser.add_argument( + '--user', '-u', + help='''The Jenkins user to use for authentication. This overrides + the user specified in the configuration file.''') + parser.add_argument( + '--password', '-p', + help='''Password or API token to use for authenticating towards + Jenkins. This overrides the password specified in the configuration + file.''') + + subparser = parser.add_subparsers( + dest='command', + help='''update, test or delete job''') + + extension_manager = extension.ExtensionManager( + namespace='jjb.cli.subcommands', + invoke_on_load=True, + ) + + def parse_subcommand_args(ext, subparser): + ext.obj.parse_args(subparser) + + extension_manager.map(parse_subcommand_args, subparser) + + return parser diff --git a/jenkins_jobs/cmd.py b/jenkins_jobs/cmd.py index 0a5a20c42..8029ef5b1 100755 --- a/jenkins_jobs/cmd.py +++ b/jenkins_jobs/cmd.py @@ -13,25 +13,15 @@ # License for the specific language governing permissions and limitations # under the License. -import fnmatch -import io import logging -import os -import platform import sys -import yaml -from six.moves import configparser from six.moves import input -from six.moves import StringIO from jenkins_jobs.builder import Builder from jenkins_jobs.errors import JenkinsJobsException -import jenkins_jobs.version -import jenkins_jobs.cli - logging.basicConfig(level=logging.INFO) logger = logging.getLogger() @@ -63,191 +53,18 @@ def confirm(question): sys.exit('Aborted') -def recurse_path(root, excludes=None): - if excludes is None: - excludes = [] - - basepath = os.path.realpath(root) - pathlist = [basepath] - - patterns = [e for e in excludes if os.path.sep not in e] - absolute = [e for e in excludes if os.path.isabs(e)] - relative = [e for e in excludes if os.path.sep in e and - not os.path.isabs(e)] - for root, dirs, files in os.walk(basepath, topdown=True): - dirs[:] = [ - d for d in dirs - if not any([fnmatch.fnmatch(d, pattern) for pattern in patterns]) - if not any([fnmatch.fnmatch(os.path.abspath(os.path.join(root, d)), - path) - for path in absolute]) - if not any([fnmatch.fnmatch(os.path.relpath(os.path.join(root, d)), - path) - for path in relative]) - ] - pathlist.extend([os.path.join(root, path) for path in dirs]) - - return pathlist - - -def get_config_file(options): - conf = '/etc/jenkins_jobs/jenkins_jobs.ini' - if options.conf: - conf = options.conf - else: - # Allow a script directory config to override. - localconf = os.path.join(os.path.dirname(__file__), - 'jenkins_jobs.ini') - if os.path.isfile(localconf): - conf = localconf - # Allow a user directory config to override. - userconf = os.path.join(os.path.expanduser('~'), '.config', - 'jenkins_jobs', 'jenkins_jobs.ini') - if os.path.isfile(userconf): - conf = userconf - return conf - - -def setup_config_settings(options): - conf = get_config_file(options) - config = configparser.ConfigParser() - # Load default config always - config.readfp(StringIO(DEFAULT_CONF)) - if os.path.isfile(conf): - options.conf = conf # remember file we read from - logger.debug("Reading config from {0}".format(conf)) - conffp = io.open(conf, 'r', encoding='utf-8') - config.readfp(conffp) - elif options.command == 'test': - logger.debug("Not requiring config for test output generation") - else: - raise JenkinsJobsException( - "A valid configuration file is required." - "\n{0} is not valid.".format(conf)) - - return config - - -def execute(options, config): - logger.debug("Config: {0}".format(config)) - - # check the ignore_cache setting: first from command line, - # if not present check from ini file - ignore_cache = False - if options.ignore_cache: - ignore_cache = options.ignore_cache - elif config.has_option('jenkins', 'ignore_cache'): - logging.warn('ignore_cache option should be moved to the [job_builder]' - ' section in the config file, the one specified in the ' - '[jenkins] section will be ignored in the future') - ignore_cache = config.getboolean('jenkins', 'ignore_cache') - elif config.has_option('job_builder', 'ignore_cache'): - ignore_cache = config.getboolean('job_builder', 'ignore_cache') - - # Jenkins supports access as an anonymous user, which can be used to - # ensure read-only behaviour when querying the version of plugins - # installed for test mode to generate XML output matching what will be - # uploaded. To enable must pass 'None' as the value for user and password - # to python-jenkins - # - # catching 'TypeError' is a workaround for python 2.6 interpolation error - # https://bugs.launchpad.net/openstack-ci/+bug/1259631 - if options.user: - user = options.user - else: - try: - user = config.get('jenkins', 'user') - except (TypeError, configparser.NoOptionError): - user = None - - if options.password: - password = options.password - else: - try: - password = config.get('jenkins', 'password') - except (TypeError, configparser.NoOptionError): - password = None - - # Inform the user as to what is likely to happen, as they may specify - # a real jenkins instance in test mode to get the plugin info to check - # the XML generated. - if user is None and password is None: - logger.info("Will use anonymous access to Jenkins if needed.") - elif (user is not None and password is None) or ( - user is None and password is not None): - raise JenkinsJobsException( - "Cannot authenticate to Jenkins with only one of User and " - "Password provided, please check your configuration." - ) - - # None -- no timeout, blocking mode; same as setblocking(True) - # 0.0 -- non-blocking mode; same as setblocking(False) <--- default - # > 0 -- timeout mode; operations time out after timeout seconds - # < 0 -- illegal; raises an exception - # to retain the default must use - # "timeout=jenkins_jobs.builder._DEFAULT_TIMEOUT" or not set timeout at - # all. - timeout = jenkins_jobs.builder._DEFAULT_TIMEOUT - try: - timeout = config.getfloat('jenkins', 'timeout') - except (ValueError): - raise JenkinsJobsException("Jenkins timeout config is invalid") - except (TypeError, configparser.NoOptionError): - pass - - plugins_info = None - - if getattr(options, 'plugins_info_path', None) is not None: - with io.open(options.plugins_info_path, 'r', - encoding='utf-8') as yaml_file: - plugins_info = yaml.load(yaml_file) - if not isinstance(plugins_info, list): - raise JenkinsJobsException("{0} must contain a Yaml list!" - .format(options.plugins_info_path)) - elif (not options.conf or not - config.getboolean("jenkins", "query_plugins_info")): - logger.debug("Skipping plugin info retrieval") - plugins_info = {} - - if options.allow_empty_variables is not None: - config.set('job_builder', - 'allow_empty_variables', - str(options.allow_empty_variables)) +def execute(jjb_config): + config = jjb_config.config_parser + options = jjb_config.arguments builder = Builder(config.get('jenkins', 'url'), - user, - password, - config, - jenkins_timeout=timeout, - ignore_cache=ignore_cache, + jjb_config.user, + jjb_config.password, + jjb_config.config_parser, + jenkins_timeout=jjb_config.timeout, + ignore_cache=jjb_config.ignore_cache, flush_cache=options.flush_cache, - plugins_list=plugins_info) - - if getattr(options, 'path', None): - if hasattr(options.path, 'read'): - logger.debug("Input file is stdin") - if options.path.isatty(): - key = 'CTRL+Z' if platform.system() == 'Windows' else 'CTRL+D' - logger.warn( - "Reading configuration from STDIN. Press %s to end input.", - key) - else: - # take list of paths - options.path = options.path.split(os.pathsep) - - do_recurse = (getattr(options, 'recursive', False) or - config.getboolean('job_builder', 'recursive')) - - excludes = [e for elist in options.exclude - for e in elist.split(os.pathsep)] or \ - config.get('job_builder', 'exclude').split(os.pathsep) - paths = [] - for path in options.path: - if do_recurse and os.path.isdir(path): - paths.extend(recurse_path(path, excludes)) - else: - paths.append(path) - options.path = paths + plugins_list=jjb_config.plugins_info) if options.command == 'delete': for job in options.name: diff --git a/jenkins_jobs/config.py b/jenkins_jobs/config.py new file mode 100644 index 000000000..b6fb221c6 --- /dev/null +++ b/jenkins_jobs/config.py @@ -0,0 +1,279 @@ +#!/usr/bin/env python +# Copyright (C) 2015 Wayne Warren +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +# Manage JJB Configuration sources, defaults, and access. + +import io +import logging +import platform +import os +import yaml + +from six.moves import configparser, StringIO + +from jenkins_jobs import builder +from jenkins_jobs import utils +from jenkins_jobs.cli.parser import create_parser +from jenkins_jobs.errors import JenkinsJobsException + +__all__ = [ + "JJBConfig" +] + +logger = logging.getLogger(__name__) + +DEFAULT_CONF = """ +[job_builder] +keep_descriptions=False +ignore_cache=False +recursive=False +exclude=.* +allow_duplicates=False +allow_empty_variables=False + +[jenkins] +url=http://localhost:8080/ +query_plugins_info=True + +[hipchat] +authtoken=dummy +send-as=Jenkins +""" + + +class JJBConfigException(JenkinsJobsException): + pass + + +class JJBConfig(object): + + def __init__(self, config_filename=None, arguments=None, + config_file_required=False): + + """ + The JJBConfig class is intended to encapsulate and resolve priority + between all sources of configuration for the JJB library. This allows + the various sources of configuration to provide a consistent accessor + interface regardless of where they are used. + + It also allows users of JJB-as-an-API to create minimally valid + configuration and easily make minor modifications to default values + without strictly adhering to the confusing setup (see the + do_magical_things method, the behavior of which largely lived in the + cmd.execute method previously) necessary for the jenkins-jobs command + line tool. + + :arg str config_filename: Name of configuration file on which to base + this config object. + :arg dict arguments: A argparse.Namespace object as produced by the + jenkins-jobs argument parser. + :arg bool config_file_required: Allows users of the JJBConfig class to + decide whether or not it's really necessary for a config file to be + passed in when creating an instance. This has two effects on the + behavior of JJBConfig initialization: + * It determines whether or not we try "local" and "global" config + files. + * It determines whether or not failure to read some config file + will raise an exception or simply print a warning message + indicating that no config file was found. + """ + + config_parser = self._init_defaults() + + if arguments is None: + jenkins_jobs_parser = create_parser() + arguments = jenkins_jobs_parser.parse_args(['test']) + + global_conf = '/etc/jenkins_jobs/jenkins_jobs.ini' + user_conf = os.path.join(os.path.expanduser('~'), '.config', + 'jenkins_jobs', 'jenkins_jobs.ini') + local_conf = os.path.join(os.path.dirname(__file__), + 'jenkins_jobs.ini') + conf = None + if config_filename is not None: + conf = config_filename + + elif hasattr(arguments, 'conf') and arguments.conf is not None: + conf = arguments.conf + + elif config_file_required: + if os.path.isfile(local_conf): + conf = local_conf + elif os.path.isfile(user_conf): + conf = user_conf + else: + conf = global_conf + + config_fp = None + if conf is not None: + try: + config_fp = self._read_config_file(conf) + except JJBConfigException as e: + if config_file_required: + raise e + else: + logger.warn("""Config file, {0}, not found. Using default + config values.""".format(conf)) + + if config_fp is not None: + config_parser.readfp(config_fp) + + self.config_parser = config_parser + self.arguments = arguments + + def _init_defaults(self): + """ Initialize default configuration values using DEFAULT_CONF + """ + config = configparser.ConfigParser() + # Load default config always + config.readfp(StringIO(DEFAULT_CONF)) + return config + + def _read_config_file(self, config_filename): + """ Given path to configuration file, read it in as a ConfigParser + object and return that object. + """ + if os.path.isfile(config_filename): + self.__config_file = config_filename # remember file we read from + logger.debug("Reading config from {0}".format(config_filename)) + config_fp = io.open(config_filename, 'r', encoding='utf-8') + else: + raise JJBConfigException("""A valid configuration file is required. + \n{0} is not valid.""".format(config_filename)) + + return config_fp + + def do_magical_things(self): + config = self.config_parser + options = self.arguments + + logger.debug("Config: {0}".format(config)) + + # check the ignore_cache setting: first from command line, + # if not present check from ini file + self.ignore_cache = False + if options.ignore_cache: + self.ignore_cache = options.ignore_cache + elif config.has_option('jenkins', 'ignore_cache'): + logging.warn('''ignore_cache option should be moved to the + [job_builder] section in the config file, the one + specified in the [jenkins] section will be ignored in + the future''') + self.ignore_cache = config.getboolean('jenkins', 'ignore_cache') + elif config.has_option('job_builder', 'ignore_cache'): + self.ignore_cache = config.getboolean('job_builder', + 'ignore_cache') + + # Jenkins supports access as an anonymous user, which can be used to + # ensure read-only behaviour when querying the version of plugins + # installed for test mode to generate XML output matching what will be + # uploaded. To enable must pass 'None' as the value for user and + # password to python-jenkins + # + # catching 'TypeError' is a workaround for python 2.6 interpolation + # error + # https://bugs.launchpad.net/openstack-ci/+bug/1259631 + if options.user: + self.user = options.user + else: + try: + self.user = config.get('jenkins', 'user') + except (TypeError, configparser.NoOptionError): + self.user = None + + if options.password: + self.password = options.password + else: + try: + self.password = config.get('jenkins', 'password') + except (TypeError, configparser.NoOptionError): + self.password = None + + # Inform the user as to what is likely to happen, as they may specify + # a real jenkins instance in test mode to get the plugin info to check + # the XML generated. + if self.user is None and self.password is None: + logger.info("Will use anonymous access to Jenkins if needed.") + elif (self.user is not None and self.password is None) or ( + self.user is None and self.password is not None): + raise JenkinsJobsException( + "Cannot authenticate to Jenkins with only one of User and " + "Password provided, please check your configuration." + ) + + # None -- no timeout, blocking mode; same as setblocking(True) + # 0.0 -- non-blocking mode; same as setblocking(False) <--- default + # > 0 -- timeout mode; operations time out after timeout seconds + # < 0 -- illegal; raises an exception + # to retain the default must use + # "timeout=jenkins_jobs.builder._DEFAULT_TIMEOUT" or not set timeout at + # all. + self.timeout = builder._DEFAULT_TIMEOUT + try: + self.timeout = config.getfloat('jenkins', 'timeout') + except (ValueError): + raise JenkinsJobsException("Jenkins timeout config is invalid") + except (TypeError, configparser.NoOptionError): + pass + + self.plugins_info = None + + if getattr(options, 'plugins_info_path', None) is not None: + with io.open(options.plugins_info_path, 'r', + encoding='utf-8') as yaml_file: + self.plugins_info = yaml.load(yaml_file) + if not isinstance(self.plugins_info, list): + raise JenkinsJobsException("{0} must contain a Yaml list!" + .format(options.plugins_info_path)) + elif (not options.conf or not + config.getboolean("jenkins", "query_plugins_info")): + logger.debug("Skipping plugin info retrieval") + self.plugins_info = {} + + if options.allow_empty_variables is not None: + config.set('job_builder', + 'allow_empty_variables', + str(options.allow_empty_variables)) + + if getattr(options, 'path', None): + if hasattr(options.path, 'read'): + logger.debug("Input file is stdin") + if options.path.isatty(): + if platform.system() == 'Windows': + key = 'CTRL+Z' + else: + key = 'CTRL+D' + logger.warn("""Reading configuration from STDIN. Press %s + to end input.""", key) + else: + # take list of paths + options.path = options.path.split(os.pathsep) + + do_recurse = (getattr(options, 'recursive', False) or + config.getboolean('job_builder', 'recursive')) + + excludes = [e for elist in options.exclude + for e in elist.split(os.pathsep)] or \ + config.get('job_builder', 'exclude').split(os.pathsep) + paths = [] + for path in options.path: + if do_recurse and os.path.isdir(path): + paths.extend(utils.recurse_path(path, excludes)) + else: + paths.append(path) + options.path = paths + + self.config_parser = config + self.arguments = options diff --git a/jenkins_jobs/utils.py b/jenkins_jobs/utils.py index 14b8443d5..612966634 100644 --- a/jenkins_jobs/utils.py +++ b/jenkins_jobs/utils.py @@ -16,7 +16,9 @@ # functions that don't fit in well elsewhere import codecs +import fnmatch import locale +import os.path def wrap_stream(stream, encoding='utf-8'): @@ -33,3 +35,30 @@ def wrap_stream(stream, encoding='utf-8'): return stream return codecs.EncodedFile(stream, encoding, stream_enc) + + +def recurse_path(root, excludes=None): + if excludes is None: + excludes = [] + + basepath = os.path.realpath(root) + pathlist = [basepath] + + patterns = [e for e in excludes if os.path.sep not in e] + absolute = [e for e in excludes if os.path.isabs(e)] + relative = [e for e in excludes if os.path.sep in e and + not os.path.isabs(e)] + for root, dirs, files in os.walk(basepath, topdown=True): + dirs[:] = [ + d for d in dirs + if not any([fnmatch.fnmatch(d, pattern) for pattern in patterns]) + if not any([fnmatch.fnmatch(os.path.abspath(os.path.join(root, d)), + path) + for path in absolute]) + if not any([fnmatch.fnmatch(os.path.relpath(os.path.join(root, d)), + path) + for path in relative]) + ] + pathlist.extend([os.path.join(root, path) for path in dirs]) + + return pathlist diff --git a/tests/base.py b/tests/base.py index 7648977ba..5742a7499 100644 --- a/tests/base.py +++ b/tests/base.py @@ -27,13 +27,12 @@ import re import xml.etree.ElementTree as XML import fixtures -from six.moves import configparser from six.moves import StringIO import testtools from testtools.content import text_content from yaml import safe_dump -from jenkins_jobs.cmd import DEFAULT_CONF +from jenkins_jobs.config import JJBConfig import jenkins_jobs.local_yaml as yaml from jenkins_jobs.modules import project_externaljob from jenkins_jobs.modules import project_flow @@ -131,12 +130,9 @@ class BaseTestCase(LoggingFixture): return yaml_content def _get_config(self): - config = configparser.ConfigParser() - config.readfp(StringIO(DEFAULT_CONF)) - if self.conf_filename is not None: - with io.open(self.conf_filename, 'r', encoding='utf-8') as cf: - config.readfp(cf) - return config + jjb_config = JJBConfig(self.conf_filename) + + return jjb_config.config_parser def test_yaml_snippet(self): if not self.in_filename: diff --git a/tests/cmd/subcommands/test_test.py b/tests/cmd/subcommands/test_test.py index 5de90b814..813d63b61 100644 --- a/tests/cmd/subcommands/test_test.py +++ b/tests/cmd/subcommands/test_test.py @@ -152,8 +152,8 @@ class TestTests(CmdTestsBase): os.path.join(self.fixtures_path, 'cmd-001.yaml')] with mock.patch('sys.stdout'): - jenkins_jobs = entry.JenkinsJobs(args) - e = self.assertRaises(JenkinsJobsException, jenkins_jobs.execute) + e = self.assertRaises(JenkinsJobsException, entry.JenkinsJobs, + args) self.assertIn("must contain a Yaml list", str(e)) diff --git a/tests/cmd/test_cmd.py b/tests/cmd/test_cmd.py index b21776150..2aa4450a4 100644 --- a/tests/cmd/test_cmd.py +++ b/tests/cmd/test_cmd.py @@ -31,7 +31,7 @@ class CmdTestsBase(LoggingFixture, testtools.TestCase): jenkins_jobs.execute() -class CmdTests(CmdTestsBase): +class TestCmd(CmdTestsBase): def test_with_empty_args(self): """ diff --git a/tests/cmd/test_config.py b/tests/cmd/test_config.py index de49353bf..d1712890e 100644 --- a/tests/cmd/test_config.py +++ b/tests/cmd/test_config.py @@ -13,6 +13,8 @@ class TestConfigs(CmdTestsBase): global_conf = '/etc/jenkins_jobs/jenkins_jobs.ini' user_conf = os.path.join(os.path.expanduser('~'), '.config', 'jenkins_jobs', 'jenkins_jobs.ini') + local_conf = os.path.join(os.path.dirname(__file__), + 'jenkins_jobs.ini') def test_use_global_config(self): """ @@ -24,15 +26,14 @@ class TestConfigs(CmdTestsBase): with patch('os.path.isfile', return_value=True) as m_isfile: def side_effect(path): - if path == self.user_conf: - return False if path == self.global_conf: return True + return False m_isfile.side_effect = side_effect with patch('io.open', return_value=conffp) as m_open: - entry.JenkinsJobs(args) + entry.JenkinsJobs(args, config_file_required=True) m_open.assert_called_with(self.global_conf, 'r', encoding='utf-8') @@ -48,10 +49,11 @@ class TestConfigs(CmdTestsBase): def side_effect(path): if path == self.user_conf: return True + return False m_isfile.side_effect = side_effect with patch('io.open', return_value=conffp) as m_open: - entry.JenkinsJobs(args) + entry.JenkinsJobs(args, config_file_required=True) m_open.assert_called_with(self.user_conf, 'r', encoding='utf-8') diff --git a/tests/cmd/test_recurse_path.py b/tests/cmd/test_recurse_path.py index 7654f96b9..7be7e3eec 100644 --- a/tests/cmd/test_recurse_path.py +++ b/tests/cmd/test_recurse_path.py @@ -3,7 +3,7 @@ import os from tests.base import mock import testtools -from jenkins_jobs import cmd +from jenkins_jobs import utils def fake_os_walk(paths): @@ -25,14 +25,14 @@ def fake_os_walk(paths): return os_walk -# Testing the cmd module can sometimes result in the CacheStorage class +# Testing the utils module can sometimes result in the CacheStorage class # attempting to create the cache directory multiple times as the tests # are run in parallel. Stub out the CacheStorage to ensure that each # test can safely create the object without effect. @mock.patch('jenkins_jobs.builder.CacheStorage', mock.MagicMock) class CmdRecursePath(testtools.TestCase): - @mock.patch('jenkins_jobs.cmd.os.walk') + @mock.patch('jenkins_jobs.utils.os.walk') def test_recursive_path_option_exclude_pattern(self, oswalk_mock): """ Test paths returned by the recursive processing when using pattern @@ -60,9 +60,9 @@ class CmdRecursePath(testtools.TestCase): paths = [k for k, v in os_walk_paths if v is not None] oswalk_mock.side_effect = fake_os_walk(os_walk_paths) - self.assertEqual(paths, cmd.recurse_path('/jjb_configs', ['test*'])) + self.assertEqual(paths, utils.recurse_path('/jjb_configs', ['test*'])) - @mock.patch('jenkins_jobs.cmd.os.walk') + @mock.patch('jenkins_jobs.utils.os.walk') def test_recursive_path_option_exclude_absolute(self, oswalk_mock): """ Test paths returned by the recursive processing when using absolute @@ -93,10 +93,10 @@ class CmdRecursePath(testtools.TestCase): oswalk_mock.side_effect = fake_os_walk(os_walk_paths) - self.assertEqual(paths, cmd.recurse_path('/jjb_configs', - ['/jjb_configs/dir1'])) + self.assertEqual(paths, utils.recurse_path('/jjb_configs', + ['/jjb_configs/dir1'])) - @mock.patch('jenkins_jobs.cmd.os.walk') + @mock.patch('jenkins_jobs.utils.os.walk') def test_recursive_path_option_exclude_relative(self, oswalk_mock): """ Test paths returned by the recursive processing when using relative @@ -132,5 +132,5 @@ class CmdRecursePath(testtools.TestCase): oswalk_mock.side_effect = fake_os_walk(rel_os_walk_paths) - self.assertEqual(paths, cmd.recurse_path('jjb_configs', - ['jjb_configs/test3/bar'])) + self.assertEqual(paths, utils.recurse_path('jjb_configs', + ['jjb_configs/test3/bar']))