From f1f97ab4bfbf643bca1a621bbade61d621efcdf8 Mon Sep 17 00:00:00 2001 From: JP Sullivan Date: Fri, 25 May 2018 17:49:47 +0100 Subject: [PATCH] Update URLS that require depth or tree filters Some API endpoints require the use of a filter or they will respond with HTTP error code 418 I'm a teapot. This was seen on CloudBees Jenkins Enterprise 2.107.2.1-rolling. Adding a depth filter will ensure the API calls will succeed. Change-Id: Ib4d6a251bf3a024a76081b2fc83baa7839ad4015 --- jenkins/__init__.py | 16 +++++++-------- tests/test_node.py | 4 ++-- tests/test_queue.py | 2 +- tests/test_rest_endpoints.py | 39 ++++++++++++++++++++++++++++++++++++ tests/test_whoami.py | 4 ++-- 5 files changed, 52 insertions(+), 13 deletions(-) create mode 100644 tests/test_rest_endpoints.py diff --git a/jenkins/__init__.py b/jenkins/__init__.py index 22a7de7..7a1e82b 100755 --- a/jenkins/__init__.py +++ b/jenkins/__init__.py @@ -95,13 +95,13 @@ DEFAULT_HEADERS = {'Content-Type': 'text/xml; charset=utf-8'} INFO = 'api/json' PLUGIN_INFO = 'pluginManager/api/json?depth=%(depth)s' CRUMB_URL = 'crumbIssuer/api/json' -WHOAMI_URL = 'me/api/json' +WHOAMI_URL = 'me/api/json?depth=%(depth)s' JOBS_QUERY = '?tree=jobs[url,color,name,jobs]' JOB_INFO = '%(folder_url)sjob/%(short_name)s/api/json?depth=%(depth)s' JOB_NAME = '%(folder_url)sjob/%(short_name)s/api/json?tree=name' ALL_BUILDS = '%(folder_url)sjob/%(short_name)s/api/json?tree=allBuilds[number,url]' Q_INFO = 'queue/api/json?depth=0' -Q_ITEM = 'queue/item/%(number)d/api/json' +Q_ITEM = 'queue/item/%(number)d/api/json?depth=%(depth)s' CANCEL_QUEUE = 'queue/cancelItem?id=%(id)s' CREATE_JOB = '%(folder_url)screateItem?name=%(short_name)s' # also post config.xml CONFIG_JOB = '%(folder_url)sjob/%(short_name)s/config.xml' @@ -122,7 +122,7 @@ BUILD_TEST_REPORT = '%(folder_url)sjob/%(short_name)s/%(number)d/testReport/api/ '?depth=%(depth)s' DELETE_BUILD = '%(folder_url)sjob/%(short_name)s/%(number)s/doDelete' WIPEOUT_JOB_WORKSPACE = '%(folder_url)sjob/%(short_name)s/doWipeOutWorkspace' -NODE_LIST = 'computer/api/json' +NODE_LIST = 'computer/api/json?depth=%(depth)s' CREATE_NODE = 'computer/doCreateItem' DELETE_NODE = 'computer/%(name)s/doDelete' NODE_INFO = 'computer/%(name)s/api/json?depth=%(depth)s' @@ -578,7 +578,7 @@ class Jenkins(object): raise TimeoutException('Error in request: %s' % (e.reason)) raise JenkinsException('Error in request: %s' % (e.reason)) - def get_queue_item(self, number): + def get_queue_item(self, number, depth=0): '''Get information about a queued item (to-be-created job). The returned dict will have a "why" key if the queued item is still @@ -755,7 +755,7 @@ class Jenkins(object): raise JenkinsException("Could not parse JSON info for server[%s]" % self.server) - def get_whoami(self): + def get_whoami(self, depth=0): """Get information about the user account that authenticated to Jenkins. This is a simple way to verify that your credentials are correct. @@ -771,7 +771,7 @@ class Jenkins(object): """ try: response = self.jenkins_open(requests.Request( - 'GET', self._build_url(WHOAMI_URL) + 'GET', self._build_url(WHOAMI_URL, locals()) )) if response is None: raise EmptyResponseException( @@ -1410,7 +1410,7 @@ class Jenkins(object): 'executor': executor_number}) return builds - def get_nodes(self): + def get_nodes(self, depth=0): '''Get a list of nodes connected to the Master Each node is a dict with keys 'name' and 'offline' @@ -1419,7 +1419,7 @@ class Jenkins(object): ''' try: nodes_data = json.loads(self.jenkins_open( - requests.Request('GET', self._build_url(NODE_LIST)))) + requests.Request('GET', self._build_url(NODE_LIST, locals())))) return [{'name': c["displayName"], 'offline': c["offline"]} for c in nodes_data["computer"]] except (req_exc.HTTPError, BadStatusLine): diff --git a/tests/test_node.py b/tests/test_node.py index cf2fb00..012ce83 100644 --- a/tests/test_node.py +++ b/tests/test_node.py @@ -45,7 +45,7 @@ class JenkinsGetNodesTest(JenkinsNodesTestBase): self.j.get_nodes() self.assertEqual( jenkins_mock.call_args[0][0].url, - self.make_url('computer/api/json')) + self.make_url('computer/api/json?depth=0')) self.assertEqual( str(context_manager.exception), 'Could not parse JSON info for server[{0}]'.format( @@ -74,7 +74,7 @@ class JenkinsGetNodesTest(JenkinsNodesTestBase): self.j.get_nodes() self.assertEqual( session_send_mock.call_args_list[1][0][1].url, - self.make_url('computer/api/json')) + self.make_url('computer/api/json?depth=0')) self.assertEqual( str(context_manager.exception), 'Error communicating with server[{0}]'.format( diff --git a/tests/test_queue.py b/tests/test_queue.py index 7502fb1..b35149a 100644 --- a/tests/test_queue.py +++ b/tests/test_queue.py @@ -107,5 +107,5 @@ class JenkinsQueueItemTest(JenkinsTestBase): self.assertEqual(queue_item, queue_item_to_return) self.assertEqual( jenkins_mock.call_args[0][0].url, - self.make_url('queue/item/25/api/json')) + self.make_url('queue/item/25/api/json?depth=0')) self._check_requests(jenkins_mock.call_args_list) diff --git a/tests/test_rest_endpoints.py b/tests/test_rest_endpoints.py new file mode 100644 index 0000000..5c06679 --- /dev/null +++ b/tests/test_rest_endpoints.py @@ -0,0 +1,39 @@ +import jenkins +from tests.base import JenkinsTestBase + +# Vars in the jenkins module scope that do not need validating +VAR_WHITELIST = ['__doc__', + '__file__', + '__name__', + '__package__', + 'CRUMB_URL', + 'DEFAULT_HEADERS', + 'EMPTY_CONFIG_XML', + 'EMPTY_FOLDER_XML', + 'EMPTY_PROMO_CONFIG_XML', + 'EMPTY_VIEW_CONFIG_XML', + 'INFO', + 'LAUNCHER_SSH', + 'LAUNCHER_COMMAND', + 'LAUNCHER_JNLP', + 'LAUNCHER_WINDOWS_SERVICE', + 'NODE_TYPE', + 'PROMO_RECONFIG_XML', + 'RECONFIG_XML'] + + +class JenkinsRestTest(JenkinsTestBase): + + # If there is no filter (depth or tree) we will get an exception + # on some Jenkins instances + def test_url_has_filter(self): + for var in dir(jenkins): + if var in VAR_WHITELIST: + continue + # Misses unicode on 2.x + val = getattr(jenkins, var) + if isinstance(val, str): + # If we end the path in api/json and don't have depth or tree encoded, fail + self.assertEqual(val.endswith('api/json'), False, + "URLS that end in 'api/json' must be called with depth or tree:" + + "var: [{}] val: [{}]".format(var, val)) diff --git a/tests/test_whoami.py b/tests/test_whoami.py index 16eb782..b9440b8 100644 --- a/tests/test_whoami.py +++ b/tests/test_whoami.py @@ -31,7 +31,7 @@ class JenkinsWhoamiTest(JenkinsTestBase): self.assertEqual(user, user_to_return) self.assertEqual( jenkins_mock.call_args[0][0].url, - self.make_url('me/api/json')) + self.make_url('me/api/json?depth=0')) self._check_requests(jenkins_mock.call_args_list) @patch('jenkins.requests.Session.send', autospec=True) @@ -45,4 +45,4 @@ class JenkinsWhoamiTest(JenkinsTestBase): self.j.get_whoami() self.assertEqual( session_send_mock.call_args_list[1][0][1].url, - self.make_url('me/api/json')) + self.make_url('me/api/json?depth=0'))