add support for FILE parameters.
authorDamien Nozay <damien.nozay@gmail.com>
Thu, 10 Oct 2013 06:05:53 +0000 (23:05 -0700)
committerDamien Nozay <damien.nozay@gmail.com>
Thu, 10 Oct 2013 06:11:01 +0000 (23:11 -0700)
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
jenkinsapi/utils/requester.py
jenkinsapi_tests/systests/job_configs.py
jenkinsapi_tests/systests/test_parameterized_builds.py

index 3d3057d..c9709b5 100644 (file)
@@ -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:
index 8f2131e..cc3fc20 100644 (file)
@@ -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')))
index 55daf99..15448e7 100644 (file)
@@ -189,3 +189,84 @@ MATRIX_JOB = """
   <publishers/>
   <buildWrappers/>
 </matrix-project>""".strip()
+
+JOB_WITH_FILE = """
+<?xml version='1.0' encoding='UTF-8'?>
+<project>
+  <actions/>
+  <description></description>
+  <keepDependencies>false</keepDependencies>
+  <properties>
+    <hudson.model.ParametersDefinitionProperty>
+      <parameterDefinitions>
+        <hudson.model.FileParameterDefinition>
+          <name>file.txt</name>
+          <description></description>
+        </hudson.model.FileParameterDefinition>
+      </parameterDefinitions>
+    </hudson.model.ParametersDefinitionProperty>
+  </properties>
+  <scm class="hudson.scm.NullSCM"/>
+  <canRoam>true</canRoam>
+  <disabled>false</disabled>
+  <blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
+  <blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
+  <triggers/>
+  <concurrentBuild>false</concurrentBuild>
+  <builders>
+    <hudson.tasks.Shell>
+      <command>cat file.txt</command>
+    </hudson.tasks.Shell>
+  </builders>
+  <publishers>
+    <hudson.tasks.ArtifactArchiver>
+      <artifacts>*</artifacts>
+      <latestOnly>false</latestOnly>
+    </hudson.tasks.ArtifactArchiver>
+  </publishers>
+  <buildWrappers/>
+</project>""".strip()
+
+JOB_WITH_PARAMETERS = """
+<?xml version='1.0' encoding='UTF-8'?>
+<project>
+  <actions/>
+  <description>A build that explores the wonderous possibilities of parameterized builds.</description>
+  <keepDependencies>false</keepDependencies>
+  <properties>
+    <hudson.model.ParametersDefinitionProperty>
+      <parameterDefinitions>
+        <hudson.model.StringParameterDefinition>
+          <name>B</name>
+          <description>B, like buzzing B.</description>
+          <defaultValue></defaultValue>
+        </hudson.model.StringParameterDefinition>
+      </parameterDefinitions>
+    </hudson.model.ParametersDefinitionProperty>
+  </properties>
+  <scm class="hudson.scm.NullSCM"/>
+  <canRoam>true</canRoam>
+  <disabled>false</disabled>
+  <blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
+  <blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
+  <triggers class="vector"/>
+  <concurrentBuild>false</concurrentBuild>
+  <builders>
+    <hudson.tasks.Shell>
+      <command>ping -c 1 localhost | tee out.txt
+echo $A &gt; a.txt
+echo $B &gt; b.txt</command>
+    </hudson.tasks.Shell>
+  </builders>
+  <publishers>
+    <hudson.tasks.ArtifactArchiver>
+      <artifacts>*</artifacts>
+      <latestOnly>false</latestOnly>
+    </hudson.tasks.ArtifactArchiver>
+    <hudson.tasks.Fingerprinter>
+      <targets></targets>
+      <recordBuildArtifacts>true</recordBuildArtifacts>
+    </hudson.tasks.Fingerprinter>
+  </publishers>
+  <buildWrappers/>
+</project>""".strip()
index 3961930..ba4cc15 100644 (file)
@@ -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 = """
-<?xml version='1.0' encoding='UTF-8'?>
-<project>
-  <actions/>
-  <description>A build that explores the wonderous possibilities of parameterized builds.</description>
-  <keepDependencies>false</keepDependencies>
-  <properties>
-    <hudson.model.ParametersDefinitionProperty>
-      <parameterDefinitions>
-        <hudson.model.StringParameterDefinition>
-          <name>B</name>
-          <description>B, like buzzing B.</description>
-          <defaultValue></defaultValue>
-        </hudson.model.StringParameterDefinition>
-      </parameterDefinitions>
-    </hudson.model.ParametersDefinitionProperty>
-  </properties>
-  <scm class="hudson.scm.NullSCM"/>
-  <canRoam>true</canRoam>
-  <disabled>false</disabled>
-  <blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
-  <blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
-  <triggers class="vector"/>
-  <concurrentBuild>false</concurrentBuild>
-  <builders>
-    <hudson.tasks.Shell>
-      <command>ping -c 1 localhost | tee out.txt
-echo $A &gt; a.txt
-echo $B &gt; b.txt</command>
-    </hudson.tasks.Shell>
-  </builders>
-  <publishers>
-    <hudson.tasks.ArtifactArchiver>
-      <artifacts>*</artifacts>
-      <latestOnly>false</latestOnly>
-    </hudson.tasks.ArtifactArchiver>
-    <hudson.tasks.Fingerprinter>
-      <targets></targets>
-      <recordBuildArtifacts>true</recordBuildArtifacts>
-    </hudson.tasks.Fingerprinter>
-  </publishers>
-  <buildWrappers/>
-</project>""".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()