2011-06-03 16:51:39 +01:00
#!/usr/bin/env python
# Software License Agreement (BSD License)
#
# Copyright (c) 2010, Willow Garage, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
# * Neither the name of Willow Garage, Inc. nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# 'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
2011-07-11 21:55:58 -07:00
# Authors:
# Ken Conley <kwc@willowgarage.com>
# James Page <james.page@canonical.com>
# Tully Foote <tfoote@willowgarage.com>
2011-07-14 11:16:22 +01:00
# Matthew Gertner <matthew.gertner@gmail.com>
2011-06-03 16:51:39 +01:00
'''
2013-04-13 23:59:59 +02:00
. . module : : jenkins
: platform : Unix , Windows
: synopsis : Python API to interact with Jenkins
See examples at : doc : ` example `
2011-06-03 16:51:39 +01:00
'''
import base64
import json
2014-04-06 08:34:22 -07:00
import six
from six . moves . http_client import BadStatusLine
from six . moves . urllib . error import HTTPError
2014-12-04 00:16:09 -08:00
from six . moves . urllib . error import URLError
2014-04-06 08:34:22 -07:00
from six . moves . urllib . parse import quote , urlencode
from six . moves . urllib . request import Request , urlopen
2011-06-03 16:51:39 +01:00
2013-04-13 20:35:11 +02:00
LAUNCHER_SSH = ' hudson.plugins.sshslaves.SSHLauncher '
LAUNCHER_COMMAND = ' hudson.slaves.CommandLauncher '
2014-04-22 16:54:18 +02:00
LAUNCHER_JNLP = ' hudson.slaves.JNLPLauncher '
2012-06-20 14:41:59 -07:00
LAUNCHER_WINDOWS_SERVICE = ' hudson.os.windows.ManagedWindowsServiceLauncher '
2014-12-04 00:16:09 -08:00
DEFAULT_CONN_TIMEOUT = 120
2013-04-13 20:35:11 +02:00
INFO = ' api/json '
2014-09-24 22:05:42 -07:00
PLUGIN_INFO = ' pluginManager/api/json?depth= %(depth)s '
2013-09-27 22:17:06 +02:00
CRUMB_URL = ' crumbIssuer/api/json '
2014-05-09 11:41:45 -07:00
JOB_INFO = ' job/ %(name)s /api/json?depth= %(depth)s '
2013-06-25 15:30:23 +02:00
JOB_NAME = ' job/ %(name)s /api/json?tree=name '
2013-04-13 20:35:11 +02:00
Q_INFO = ' queue/api/json?depth=0 '
2014-12-03 22:36:20 -08:00
CANCEL_QUEUE = ' queue/cancelItem?id= %(id)s '
2013-04-13 20:35:11 +02:00
CREATE_JOB = ' createItem?name= %(name)s ' # also post config.xml
CONFIG_JOB = ' job/ %(name)s /config.xml '
DELETE_JOB = ' job/ %(name)s /doDelete '
ENABLE_JOB = ' job/ %(name)s /enable '
DISABLE_JOB = ' job/ %(name)s /disable '
COPY_JOB = ' createItem?name= %(to_name)s &mode=copy&from= %(from_name)s '
2014-12-03 14:39:45 -08:00
RENAME_JOB = ' job/ %(from_name)s /doRename?newName= %(to_name)s '
2013-04-13 20:35:11 +02:00
BUILD_JOB = ' job/ %(name)s /build '
STOP_BUILD = ' job/ %(name)s / %(number)s /stop '
2011-06-03 16:51:39 +01:00
BUILD_WITH_PARAMS_JOB = ' job/ %(name)s /buildWithParameters '
2014-05-09 11:41:45 -07:00
BUILD_INFO = ' job/ %(name)s / %(number)d /api/json?depth= %(depth)s '
2013-05-11 20:48:02 -07:00
BUILD_CONSOLE_OUTPUT = ' job/ %(name)s / %(number)d /consoleText '
2011-06-03 16:51:39 +01:00
CREATE_NODE = ' computer/doCreateItem? %s '
DELETE_NODE = ' computer/ %(name)s /doDelete '
2014-05-09 11:41:45 -07:00
NODE_INFO = ' computer/ %(name)s /api/json?depth= %(depth)s '
2013-04-13 20:35:11 +02:00
NODE_TYPE = ' hudson.slaves.DumbSlave$DescriptorImpl '
2012-06-25 12:48:32 +01:00
TOGGLE_OFFLINE = ' computer/ %(name)s /toggleOffline?offlineMessage= %(msg)s '
2014-04-22 16:55:37 +02:00
CONFIG_NODE = ' computer/ %(name)s /config.xml '
2011-06-03 16:51:39 +01:00
2014-04-22 11:38:02 +02:00
# for testing only
2011-06-03 16:51:39 +01:00
EMPTY_CONFIG_XML = ''' <?xml version= ' 1.0 ' encoding= ' UTF-8 ' ?>
< project >
< keepDependencies > false < / keepDependencies >
< properties / >
< scm class = ' jenkins.scm.NullSCM ' / >
< canRoam > true < / canRoam >
< disabled > false < / disabled >
< blockBuildWhenUpstreamBuilding > false < / blockBuildWhenUpstreamBuilding >
< triggers class = ' vector ' / >
< concurrentBuild > false < / concurrentBuild >
< builders / >
< publishers / >
< buildWrappers / >
< / project > '''
2014-04-22 11:38:02 +02:00
# for testing only
2011-06-03 16:51:39 +01:00
RECONFIG_XML = ''' <?xml version= ' 1.0 ' encoding= ' UTF-8 ' ?>
< project >
< keepDependencies > false < / keepDependencies >
< properties / >
< scm class = ' jenkins.scm.NullSCM ' / >
< canRoam > true < / canRoam >
< disabled > false < / disabled >
< blockBuildWhenUpstreamBuilding > false < / blockBuildWhenUpstreamBuilding >
< triggers class = ' vector ' / >
< concurrentBuild > false < / concurrentBuild >
2013-04-13 20:35:11 +02:00
< builders >
< jenkins . tasks . Shell >
< command > export FOO = bar < / command >
< / jenkins . tasks . Shell >
< / builders >
2011-06-03 16:51:39 +01:00
< publishers / >
< buildWrappers / >
< / project > '''
2013-04-13 20:35:11 +02:00
2011-09-03 17:00:30 -07:00
class JenkinsException ( Exception ) :
2014-06-03 20:12:41 -07:00
''' General exception type for jenkins-API-related failures. '''
2011-09-03 17:00:30 -07:00
pass
2011-06-03 16:51:39 +01:00
2013-04-13 20:35:11 +02:00
2015-01-13 16:38:15 -06:00
class NotFoundException ( JenkinsException ) :
''' A special exception to call out the case of receiving a 404. '''
pass
2015-02-05 23:02:21 +01:00
class EmptyResponseException ( JenkinsException ) :
''' A special exception to call out the case receiving an empty response. '''
pass
2011-06-03 16:51:39 +01:00
def auth_headers ( username , password ) :
2014-06-03 20:12:41 -07:00
''' Simple implementation of HTTP Basic Authentication.
Returns the ' Authentication ' header value .
2011-06-03 16:51:39 +01:00
'''
2014-04-06 08:34:22 -07:00
auth = ' %s : %s ' % ( username , password )
if isinstance ( auth , six . text_type ) :
auth = auth . encode ( ' utf-8 ' )
2014-11-01 23:57:22 +01:00
return b ' Basic ' + base64 . b64encode ( auth )
2011-06-03 16:51:39 +01:00
2013-04-13 20:35:11 +02:00
2011-06-03 16:51:39 +01:00
class Jenkins ( object ) :
2012-03-02 16:26:13 +00:00
2014-12-04 00:16:09 -08:00
def __init__ ( self , url , username = None , password = None , timeout = DEFAULT_CONN_TIMEOUT ) :
2014-06-03 20:12:41 -07:00
''' Create handle to Jenkins instance.
2011-06-03 16:51:39 +01:00
2013-04-13 23:59:59 +02:00
All methods will raise : class : ` JenkinsException ` on failure .
: param username : Server username , ` ` str ` `
: param password : Server password , ` ` str ` `
2011-09-03 17:00:30 -07:00
: param url : URL of Jenkins server , ` ` str ` `
2014-12-04 00:16:09 -08:00
: param timeout : Server connection timeout ( in seconds ) , ` ` int ` `
2011-06-03 16:51:39 +01:00
'''
if url [ - 1 ] == ' / ' :
self . server = url
else :
self . server = url + ' / '
2012-03-02 16:26:13 +00:00
if username is not None and password is not None :
2011-06-03 16:51:39 +01:00
self . auth = auth_headers ( username , password )
else :
self . auth = None
2013-09-28 21:58:55 +02:00
self . crumb = None
2014-12-04 00:16:09 -08:00
self . timeout = timeout
2012-03-02 16:26:13 +00:00
2014-12-03 14:39:45 -08:00
def _get_encoded_params ( self , params ) :
for k , v in params . items ( ) :
if k in [ " name " , " to_name " , " from_name " , " msg " ] :
params [ k ] = quote ( v )
return params
2013-09-28 22:00:43 +02:00
def maybe_add_crumb ( self , req ) :
2013-09-28 21:58:55 +02:00
# We don't know yet whether we need a crumb
if self . crumb is None :
2015-01-13 16:38:15 -06:00
try :
response = self . jenkins_open ( Request (
self . server + CRUMB_URL ) , add_crumb = False )
2015-02-05 22:38:59 +01:00
except ( NotFoundException , EmptyResponseException ) :
2013-09-28 21:58:55 +02:00
self . crumb = False
2015-01-13 16:38:15 -06:00
else :
self . crumb = json . loads ( response . decode ( ' utf-8 ' ) )
2013-09-28 21:58:55 +02:00
if self . crumb :
req . add_header ( self . crumb [ ' crumbRequestField ' ] , self . crumb [ ' crumb ' ] )
2013-09-27 22:17:06 +02:00
2014-05-09 11:41:45 -07:00
def get_job_info ( self , name , depth = 0 ) :
2014-06-03 20:12:41 -07:00
''' Get job information dictionary.
2011-09-03 17:24:56 -07:00
: param name : Job name , ` ` str ` `
2014-05-09 11:41:45 -07:00
: param depth : JSON depth , ` ` int ` `
2011-09-03 17:24:56 -07:00
: returns : dictionary of job information
'''
2011-06-03 16:51:39 +01:00
try :
2014-04-08 09:18:17 -07:00
response = self . jenkins_open ( Request (
2014-12-03 14:39:45 -08:00
self . server + JOB_INFO % self . _get_encoded_params ( locals ( ) ) ) )
2011-07-19 17:06:18 +01:00
if response :
return json . loads ( response )
else :
2013-04-13 20:35:11 +02:00
raise JenkinsException ( ' job[ %s ] does not exist ' % name )
2014-04-08 09:18:17 -07:00
except HTTPError :
2013-04-13 20:35:11 +02:00
raise JenkinsException ( ' job[ %s ] does not exist ' % name )
2011-07-19 17:06:18 +01:00
except ValueError :
2013-04-13 20:35:11 +02:00
raise JenkinsException (
" Could not parse JSON info for job[ %s ] " % name )
2012-03-02 16:26:13 +00:00
2013-06-25 15:30:23 +02:00
def get_job_name ( self , name ) :
2014-06-03 20:12:41 -07:00
''' Return the name of a job using the API.
That is roughly an identity method which can be used to quickly verify
a job exist or is accessible without causing too much stress on the
server side .
2013-06-25 15:30:23 +02:00
: param name : Job name , ` ` str ` `
: returns : Name of job or None
'''
2015-01-13 16:38:15 -06:00
try :
response = self . jenkins_open (
Request ( self . server + JOB_NAME %
self . _get_encoded_params ( locals ( ) ) ) )
except NotFoundException :
return None
else :
2014-04-22 16:23:45 +02:00
actual = json . loads ( response ) [ ' name ' ]
if actual != name :
2013-06-25 15:30:23 +02:00
raise JenkinsException (
2014-04-22 16:23:45 +02:00
' Jenkins returned an unexpected job name %s '
' (expected: %s ) ' % ( actual , name ) )
return actual
2013-06-25 15:30:23 +02:00
2011-06-03 16:51:39 +01:00
def debug_job_info ( self , job_name ) :
2014-06-03 20:12:41 -07:00
''' Print out job info in more readable format. '''
2014-04-06 08:34:22 -07:00
for k , v in self . get_job_info ( job_name ) . items ( ) :
print ( k , v )
2011-06-03 16:51:39 +01:00
2013-09-27 22:17:06 +02:00
def jenkins_open ( self , req , add_crumb = True ) :
2014-06-03 20:12:41 -07:00
''' Utility routine for opening an HTTP request to a Jenkins server.
2013-09-27 22:17:06 +02:00
2014-06-03 20:12:41 -07:00
This should only be used to extends the : class : ` Jenkins ` API .
2011-06-03 16:51:39 +01:00
'''
try :
if self . auth :
req . add_header ( ' Authorization ' , self . auth )
2013-09-27 22:17:06 +02:00
if add_crumb :
2013-09-28 22:00:43 +02:00
self . maybe_add_crumb ( req )
2014-12-04 00:16:09 -08:00
response = urlopen ( req , timeout = self . timeout ) . read ( )
2015-02-05 23:02:21 +01:00
if response is None :
raise EmptyResponseException (
" Error communicating with server[ %s ]: "
" empty response " % self . server )
2014-12-04 00:16:09 -08:00
return response
2014-04-06 08:34:22 -07:00
except HTTPError as e :
2013-04-13 20:35:11 +02:00
# Jenkins's funky authentication means its nigh impossible to
# distinguish errors.
2011-06-03 16:51:39 +01:00
if e . code in [ 401 , 403 , 500 ] :
2014-07-11 11:17:37 +02:00
# six.moves.urllib.error.HTTPError provides a 'reason'
# attribute for all python version except for ver 2.6
# Falling back to HTTPError.msg since it contains the
# same info as reason
2013-04-13 20:35:11 +02:00
raise JenkinsException (
2014-07-11 11:17:37 +02:00
' Error in request. ' +
' Possibly authentication failed [ %s ]: %s ' % (
e . code , e . msg )
2013-04-13 20:35:11 +02:00
)
2015-01-13 16:38:15 -06:00
elif e . code == 404 :
raise NotFoundException ( ' Requested item could not be found ' )
2015-02-06 22:22:40 +01:00
else :
raise
2014-12-04 00:16:09 -08:00
except URLError as e :
raise JenkinsException ( ' Error in request: %s ' % ( e . reason ) )
2012-02-29 10:23:45 -07:00
2014-05-09 11:41:45 -07:00
def get_build_info ( self , name , number , depth = 0 ) :
2014-06-03 20:12:41 -07:00
''' Get build information dictionary.
2012-03-01 11:03:09 -07:00
2012-03-02 16:26:13 +00:00
: param name : Job name , ` ` str ` `
: param name : Build number , ` ` int ` `
2014-05-09 11:41:45 -07:00
: param depth : JSON depth , ` ` int ` `
2012-03-02 16:26:13 +00:00
: returns : dictionary of build information , ` ` dict ` `
2012-03-01 11:03:09 -07:00
Example : :
2014-06-05 11:26:16 -07:00
>> > j = Jenkins ( )
2012-03-01 11:03:09 -07:00
>> > next_build_number = j . get_job_info ( ' build_name ' ) [ ' next_build_number ' ]
2014-06-05 11:26:16 -07:00
>> > output = j . build_job ( ' build_name ' )
>> > from time import sleep ; sleep ( 10 )
2012-03-01 11:03:09 -07:00
>> > build_info = j . get_build_info ( ' build_name ' , next_build_number )
>> > print ( build_info )
{ u ' building ' : False , u ' changeSet ' : { u ' items ' : [ { u ' date ' : u ' 2011-12-19T18:01:52.540557Z ' , u ' msg ' : u ' test ' , u ' revision ' : 66 , u ' user ' : u ' unknown ' , u ' paths ' : [ { u ' editType ' : u ' edit ' , u ' file ' : u ' /branches/demo/index.html ' } ] } ] , u ' kind ' : u ' svn ' , u ' revisions ' : [ { u ' module ' : u ' http://eaas-svn01.i3.level3.com/eaas ' , u ' revision ' : 66 } ] } , u ' builtOn ' : u ' ' , u ' description ' : None , u ' artifacts ' : [ { u ' relativePath ' : u ' dist/eaas-87-2011-12-19_18-01-57.war ' , u ' displayPath ' : u ' eaas-87-2011-12-19_18-01-57.war ' , u ' fileName ' : u ' eaas-87-2011-12-19_18-01-57.war ' } , { u ' relativePath ' : u ' dist/eaas-87-2011-12-19_18-01-57.war.zip ' , u ' displayPath ' : u ' eaas-87-2011-12-19_18-01-57.war.zip ' , u ' fileName ' : u ' eaas-87-2011-12-19_18-01-57.war.zip ' } ] , u ' timestamp ' : 1324317717000 , u ' number ' : 87 , u ' actions ' : [ { u ' parameters ' : [ { u ' name ' : u ' SERVICE_NAME ' , u ' value ' : u ' eaas ' } , { u ' name ' : u ' PROJECT_NAME ' , u ' value ' : u ' demo ' } ] } , { u ' causes ' : [ { u ' userName ' : u ' anonymous ' , u ' shortDescription ' : u ' Started by user anonymous ' } ] } , { } , { } , { } ] , u ' id ' : u ' 2011-12-19_18-01-57 ' , u ' keepLog ' : False , u ' url ' : u ' http://eaas-jenkins01.i3.level3.com:9080/job/build_war/87/ ' , u ' culprits ' : [ { u ' absoluteUrl ' : u ' http://eaas-jenkins01.i3.level3.com:9080/user/unknown ' , u ' fullName ' : u ' unknown ' } ] , u ' result ' : u ' SUCCESS ' , u ' duration ' : 8826 , u ' fullDisplayName ' : u ' build_war #87 ' }
'''
2012-02-29 10:23:45 -07:00
try :
2014-04-08 09:18:17 -07:00
response = self . jenkins_open ( Request (
2014-12-03 14:39:45 -08:00
self . server + BUILD_INFO % self . _get_encoded_params ( locals ( ) ) ) )
2012-02-29 10:23:45 -07:00
if response :
return json . loads ( response )
else :
2013-04-13 20:35:11 +02:00
raise JenkinsException ( ' job[ %s ] number[ %d ] does not exist '
% ( name , number ) )
2014-04-08 09:18:17 -07:00
except HTTPError :
2013-04-13 20:35:11 +02:00
raise JenkinsException ( ' job[ %s ] number[ %d ] does not exist '
% ( name , number ) )
2012-02-29 10:23:45 -07:00
except ValueError :
2013-04-13 20:35:11 +02:00
raise JenkinsException (
' Could not parse JSON info for job[ %s ] number[ %d ] '
% ( name , number )
)
2012-03-02 16:26:13 +00:00
2011-06-03 16:51:39 +01:00
def get_queue_info ( self ) :
2014-06-03 20:12:41 -07:00
''' :returns: list of job dictionaries, ``[dict]``
2011-09-03 17:00:30 -07:00
Example : :
2014-06-05 11:26:16 -07:00
>> > j = Jenkins ( )
2011-09-03 17:00:30 -07:00
>> > queue_info = j . get_queue_info ( )
>> > print ( queue_info [ 0 ] )
{ u ' task ' : { u ' url ' : u ' http://your_url/job/my_job/ ' , u ' color ' : u ' aborted_anime ' , u ' name ' : u ' my_job ' } , u ' stuck ' : False , u ' actions ' : [ { u ' causes ' : [ { u ' shortDescription ' : u ' Started by timer ' } ] } ] , u ' buildable ' : False , u ' params ' : u ' ' , u ' buildableStartMilliseconds ' : 1315087293316 , u ' why ' : u ' Build #2,532 is already in progress (ETA:10 min) ' , u ' blocked ' : True }
2011-06-03 16:51:39 +01:00
'''
2013-04-13 20:35:11 +02:00
return json . loads ( self . jenkins_open (
2014-04-08 09:18:17 -07:00
Request ( self . server + Q_INFO )
2013-04-13 20:35:11 +02:00
) ) [ ' items ' ]
2011-06-03 16:51:39 +01:00
2014-12-03 22:36:20 -08:00
def cancel_queue ( self , id ) :
2014-06-03 20:12:41 -07:00
''' Cancel a queued build.
2012-06-20 14:41:59 -07:00
2014-12-03 22:36:20 -08:00
: param id : Jenkins job id number for the build , ` ` int ` `
2012-06-20 14:41:59 -07:00
'''
2014-12-03 22:36:20 -08:00
# Jenkins seems to always return a 404 when using this REST endpoint
# https://issues.jenkins-ci.org/browse/JENKINS-21311
2015-01-13 16:38:15 -06:00
try :
self . jenkins_open (
Request ( self . server + CANCEL_QUEUE % locals ( ) , ' ' ,
headers = { ' Referer ' : self . server } ) )
except NotFoundException :
# Exception is expected; cancel_queue() is a best-effort
# mechanism, so ignore it
pass
2012-06-20 14:41:59 -07:00
2011-07-11 21:55:58 -07:00
def get_info ( self ) :
2014-06-03 20:12:41 -07:00
""" Get information on this Master.
This information includes job list and view information .
2011-07-11 21:55:58 -07:00
2011-09-03 17:24:56 -07:00
: returns : dictionary of information about Master , ` ` dict ` `
2011-09-03 17:00:30 -07:00
Example : :
2014-06-05 11:26:16 -07:00
>> > j = Jenkins ( )
2011-09-03 17:00:30 -07:00
>> > info = j . get_info ( )
>> > jobs = info [ ' jobs ' ]
>> > print ( jobs [ 0 ] )
2013-04-13 20:35:11 +02:00
{ u ' url ' : u ' http://your_url_here/job/my_job/ ' , u ' color ' : u ' blue ' ,
u ' name ' : u ' my_job ' }
2011-09-03 17:00:30 -07:00
2011-07-11 21:55:58 -07:00
"""
try :
2013-04-13 20:35:11 +02:00
return json . loads ( self . jenkins_open (
2014-04-08 09:18:17 -07:00
Request ( self . server + INFO ) ) )
except HTTPError :
2013-04-13 20:35:11 +02:00
raise JenkinsException ( " Error communicating with server[ %s ] "
% self . server )
2014-04-08 09:18:17 -07:00
except BadStatusLine :
2013-04-13 20:35:11 +02:00
raise JenkinsException ( " Error communicating with server[ %s ] "
% self . server )
2011-07-19 17:06:18 +01:00
except ValueError :
2013-04-13 20:35:11 +02:00
raise JenkinsException ( " Could not parse JSON info for server[ %s ] "
% self . server )
2011-07-11 21:55:58 -07:00
2014-09-29 14:03:08 -07:00
def get_version ( self ) :
""" Get the version of this Master.
: returns : This master ' s version number ``str``
Example : :
>> > j = Jenkins ( )
>> > info = j . get_version ( )
>> > print info
>> > 1.541
"""
try :
request = Request ( self . server )
request . add_header ( ' X-Jenkins ' , ' 0.0 ' )
2014-12-04 00:16:09 -08:00
response = urlopen ( request , timeout = self . timeout )
2015-02-05 23:02:21 +01:00
if response is None :
raise EmptyResponseException (
" Error communicating with server[ %s ]: "
" empty response " % self . server )
2014-09-29 14:03:08 -07:00
return response . info ( ) . getheader ( ' X-Jenkins ' )
except HTTPError :
raise JenkinsException ( " Error communicating with server[ %s ] "
% self . server )
except BadStatusLine :
raise JenkinsException ( " Error communicating with server[ %s ] "
% self . server )
2014-09-24 22:05:42 -07:00
def get_plugins_info ( self , depth = 2 ) :
""" Get all installed plugins information on this Master.
This method retrieves information about each plugin that is installed
on master .
: param depth : JSON depth , ` ` int ` `
: returns : info on all plugins ` ` [ dict ] ` `
Example : :
>> > j = Jenkins ( )
>> > info = j . get_plugins_info ( )
>> > print ( info )
[ { u ' backupVersion ' : None , u ' version ' : u ' 0.0.4 ' , u ' deleted ' : False ,
u ' supportsDynamicLoad ' : u ' MAYBE ' , u ' hasUpdate ' : True ,
u ' enabled ' : True , u ' pinned ' : False , u ' downgradable ' : False ,
u ' dependencies ' : [ ] , u ' url ' :
u ' http://wiki.jenkins-ci.org/display/JENKINS/Gearman+Plugin ' ,
u ' longName ' : u ' Gearman Plugin ' , u ' active ' : True , u ' shortName ' :
u ' gearman-plugin ' , u ' bundled ' : False } , . . ]
"""
try :
plugins_info = json . loads ( self . jenkins_open (
Request ( self . server + PLUGIN_INFO % locals ( ) ) ) )
return plugins_info [ ' plugins ' ]
except HTTPError :
raise JenkinsException ( " Error communicating with server[ %s ] "
% self . server )
except BadStatusLine :
raise JenkinsException ( " Error communicating with server[ %s ] "
% self . server )
except ValueError :
raise JenkinsException ( " Could not parse JSON info for server[ %s ] "
% self . server )
def get_plugin_info ( self , name , depth = 2 ) :
""" Get an installed plugin information on this Master.
This method retrieves information about a speicifc plugin .
The passed in plugin name ( short or long ) must be an exact match .
: param name : Name ( short or long ) of plugin , ` ` str ` `
: param depth : JSON depth , ` ` int ` `
: returns : a specific plugin ` ` dict ` `
Example : :
>> > j = Jenkins ( )
>> > info = j . get_plugin_info ( " Gearman Plugin " )
>> > print ( info )
{ u ' backupVersion ' : None , u ' version ' : u ' 0.0.4 ' , u ' deleted ' : False ,
u ' supportsDynamicLoad ' : u ' MAYBE ' , u ' hasUpdate ' : True ,
u ' enabled ' : True , u ' pinned ' : False , u ' downgradable ' : False ,
u ' dependencies ' : [ ] , u ' url ' :
u ' http://wiki.jenkins-ci.org/display/JENKINS/Gearman+Plugin ' ,
u ' longName ' : u ' Gearman Plugin ' , u ' active ' : True , u ' shortName ' :
u ' gearman-plugin ' , u ' bundled ' : False }
"""
try :
plugins_info = json . loads ( self . jenkins_open (
2014-12-03 14:39:45 -08:00
Request ( self . server + PLUGIN_INFO % self . _get_encoded_params ( locals ( ) ) ) ) )
2014-09-24 22:05:42 -07:00
for plugin in plugins_info [ ' plugins ' ] :
if plugin [ ' longName ' ] == name or plugin [ ' shortName ' ] == name :
return plugin
except HTTPError :
raise JenkinsException ( " Error communicating with server[ %s ] "
% self . server )
except BadStatusLine :
raise JenkinsException ( " Error communicating with server[ %s ] "
% self . server )
except ValueError :
raise JenkinsException ( " Could not parse JSON info for server[ %s ] "
% self . server )
2011-07-11 21:55:58 -07:00
def get_jobs ( self ) :
2014-06-03 20:12:41 -07:00
""" Get list of jobs running.
Each job is a dictionary with ' name ' , ' url ' , and ' color ' keys .
2011-07-11 21:55:58 -07:00
2011-09-03 17:00:30 -07:00
: returns : list of jobs , ` ` [ { str : str } ] ` `
2011-07-11 21:55:58 -07:00
"""
return self . get_info ( ) [ ' jobs ' ]
2011-06-03 16:51:39 +01:00
def copy_job ( self , from_name , to_name ) :
2014-06-03 20:12:41 -07:00
''' Copy a Jenkins job
2011-06-03 16:51:39 +01:00
2011-09-03 17:00:30 -07:00
: param from_name : Name of Jenkins job to copy from , ` ` str ` `
: param to_name : Name of Jenkins job to copy to , ` ` str ` `
2011-06-03 16:51:39 +01:00
'''
2014-04-08 09:18:17 -07:00
self . jenkins_open ( Request (
2014-12-03 14:39:45 -08:00
self . server + COPY_JOB % self . _get_encoded_params ( locals ( ) ) , ' ' ) )
2014-04-22 16:25:59 +02:00
self . assert_job_exists ( to_name , ' create[ %s ] failed ' )
2011-06-03 16:51:39 +01:00
2014-12-03 14:39:45 -08:00
def rename_job ( self , from_name , to_name ) :
2014-06-03 20:12:41 -07:00
''' Rename an existing Jenkins job
2012-10-19 14:20:43 -07:00
2014-12-03 14:39:45 -08:00
: param from_name : Name of Jenkins job to rename , ` ` str ` `
: param to_name : New Jenkins job name , ` ` str ` `
2012-10-19 14:20:43 -07:00
'''
2014-04-08 09:18:17 -07:00
self . jenkins_open ( Request (
2014-12-03 14:39:45 -08:00
self . server + RENAME_JOB % self . _get_encoded_params ( locals ( ) ) , ' ' ) )
self . assert_job_exists ( to_name , ' rename[ %s ] failed ' )
2012-10-19 14:20:43 -07:00
2011-06-03 16:51:39 +01:00
def delete_job ( self , name ) :
2014-06-03 20:12:41 -07:00
''' Delete Jenkins job permanently.
2013-04-13 20:35:11 +02:00
2011-09-03 17:00:30 -07:00
: param name : Name of Jenkins job , ` ` str ` `
2011-06-03 16:51:39 +01:00
'''
2014-04-08 09:18:17 -07:00
self . jenkins_open ( Request (
2014-12-03 14:39:45 -08:00
self . server + DELETE_JOB % self . _get_encoded_params ( locals ( ) ) , ' ' ) )
2011-06-03 16:51:39 +01:00
if self . job_exists ( name ) :
2013-04-13 20:35:11 +02:00
raise JenkinsException ( ' delete[ %s ] failed ' % ( name ) )
2012-03-02 16:26:13 +00:00
2011-06-03 16:51:39 +01:00
def enable_job ( self , name ) :
2014-06-03 20:12:41 -07:00
''' Enable Jenkins job.
2011-06-03 16:51:39 +01:00
2011-09-03 17:00:30 -07:00
: param name : Name of Jenkins job , ` ` str ` `
2011-06-03 16:51:39 +01:00
'''
2014-04-08 09:18:17 -07:00
self . jenkins_open ( Request (
2014-12-03 14:39:45 -08:00
self . server + ENABLE_JOB % self . _get_encoded_params ( locals ( ) ) , ' ' ) )
2011-06-03 16:51:39 +01:00
def disable_job ( self , name ) :
2014-06-03 20:12:41 -07:00
''' Disable Jenkins job.
To re - enable , call : meth : ` Jenkins . enable_job ` .
2011-06-03 16:51:39 +01:00
2011-09-03 17:00:30 -07:00
: param name : Name of Jenkins job , ` ` str ` `
2011-06-03 16:51:39 +01:00
'''
2014-04-08 09:18:17 -07:00
self . jenkins_open ( Request (
2014-12-03 14:39:45 -08:00
self . server + DISABLE_JOB % self . _get_encoded_params ( locals ( ) ) , ' ' ) )
2011-06-03 16:51:39 +01:00
def job_exists ( self , name ) :
2014-06-03 20:12:41 -07:00
''' Check whether a job exists
2011-09-03 17:00:30 -07:00
: param name : Name of Jenkins job , ` ` str ` `
: returns : ` ` True ` ` if Jenkins job exists
2011-06-03 16:51:39 +01:00
'''
2013-06-25 15:30:23 +02:00
if self . get_job_name ( name ) == name :
2011-06-03 16:51:39 +01:00
return True
2014-04-22 16:25:59 +02:00
def assert_job_exists ( self , name ,
exception_message = ' job[ %s ] does not exist ' ) :
2014-07-11 15:28:38 +02:00
''' Raise an exception if a job does not exist
2014-04-22 16:25:59 +02:00
: param name : Name of Jenkins job , ` ` str ` `
: param exception_message : Message to use for the exception . Formatted
with ` ` name ` `
: throws : : class : ` JenkinsException ` whenever the job does not exist
'''
if not self . job_exists ( name ) :
raise JenkinsException ( exception_message % name )
2011-06-03 16:51:39 +01:00
def create_job ( self , name , config_xml ) :
2014-06-03 20:12:41 -07:00
''' Create a new Jenkins job
2011-06-03 16:51:39 +01:00
2011-09-03 17:00:30 -07:00
: param name : Name of Jenkins job , ` ` str ` `
: param config_xml : config file text , ` ` str ` `
2011-06-03 16:51:39 +01:00
'''
if self . job_exists ( name ) :
2013-04-13 20:35:11 +02:00
raise JenkinsException ( ' job[ %s ] already exists ' % ( name ) )
2011-06-03 16:51:39 +01:00
headers = { ' Content-Type ' : ' text/xml ' }
2014-04-08 09:18:17 -07:00
self . jenkins_open ( Request (
2014-12-03 14:39:45 -08:00
self . server + CREATE_JOB % self . _get_encoded_params ( locals ( ) ) , config_xml , headers ) )
2014-04-22 16:25:59 +02:00
self . assert_job_exists ( name , ' create[ %s ] failed ' )
2012-03-02 16:26:13 +00:00
2011-07-14 11:16:22 +01:00
def get_job_config ( self , name ) :
2014-06-03 20:12:41 -07:00
''' Get configuration of existing Jenkins job.
2011-07-14 11:16:22 +01:00
2011-09-03 17:00:30 -07:00
: param name : Name of Jenkins job , ` ` str ` `
2011-09-03 17:24:56 -07:00
: returns : job configuration ( XML format )
2011-07-14 11:16:22 +01:00
'''
2014-12-03 14:39:45 -08:00
request = Request ( self . server + CONFIG_JOB % self . _get_encoded_params ( locals ( ) ) )
2012-05-17 16:10:00 +01:00
return self . jenkins_open ( request )
2011-07-14 11:16:22 +01:00
2011-06-03 16:51:39 +01:00
def reconfig_job ( self , name , config_xml ) :
2014-06-03 20:12:41 -07:00
''' Change configuration of existing Jenkins job.
To create a new job , see : meth : ` Jenkins . create_job ` .
2011-06-03 16:51:39 +01:00
2011-09-03 17:00:30 -07:00
: param name : Name of Jenkins job , ` ` str ` `
: param config_xml : New XML configuration , ` ` str ` `
2011-06-03 16:51:39 +01:00
'''
headers = { ' Content-Type ' : ' text/xml ' }
2014-12-03 14:39:45 -08:00
reconfig_url = self . server + CONFIG_JOB % self . _get_encoded_params ( locals ( ) )
2014-04-08 09:18:17 -07:00
self . jenkins_open ( Request ( reconfig_url , config_xml , headers ) )
2011-06-03 16:51:39 +01:00
def build_job_url ( self , name , parameters = None , token = None ) :
2014-06-03 20:12:41 -07:00
''' Get URL to trigger build job.
Authenticated setups may require configuring a token on the server
side .
2013-04-13 20:35:11 +02:00
2011-09-03 17:00:30 -07:00
: param parameters : parameters for job , or None . , ` ` dict ` `
: param token : ( optional ) token for building job , ` ` str ` `
: returns : URL for building job
2011-06-03 16:51:39 +01:00
'''
if parameters :
if token :
parameters [ ' token ' ] = token
2014-12-03 14:39:45 -08:00
return ( self . server + BUILD_WITH_PARAMS_JOB % self . _get_encoded_params ( locals ( ) ) +
2014-04-06 08:34:22 -07:00
' ? ' + urlencode ( parameters ) )
2011-06-03 16:51:39 +01:00
elif token :
2014-12-03 14:39:45 -08:00
return ( self . server + BUILD_JOB % self . _get_encoded_params ( locals ( ) ) +
2014-04-06 08:34:22 -07:00
' ? ' + urlencode ( { ' token ' : token } ) )
2011-06-03 16:51:39 +01:00
else :
2014-12-03 14:39:45 -08:00
return self . server + BUILD_JOB % self . _get_encoded_params ( locals ( ) )
2011-06-03 16:51:39 +01:00
def build_job ( self , name , parameters = None , token = None ) :
2014-06-03 20:12:41 -07:00
''' Trigger build job.
2013-04-13 20:35:11 +02:00
2013-04-13 23:59:59 +02:00
: param name : name of job
2011-09-03 17:00:30 -07:00
: param parameters : parameters for job , or ` ` None ` ` , ` ` dict ` `
2013-04-13 23:59:59 +02:00
: param token : Jenkins API token
2011-06-03 16:51:39 +01:00
'''
2014-04-08 09:18:17 -07:00
return self . jenkins_open ( Request (
2014-09-18 10:31:34 -06:00
self . build_job_url ( name , parameters , token ) , " " ) )
2012-03-02 16:26:13 +00:00
2012-06-20 14:41:59 -07:00
def stop_build ( self , name , number ) :
2014-06-03 20:12:41 -07:00
''' Stop a running Jenkins build.
2012-06-20 14:41:59 -07:00
: param name : Name of Jenkins job , ` ` str ` `
: param number : Jenkins build number for the job , ` ` int ` `
'''
2014-12-03 14:39:45 -08:00
self . jenkins_open ( Request ( self . server + STOP_BUILD % self . _get_encoded_params ( locals ( ) ) ) )
2012-06-20 14:41:59 -07:00
2014-05-09 11:41:45 -07:00
def get_node_info ( self , name , depth = 0 ) :
2014-06-03 20:12:41 -07:00
''' Get node information dictionary
2011-09-03 17:24:56 -07:00
: param name : Node name , ` ` str ` `
2014-05-09 11:41:45 -07:00
: param depth : JSON depth , ` ` int ` `
2011-09-03 17:24:56 -07:00
: returns : Dictionary of node info , ` ` dict ` `
'''
2011-06-03 16:51:39 +01:00
try :
2014-04-08 09:18:17 -07:00
response = self . jenkins_open ( Request (
2014-12-03 14:39:45 -08:00
self . server + NODE_INFO % self . _get_encoded_params ( locals ( ) ) ) )
2011-07-19 17:06:18 +01:00
if response :
return json . loads ( response )
else :
2013-04-13 20:35:11 +02:00
raise JenkinsException ( ' node[ %s ] does not exist ' % name )
2014-04-08 09:18:17 -07:00
except HTTPError :
2013-04-13 20:35:11 +02:00
raise JenkinsException ( ' node[ %s ] does not exist ' % name )
2011-07-19 17:06:18 +01:00
except ValueError :
2013-04-13 20:35:11 +02:00
raise JenkinsException ( " Could not parse JSON info for node[ %s ] "
% name )
2012-03-02 16:26:13 +00:00
2011-06-03 16:51:39 +01:00
def node_exists ( self , name ) :
2014-06-03 20:12:41 -07:00
''' Check whether a node exists
2011-09-03 17:00:30 -07:00
: param name : Name of Jenkins node , ` ` str ` `
: returns : ` ` True ` ` if Jenkins node exists
2011-06-03 16:51:39 +01:00
'''
try :
self . get_node_info ( name )
return True
2011-07-19 17:06:18 +01:00
except JenkinsException :
2011-06-03 16:51:39 +01:00
return False
2012-03-02 16:26:13 +00:00
2014-06-18 11:20:16 +02:00
def assert_node_exists ( self , name ,
exception_message = ' node[ %s ] does not exist ' ) :
2014-07-11 15:28:38 +02:00
''' Raise an exception if a node does not exist
2014-06-18 11:20:16 +02:00
: param name : Name of Jenkins node , ` ` str ` `
: param exception_message : Message to use for the exception . Formatted
with ` ` name ` `
: throws : : class : ` JenkinsException ` whenever the node does not exist
'''
if not self . node_exists ( name ) :
raise JenkinsException ( exception_message % name )
2011-06-03 16:51:39 +01:00
def delete_node ( self , name ) :
2014-06-03 20:12:41 -07:00
''' Delete Jenkins node permanently.
2013-04-13 20:35:11 +02:00
2011-09-03 17:00:30 -07:00
: param name : Name of Jenkins node , ` ` str ` `
2011-06-03 16:51:39 +01:00
'''
self . get_node_info ( name )
2014-04-08 09:18:17 -07:00
self . jenkins_open ( Request (
2014-12-03 14:39:45 -08:00
self . server + DELETE_NODE % self . _get_encoded_params ( locals ( ) ) , ' ' ) )
2011-06-03 16:51:39 +01:00
if self . node_exists ( name ) :
2013-04-13 20:35:11 +02:00
raise JenkinsException ( ' delete[ %s ] failed ' % ( name ) )
2012-03-02 16:26:13 +00:00
2012-06-20 14:41:59 -07:00
def disable_node ( self , name , msg = ' ' ) :
2014-06-03 20:12:41 -07:00
''' Disable a node
2013-04-13 20:35:11 +02:00
2012-06-20 14:41:59 -07:00
: param name : Jenkins node name , ` ` str ` `
: param msg : Offline message , ` ` str ` `
'''
info = self . get_node_info ( name )
if info [ ' offline ' ] :
return
2014-04-08 09:18:17 -07:00
self . jenkins_open ( Request (
2014-12-19 09:18:05 -08:00
self . server + TOGGLE_OFFLINE % self . _get_encoded_params ( locals ( ) ) , ' ' ) )
2012-06-20 14:41:59 -07:00
def enable_node ( self , name ) :
2014-06-03 20:12:41 -07:00
''' Enable a node
2013-04-13 20:35:11 +02:00
2012-06-20 14:41:59 -07:00
: param name : Jenkins node name , ` ` str ` `
'''
info = self . get_node_info ( name )
if not info [ ' offline ' ] :
return
msg = ' '
2014-04-08 09:18:17 -07:00
self . jenkins_open ( Request (
2014-12-19 09:18:05 -08:00
self . server + TOGGLE_OFFLINE % self . _get_encoded_params ( locals ( ) ) , ' ' ) )
2012-06-20 14:41:59 -07:00
2011-06-03 16:51:39 +01:00
def create_node ( self , name , numExecutors = 2 , nodeDescription = None ,
2012-06-20 14:41:59 -07:00
remoteFS = ' /var/lib/jenkins ' , labels = None , exclusive = False ,
launcher = LAUNCHER_COMMAND , launcher_params = { } ) :
2014-06-03 20:12:41 -07:00
''' Create a node
2011-09-03 17:00:30 -07:00
: param name : name of node to create , ` ` str ` `
: param numExecutors : number of executors for node , ` ` int ` `
: param nodeDescription : Description of node , ` ` str ` `
: param remoteFS : Remote filesystem location to use , ` ` str ` `
: param labels : Labels to associate with node , ` ` str ` `
: param exclusive : Use this node for tied jobs only , ` ` bool ` `
2014-04-22 16:54:18 +02:00
: param launcher : The launch method for the slave , ` ` jenkins . LAUNCHER_COMMAND ` ` , ` ` jenkins . LAUNCHER_SSH ` ` , ` ` jenkins . LAUNCHER_JNLP ` ` , ` ` jenkins . LAUNCHER_WINDOWS_SERVICE ` `
2012-06-20 14:41:59 -07:00
: param launcher_params : Additional parameters for the launcher , ` ` dict ` `
2011-06-03 16:51:39 +01:00
'''
if self . node_exists ( name ) :
2013-04-13 20:35:11 +02:00
raise JenkinsException ( ' node[ %s ] already exists ' % ( name ) )
2012-03-02 16:26:13 +00:00
2011-08-08 20:38:39 +01:00
mode = ' NORMAL '
if exclusive :
mode = ' EXCLUSIVE '
2012-03-02 16:26:13 +00:00
2012-06-20 14:41:59 -07:00
launcher_params [ ' stapler-class ' ] = launcher
inner_params = {
2013-04-13 20:35:11 +02:00
' name ' : name ,
' nodeDescription ' : nodeDescription ,
' numExecutors ' : numExecutors ,
' remoteFS ' : remoteFS ,
' labelString ' : labels ,
' mode ' : mode ,
' type ' : NODE_TYPE ,
' retentionStrategy ' : {
' stapler-class ' :
' hudson.slaves.RetentionStrategy$Always '
} ,
' nodeProperties ' : { ' stapler-class-bag ' : ' true ' } ,
' launcher ' : launcher_params
2012-06-20 14:41:59 -07:00
}
params = {
2013-04-13 20:35:11 +02:00
' name ' : name ,
' type ' : NODE_TYPE ,
' json ' : json . dumps ( inner_params )
2011-06-03 16:51:39 +01:00
}
2012-03-02 16:26:13 +00:00
2014-04-08 09:18:17 -07:00
self . jenkins_open ( Request (
2014-04-06 08:34:22 -07:00
self . server + CREATE_NODE % urlencode ( params ) ) )
2012-06-20 14:41:59 -07:00
2014-06-18 11:20:16 +02:00
self . assert_node_exists ( name , ' create[ %s ] failed ' )
2013-05-11 20:48:02 -07:00
2014-04-22 16:55:37 +02:00
def get_node_config ( self , name ) :
''' Get the configuration for a node.
: param name : Jenkins node name , ` ` str ` `
'''
2014-12-03 14:39:45 -08:00
get_config_url = self . server + CONFIG_NODE % self . _get_encoded_params ( locals ( ) )
2014-04-22 16:55:37 +02:00
return self . jenkins_open ( Request ( get_config_url ) )
def reconfig_node ( self , name , config_xml ) :
''' Change the configuration for an existing node.
: param name : Jenkins node name , ` ` str ` `
: param config_xml : New XML configuration , ` ` str ` `
'''
headers = { ' Content-Type ' : ' text/xml ' }
2014-12-03 14:39:45 -08:00
reconfig_url = self . server + CONFIG_NODE % self . _get_encoded_params ( locals ( ) )
2014-04-22 16:55:37 +02:00
self . jenkins_open ( Request ( reconfig_url , config_xml , headers ) )
2013-05-11 20:48:02 -07:00
def get_build_console_output ( self , name , number ) :
2014-06-03 20:12:41 -07:00
''' Get build console text.
2013-05-11 20:48:02 -07:00
: param name : Job name , ` ` str ` `
: param name : Build number , ` ` int ` `
: returns : Build console output , ` ` str ` `
'''
try :
2014-04-08 09:18:17 -07:00
response = self . jenkins_open ( Request (
2014-12-03 14:39:45 -08:00
self . server + BUILD_CONSOLE_OUTPUT % self . _get_encoded_params ( locals ( ) ) ) )
2013-05-11 20:48:02 -07:00
if response :
return response
else :
raise JenkinsException ( ' job[ %s ] number[ %d ] does not exist '
% ( name , number ) )
2014-04-08 09:18:17 -07:00
except HTTPError :
2013-05-11 20:48:02 -07:00
raise JenkinsException ( ' job[ %s ] number[ %d ] does not exist '
% ( name , number ) )