# simplejenkinsapi/api.py -- simple job management in Jenkins # # Copyright 2012 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . '''Simple job management in Jenkins. This module provides tools for listing, creating, updating, and removing jobs in a running Jenkins instance, using its HTTP API. ''' import httplib import json import logging import urlparse class Jenkins(object): '''Access a running Jenkins server.''' def __init__(self, url): # pragma: no cover self._url = url def _connect(self): # pragma: no cover '''Connect to HTTP server, return httplib.HTTPConnection.''' (scheme, netloc, path, params, query, fragment) = urlparse.urlparse(self._url) assert path == '/' assert params == '' assert fragment == '' if ':' in netloc: host, port = netloc.split(':', 1) port = int(port) else: host = netloc port = None if scheme == 'https': return httplib.HTTPSConnection(host, port) else: return httplib.HTTPConnection(host, port) def _do(self, method, path, body='', headers={}): '''Make an HTTP request, return result. If server returned JSON, return it as a Python object. Otherwise, return as string. ''' logging.debug('HTTP method: %s' % repr(method)) logging.debug('HTTP path: %s' % repr(path)) logging.debug('HTTP body: %s' % repr(body)) logging.debug('HTTP headers: %s' % repr(headers)) conn = self._connect() conn.request(method, path, body, headers) resp = conn.getresponse() text = resp.read() logging.debug('HTTP status: %d' % resp.status) logging.debug('HTTP reason: %s' % resp.reason) logging.debug('HTTP text: %s' % repr(text)) ok = [httplib.OK, httplib.CREATED, httplib.FOUND] if resp.status not in ok: # pragma: no cover raise httplib.HTTPException('Error %d (%s) from server:\n%s' % (resp.status, resp.reason, text)) typ = resp.getheader('Content-Type') or 'application/octet-stream' if typ == 'application/json' or typ.startswith('application/json;'): return json.loads(text) else: return text def list_jobs(self): '''Return list of job ids on the server.''' obj = self._do('GET', '/api/json') logging.debug('obj: %s' % repr(obj)) return [job['name'] for job in obj['jobs']] def delete_job(self, job_id): '''Delete a job from Jenkins server, given job id.''' self._do('POST', '/job/%s/doDelete' % job_id) def create_job(self, job_id, config_xml): '''Create a new job. ``config_xml`` is a string containing the XML format configuration of the job. ''' self._do('POST', '/createItem?name=%s' % job_id, body=config_xml, headers={'Content-Type': 'text/xml'}) def update_job(self, job_id, config_xml): '''Update the configuration of a new job. ``config_xml`` is a string containing the XML format configuration of the job. ''' self._do('POST', '/job/%s/config.xml' % job_id, body=config_xml, headers={ 'Content-Type': 'text/xml' }) def get_job_config(self, job_id): '''Return the XML configuration of a job.''' return self._do('GET', '/job/%s/config.xml' % job_id) def run_job(self, job_id): # pragma: no cover '''Run an existing job.''' self._do('POST', '/job/%s/build?delay=0sec' % job_id) def get_latest_build_number(self, job_id): # pragma: no cover '''Return number of latest finished build for a job.''' obj = self._do('GET', '/job/%s/api/json' % job_id) last = obj.get('lastBuild', None) or {} return last.get('number', None) def get_build_info(self, job_id, build_number): # pragma: no cover '''Return information about a specific build for a job.''' return self._do('GET', '/job/%s/%s/api/json' % (job_id, build_number))