diff --git a/doc/source/examples.rst b/doc/source/examples.rst index 9fc5f27..83911b4 100644 --- a/doc/source/examples.rst +++ b/doc/source/examples.rst @@ -86,6 +86,10 @@ This is an example showing how to create, configure and delete Jenkins jobs. build_info = server.get_job_info('api-test', last_build_number) print build_info + # get all jobs from the specific view + jobs = server.get_jobs(view_name='View Name') + print jobs + Example 4: Working with Jenkins Views ------------------------------------- diff --git a/jenkins/__init__.py b/jenkins/__init__.py index 677fa5e..ee9dc7d 100644 --- a/jenkins/__init__.py +++ b/jenkins/__init__.py @@ -116,6 +116,7 @@ NODE_TYPE = 'hudson.slaves.DumbSlave$DescriptorImpl' TOGGLE_OFFLINE = 'computer/%(name)s/toggleOffline?offlineMessage=%(msg)s' CONFIG_NODE = 'computer/%(name)s/config.xml' VIEW_NAME = 'view/%(name)s/api/json?tree=name' +VIEW_JOBS = 'view/%(name)s/api/json?tree=jobs[url,color,name]' CREATE_VIEW = 'createView?name=%(name)s' CONFIG_VIEW = 'view/%(name)s/config.xml' DELETE_VIEW = 'view/%(name)s/doDelete' @@ -657,18 +658,41 @@ class Jenkins(object): return plugins_data - def get_jobs(self, folder_depth=0): + def get_jobs(self, folder_depth=0, view_name=None): """Get list of jobs. Each job is a dictionary with 'name', 'url', 'color' and 'fullname' keys. + If the ``view_name`` parameter is present, the list of + jobs will be limited to only those configured in the + specified view. In this case, the job dictionary 'fullname' key + would be equal to the job name. + :param folder_depth: Number of levels to search, ``int``. By default 0, which will limit search to toplevel. None disables the limit. - :returns: list of jobs, ``[ { str: str} ]`` + :param view_name: Name of a Jenkins view for which to + retrieve jobs, ``str``. By default, the job list is + not limited to a specific view. + :returns: list of jobs, ``[{str: str, str: str, str: str, str: str}]`` + + Example:: + + >>> jobs = server.get_jobs() + >>> print(jobs) + [{ + u'name': u'all_tests', + u'url': u'http://your_url.here/job/all_tests/', + u'color': u'blue', + u'fullname': u'all_tests' + }] + """ - return self.get_all_jobs(folder_depth=folder_depth) + if view_name: + return self._get_view_jobs(view_name=view_name) + else: + return self.get_all_jobs(folder_depth=folder_depth) def get_all_jobs(self, folder_depth=None): """Get list of all jobs recursively to the given folder depth. @@ -1268,6 +1292,40 @@ class Jenkins(object): return folder_url, short_name + def _get_view_jobs(self, view_name): + '''Get list of jobs on the view specified. + + Each job is a dictionary with 'name', 'url', 'color' and 'fullname' + keys. + + The list of jobs is limited to only those configured in the + specified view. Each job dictionary 'fullname' key + is equal to the job name. + + :param view_name: Name of a Jenkins view for which to + retrieve jobs, ``str``. + :returns: list of jobs, ``[{str: str, str: str, str: str, str: str}]`` + ''' + + try: + response = self.jenkins_open(Request( + self._build_url(VIEW_JOBS, {u'name': view_name}) + )) + if response: + jobs = json.loads(response)['jobs'] + else: + raise JenkinsException('view[%s] does not exist' % view_name) + except HTTPError: + raise JenkinsException('view[%s] does not exist' % view_name) + except ValueError: + raise JenkinsException( + 'Could not parse JSON info for view[%s]' % view_name) + + for job_dict in jobs: + job_dict.update({u'fullname': job_dict[u'name']}) + + return jobs + def get_view_name(self, name): '''Return the name of a view using the API. diff --git a/tests/jobs/test_get.py b/tests/jobs/test_get.py index c6ba22b..483fd85 100644 --- a/tests/jobs/test_get.py +++ b/tests/jobs/test_get.py @@ -57,3 +57,87 @@ class JenkinsGetJobsTest(JenkinsGetJobsTestBase): self.assertEqual(len(expected_fullnames), len(jobs_info)) got_fullnames = [job[u"fullname"] for job in jobs_info] self.assertEqual(expected_fullnames, got_fullnames) + + @patch.object(jenkins.Jenkins, 'jenkins_open') + def test_get_view_jobs(self, jenkins_mock): + view_jobs_to_return = { + u'jobs': [{ + u'name': u'community.all', + u'url': u'http://your_url_here', + u'color': u'grey' + }, { + u'name': u'community.first', + u'url': u'http://your_url_here', + u'color': u'red' + }] + } + jenkins_mock.return_value = json.dumps(view_jobs_to_return) + + view_jobs = self.j.get_jobs(view_name=u'Test View') + + self.assertEqual(view_jobs[0][u'color'], u'grey') + self.assertEqual(view_jobs[1][u'name'], u'community.first') + self.assertEqual(view_jobs[1][u'name'], view_jobs[1][u'fullname']) + self.assertEqual( + jenkins_mock.call_args[0][0].get_full_url(), + self.make_url( + 'view/Test%20View/api/json?tree=jobs[url,color,name]' + )) + self._check_requests(jenkins_mock.call_args_list) + + @patch.object(jenkins.Jenkins, 'jenkins_open') + def test_get_view_jobs_return_none(self, jenkins_mock): + jenkins_mock.return_value = None + + with self.assertRaises(jenkins.JenkinsException) as context_manager: + self.j.get_jobs(view_name=u'Test View') + + self.assertEqual( + jenkins_mock.call_args[0][0].get_full_url(), + self.make_url( + 'view/Test%20View/api/json?tree=jobs[url,color,name]' + )) + self.assertEqual( + str(context_manager.exception), + 'view[Test View] does not exist') + self._check_requests(jenkins_mock.call_args_list) + + @patch.object(jenkins.Jenkins, 'jenkins_open') + def test_get_view_jobs_return_invalid_json(self, jenkins_mock): + jenkins_mock.return_value = 'Invalid JSON' + + with self.assertRaises(jenkins.JenkinsException) as context_manager: + self.j.get_jobs(view_name=u'Test View') + + self.assertEqual( + jenkins_mock.call_args[0][0].get_full_url(), + self.make_url( + 'view/Test%20View/api/json?tree=jobs[url,color,name]' + )) + self.assertEqual( + str(context_manager.exception), + 'Could not parse JSON info for view[Test View]') + self._check_requests(jenkins_mock.call_args_list) + + @patch.object(jenkins.Jenkins, 'jenkins_open') + def test_get_view_jobs_raise_HTTPError(self, jenkins_mock): + jenkins_mock.side_effect = jenkins.HTTPError( + self.make_url( + 'view/Test%20View/api/json?tree=jobs[url,color,name]'), + code=401, + msg="basic auth failed", + hdrs=[], + fp=None) + + with self.assertRaises(jenkins.JenkinsException) as context_manager: + self.j.get_jobs(view_name=u'Test View') + + self.assertEqual( + jenkins_mock.call_args[0][0].get_full_url(), + self.make_url( + 'view/Test%20View/api/json?tree=jobs[url,color,name]' + )) + self.assertEqual( + str(context_manager.exception), + 'view[Test View] does not exist') + self._check_requests(jenkins_mock.call_args_list)