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
2015-08-25 00:47:22 +01:00
: noindex :
2013-04-13 23:59:59 +02:00
2015-07-30 12:37:25 -07:00
See examples at : doc : ` examples `
2011-06-03 16:51:39 +01:00
'''
import base64
import json
2015-02-13 12:30:02 -08:00
import re
2015-02-05 21:27:33 -08:00
import socket
2015-07-08 13:08:40 +01:00
import sys
2015-08-07 09:44:56 -07:00
import time
2015-07-08 13:08:40 +01:00
import warnings
2014-04-06 08:34:22 -07:00
2015-02-10 01:44:33 +00:00
import multi_key_dict
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
2015-09-14 13:15:36 -07:00
from six . moves . urllib . parse import quote , urlencode , urljoin , urlparse
2015-10-22 16:28:13 +02:00
from six . moves . urllib . request import Request , install_opener , build_opener , urlopen
2011-06-03 16:51:39 +01:00
2015-02-10 01:44:33 +00:00
from jenkins import plugins
2015-10-22 16:28:13 +02:00
try :
import kerberos
assert kerberos # pyflakes
import urllib_kerb
opener = build_opener ( )
opener . add_handler ( urllib_kerb . HTTPNegotiateHandler ( ) )
install_opener ( opener )
except ImportError :
pass
2015-02-10 01:44:33 +00:00
warnings . simplefilter ( " default " , DeprecationWarning )
2015-07-08 13:08:40 +01:00
if sys . version_info < ( 2 , 7 , 0 ) :
warnings . warn ( " Support for python 2.6 is deprecated and will be removed. " )
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 '
2015-03-17 16:36:06 -07:00
DEFAULT_HEADERS = { ' Content-Type ' : ' text/xml; charset=utf-8 ' }
2012-06-20 14:41:59 -07:00
2015-03-17 16:36:06 -07:00
# REST Endpoints
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 '
2016-04-19 10:04:59 -06:00
WHOAMI_URL = ' me/api/json '
2015-05-05 16:56:09 +02:00
JOBS_QUERY = ' ?tree=jobs[url,color,name,jobs] '
JOB_INFO = ' %(folder_url)s job/ %(short_name)s /api/json?depth= %(depth)s '
JOB_NAME = ' %(folder_url)s job/ %(short_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 '
2015-05-05 16:56:09 +02:00
CREATE_JOB = ' %(folder_url)s createItem?name= %(short_name)s ' # also post config.xml
CONFIG_JOB = ' %(folder_url)s job/ %(short_name)s /config.xml '
DELETE_JOB = ' %(folder_url)s job/ %(short_name)s /doDelete '
ENABLE_JOB = ' %(folder_url)s job/ %(short_name)s /enable '
DISABLE_JOB = ' %(folder_url)s job/ %(short_name)s /disable '
2015-09-24 10:06:05 -05:00
SET_JOB_BUILD_NUMBER = ' %(folder_url)s job/ %(short_name)s /nextbuildnumber/submit '
2015-05-05 16:56:09 +02:00
COPY_JOB = ' %(from_folder_url)s createItem?name= %(to_short_name)s &mode=copy&from= %(from_short_name)s '
RENAME_JOB = ' %(from_folder_url)s job/ %(from_short_name)s /doRename?newName= %(to_short_name)s '
BUILD_JOB = ' %(folder_url)s job/ %(short_name)s /build '
STOP_BUILD = ' %(folder_url)s job/ %(short_name)s / %(number)s /stop '
BUILD_WITH_PARAMS_JOB = ' %(folder_url)s job/ %(short_name)s /buildWithParameters '
BUILD_INFO = ' %(folder_url)s job/ %(short_name)s / %(number)d /api/json?depth= %(depth)s '
BUILD_CONSOLE_OUTPUT = ' %(folder_url)s job/ %(short_name)s / %(number)d /consoleText '
2015-03-10 18:13:45 +02:00
NODE_LIST = ' computer/api/json '
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 '
2015-06-22 13:22:45 -05:00
VIEW_NAME = ' view/ %(name)s /api/json?tree=name '
2016-02-12 22:40:56 +03:00
VIEW_JOBS = ' view/ %(name)s /api/json?tree=jobs[url,color,name] '
2015-06-22 13:22:45 -05:00
CREATE_VIEW = ' createView?name= %(name)s '
CONFIG_VIEW = ' view/ %(name)s /config.xml '
DELETE_VIEW = ' view/ %(name)s /doDelete '
Support scriptText api to execute groovy scripts on the server
Jenkins features a nice script console[1] which allows users
to remotely run arbitrary groovy scripts on the Jenkins master.
Examples:
$ curl -d 'script=println("hello world")' -X POST
http://localhost:8080/scriptText
hello world
$ curl -d 'script=println(Jenkins.instance.pluginManager.plugins)' -X POST
http://localhost:8080/scriptText
[Plugin:windows-slaves, Plugin:ssh-slaves, Plugin:translation, Plugin:cvs,
Plugin:nodelabelparameter, Plugin:external-monitor-job, Plugin:subversion,
Plugin:ssh-credentials, Plugin:token-macro, Plugin:ant, Plugin:ldap,
Plugin:credentials, Plugin:matrix-auth, Plugin:matrix-project, Plugin:mailer,
Plugin:jquery, Plugin:maven-plugin, Plugin:pam-auth]
[1] https://wiki.jenkins-ci.org/display/JENKINS/Jenkins+Script+Console
Change-Id: Ia4fbfca970165d890d7e076f47ddcde7633afa9b
2015-06-22 22:13:16 -04:00
SCRIPT_TEXT = ' scriptText '
2016-01-12 18:42:17 +01:00
PROMOTION_NAME = ' %(folder_url)s job/ %(short_name)s /promotion/process/ %(name)s /api/json?tree=name '
PROMOTION_INFO = ' %(folder_url)s job/ %(short_name)s /promotion/api/json?depth= %(depth)s '
DELETE_PROMOTION = ' %(folder_url)s job/ %(short_name)s /promotion/process/ %(name)s /doDelete '
CREATE_PROMOTION = ' %(folder_url)s job/ %(short_name)s /promotion/createProcess?name= %(name)s '
CONFIG_PROMOTION = ' %(folder_url)s job/ %(short_name)s /promotion/process/ %(name)s /config.xml '
2015-09-10 15:04:51 -07:00
QUIET_DOWN = ' quietDown '
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 >
2015-06-22 13:22:45 -05:00
< builders >
2013-04-13 20:35:11 +02:00
< jenkins . tasks . Shell >
< command > export FOO = bar < / command >
< / jenkins . tasks . Shell >
< / builders >
2011-06-03 16:51:39 +01:00
< publishers / >
< buildWrappers / >
< / project > '''
2015-06-22 13:22:45 -05:00
# for testing only
EMPTY_VIEW_CONFIG_XML = ''' <?xml version= " 1.0 " encoding= " UTF-8 " ?>
< hudson . model . ListView >
< name > EMPTY < / name >
< filterExecutors > false < / filterExecutors >
< filterQueue > false < / filterQueue >
< properties class = " hudson.model.View$PropertyList " / >
< jobNames >
< comparator class = " hudson.util.CaseInsensitiveComparator " / >
< / jobNames >
< jobFilters / >
< columns >
< hudson . views . StatusColumn / >
< hudson . views . WeatherColumn / >
< hudson . views . JobColumn / >
< hudson . views . LastSuccessColumn / >
< hudson . views . LastFailureColumn / >
< hudson . views . LastDurationColumn / >
< hudson . views . BuildButtonColumn / >
< / columns >
< / hudson . model . ListView > '''
2016-01-12 18:42:17 +01:00
# for testing only
EMPTY_PROMO_CONFIG_XML = ''' <?xml version= ' 1.0 ' encoding= ' UTF-8 ' ?>
< hudson . plugins . promoted__builds . PromotionProcess >
< properties / >
< scm class = " hudson.scm.NullSCM " / >
< canRoam > false < / canRoam >
< triggers / >
< conditions / >
< icon > star - gold < / icon >
< buildSteps / >
< / hudson . plugins . promoted__builds . PromotionProcess > '''
# for testing only
PROMO_RECONFIG_XML = ''' <?xml version= ' 1.0 ' encoding= ' UTF-8 ' ?>
< hudson . plugins . promoted__builds . PromotionProcess >
< keepDependencies > false < / keepDependencies >
< properties / >
< scm class = " hudson.scm.NullSCM " / >
< triggers / >
< icon > star - green < / icon >
< buildSteps >
< hudson . tasks . Shell >
< command > ls - l < / command >
< / hudson . tasks . Shell >
< / buildSteps >
< / hudson . plugins . promoted__builds . PromotionProcess >
'''
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
2015-02-12 12:17:12 +01:00
class BadHTTPException ( JenkinsException ) :
''' A special exception to call out the case of a broken HTTP response. '''
pass
2015-07-08 13:10:49 +01:00
class TimeoutException ( JenkinsException ) :
''' A special exception to call out in the case of a socket timeout. '''
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
2015-02-05 21:27:33 -08:00
def __init__ ( self , url , username = None , password = None ,
timeout = socket . _GLOBAL_DEFAULT_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 ` `
2015-02-05 21:27:33 -08:00
: param timeout : Server connection timeout in secs ( default : not set ) , ` ` 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 ( ) :
2015-05-05 16:56:09 +02:00
if k in [ " name " , " msg " , " short_name " , " from_short_name " ,
" to_short_name " , " folder_url " , " from_folder_url " , " to_folder_url " ] :
2014-12-03 14:39:45 -08:00
params [ k ] = quote ( v )
return params
2015-07-24 18:20:22 +01:00
def _build_url ( self , format_spec , variables = None ) :
if variables :
2015-12-08 17:49:38 +08:00
if format_spec == CREATE_NODE :
url_path = format_spec % urlencode ( self . _get_encoded_params ( variables ) )
else :
url_path = format_spec % self . _get_encoded_params ( variables )
2015-07-24 18:20:22 +01:00
else :
url_path = format_spec
return urljoin ( self . server , url_path )
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 (
2015-07-24 18:20:22 +01:00
self . _build_url ( 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 :
2015-05-14 11:32:12 +02:00
self . crumb = json . loads ( response )
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
'''
2015-05-05 16:56:09 +02:00
folder_url , short_name = self . _get_job_folder ( name )
2011-06-03 16:51:39 +01:00
try :
2014-04-08 09:18:17 -07:00
response = self . jenkins_open ( Request (
2015-07-24 18:20:22 +01:00
self . _build_url ( 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
2015-05-05 16:56:09 +02:00
def get_job_info_regex ( self , pattern , depth = 0 , folder_depth = 0 ) :
2015-02-13 12:30:02 -08:00
''' Get a list of jobs information that contain names which match the
regex pattern .
: param pattern : regex pattern , ` ` str ` `
: param depth : JSON depth , ` ` int ` `
2015-05-05 16:56:09 +02:00
: param folder_depth : folder level depth to search ` ` int ` `
2015-02-13 12:30:02 -08:00
: returns : List of jobs info , ` ` list ` `
'''
result = [ ]
2015-05-05 16:56:09 +02:00
jobs = self . get_all_jobs ( folder_depth )
2015-02-13 12:30:02 -08:00
for job in jobs :
if re . search ( pattern , job [ ' name ' ] ) :
result . append ( self . get_job_info ( job [ ' name ' ] , depth = depth ) )
return result
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-05-05 16:56:09 +02:00
folder_url , short_name = self . _get_job_folder ( name )
2015-01-13 16:38:15 -06:00
try :
2015-07-24 18:20:22 +01:00
response = self . jenkins_open ( Request (
self . _build_url ( JOB_NAME , locals ( ) )
) )
2015-01-13 16:38:15 -06:00
except NotFoundException :
return None
else :
2014-04-22 16:23:45 +02:00
actual = json . loads ( response ) [ ' name ' ]
2015-05-05 16:56:09 +02:00
if actual != short_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 )
2015-05-14 11:32:12 +02:00
return response . decode ( ' utf-8 ' )
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
2015-07-08 13:10:49 +01:00
except socket . timeout as e :
raise TimeoutException ( ' Error in request: %s ' % ( e ) )
2014-12-04 00:16:09 -08:00
except URLError as e :
2015-07-08 13:10:49 +01:00
# python 2.6 compatibility to ensure same exception raised
# since URLError wraps a socket timeout on python 2.6.
if str ( e . reason ) == " timed out " :
raise TimeoutException ( ' Error in request: %s ' % ( e . reason ) )
2015-02-06 22:15:30 +01:00
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 : :
2015-07-30 12:37:25 -07:00
>> > next_build_number = server . get_job_info ( ' build_name ' ) [ ' nextBuildNumber ' ]
>> > output = server . build_job ( ' build_name ' )
2014-06-05 11:26:16 -07:00
>> > from time import sleep ; sleep ( 10 )
2015-07-30 12:37:25 -07:00
>> > build_info = server . get_build_info ( ' build_name ' , next_build_number )
2012-03-01 11:03:09 -07:00
>> > 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 ' }
'''
2015-05-05 16:56:09 +02:00
folder_url , short_name = self . _get_job_folder ( name )
2012-02-29 10:23:45 -07:00
try :
2014-04-08 09:18:17 -07:00
response = self . jenkins_open ( Request (
2015-07-24 18:20:22 +01:00
self . _build_url ( 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 : :
2015-07-30 12:37:25 -07:00
>> > queue_info = server . get_queue_info ( )
2011-09-03 17:00:30 -07:00
>> > 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 (
2015-07-24 18:20:22 +01:00
Request ( self . _build_url ( 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 (
2015-07-24 18:20:22 +01:00
Request ( self . _build_url ( CANCEL_QUEUE , locals ( ) ) , b ' ' ,
2015-01-13 16:38:15 -06:00
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
2015-05-05 16:56:09 +02:00
def get_info ( self , item = " " , query = None ) :
""" Get information on this Master or item on Master.
2014-06-03 20:12:41 -07:00
2015-05-05 16:56:09 +02:00
This information includes job list and view information and can be
used to retreive information on items such as job folders .
2011-07-11 21:55:58 -07:00
2015-05-05 16:56:09 +02:00
: param item : item to get information about on this Master
: param query : xpath to extract information about on this Master
: returns : dictionary of information about Master or item , ` ` dict ` `
2011-09-03 17:00:30 -07:00
Example : :
2015-07-30 12:37:25 -07:00
>> > info = server . get_info ( )
2011-09-03 17:00:30 -07:00
>> > 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
"""
2015-10-05 17:36:58 +01:00
url = ' / ' . join ( ( item , INFO ) ) . lstrip ( ' / ' )
2015-05-05 16:56:09 +02:00
if query :
url + = query
2011-07-11 21:55:58 -07:00
try :
2013-04-13 20:35:11 +02:00
return json . loads ( self . jenkins_open (
2015-05-05 16:56:09 +02:00
Request ( self . _build_url ( url ) )
2015-07-24 18:20:22 +01:00
) )
2015-02-18 14:12:46 +00:00
except ( HTTPError , BadStatusLine ) :
2015-02-12 12:17:12 +01:00
raise BadHTTPException ( " Error communicating with server[ %s ] "
2013-04-13 20:35:11 +02:00
% 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
2016-04-19 10:04:59 -06:00
def get_whoami ( self ) :
""" Get information about the user account that authenticated to
Jenkins . This is a simple way to verify that your credentials are
correct .
: returns : Information about the current user ` ` dict ` `
Example : :
>> > me = server . get_whoami ( )
>> > print me [ ' fullName ' ]
>> > ' John '
"""
try :
response = self . jenkins_open ( Request ( self . _build_url ( WHOAMI_URL ) ) )
if response is None :
raise EmptyResponseException (
" Error communicating with server[ %s ]: "
" empty response " % self . server )
return json . loads ( response )
except ( HTTPError , BadStatusLine ) :
raise BadHTTPException ( " Error communicating with server[ %s ] "
% self . server )
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 : :
2015-07-30 12:37:25 -07:00
>> > info = server . get_version ( )
2014-09-29 14:03:08 -07:00
>> > print info
>> > 1.541
"""
try :
2015-07-24 18:20:22 +01:00
request = Request ( self . _build_url ( ' ' ) )
2014-09-29 14:03:08 -07:00
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 )
2015-05-14 11:32:12 +02:00
if six . PY2 :
return response . info ( ) . getheader ( ' X-Jenkins ' )
if six . PY3 :
return response . getheader ( ' X-Jenkins ' )
2015-02-18 14:12:46 +00:00
except ( HTTPError , BadStatusLine ) :
2015-02-12 12:17:12 +01:00
raise BadHTTPException ( " Error communicating with server[ %s ] "
2014-09-29 14:03:08 -07:00
% 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
2015-02-10 01:44:33 +00:00
on master returning the raw plugin data in a JSON format .
. . deprecated : : 0.4 .9
Use : func : ` get_plugins ` instead .
2014-09-24 22:05:42 -07:00
: param depth : JSON depth , ` ` int ` `
: returns : info on all plugins ` ` [ dict ] ` `
Example : :
2015-07-30 12:37:25 -07:00
>> > info = server . get_plugins_info ( )
2014-09-24 22:05:42 -07:00
>> > 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 } , . . ]
"""
2015-02-10 01:44:33 +00:00
warnings . warn ( " get_plugins_info() is deprecated, use get_plugins() " ,
DeprecationWarning )
return [ plugin_data for plugin_data in self . get_plugins ( depth ) . values ( ) ]
2014-09-24 22:05:42 -07:00
def get_plugin_info ( self , name , depth = 2 ) :
""" Get an installed plugin information on this Master.
2015-02-10 01:44:33 +00:00
This method retrieves information about a specific plugin and returns
the raw plugin data in a JSON format .
2014-09-24 22:05:42 -07:00
The passed in plugin name ( short or long ) must be an exact match .
2015-02-10 01:44:33 +00:00
. . note : : Calling this method will query Jenkins fresh for the
information for all plugins on each call . If you need to retrieve
information for multiple plugins it ' s recommended to use
: func : ` get_plugins ` instead , which will return a multi key
dictionary that can be accessed via either the short or long name
of the plugin .
2014-09-24 22:05:42 -07:00
: param name : Name ( short or long ) of plugin , ` ` str ` `
: param depth : JSON depth , ` ` int ` `
: returns : a specific plugin ` ` dict ` `
Example : :
2015-07-30 12:37:25 -07:00
>> > info = server . get_plugin_info ( " Gearman Plugin " )
2014-09-24 22:05:42 -07:00
>> > 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 }
"""
2015-02-10 01:44:33 +00:00
plugins_info = self . get_plugins ( depth )
2014-09-24 22:05:42 -07:00
try :
2015-02-10 01:44:33 +00:00
return plugins_info [ name ]
except KeyError :
pass
def get_plugins ( self , depth = 2 ) :
""" Return plugins info using helper class for version comparison
This method retrieves information about all the installed plugins and
uses a Plugin helper class to simplify version comparison . Also uses
a multi key dict to allow retrieval via either short or long names .
When printing / dumping the data , the version will transparently return
a unicode string , which is exactly what was previously returned by the
API .
: param depth : JSON depth , ` ` int ` `
: returns : info on all plugins ` ` [ dict ] ` `
Example : :
>> > j = Jenkins ( )
>> > info = j . get_plugins ( )
>> > print ( info )
{ ( ' gearman-plugin ' , ' Gearman Plugin ' ) :
{ 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 = json . loads ( self . jenkins_open (
2015-07-24 18:20:22 +01:00
Request ( self . _build_url ( PLUGIN_INFO , locals ( ) ) ) ) )
2015-02-18 14:12:46 +00:00
except ( HTTPError , BadStatusLine ) :
2015-02-12 12:17:12 +01:00
raise BadHTTPException ( " Error communicating with server[ %s ] "
2014-09-24 22:05:42 -07:00
% self . server )
except ValueError :
raise JenkinsException ( " Could not parse JSON info for server[ %s ] "
% self . server )
2015-02-10 01:44:33 +00:00
plugins_data = multi_key_dict . multi_key_dict ( )
for plugin_data in plugins_info_json [ ' plugins ' ] :
keys = ( str ( plugin_data [ ' shortName ' ] ) , str ( plugin_data [ ' longName ' ] ) )
plugins_data [ keys ] = plugins . Plugin ( * * plugin_data )
return plugins_data
2016-02-12 22:40:56 +03:00
def get_jobs ( self , folder_depth = 0 , view_name = None ) :
2015-05-05 16:56:09 +02:00
""" Get list of jobs.
2014-06-03 20:12:41 -07:00
2015-05-05 16:56:09 +02:00
Each job is a dictionary with ' name ' , ' url ' , ' color ' and ' fullname '
keys .
2011-07-11 21:55:58 -07:00
2016-02-12 22:40:56 +03:00
If the ` ` view_name ` ` parameter is present , the list of
jobs will be limited to only those configured in the
specified view . In this case , the job dictionary ' fullname ' key
would be equal to the job name .
2015-05-05 16:56:09 +02:00
: param folder_depth : Number of levels to search , ` ` int ` ` . By default
0 , which will limit search to toplevel . None disables the limit .
2016-02-12 22:40:56 +03:00
: param view_name : Name of a Jenkins view for which to
retrieve jobs , ` ` str ` ` . By default , the job list is
not limited to a specific view .
: returns : list of jobs , ` ` [ { str : str , str : str , str : str , str : str } ] ` `
Example : :
>> > jobs = server . get_jobs ( )
>> > print ( jobs )
[ {
u ' name ' : u ' all_tests ' ,
u ' url ' : u ' http://your_url.here/job/all_tests/ ' ,
u ' color ' : u ' blue ' ,
u ' fullname ' : u ' all_tests '
} ]
2011-07-11 21:55:58 -07:00
"""
2015-05-05 16:56:09 +02:00
2016-02-12 22:40:56 +03:00
if view_name :
return self . _get_view_jobs ( view_name = view_name )
else :
return self . get_all_jobs ( folder_depth = folder_depth )
2015-05-05 16:56:09 +02:00
def get_all_jobs ( self , folder_depth = None ) :
""" Get list of all jobs recursively to the given folder depth.
Each job is a dictionary with ' name ' , ' url ' , ' color ' and ' fullname '
keys .
: param folder_depth : Number of levels to search , ` ` int ` ` . By default
None , which will search all levels . 0 limits to toplevel .
: returns : list of jobs , ` ` [ { str : str } ] ` `
. . note : :
On instances with many folders it may be more efficient to use the
run_script method to retrieve all jobs instead .
Example : :
server . run_script ( \" \" \"
import groovy . json . JsonBuilder ;
/ / get all projects excluding matrix configuration
/ / as they are simply part of a matrix project .
/ / there may be better ways to get just jobs
items = Jenkins . instance . getAllItems ( AbstractProject ) ;
items . removeAll {
it instanceof hudson . matrix . MatrixConfiguration
} ;
def json = new JsonBuilder ( )
def root = json {
jobs items . collect {
[
name : it . name ,
url : Jenkins . instance . getRootUrl ( ) + it . getUrl ( ) ,
color : it . getIconColor ( ) . toString ( ) ,
fullname : it . getFullName ( )
]
}
}
/ / use json . toPrettyString ( ) if viewing
println json . toString ( )
\" \" \" )
"""
jobs_list = [ ]
jobs = [ ( 0 , " " , self . get_info ( query = JOBS_QUERY ) [ ' jobs ' ] ) ]
for lvl , root , lvl_jobs in jobs :
if not isinstance ( lvl_jobs , list ) :
lvl_jobs = [ lvl_jobs ]
for job in lvl_jobs :
if ' jobs ' in job : # folder
if folder_depth is None or lvl < folder_depth :
path = ' /job/ ' . join ( ( root , job [ u ' name ' ] ) )
jobs . append (
( lvl + 1 , path ,
self . get_info ( path ,
query = JOBS_QUERY ) [ ' jobs ' ] ) )
else :
# insert fullname info if it doesn't exist to
# allow callers to easily reference unambiguously
if u ' fullname ' not in job :
job [ u ' fullname ' ] = ' / ' . join (
[ p for p in root . split ( ' / ' )
if p and p != ' job ' ] +
[ job [ u ' name ' ] ] )
jobs_list . append ( job )
return jobs_list
2011-07-11 21:55:58 -07:00
2011-06-03 16:51:39 +01:00
def copy_job ( self , from_name , to_name ) :
2015-05-05 16:56:09 +02:00
''' Copy a Jenkins job.
Will raise an exception whenever the source and destination folder
for this jobs won ' t be the same.
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 ` `
2015-05-05 16:56:09 +02:00
: throws : : class : ` JenkinsException ` whenever the source and destination
folder are not the same
2011-06-03 16:51:39 +01:00
'''
2015-05-05 16:56:09 +02:00
from_folder_url , from_short_name = self . _get_job_folder ( from_name )
to_folder_url , to_short_name = self . _get_job_folder ( to_name )
if from_folder_url != to_folder_url :
raise JenkinsException ( ' copy[ %s to %s ] failed, source and destination '
' folder must be the same ' % ( from_name , to_name ) )
2014-04-08 09:18:17 -07:00
self . jenkins_open ( Request (
2015-07-24 18:20:22 +01:00
self . _build_url ( COPY_JOB , locals ( ) ) , b ' ' ) )
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
2015-05-05 16:56:09 +02:00
Will raise an exception whenever the source and destination folder
for this jobs won ' t be the same.
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 ` `
2015-05-05 16:56:09 +02:00
: throws : : class : ` JenkinsException ` whenever the source and destination
folder are not the same
2012-10-19 14:20:43 -07:00
'''
2015-05-05 16:56:09 +02:00
from_folder_url , from_short_name = self . _get_job_folder ( from_name )
to_folder_url , to_short_name = self . _get_job_folder ( to_name )
if from_folder_url != to_folder_url :
raise JenkinsException ( ' rename[ %s to %s ] failed, source and destination folder '
' must be the same ' % ( from_name , to_name ) )
2014-04-08 09:18:17 -07:00
self . jenkins_open ( Request (
2015-07-24 18:20:22 +01:00
self . _build_url ( RENAME_JOB , locals ( ) ) , b ' ' ) )
2014-12-03 14:39:45 -08:00
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
'''
2015-05-05 16:56:09 +02:00
folder_url , short_name = self . _get_job_folder ( name )
2014-04-08 09:18:17 -07:00
self . jenkins_open ( Request (
2015-07-24 18:20:22 +01:00
self . _build_url ( DELETE_JOB , locals ( ) ) , b ' ' ) )
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
'''
2015-05-05 16:56:09 +02:00
folder_url , short_name = self . _get_job_folder ( name )
2014-04-08 09:18:17 -07:00
self . jenkins_open ( Request (
2015-07-24 18:20:22 +01:00
self . _build_url ( ENABLE_JOB , locals ( ) ) , b ' ' ) )
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
'''
2015-05-05 16:56:09 +02:00
folder_url , short_name = self . _get_job_folder ( name )
2014-04-08 09:18:17 -07:00
self . jenkins_open ( Request (
2015-07-24 18:20:22 +01:00
self . _build_url ( DISABLE_JOB , locals ( ) ) , b ' ' ) )
2011-06-03 16:51:39 +01:00
2015-09-24 10:06:05 -05:00
def set_next_build_number ( self , name , number ) :
''' Set a job ' s next build number.
The current next build number is contained within the job
information retrieved using : meth : ` Jenkins . get_job_info ` . If
the specified next build number is less than the last build
number , Jenkins will ignore the request .
Note that the ` Next Build Number Plugin
< https : / / wiki . jenkins - ci . org / display / JENKINS / Next + Build + Number + Plugin > ` _
must be installed to enable this functionality .
: param name : Name of Jenkins job , ` ` str ` `
: param number : Next build number to set , ` ` int ` `
Example : :
>> > next_bn = server . get_job_info ( ' job_name ' ) [ ' nextBuildNumber ' ]
>> > server . set_next_build_number ( ' job_name ' , next_bn + 50 )
'''
folder_url , short_name = self . _get_job_folder ( name )
self . jenkins_open (
Request ( self . _build_url ( SET_JOB_BUILD_NUMBER , locals ( ) ) ,
( " nextBuildNumber= %d " % number ) . encode ( ' utf-8 ' ) ) )
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
'''
2015-05-05 16:56:09 +02:00
folder_url , short_name = self . _get_job_folder ( name )
if self . get_job_name ( name ) == short_name :
2011-06-03 16:51:39 +01:00
return True
2015-02-13 09:34:25 -08:00
def jobs_count ( self ) :
''' Get the number of jobs on the Jenkins server
: returns : Total number of jobs , ` ` int ` `
2015-05-05 16:56:09 +02:00
. . note : :
On instances with many folders it may be more efficient to use the
run_script method to retrieve the total number of jobs instead .
Example : :
# get all projects excluding matrix configuration
# as they are simply part of a matrix project.
server . run_script (
" print(Hudson.instance.getAllItems( "
" hudson.model.AbstractProject).count { "
" !(it instanceof hudson.matrix.MatrixConfiguration) "
" }) " )
2015-02-13 09:34:25 -08:00
'''
2015-05-05 16:56:09 +02:00
return len ( self . get_all_jobs ( ) )
2015-02-13 09:34:25 -08:00
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
'''
2015-05-05 16:56:09 +02:00
folder_url , short_name = self . _get_job_folder ( name )
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
2015-05-05 16:56:09 +02:00
try :
self . jenkins_open ( Request (
self . _build_url ( CREATE_JOB , locals ( ) ) ,
config_xml . encode ( ' utf-8 ' ) , DEFAULT_HEADERS ) )
except NotFoundException :
raise JenkinsException ( ' Cannot create job[ %s ] because folder '
' for the job does not exist ' % ( name ) )
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
'''
2015-05-05 16:56:09 +02:00
folder_url , short_name = self . _get_job_folder ( name )
2015-07-24 18:20:22 +01:00
request = Request ( self . _build_url ( CONFIG_JOB , 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
'''
2015-05-05 16:56:09 +02:00
folder_url , short_name = self . _get_job_folder ( name )
2015-07-24 18:20:22 +01:00
reconfig_url = self . _build_url ( CONFIG_JOB , locals ( ) )
2015-05-14 18:07:36 +01:00
self . jenkins_open ( Request ( reconfig_url , config_xml . encode ( ' utf-8 ' ) ,
DEFAULT_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
'''
2015-05-05 16:56:09 +02:00
folder_url , short_name = self . _get_job_folder ( name )
2011-06-03 16:51:39 +01:00
if parameters :
if token :
parameters [ ' token ' ] = token
2015-07-24 18:20:22 +01:00
return ( self . _build_url ( 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 :
2015-07-24 18:20:22 +01:00
return ( self . _build_url ( BUILD_JOB , locals ( ) ) +
2014-04-06 08:34:22 -07:00
' ? ' + urlencode ( { ' token ' : token } ) )
2011-06-03 16:51:39 +01:00
else :
2015-07-24 18:20:22 +01:00
return self . _build_url ( 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-08 09:18:17 -07:00
return self . jenkins_open ( Request (
2015-05-14 18:07:36 +01:00
self . build_job_url ( name , parameters , token ) , b ' ' ) )
2012-03-02 16:26:13 +00:00
Support scriptText api to execute groovy scripts on the server
Jenkins features a nice script console[1] which allows users
to remotely run arbitrary groovy scripts on the Jenkins master.
Examples:
$ curl -d 'script=println("hello world")' -X POST
http://localhost:8080/scriptText
hello world
$ curl -d 'script=println(Jenkins.instance.pluginManager.plugins)' -X POST
http://localhost:8080/scriptText
[Plugin:windows-slaves, Plugin:ssh-slaves, Plugin:translation, Plugin:cvs,
Plugin:nodelabelparameter, Plugin:external-monitor-job, Plugin:subversion,
Plugin:ssh-credentials, Plugin:token-macro, Plugin:ant, Plugin:ldap,
Plugin:credentials, Plugin:matrix-auth, Plugin:matrix-project, Plugin:mailer,
Plugin:jquery, Plugin:maven-plugin, Plugin:pam-auth]
[1] https://wiki.jenkins-ci.org/display/JENKINS/Jenkins+Script+Console
Change-Id: Ia4fbfca970165d890d7e076f47ddcde7633afa9b
2015-06-22 22:13:16 -04:00
def run_script ( self , script ) :
''' Execute a groovy script on the jenkins master.
: param script : The groovy script , ` ` string ` `
: returns : The result of the script run .
Example : :
2015-07-30 12:37:25 -07:00
>> > info = server . run_script ( " println(Jenkins.instance.pluginManager.plugins) " )
Support scriptText api to execute groovy scripts on the server
Jenkins features a nice script console[1] which allows users
to remotely run arbitrary groovy scripts on the Jenkins master.
Examples:
$ curl -d 'script=println("hello world")' -X POST
http://localhost:8080/scriptText
hello world
$ curl -d 'script=println(Jenkins.instance.pluginManager.plugins)' -X POST
http://localhost:8080/scriptText
[Plugin:windows-slaves, Plugin:ssh-slaves, Plugin:translation, Plugin:cvs,
Plugin:nodelabelparameter, Plugin:external-monitor-job, Plugin:subversion,
Plugin:ssh-credentials, Plugin:token-macro, Plugin:ant, Plugin:ldap,
Plugin:credentials, Plugin:matrix-auth, Plugin:matrix-project, Plugin:mailer,
Plugin:jquery, Plugin:maven-plugin, Plugin:pam-auth]
[1] https://wiki.jenkins-ci.org/display/JENKINS/Jenkins+Script+Console
Change-Id: Ia4fbfca970165d890d7e076f47ddcde7633afa9b
2015-06-22 22:13:16 -04:00
>> > print ( info )
u ' [Plugin:windows-slaves, Plugin:ssh-slaves, Plugin:translation,
Plugin : cvs , Plugin : nodelabelparameter , Plugin : external - monitor - job ,
Plugin : mailer , Plugin : jquery , Plugin : antisamy - markup - formatter ,
Plugin : maven - plugin , Plugin : pam - auth ] '
'''
2015-07-24 18:20:22 +01:00
return self . jenkins_open ( Request ( self . _build_url ( SCRIPT_TEXT ) ,
Support scriptText api to execute groovy scripts on the server
Jenkins features a nice script console[1] which allows users
to remotely run arbitrary groovy scripts on the Jenkins master.
Examples:
$ curl -d 'script=println("hello world")' -X POST
http://localhost:8080/scriptText
hello world
$ curl -d 'script=println(Jenkins.instance.pluginManager.plugins)' -X POST
http://localhost:8080/scriptText
[Plugin:windows-slaves, Plugin:ssh-slaves, Plugin:translation, Plugin:cvs,
Plugin:nodelabelparameter, Plugin:external-monitor-job, Plugin:subversion,
Plugin:ssh-credentials, Plugin:token-macro, Plugin:ant, Plugin:ldap,
Plugin:credentials, Plugin:matrix-auth, Plugin:matrix-project, Plugin:mailer,
Plugin:jquery, Plugin:maven-plugin, Plugin:pam-auth]
[1] https://wiki.jenkins-ci.org/display/JENKINS/Jenkins+Script+Console
Change-Id: Ia4fbfca970165d890d7e076f47ddcde7633afa9b
2015-06-22 22:13:16 -04:00
" script= " . encode ( ' utf-8 ' ) + script . encode ( ' utf-8 ' ) ) )
2015-08-07 09:44:56 -07:00
def install_plugin ( self , name , include_dependencies = True ) :
''' Install a plugin and its dependencies from the Jenkins public
repository at http : / / repo . jenkins - ci . org / repo / org / jenkins - ci / plugins
: param name : The plugin short name , ` ` string ` `
: param include_dependencies : Install the plugin ' s dependencies, ``bool``
: returns : Whether a Jenkins restart is required , ` ` bool ` `
Example : :
>> > info = server . install_plugin ( " jabber " )
>> > print ( info )
True
'''
# using a groovy script because Jenkins does not provide a REST endpoint
# for installing plugins.
install = ( ' Jenkins.instance.updateCenter.getPlugin( \" ' + name + ' \" ) '
' .deploy(); ' )
if include_dependencies :
install = ( ' Jenkins.instance.updateCenter.getPlugin( \" ' + name + ' \" ) '
' .getNeededDependencies().each { it.deploy()}; ' ) + install
self . run_script ( install )
# run_script is an async call to run groovy. we need to wait a little
# before we can get a reliable response on whether a restart is needed
time . sleep ( 2 )
is_restart_required = ( ' Jenkins.instance.updateCenter '
' .isRestartRequiredForCompletion() ' )
# response is a string (i.e. u'Result: true\n'), return a bool instead
response_str = self . run_script ( is_restart_required )
response = response_str . split ( ' : ' ) [ 1 ] . strip ( ) . lower ( ) == ' true '
return response
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 ` `
'''
2015-05-05 16:56:09 +02:00
folder_url , short_name = self . _get_job_folder ( name )
2015-07-01 14:52:16 -07:00
self . jenkins_open ( Request (
2015-07-24 18:20:22 +01:00
self . _build_url ( STOP_BUILD , locals ( ) ) , b ' ' ) )
2012-06-20 14:41:59 -07:00
2015-09-14 13:15:36 -07:00
def get_running_builds ( self ) :
''' Return list of running builds.
Each build is a dict with keys ' name ' , ' number ' , ' url ' , ' node ' ,
and ' executor ' .
: returns : List of builds ,
` ` [ { str : str , str : int , str : str , str : str , str : int } ] ` `
Example : :
>> > builds = server . get_running_builds ( )
>> > print ( builds )
[ { ' node ' : ' foo-slave ' , ' url ' : ' https://localhost/job/test/15/ ' ,
' executor ' : 0 , ' name ' : ' test ' , ' number ' : 15 } ]
'''
builds = [ ]
nodes = self . get_nodes ( )
for node in nodes :
# the name returned is not the name to lookup when
# dealing with master :/
if node [ ' name ' ] == ' master ' :
node_name = ' (master) '
else :
node_name = node [ ' name ' ]
2015-10-07 15:25:45 -07:00
try :
info = self . get_node_info ( node_name , depth = 2 )
except JenkinsException as e :
# Jenkins may 500 on depth >0. If the node info comes back
# at depth 0 treat it as a node not running any jobs.
if ( ' [500] ' in str ( e ) and
self . get_node_info ( node_name , depth = 0 ) ) :
continue
else :
raise
2015-09-14 13:15:36 -07:00
for executor in info [ ' executors ' ] :
executable = executor [ ' currentExecutable ' ]
if executable :
executor_number = executor [ ' number ' ]
build_number = executable [ ' number ' ]
url = executable [ ' url ' ]
m = re . match ( r ' /job/([^/]+)/.* ' , urlparse ( url ) . path )
job_name = m . group ( 1 )
builds . append ( { ' name ' : job_name ,
' number ' : build_number ,
' url ' : url ,
' node ' : node_name ,
' executor ' : executor_number } )
return builds
2015-03-10 18:13:45 +02:00
def get_nodes ( self ) :
''' Get a list of nodes connected to the Master
Each node is a dict with keys ' name ' and ' offline '
: returns : List of nodes , ` ` [ { str : str , str : bool } ] ` `
'''
try :
2015-07-24 18:20:22 +01:00
nodes_data = json . loads ( self . jenkins_open ( Request ( self . _build_url ( NODE_LIST ) ) ) )
2015-03-10 18:13:45 +02:00
return [ { ' name ' : c [ " displayName " ] , ' offline ' : c [ " offline " ] }
for c in nodes_data [ " computer " ] ]
except ( HTTPError , BadStatusLine ) :
raise BadHTTPException ( " Error communicating with server[ %s ] "
% self . server )
except ValueError :
raise JenkinsException ( " Could not parse JSON info for server[ %s ] "
% self . server )
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 (
2015-07-24 18:20:22 +01:00
self . _build_url ( 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 (
2015-07-24 18:20:22 +01:00
self . _build_url ( DELETE_NODE , locals ( ) ) , b ' ' ) )
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 (
2015-07-24 18:20:22 +01:00
self . _build_url ( TOGGLE_OFFLINE , locals ( ) ) , b ' ' ) )
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 (
2015-07-24 18:20:22 +01:00
self . _build_url ( TOGGLE_OFFLINE , locals ( ) ) , b ' ' ) )
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 (
2015-07-24 18:20:22 +01:00
self . _build_url ( CREATE_NODE , params ) , b ' ' ) )
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 ` `
'''
2015-07-24 18:20:22 +01:00
get_config_url = self . _build_url ( CONFIG_NODE , 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 ` `
'''
2015-07-24 18:20:22 +01:00
reconfig_url = self . _build_url ( CONFIG_NODE , locals ( ) )
2015-05-18 12:30:52 -07:00
self . jenkins_open ( Request ( reconfig_url , config_xml . encode ( ' utf-8 ' ) , DEFAULT_HEADERS ) )
2014-04-22 16:55:37 +02:00
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 ` `
'''
2015-05-05 16:56:09 +02:00
folder_url , short_name = self . _get_job_folder ( name )
2013-05-11 20:48:02 -07:00
try :
2014-04-08 09:18:17 -07:00
response = self . jenkins_open ( Request (
2015-07-24 18:20:22 +01:00
self . _build_url ( BUILD_CONSOLE_OUTPUT , 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 ) )
2015-06-22 13:22:45 -05:00
2015-05-05 16:56:09 +02:00
def _get_job_folder ( self , name ) :
''' Return the name and folder (see cloudbees plugin).
This is a method to support cloudbees folder plugin .
Url request should take into account folder path when the job name specify it
( ex . : ' folder/job ' )
: param name : Job name , ` ` str ` `
: returns : Tuple [ ' folder path for Request ' , ' Name of job without folder path ' ]
'''
a_path = name . split ( ' / ' )
short_name = a_path [ - 1 ]
folder_url = ( ( ' job/ ' + ' /job/ ' . join ( a_path [ : - 1 ] ) + ' / ' )
if len ( a_path ) > 1 else ' ' )
return folder_url , short_name
2016-02-12 22:40:56 +03:00
def _get_view_jobs ( self , view_name ) :
''' Get list of jobs on the view specified.
Each job is a dictionary with ' name ' , ' url ' , ' color ' and ' fullname '
keys .
The list of jobs is limited to only those configured in the
specified view . Each job dictionary ' fullname ' key
is equal to the job name .
: param view_name : Name of a Jenkins view for which to
retrieve jobs , ` ` str ` ` .
: returns : list of jobs , ` ` [ { str : str , str : str , str : str , str : str } ] ` `
'''
try :
response = self . jenkins_open ( Request (
self . _build_url ( VIEW_JOBS , { u ' name ' : view_name } )
) )
if response :
jobs = json . loads ( response ) [ ' jobs ' ]
else :
raise JenkinsException ( ' view[ %s ] does not exist ' % view_name )
except HTTPError :
raise JenkinsException ( ' view[ %s ] does not exist ' % view_name )
except ValueError :
raise JenkinsException (
' Could not parse JSON info for view[ %s ] ' % view_name )
for job_dict in jobs :
job_dict . update ( { u ' fullname ' : job_dict [ u ' name ' ] } )
return jobs
2015-06-22 13:22:45 -05:00
def get_view_name ( self , name ) :
''' Return the name of a view using the API.
That is roughly an identity method which can be used to quickly verify
a view exists or is accessible without causing too much stress on the
server side .
: param name : View name , ` ` str ` `
: returns : Name of view or None
'''
try :
2015-07-24 18:20:22 +01:00
response = self . jenkins_open ( Request (
self . _build_url ( VIEW_NAME , locals ( ) ) ) )
2015-06-22 13:22:45 -05:00
except NotFoundException :
return None
else :
actual = json . loads ( response ) [ ' name ' ]
if actual != name :
raise JenkinsException (
' Jenkins returned an unexpected view name %s '
' (expected: %s ) ' % ( actual , name ) )
return actual
def assert_view_exists ( self , name ,
exception_message = ' view[ %s ] does not exist ' ) :
''' Raise an exception if a view does not exist
: param name : Name of Jenkins view , ` ` str ` `
: param exception_message : Message to use for the exception . Formatted
with ` ` name ` `
: throws : : class : ` JenkinsException ` whenever the view does not exist
'''
if not self . view_exists ( name ) :
raise JenkinsException ( exception_message % name )
def view_exists ( self , name ) :
''' Check whether a view exists
: param name : Name of Jenkins view , ` ` str ` `
: returns : ` ` True ` ` if Jenkins view exists
'''
if self . get_view_name ( name ) == name :
return True
def get_views ( self ) :
""" Get list of views running.
Each view is a dictionary with ' name ' and ' url ' keys .
: returns : list of views , ` ` [ { str : str } ] ` `
"""
return self . get_info ( ) [ ' views ' ]
def delete_view ( self , name ) :
''' Delete Jenkins view permanently.
: param name : Name of Jenkins view , ` ` str ` `
'''
self . jenkins_open ( Request (
2015-07-24 18:20:22 +01:00
self . _build_url ( DELETE_VIEW , locals ( ) ) , b ' '
) )
2015-06-22 13:22:45 -05:00
if self . view_exists ( name ) :
raise JenkinsException ( ' delete[ %s ] failed ' % ( name ) )
def create_view ( self , name , config_xml ) :
''' Create a new Jenkins view
: param name : Name of Jenkins view , ` ` str ` `
: param config_xml : config file text , ` ` str ` `
'''
if self . view_exists ( name ) :
raise JenkinsException ( ' view[ %s ] already exists ' % ( name ) )
self . jenkins_open ( Request (
2015-07-24 18:20:22 +01:00
self . _build_url ( CREATE_VIEW , locals ( ) ) ,
2015-06-22 13:22:45 -05:00
config_xml . encode ( ' utf-8 ' ) , DEFAULT_HEADERS ) )
self . assert_view_exists ( name , ' create[ %s ] failed ' )
def reconfig_view ( self , name , config_xml ) :
''' Change configuration of existing Jenkins view.
To create a new view , see : meth : ` Jenkins . create_view ` .
: param name : Name of Jenkins view , ` ` str ` `
: param config_xml : New XML configuration , ` ` str ` `
'''
2015-07-24 18:20:22 +01:00
reconfig_url = self . _build_url ( CONFIG_VIEW , locals ( ) )
2015-06-22 13:22:45 -05:00
self . jenkins_open ( Request ( reconfig_url , config_xml . encode ( ' utf-8 ' ) ,
DEFAULT_HEADERS ) )
def get_view_config ( self , name ) :
''' Get configuration of existing Jenkins view.
: param name : Name of Jenkins view , ` ` str ` `
: returns : view configuration ( XML format )
'''
2015-07-24 18:20:22 +01:00
request = Request ( self . _build_url ( CONFIG_VIEW , locals ( ) ) )
2015-06-22 13:22:45 -05:00
return self . jenkins_open ( request )
2015-09-10 15:04:51 -07:00
2016-01-12 18:42:17 +01:00
def get_promotion_name ( self , name , job_name ) :
''' Return the name of a promotion using the API.
That is roughly an identity method which can be used to
quickly verify a promotion exists for a job or is accessible
without causing too much stress on the server side .
: param job_name : Job name , ` ` str ` `
: param name : Promotion name , ` ` str ` `
: returns : Name of promotion or None
'''
folder_url , short_name = self . _get_job_folder ( job_name )
try :
response = self . jenkins_open ( Request (
self . _build_url ( PROMOTION_NAME , locals ( ) ) ) )
except NotFoundException :
return None
else :
actual = json . loads ( response ) [ ' name ' ]
if actual != name :
raise JenkinsException (
' Jenkins returned an unexpected promotion name %s '
' (expected: %s ) ' % ( actual , name ) )
return actual
def assert_promotion_exists ( self , name , job_name ,
exception_message = ' promotion[ %s ] does not '
' exist for job[ %s ] ' ) :
''' Raise an exception if a job lacks a promotion
: param job_name : Job name , ` ` str ` `
: param name : Name of Jenkins promotion , ` ` str ` `
: param exception_message : Message to use for the exception . Formatted
with ` ` name ` ` and ` ` job_name ` `
: throws : : class : ` JenkinsException ` whenever the promotion
does not exist on a job
'''
if not self . promotion_exists ( name , job_name ) :
raise JenkinsException ( exception_message % ( name , job_name ) )
def promotion_exists ( self , name , job_name ) :
''' Check whether a job has a certain promotion
: param job_name : Job name , ` ` str ` `
: param name : Name of Jenkins promotion , ` ` str ` `
: returns : ` ` True ` ` if Jenkins promotion exists
'''
return self . get_promotion_name ( name , job_name ) == name
def get_promotions_info ( self , job_name , depth = 0 ) :
''' Get promotion information dictionary of a job
: param name : job_name , ` ` str ` `
: param depth : JSON depth , ` ` int ` `
: returns : Dictionary of promotion info , ` ` dict ` `
'''
folder_url , short_name = self . _get_job_folder ( job_name )
try :
response = self . jenkins_open ( Request (
self . _build_url ( PROMOTION_INFO , locals ( ) ) ) )
if response :
return json . loads ( response )
else :
raise JenkinsException ( ' job[ %s ] does not exist ' % job_name )
except HTTPError :
raise JenkinsException ( ' job[ %s ] does not exist ' % job_name )
except ValueError :
raise JenkinsException ( " Could not parse JSON info for "
" promotions of job[ %s ] " % job_name )
def get_promotions ( self , job_name ) :
""" Get list of promotions running.
Each promotion is a dictionary with ' name ' and ' url ' keys .
: param job_name : Job name , ` ` str ` `
: returns : list of promotions , ` ` [ { str : str } ] ` `
"""
return self . get_promotions_info ( job_name ) [ ' processes ' ]
def delete_promotion ( self , name , job_name ) :
''' Delete Jenkins promotion permanently.
: param job_name : Job name , ` ` str ` `
: param name : Name of Jenkins promotion , ` ` str ` `
'''
folder_url , short_name = self . _get_job_folder ( job_name )
self . jenkins_open ( Request (
self . _build_url ( DELETE_PROMOTION , locals ( ) ) , b ' '
) )
if self . promotion_exists ( name , job_name ) :
raise JenkinsException ( ' delete[ %s ] from job[ %s ] failed ' %
( name , job_name ) )
def create_promotion ( self , name , job_name , config_xml ) :
''' Create a new Jenkins promotion
: param name : Name of Jenkins promotion , ` ` str ` `
: param job_name : Job name , ` ` str ` `
: param config_xml : config file text , ` ` str ` `
'''
if self . promotion_exists ( name , job_name ) :
raise JenkinsException ( ' promotion[ %s ] already exists at job[ %s ] '
% ( name , job_name ) )
folder_url , short_name = self . _get_job_folder ( job_name )
self . jenkins_open ( Request (
self . _build_url ( CREATE_PROMOTION , locals ( ) ) ,
config_xml . encode ( ' utf-8 ' ) , DEFAULT_HEADERS ) )
self . assert_promotion_exists ( name , job_name , ' create[ %s ] at '
' job[ %s ] failed ' )
def reconfig_promotion ( self , name , job_name , config_xml ) :
''' Change configuration of existing Jenkins promotion.
To create a new promotion , see : meth : ` Jenkins . create_promotion ` .
: param name : Name of Jenkins promotion , ` ` str ` `
: param job_name : Job name , ` ` str ` `
: param config_xml : New XML configuration , ` ` str ` `
'''
folder_url , short_name = self . _get_job_folder ( job_name )
reconfig_url = self . _build_url ( CONFIG_PROMOTION , locals ( ) )
self . jenkins_open ( Request ( reconfig_url , config_xml . encode ( ' utf-8 ' ) ,
DEFAULT_HEADERS ) )
def get_promotion_config ( self , name , job_name ) :
''' Get configuration of existing Jenkins promotion.
: param name : Name of Jenkins promotion , ` ` str ` `
: param job_name : Job name , ` ` str ` `
: returns : promotion configuration ( XML format )
'''
folder_url , short_name = self . _get_job_folder ( job_name )
request = Request ( self . _build_url ( CONFIG_PROMOTION , locals ( ) ) )
return self . jenkins_open ( request )
2015-09-10 15:04:51 -07:00
def quiet_down ( self ) :
''' Prepare Jenkins for shutdown.
No new builds will be started allowing running builds to complete
prior to shutdown of the server .
'''
request = Request ( self . _build_url ( QUIET_DOWN ) )
self . jenkins_open ( request )
info = self . get_info ( )
if not info [ ' quietingDown ' ] :
raise JenkinsException ( ' quiet down failed ' )