From cfe4fe7871b990555111541cd88d5df0a3f6271d Mon Sep 17 00:00:00 2001 From: salimfadhley Date: Wed, 12 Jun 2013 22:44:08 +0100 Subject: [PATCH] fixes for parameterized builds and enabling/disabling --- jenkinsapi/job.py | 57 ++++++++++----- jenkinsapi_tests/systests/test_jenkins.py | 13 ++++ .../systests/test_parameterized_builds.py | 81 ++++++++++++++++++++++ jenkinsapi_tests/test_utils/simple_post_logger.py | 31 +++++++++ 4 files changed, 164 insertions(+), 18 deletions(-) create mode 100644 jenkinsapi_tests/systests/test_parameterized_builds.py create mode 100644 jenkinsapi_tests/test_utils/simple_post_logger.py diff --git a/jenkinsapi/job.py b/jenkinsapi/job.py index d7d8889..2f8b8ca 100644 --- a/jenkinsapi/job.py +++ b/jenkinsapi/job.py @@ -1,7 +1,6 @@ +import json import logging import urlparse -import urllib2 -import urllib import xml.etree.ElementTree as ET from collections import defaultdict from time import sleep @@ -68,14 +67,31 @@ class Job(JenkinsBase, MutableJenkinsThing): def get_build_triggerurl(self): return "%s/build" % self.baseurl - def invoke(self, securitytoken=None, block=False, skip_if_running=False, invoke_pre_check_delay=3, invoke_block_delay=15, params=None, cause=None): + @staticmethod + def _mk_json_from_build_parameters(build_params): + """ + Build parameters must be submitted in a particular format - Key-Value pairs would be + far too simple, no no! Watch and read on and behold! + """ + assert isinstance(build_params, dict), 'Build parameters must be a dict' + return {'parameter':[ + {'name':k, 'value':v} for k,v in build_params.iteritems() + ]} + + @staticmethod + def mk_json_from_build_parameters(build_params): + 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): assert isinstance( invoke_pre_check_delay, (int, float) ) assert isinstance( invoke_block_delay, (int, float) ) assert isinstance( block, bool ) assert isinstance( skip_if_running, bool ) # Either copy the params dict or make a new one. - params = params and dict(params.items()) or {} + build_params = build_params and dict(build_params.items()) or {} # Via POSTed JSON + params = {} # Via Get string if self.is_queued(): raise WillNotBuild('%s is already queued' % repr(self)) @@ -91,12 +107,16 @@ class Job(JenkinsBase, MutableJenkinsThing): url = self.get_build_triggerurl() if cause: - params['cause'] = cause + build_params['cause'] = cause if securitytoken: - params['securitytoken'] = securitytoken + params['token'] = securitytoken - response = self.jenkins.requester.post_url(url, params, data='') + response = self.jenkins.requester.post_and_confirm_status( + url, + data=self.mk_json_from_build_parameters(build_params), # See above - build params have to be JSON encoded & posted. + params=params + ) assert len( response.text ) > 0 if invoke_pre_check_delay > 0: @@ -104,6 +124,7 @@ class Job(JenkinsBase, MutableJenkinsThing): sleep( invoke_pre_check_delay ) if block: total_wait = 0 + while self.is_queued(): log.info( "Waited %is for %s to begin...", total_wait, self.name ) sleep( invoke_block_delay ) @@ -111,6 +132,7 @@ class Job(JenkinsBase, MutableJenkinsThing): if self.is_running(): running_build = self.get_last_build() running_build.block_until_complete( delay=invoke_pre_check_delay ) + assert self.get_last_buildnumber() > original_build_no, "Job does not appear to have run." else: if self.is_queued(): @@ -398,15 +420,19 @@ class Job(JenkinsBase, MutableJenkinsThing): return [] return upstream_jobs + def is_enabled(self): + self.poll() + return self._data["color"] != 'disabled' + def disable(self): '''Disable job''' - disableurl = urlparse.urljoin(self.baseurl, 'disable' ) - return self.post_data(disableurl, '') + url = "%s/disable" % self.baseurl + return self.get_jenkins_obj().requester.post_url(url, data='') def enable(self): '''Enable job''' - enableurl = urlparse.urljoin(self.baseurl, 'enable' ) - return self.post_data(enableurl, '') + url = "%s/enable" % self.baseurl + return self.get_jenkins_obj().requester.post_url(url, data='') def delete_from_queue(self): """ @@ -416,14 +442,9 @@ class Job(JenkinsBase, MutableJenkinsThing): if not self.is_queued(): raise NotInQueue() queue_id = self._data['queueItem']['id'] - cancelurl = urlparse.urljoin(self.get_jenkins_obj().get_queue().baseurl, + url = urlparse.urljoin(self.get_jenkins_obj().get_queue().baseurl, 'cancelItem?id=%s' % queue_id) - try: - self.post_data(cancelurl, '') - except urllib2.HTTPError: - # The request doesn't have a response, so it returns 404, - # it's the expected behaviour - pass + self.get_jenkins_obj().requester.post_and_confirm_status(url, data='') return True def get_params(self): diff --git a/jenkinsapi_tests/systests/test_jenkins.py b/jenkinsapi_tests/systests/test_jenkins.py index 7f52eef..1985157 100644 --- a/jenkinsapi_tests/systests/test_jenkins.py +++ b/jenkinsapi_tests/systests/test_jenkins.py @@ -14,6 +14,19 @@ class JobTests(BaseSystemTest): self.jenkins.create_job(job_name, EMPTY_JOB_CONFIG) self.assertJobIsPresent(job_name) + def test_enable_disable_job(self): + job_name = 'create_%s' % random_string() + self.jenkins.create_job(job_name, EMPTY_JOB_CONFIG) + self.assertJobIsPresent(job_name) + + j = self.jenkins[job_name] + j.invoke(block=True) # run this at least once + + j.disable() + self.assertEquals(j.is_enabled(), False, 'A disabled job is reporting incorrectly') + j.enable() + self.assertEquals(j.is_enabled(), True, 'An enabled job is reporting incorrectly') + def test_get_job_and_update_config(self): job_name = 'config_%s' % random_string() self.jenkins.create_job(job_name, EMPTY_JOB_CONFIG) diff --git a/jenkinsapi_tests/systests/test_parameterized_builds.py b/jenkinsapi_tests/systests/test_parameterized_builds.py new file mode 100644 index 0000000..5845c93 --- /dev/null +++ b/jenkinsapi_tests/systests/test_parameterized_builds.py @@ -0,0 +1,81 @@ +''' +System tests for `jenkinsapi.jenkins` module. +''' +import os +import time +import shutil +import random +import tempfile +import unittest +from jenkinsapi_tests.test_utils.random_strings import random_string +from jenkinsapi_tests.systests.base import 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 +cat $A > a.txt +cat $B > b.txt + + + + + + false + + + + false + + + +""".strip() + +class TestParameterizedBuilds(BaseSystemTest): + + 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.invoke(block=True, build_params={ 'B':param_B}) + + b = job.get_last_build() + while b.is_running(): + time.sleep(0.25) + + artifacts = b.get_artifact_dict() + self.assertIsInstance(artifacts, dict) + + artA = artifacts['A.txt'] + artB = artifacts['B.txt'] + + # TODO: Actually verify the download + + +if __name__ == '__main__': + unittest.main() diff --git a/jenkinsapi_tests/test_utils/simple_post_logger.py b/jenkinsapi_tests/test_utils/simple_post_logger.py new file mode 100644 index 0000000..ef1050c --- /dev/null +++ b/jenkinsapi_tests/test_utils/simple_post_logger.py @@ -0,0 +1,31 @@ +import SimpleHTTPServer +import SocketServer +import logging +import cgi + +PORT = 8000 + +class ServerHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): + + def do_GET(self): + logging.error(self.headers) + SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self) + + def do_POST(self): + logging.error(self.headers) + form = cgi.FieldStorage( + fp=self.rfile, + headers=self.headers, + environ={'REQUEST_METHOD':'POST', + 'CONTENT_TYPE':self.headers['Content-Type'], + }) + for item in form.list: + logging.error(item) + SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self) + +Handler = ServerHandler + +httpd = SocketServer.TCPServer(("", PORT), Handler) + +print "serving at port", PORT +httpd.serve_forever() -- 2.7.4