Browse Source

Add a "list" subcommand

Add a list subcommand that allows listing of all the jobs defined in
yaml configuration, or on the Jenkins master, with the option of taking
glob parameters to allow matching some job names.

Also supports the recursive and exclude path control options.

  jenkins-jobs list 'wikimedia-fundraising*'

  jenkins-jobs list -r -p configs/ 'wikimedia-fundraising*'

Co-Authored-By: Darragh Bailey <dbailey@hpe.com>
Co-Authored-By: Sorin Sbarnea <ssbarnea@redhat.com>
Change-Id: I897b9ed35561e455dc6b89c3bacec74b54777903
Signed-off-by: Sorin Sbarnea <ssbarnea@redhat.com>
tags/2.0.4
Marc Abramowitz 5 years ago
parent
commit
8d4671e3a7
No account linked to committer's email address
4 changed files with 161 additions and 1 deletions
  1. 1
    1
      jenkins_jobs/cli/parser.py
  2. 74
    0
      jenkins_jobs/cli/subcommand/list.py
  3. 1
    0
      setup.cfg
  4. 85
    0
      tests/cmd/subcommands/test_list.py

+ 1
- 1
jenkins_jobs/cli/parser.py View File

@@ -81,7 +81,7 @@ def create_parser():
81 81
 
82 82
     subparser = parser.add_subparsers(
83 83
         dest='command',
84
-        help="update, test or delete job")
84
+        help="update, test, list or delete job")
85 85
 
86 86
     extension_manager = extension.ExtensionManager(
87 87
         namespace='jjb.cli.subcommands',

+ 74
- 0
jenkins_jobs/cli/subcommand/list.py View File

@@ -0,0 +1,74 @@
1
+#!/usr/bin/env python
2
+# Copyright (C) 2018 Sorin Sbarnea
3
+#
4
+# Licensed under the Apache License, Version 2.0 (the "License");
5
+# you may not use this file except in compliance with the License.
6
+# You may obtain a copy of the License at
7
+#
8
+# http://www.apache.org/licenses/LICENSE-2.0
9
+#
10
+# Unless required by applicable law or agreed to in writing, software
11
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
+# License for the specific language governing permissions and limitations
14
+# under the License.
15
+import logging
16
+import sys
17
+import jenkins_jobs.cli.subcommand.base as base
18
+import jenkins_jobs.utils as utils
19
+import jenkins_jobs.builder as builder
20
+import jenkins_jobs.parser as parser
21
+import jenkins_jobs.registry as registry
22
+
23
+
24
+def list_duplicates(seq):
25
+    seen = set()
26
+    return set(x for x in seq if x in seen or seen.add(x))
27
+
28
+
29
+class ListSubCommand(base.BaseSubCommand):
30
+
31
+    def parse_args(self, subparser):
32
+        list = subparser.add_parser('list', help="List jobs")
33
+
34
+        self.parse_option_recursive_exclude(list)
35
+
36
+        list.add_argument('names',
37
+                          help='name(s) of job(s)',
38
+                          nargs='*',
39
+                          default=None)
40
+        list.add_argument('-p', '--path', default=None,
41
+                          help='path to YAML file or directory')
42
+
43
+    def execute(self, options, jjb_config):
44
+        self.jjb_config = jjb_config
45
+        self.jenkins = builder.JenkinsManager(jjb_config)
46
+
47
+        jobs = self.get_jobs(options.names, options.path)
48
+
49
+        logging.info("Matching jobs: %d", len(jobs))
50
+        stdout = utils.wrap_stream(sys.stdout)
51
+
52
+        for job in jobs:
53
+            stdout.write((job + '\n').encode('utf-8'))
54
+
55
+    def get_jobs(self, jobs_glob=None, fn=None):
56
+        if fn:
57
+            r = registry.ModuleRegistry(self.jjb_config,
58
+                                      self.jenkins.plugins_list)
59
+            p = parser.YamlParser(self.jjb_config)
60
+            p.load_files(fn)
61
+            p.expandYaml(r, jobs_glob)
62
+            jobs = [j['name'] for j in p.jobs]
63
+        else:
64
+            jobs = [j['name'] for j in self.jenkins.get_jobs()
65
+                    if not jobs_glob or parser.matches(j['name'], jobs_glob)]
66
+
67
+        jobs = sorted(jobs)
68
+        for duplicate in list_duplicates(jobs):
69
+            logging.warning("Found duplicate job name '%s', likely bug.",
70
+                            duplicate)
71
+
72
+        logging.debug("Builder.get_jobs: returning %r", jobs)
73
+
74
+        return jobs

+ 1
- 0
setup.cfg View File

@@ -48,6 +48,7 @@ jjb.cli.subcommands =
48 48
     delete=jenkins_jobs.cli.subcommand.delete:DeleteSubCommand
49 49
     delete-all=jenkins_jobs.cli.subcommand.delete_all:DeleteAllSubCommand
50 50
     get-plugins-info=jenkins_jobs.cli.subcommand.get_plugins_info:GetPluginsInfoSubCommand
51
+    list=jenkins_jobs.cli.subcommand.list:ListSubCommand
51 52
 jenkins_jobs.projects =
52 53
     externaljob=jenkins_jobs.modules.project_externaljob:ExternalJob
53 54
     flow=jenkins_jobs.modules.project_flow:Flow

+ 85
- 0
tests/cmd/subcommands/test_list.py View File

@@ -0,0 +1,85 @@
1
+#!/usr/bin/env python
2
+# Copyright (C) 2018 Sorin Sbarnea
3
+#
4
+# Licensed under the Apache License, Version 2.0 (the "License");
5
+# you may not use this file except in compliance with the License.
6
+# You may obtain a copy of the License at
7
+#
8
+# http://www.apache.org/licenses/LICENSE-2.0
9
+#
10
+# Unless required by applicable law or agreed to in writing, software
11
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
+# License for the specific language governing permissions and limitations
14
+# under the License.
15
+import io
16
+import os
17
+
18
+from testscenarios.testcase import TestWithScenarios
19
+
20
+from tests.base import mock
21
+from tests.cmd.test_cmd import CmdTestsBase
22
+
23
+
24
+@mock.patch('jenkins_jobs.builder.JenkinsManager.get_plugins_info',
25
+            mock.MagicMock)
26
+class ListFromJenkinsTests(TestWithScenarios, CmdTestsBase):
27
+
28
+    scenarios = [
29
+        ('single',
30
+            dict(jobs=['job1'], globs=[], found=['job1'])),
31
+        ('multiple',
32
+            dict(jobs=['job1', 'job2'], globs=[], found=['job1', 'job2'])),
33
+        ('multiple_with_glob',
34
+            dict(jobs=['job1', 'job2', 'job3'], globs=["job[1-2]"],
35
+                 found=['job1', 'job2'])),
36
+        ('multiple_with_multi_glob',
37
+            dict(jobs=['job1', 'job2', 'job3', 'job4'],
38
+                 globs=["job1", "job[24]"],
39
+                 found=['job1', 'job2', 'job4'])),
40
+    ]
41
+
42
+    @mock.patch('jenkins_jobs.builder.JenkinsManager.get_jobs')
43
+    def test_list(self, get_jobs_mock):
44
+
45
+        def _get_jobs():
46
+            return [{'name': name} for name in self.jobs]
47
+
48
+        get_jobs_mock.side_effect = _get_jobs
49
+        console_out = io.BytesIO()
50
+
51
+        args = ['--conf', self.default_config_file, 'list'] + self.globs
52
+
53
+        with mock.patch('sys.stdout', console_out):
54
+            self.execute_jenkins_jobs_with_args(args)
55
+
56
+        self.assertEqual(console_out.getvalue().decode('utf-8').rstrip(),
57
+                         ('\n'.join(self.found)))
58
+
59
+
60
+@mock.patch('jenkins_jobs.builder.JenkinsManager.get_plugins_info',
61
+            mock.MagicMock)
62
+class ListFromYamlTests(TestWithScenarios, CmdTestsBase):
63
+
64
+    scenarios = [
65
+        ('all',
66
+            dict(globs=[], found=['bam001', 'bar001', 'bar002', 'baz001'])),
67
+        ('some',
68
+            dict(globs=["*am*", "*002", "bar001"],
69
+                 found=['bam001', 'bar001', 'bar002'])),
70
+    ]
71
+
72
+    def test_list(self):
73
+        path = os.path.join(self.fixtures_path, 'cmd-002.yaml')
74
+
75
+        console_out = io.BytesIO()
76
+        with mock.patch('sys.stdout', console_out):
77
+            self.execute_jenkins_jobs_with_args(
78
+                ['--conf',
79
+                 self.default_config_file,
80
+                 'list',
81
+                 '-p',
82
+                 path] + self.globs)
83
+
84
+        self.assertEqual(console_out.getvalue().decode('utf-8').rstrip(),
85
+                         ('\n'.join(self.found)))

Loading…
Cancel
Save