From f93777094abd6926c1f683bd5d4664aa348cfa8b Mon Sep 17 00:00:00 2001 From: Damien Nozay Date: Wed, 9 Oct 2013 23:05:53 -0700 Subject: [PATCH] add support for FILE parameters. if a job requires file paremeters, then we cannot use the /build form to request the build, we need to switch to the /buildWithParameters trigger and provide a multipart encoded upload. thankfully requests library has all the plumbing. --- jenkinsapi/job.py | 14 ++-- jenkinsapi/utils/requester.py | 21 ++--- jenkinsapi_tests/systests/job_configs.py | 81 +++++++++++++++++++ .../systests/test_parameterized_builds.py | 64 +++++---------- 4 files changed, 121 insertions(+), 59 deletions(-) diff --git a/jenkinsapi/job.py b/jenkinsapi/job.py index 3d3057d..c9709b5 100644 --- a/jenkinsapi/job.py +++ b/jenkinsapi/job.py @@ -91,7 +91,9 @@ class Job(JenkinsBase, MutableJenkinsThing): self._element_tree = ET.fromstring(self._config) return self._element_tree - def get_build_triggerurl(self): + def get_build_triggerurl(self, build_params=None, files=None): + if build_params or files: + return "%s/buildWithParameters" % self.baseurl return "%s/build" % self.baseurl @staticmethod @@ -111,7 +113,7 @@ class Job(JenkinsBase, MutableJenkinsThing): to_json_structure = Job._mk_json_from_build_parameters(build_params) return json.dumps(to_json_structure) - def invoke(self, securitytoken=None, block=False, skip_if_running=False, invoke_pre_check_delay=3, invoke_block_delay=15, build_params=None, cause=None): + def invoke(self, securitytoken=None, block=False, skip_if_running=False, invoke_pre_check_delay=3, invoke_block_delay=15, build_params=None, cause=None, files=None): assert isinstance(invoke_pre_check_delay, (int, float)) assert isinstance(invoke_block_delay, (int, float)) assert isinstance(block, bool) @@ -140,7 +142,7 @@ class Job(JenkinsBase, MutableJenkinsThing): log.info("Attempting to start %s on %s", self.name, repr( self.get_jenkins_obj())) - url = self.get_build_triggerurl() + url = self.get_build_triggerurl(build_params, files) if cause: build_params['cause'] = cause @@ -148,11 +150,13 @@ class Job(JenkinsBase, MutableJenkinsThing): if securitytoken: params['token'] = securitytoken + data = build_params + response = self.jenkins.requester.post_and_confirm_status( url, - data={'json': self.mk_json_from_build_parameters( - build_params)}, # See above - build params have to be JSON encoded & posted. + data=data, params=params, + files=files, valid=[200, 201] ) if invoke_pre_check_delay > 0: diff --git a/jenkinsapi/utils/requester.py b/jenkinsapi/utils/requester.py index 8f2131e..cc3fc20 100644 --- a/jenkinsapi/utils/requester.py +++ b/jenkinsapi/utils/requester.py @@ -34,7 +34,7 @@ class Requester(object): self.password = password self.ssl_verify = ssl_verify - def get_request_dict(self, url, params, data, headers): + def get_request_dict(self, url, params=None, data=None, files=None, headers=None): requestKwargs = {} if self.username: requestKwargs['auth'] = (self.username, self.password) @@ -55,31 +55,34 @@ class Requester(object): # It may seem odd, but some Jenkins operations require posting # an empty string. requestKwargs['data'] = data + + if files: + requestKwargs['files'] = files + return requestKwargs def get_url(self, url, params=None, headers=None): - requestKwargs = self.get_request_dict(url, params, None, headers) + requestKwargs = self.get_request_dict(url, params=params, headers=headers) return requests.get(url, **requestKwargs) - - def post_url(self, url, params=None, data=None, headers=None): - requestKwargs = self.get_request_dict(url, params, data, headers) + def post_url(self, url, params=None, data=None, files=None, headers=None): + requestKwargs = self.get_request_dict(url, params=params, data=data, files=files, headers=headers) return requests.post(url, **requestKwargs) def post_xml_and_confirm_status(self, url, params=None, data=None, valid=None): headers = {'Content-Type': 'text/xml'} - return self.post_and_confirm_status(url, params, data, headers, valid) + return self.post_and_confirm_status(url, params=params, data=data, headers=headers, valid=valid) - def post_and_confirm_status(self, url, params=None, data=None, headers=None, valid=None): + def post_and_confirm_status(self, url, params=None, data=None, files=None, headers=None, valid=None): valid = valid or self.VALID_STATUS_CODES assert isinstance(data, ( str, dict)), \ "Unexpected type of parameter 'data': %s. Expected (str, dict)" % type(data) - if not headers: + if not headers and not files: headers = {'Content-Type': 'application/x-www-form-urlencoded'} - response = self.post_url(url, params, data, headers) + response = self.post_url(url, params, data, files, headers) if not response.status_code in valid: raise JenkinsAPIException('Operation failed. url={0}, data={1}, headers={2}, status={3}, text={4}'.format( response.url, data, headers, response.status_code, response.text.encode('UTF-8'))) diff --git a/jenkinsapi_tests/systests/job_configs.py b/jenkinsapi_tests/systests/job_configs.py index 55daf99..15448e7 100644 --- a/jenkinsapi_tests/systests/job_configs.py +++ b/jenkinsapi_tests/systests/job_configs.py @@ -189,3 +189,84 @@ MATRIX_JOB = """ """.strip() + +JOB_WITH_FILE = """ + + + + + false + + + + + file.txt + + + + + + + true + false + false + false + + false + + + cat file.txt + + + + + * + false + + + +""".strip() + +JOB_WITH_PARAMETERS = """ + + + + A build that explores the wonderous possibilities of parameterized builds. + false + + + + + B + B, like buzzing B. + + + + + + + true + false + false + false + + false + + + ping -c 1 localhost | tee out.txt +echo $A > a.txt +echo $B > b.txt + + + + + * + false + + + + true + + + +""".strip() diff --git a/jenkinsapi_tests/systests/test_parameterized_builds.py b/jenkinsapi_tests/systests/test_parameterized_builds.py index 3961930..ba4cc15 100644 --- a/jenkinsapi_tests/systests/test_parameterized_builds.py +++ b/jenkinsapi_tests/systests/test_parameterized_builds.py @@ -3,62 +3,36 @@ System tests for `jenkinsapi.jenkins` module. ''' import time import unittest +from StringIO import StringIO from jenkinsapi_tests.systests.base import BaseSystemTest from jenkinsapi_tests.test_utils.random_strings import random_string +from jenkinsapi_tests.systests.job_configs import JOB_WITH_FILE +from jenkinsapi_tests.systests.job_configs import JOB_WITH_PARAMETERS +class TestParameterizedBuilds(BaseSystemTest): -JOB_CONFIG = """ - - - - A build that explores the wonderous possibilities of parameterized builds. - false - - - - - B - B, like buzzing B. - - - - - - - true - false - false - false - - false - - - ping -c 1 localhost | tee out.txt -echo $A > a.txt -echo $B > b.txt - - - - - * - false - - - - true - - - -""".strip() + def test_invoke_job_with_file(self): + file_data = random_string() + param_file = StringIO(file_data) + job_name = 'create_%s' % random_string() + job = self.jenkins.create_job(job_name, JOB_WITH_FILE) + job.invoke(block=True, files={'file.txt': param_file}) -class TestParameterizedBuilds(BaseSystemTest): + b = job.get_last_build() + while b.is_running(): + time.sleep(0.25) + + artifacts = b.get_artifact_dict() + self.assertIsInstance(artifacts, dict) + art_file = artifacts['file.txt'] + self.assertTrue(art_file.get_data().strip(), file_data) def test_invoke_job_parameterized(self): param_B = random_string() job_name = 'create_%s' % random_string() - job = self.jenkins.create_job(job_name, JOB_CONFIG) + job = self.jenkins.create_job(job_name, JOB_WITH_PARAMETERS) job.invoke(block=True, build_params={'B': param_B}) b = job.get_last_build() -- 2.34.1