Accept list of paths in 'path' arg.

This adds the behavior of splitting the path positional argument on colons then
processing each resulting string the same way that the path was being processed
previous to this commit.

* Adds a unit test to validate correct `path` parsing.
* Adds a unit test to validate correct `path` parsing in the recursive case.
* Documents the new behavior.

Change-Id: I0f465e02077569352cd276a8366ead5bab0eb117
This commit is contained in:
Wayne 2014-10-02 19:08:06 -07:00
parent 1db5ad59c1
commit f0e53d34a1
4 changed files with 119 additions and 10 deletions
doc/source
jenkins_jobs
tests/cmd
fixtures/multipath
test_cmd.py

@ -146,6 +146,19 @@ arguments after the job definition path. To update Foo1 and Foo2 run::
jenkins-jobs update /path/to/defs Foo1 Foo2
Passing Multiple Paths
^^^^^^^^^^^^^^^^^^^^^^
It is possible to pass multiple paths to JJB using colons as a path separator on
\*nix systems and semi-colons on Windows systems. For example::
jenkins-jobs test /path/to/global:/path/to/instance:/path/to/instance/project
This helps when structuring directory layouts as you may selectively include
directories in different ways to suit different needs. If you maintain multiple
Jenkins instances suited to various needs you may want to share configuration
between those instances (global). Furthermore, there may be various ways you
would like to structure jobs within a given instance.
.. rubric:: Footnotes
.. [#f1] The cache default location is at ``~/.cache/jenkins_jobs``, which

@ -69,13 +69,15 @@ def create_parser():
subparser = parser.add_subparsers(help='update, test or delete job',
dest='command')
parser_update = subparser.add_parser('update', parents=[recursive_parser])
parser_update.add_argument('path', help='path to YAML file or directory')
parser_update.add_argument('path', help='colon-separated list of paths to'
' YAML files or directories')
parser_update.add_argument('names', help='name(s) of job(s)', nargs='*')
parser_update.add_argument('--delete-old', help='delete obsolete jobs',
action='store_true',
dest='delete_old', default=False,)
parser_test = subparser.add_parser('test', parents=[recursive_parser])
parser_test.add_argument('path', help='path to YAML file or directory',
parser_test.add_argument('path', help='colon-separated list of paths to'
' YAML files or directories',
nargs='?', default=sys.stdin)
parser_test.add_argument('-o', dest='output_dir', default=sys.stdout,
help='path to output XML')
@ -83,7 +85,8 @@ def create_parser():
parser_delete = subparser.add_parser('delete')
parser_delete.add_argument('name', help='name of job', nargs='+')
parser_delete.add_argument('-p', '--path', default=None,
help='path to YAML file or directory')
help='colon-separated list of paths to'
' YAML files or directories')
subparser.add_parser('delete-all',
help='delete *ALL* jobs from Jenkins server, '
'including those not managed by Jenkins Job '
@ -195,13 +198,19 @@ def execute(options, config):
"Reading configuration from STDIN. Press %s to end input.",
key)
# expand or convert options.path to a list
if (getattr(options, 'recursive', False)
or config.getboolean('job_builder', 'recursive')) and \
os.path.isdir(options.path):
options.path = recurse_path(options.path)
else:
options.path = [options.path]
# take list of paths
options.path = options.path.split(os.pathsep)
do_recurse = (getattr(options, 'recursive', False) or
config.getboolean('job_builder', 'recursive'))
paths = []
for path in options.path:
if do_recurse and os.path.isdir(path):
paths.extend(recurse_path(path))
else:
paths.append(path)
options.path = paths
if options.command == 'delete':
for job in options.name:

@ -0,0 +1,10 @@
# this file is here to ensure the directory containing it is caught by git for
# the sake of multipath testing.
- job-template:
name: 'herp-job'
description: 'a simple job'
- project:
name: herp-project
jobs:
- herp-job

@ -69,6 +69,83 @@ class CmdTests(testtools.TestCase):
config.readfp(StringIO(cmd.DEFAULT_CONF))
cmd.execute(args, config) # probably better to fail here
def test_multi_path(self):
"""
Run test mode and pass multiple paths.
"""
path_list = [os.path.join(self.fixtures_path, 'multipath'),
self.fixtures_path]
multipath = os.pathsep.join(path_list)
args = self.parser.parse_args(['test', multipath])
args.output_dir = mock.MagicMock()
config = configparser.ConfigParser()
config.readfp(StringIO(cmd.DEFAULT_CONF))
cmd.execute(args, config)
self.assertEqual(args.path, path_list)
@mock.patch('jenkins_jobs.cmd.Builder.update_job')
@mock.patch('jenkins_jobs.cmd.os.path.isdir')
@mock.patch('jenkins_jobs.cmd.os.walk')
def test_recursive_multi_path(self, os_walk_mock, isdir_mock,
update_job_mock):
"""
Run test mode and pass multiple paths with recursive path option.
"""
os_walk_return_values = {
'/jjb_projects': [
('/jjb_projects', ('dir1', 'dir2', 'dir3'), ()),
('/jjb_projects/dir1', ('bar',), ()),
('/jjb_projects/dir2', ('baz',), ()),
('/jjb_projects/dir3', (), ()),
('/jjb_projects/dir1/bar', (), ()),
('/jjb_projects/dir2/baz', (), ()),
],
'/jjb_templates': [
('/jjb_templates', ('dir1', 'dir2', 'dir3'), ()),
('/jjb_templates/dir1', ('bar',), ()),
('/jjb_templates/dir2', ('baz',), ()),
('/jjb_templates/dir3', (), ()),
('/jjb_templates/dir1/bar', (), ()),
('/jjb_templates/dir2/baz', (), ()),
],
'/jjb_macros': [
('/jjb_macros', ('dir1', 'dir2', 'dir3'), ()),
('/jjb_macros/dir1', ('bar',), ()),
('/jjb_macros/dir2', ('baz',), ()),
('/jjb_macros/dir3', (), ()),
('/jjb_macros/dir1/bar', (), ()),
('/jjb_macros/dir2/baz', (), ()),
],
}
def os_walk_side_effects(path_name, topdown):
return os_walk_return_values[path_name]
os_walk_mock.side_effect = os_walk_side_effects
isdir_mock.return_value = True
path_list = os_walk_return_values.keys()
paths = []
for path in path_list:
paths.extend([p for p, _, _ in os_walk_return_values[path]])
multipath = os.pathsep.join(path_list)
args = self.parser.parse_args(['test', '-r', multipath])
args.output_dir = mock.MagicMock()
config = configparser.ConfigParser()
config.readfp(StringIO(cmd.DEFAULT_CONF))
cmd.execute(args, config)
update_job_mock.assert_called_with(paths, [], output=args.output_dir)
args = self.parser.parse_args(['test', multipath])
config.set('job_builder', 'recursive', 'True')
cmd.execute(args, config)
update_job_mock.assert_called_with(paths, [], output=args.output_dir)
def test_console_output(self):
"""
Run test mode and verify that resulting XML gets sent to the console.