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
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 '
2013-04-13 20:35:11 +02:00
INFO = ' api/json '
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 '
2012-06-20 14:41:59 -07:00
CANCEL_QUEUE = ' queue/item/ %(number)s /cancelQueue '
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-04-22 11:38:02 +02:00
RENAME_JOB = ' job/ %(name)s /doRename?newName= %(new_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
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 ' )
return b ' Basic ' + base64 . encodestring ( auth ) [ : - 1 ]
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
2011-06-03 16:51:39 +01:00
def __init__ ( self , url , username = None , password = None ) :
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 ` `
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
2012-03-02 16:26:13 +00:00
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 :
2014-04-08 09:18:17 -07:00
response = self . jenkins_open ( Request (
2013-09-28 21:58:55 +02:00
self . server + CRUMB_URL ) , add_crumb = False )
if response :
2014-04-06 08:34:22 -07:00
self . crumb = json . loads ( response . decode ( ' utf-8 ' ) )
2013-09-28 21:58:55 +02:00
else :
# Don't need crumbs
self . crumb = False
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 (
2013-04-13 20:35:11 +02:00
self . server + JOB_INFO % 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
'''
response = self . jenkins_open (
2014-04-08 09:18:17 -07:00
Request ( self . server + JOB_NAME % locals ( ) ) )
2013-06-25 15:30:23 +02:00
if response :
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
else :
return None
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-04-08 09:18:17 -07:00
return urlopen ( req ) . read ( )
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 ] :
2013-04-13 20:35:11 +02:00
raise JenkinsException (
' Error in request. ' +
' Possibly authentication failed [ %s ] ' % ( e . code )
)
2011-06-03 16:51:39 +01:00
# right now I'm getting 302 infinites on a successful delete
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 (
2013-04-13 20:35:11 +02:00
self . server + BUILD_INFO % 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
2012-06-20 14:41:59 -07:00
def cancel_queue ( self , number ) :
2014-06-03 20:12:41 -07:00
''' Cancel a queued build.
2012-06-20 14:41:59 -07:00
: param number : Jenkins queue number for the build , ` ` int ` `
'''
# Jenkins returns a 302 from this URL, unless Referer is not set,
# then you get a 404.
2014-04-08 09:18:17 -07:00
self . jenkins_open (
Request (
self . server + CANCEL_QUEUE % locals ( ) ,
headers = { ' Referer ' : self . server } ) )
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
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-22 16:25:59 +02:00
self . assert_job_exists ( from_name )
2014-04-08 09:18:17 -07:00
self . jenkins_open ( Request (
2013-04-13 20:35:11 +02:00
self . server + COPY_JOB % 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
2012-10-19 14:20:43 -07:00
def rename_job ( self , name , new_name ) :
2014-06-03 20:12:41 -07:00
''' Rename an existing Jenkins job
2012-10-19 14:20:43 -07:00
: param name : Name of Jenkins job to rename , ` ` str ` `
: param new_name : New Jenkins job name , ` ` str ` `
'''
2014-04-22 16:25:59 +02:00
self . assert_job_exists ( name )
2014-04-08 09:18:17 -07:00
self . jenkins_open ( Request (
2013-05-11 20:52:11 -07:00
self . server + RENAME_JOB % locals ( ) , ' ' ) )
2014-04-22 16:25:59 +02:00
self . assert_job_exists ( new_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-22 16:25:59 +02:00
self . assert_job_exists ( name )
2014-04-08 09:18:17 -07:00
self . jenkins_open ( Request (
2013-04-13 20:35:11 +02:00
self . server + DELETE_JOB % 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-22 16:25:59 +02:00
self . assert_job_exists ( name )
2014-04-08 09:18:17 -07:00
self . jenkins_open ( Request (
2013-04-13 20:35:11 +02:00
self . server + ENABLE_JOB % 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-22 16:25:59 +02:00
self . assert_job_exists ( name )
2014-04-08 09:18:17 -07:00
self . jenkins_open ( Request (
2013-04-13 20:35:11 +02:00
self . server + DISABLE_JOB % 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 (
2013-04-13 20:35:11 +02:00
self . server + CREATE_JOB % 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-04-08 09:18:17 -07:00
request = Request ( self . server + CONFIG_JOB %
2014-04-06 08:34:22 -07:00
{ " name " : quote ( name ) } )
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
'''
2014-04-22 16:25:59 +02:00
self . assert_job_exists ( name )
2011-06-03 16:51:39 +01:00
headers = { ' Content-Type ' : ' text/xml ' }
2013-04-13 20:35:11 +02:00
reconfig_url = self . server + CONFIG_JOB % 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
2013-04-13 20:35:11 +02:00
return ( self . server + BUILD_WITH_PARAMS_JOB % locals ( ) +
2014-04-06 08:34:22 -07:00
' ? ' + urlencode ( parameters ) )
2011-06-03 16:51:39 +01:00
elif token :
2013-04-13 20:35:11 +02:00
return ( self . server + BUILD_JOB % locals ( ) +
2014-04-06 08:34:22 -07:00
' ? ' + urlencode ( { ' token ' : token } ) )
2011-06-03 16:51:39 +01:00
else :
2013-04-13 20:35:11 +02:00
return self . server + BUILD_JOB % 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-22 16:25:59 +02:00
self . assert_job_exists ( name , ' no such job[ %s ] ' )
2014-04-08 09:18:17 -07:00
return self . jenkins_open ( Request (
2013-04-13 20:35:11 +02: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-04-08 09:18:17 -07:00
self . jenkins_open ( Request ( self . server + STOP_BUILD % 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 (
2013-04-13 20:35:11 +02:00
self . server + NODE_INFO % 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 (
2013-04-13 20:35:11 +02:00
self . server + DELETE_NODE % 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 (
2013-04-13 20:35:11 +02:00
self . server + TOGGLE_OFFLINE % 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 (
2013-04-13 20:35:11 +02:00
self . server + TOGGLE_OFFLINE % 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 ` `
'''
get_config_url = self . server + CONFIG_NODE % locals ( )
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 ' }
reconfig_url = self . server + CONFIG_NODE % locals ( )
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 (
2013-05-11 20:48:02 -07:00
self . server + BUILD_CONSOLE_OUTPUT % locals ( ) ) )
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 ) )