Merge "Migration to using requests"
This commit is contained in:
commit
7166e97eb2
@ -46,7 +46,6 @@
|
||||
See examples at :doc:`examples`
|
||||
'''
|
||||
|
||||
import base64
|
||||
import json
|
||||
import re
|
||||
import socket
|
||||
@ -55,24 +54,18 @@ import time
|
||||
import warnings
|
||||
|
||||
import multi_key_dict
|
||||
import six
|
||||
import requests
|
||||
import requests.exceptions as req_exc
|
||||
from six.moves.http_client import BadStatusLine
|
||||
from six.moves.urllib.error import HTTPError
|
||||
from six.moves.urllib.error import URLError
|
||||
from six.moves.urllib.parse import quote, urlencode, urljoin, urlparse
|
||||
from six.moves.urllib.request import Request, install_opener, build_opener, urlopen
|
||||
|
||||
from jenkins import plugins
|
||||
|
||||
try:
|
||||
import kerberos
|
||||
assert kerberos # pyflakes
|
||||
from jenkins import urllib_kerb
|
||||
opener = build_opener()
|
||||
opener.add_handler(urllib_kerb.HTTPNegotiateHandler())
|
||||
install_opener(opener)
|
||||
import requests_kerberos
|
||||
except ImportError:
|
||||
pass
|
||||
requests_kerberos = None
|
||||
|
||||
|
||||
if sys.version_info < (2, 7, 0):
|
||||
@ -240,17 +233,6 @@ class TimeoutException(JenkinsException):
|
||||
'''A special exception to call out in the case of a socket timeout.'''
|
||||
|
||||
|
||||
def auth_headers(username, password):
|
||||
'''Simple implementation of HTTP Basic Authentication.
|
||||
|
||||
Returns the 'Authentication' header value.
|
||||
'''
|
||||
auth = '%s:%s' % (username, password)
|
||||
if isinstance(auth, six.text_type):
|
||||
auth = auth.encode('utf-8')
|
||||
return b'Basic ' + base64.b64encode(auth)
|
||||
|
||||
|
||||
class Jenkins(object):
|
||||
_timeout_warning_issued = False
|
||||
|
||||
@ -269,12 +251,25 @@ class Jenkins(object):
|
||||
self.server = url
|
||||
else:
|
||||
self.server = url + '/'
|
||||
|
||||
self._auths = [('anonymous', None)]
|
||||
self._auth_resolved = False
|
||||
if username is not None and password is not None:
|
||||
self.auth = auth_headers(username, password)
|
||||
else:
|
||||
self.auth = None
|
||||
self._auths[0] = (
|
||||
'basic',
|
||||
requests.auth.HTTPBasicAuth(
|
||||
username.encode('utf-8'), password.encode('utf-8'))
|
||||
)
|
||||
|
||||
if requests_kerberos is not None:
|
||||
self._auths.append(
|
||||
('kerberos', requests_kerberos.HTTPKerberosAuth())
|
||||
)
|
||||
|
||||
self.auth = None
|
||||
self.crumb = None
|
||||
self.timeout = timeout
|
||||
self._session = requests.Session()
|
||||
|
||||
def _get_encoded_params(self, params):
|
||||
for k, v in params.items():
|
||||
@ -296,14 +291,48 @@ class Jenkins(object):
|
||||
# We don't know yet whether we need a crumb
|
||||
if self.crumb is None:
|
||||
try:
|
||||
response = self.jenkins_open(Request(
|
||||
self._build_url(CRUMB_URL)), add_crumb=False)
|
||||
response = self.jenkins_open(requests.Request(
|
||||
'GET', self._build_url(CRUMB_URL)), add_crumb=False)
|
||||
except (NotFoundException, EmptyResponseException):
|
||||
self.crumb = False
|
||||
else:
|
||||
self.crumb = json.loads(response)
|
||||
if self.crumb:
|
||||
req.add_header(self.crumb['crumbRequestField'], self.crumb['crumb'])
|
||||
req.headers[self.crumb['crumbRequestField']] = self.crumb['crumb']
|
||||
|
||||
def _maybe_add_auth(self):
|
||||
|
||||
if self._auth_resolved:
|
||||
return
|
||||
|
||||
if len(self._auths) == 1:
|
||||
# If we only have one auth mechanism specified, just require it
|
||||
self._session.auth = self._auths[0][1]
|
||||
else:
|
||||
# Attempt the list of auth mechanisms and keep the first that works
|
||||
# otherwise default to the first one in the list (last popped).
|
||||
# This is a hack to allow the transparent use of kerberos to work
|
||||
# in future, we should require explicit request to use kerberos
|
||||
failures = []
|
||||
for name, auth in reversed(self._auths):
|
||||
try:
|
||||
self.jenkins_open(
|
||||
requests.Request('GET', self._build_url(INFO),
|
||||
auth=auth),
|
||||
add_crumb=False, resolve_auth=False)
|
||||
self._session.auth = auth
|
||||
break
|
||||
except Exception as exc:
|
||||
# assume authentication failure
|
||||
failures.append("auth(%s) %s" % (name, exc))
|
||||
continue
|
||||
else:
|
||||
raise JenkinsException(
|
||||
'Unable to authenticate with any scheme:\n%s'
|
||||
% '\n'.join(failures))
|
||||
|
||||
self._auth_resolved = True
|
||||
self.auth = self._session.auth
|
||||
|
||||
def _add_missing_builds(self, data):
|
||||
"""Query Jenkins to get all builds of a job.
|
||||
@ -327,8 +356,9 @@ class Jenkins(object):
|
||||
if all_builds_loaded:
|
||||
return data
|
||||
folder_url, short_name = self._get_job_folder(data["name"])
|
||||
response = self.jenkins_open(Request(self._build_url(ALL_BUILDS,
|
||||
locals())))
|
||||
response = self.jenkins_open(requests.Request(
|
||||
'GET', self._build_url(ALL_BUILDS, locals())
|
||||
))
|
||||
if response:
|
||||
data["builds"] = json.loads(response)["allBuilds"]
|
||||
else:
|
||||
@ -352,8 +382,8 @@ class Jenkins(object):
|
||||
'''
|
||||
folder_url, short_name = self._get_job_folder(name)
|
||||
try:
|
||||
response = self.jenkins_open(Request(
|
||||
self._build_url(JOB_INFO, locals())
|
||||
response = self.jenkins_open(requests.Request(
|
||||
'GET', self._build_url(JOB_INFO, locals())
|
||||
))
|
||||
if response:
|
||||
if fetch_all_builds:
|
||||
@ -362,7 +392,7 @@ class Jenkins(object):
|
||||
return json.loads(response)
|
||||
else:
|
||||
raise JenkinsException('job[%s] does not exist' % name)
|
||||
except HTTPError:
|
||||
except (req_exc.HTTPError, NotFoundException):
|
||||
raise JenkinsException('job[%s] does not exist' % name)
|
||||
except ValueError:
|
||||
raise JenkinsException(
|
||||
@ -397,8 +427,8 @@ class Jenkins(object):
|
||||
'''
|
||||
folder_url, short_name = self._get_job_folder(name)
|
||||
try:
|
||||
response = self.jenkins_open(Request(
|
||||
self._build_url(JOB_NAME, locals())
|
||||
response = self.jenkins_open(requests.Request(
|
||||
'GET', self._build_url(JOB_NAME, locals())
|
||||
))
|
||||
except NotFoundException:
|
||||
return None
|
||||
@ -415,40 +445,57 @@ class Jenkins(object):
|
||||
for k, v in self.get_job_info(job_name).items():
|
||||
print(k, v)
|
||||
|
||||
def jenkins_open(self, req, add_crumb=True):
|
||||
def _response_handler(self, response):
|
||||
'''Handle response objects'''
|
||||
|
||||
# raise exceptions if occurred
|
||||
response.raise_for_status()
|
||||
|
||||
headers = response.headers
|
||||
if (headers.get('content-length') is None and
|
||||
headers.get('transfer-encoding') is None):
|
||||
# response body should only exist if one of these is provided
|
||||
raise EmptyResponseException(
|
||||
"Error communicating with server[%s]: "
|
||||
"empty response" % self.server)
|
||||
|
||||
# Reponse objects will automatically return unicode encoded
|
||||
# when accessing .text property
|
||||
return response
|
||||
|
||||
def _request(self, req):
|
||||
|
||||
r = self._session.prepare_request(req)
|
||||
return self._session.send(r, timeout=self.timeout)
|
||||
|
||||
def jenkins_open(self, req, add_crumb=True, resolve_auth=True):
|
||||
'''Utility routine for opening an HTTP request to a Jenkins server.
|
||||
|
||||
This should only be used to extends the :class:`Jenkins` API.
|
||||
'''
|
||||
try:
|
||||
if self.auth:
|
||||
req.add_header('Authorization', self.auth)
|
||||
if resolve_auth:
|
||||
self._maybe_add_auth()
|
||||
if add_crumb:
|
||||
self.maybe_add_crumb(req)
|
||||
response = urlopen(req, timeout=self.timeout).read()
|
||||
if response is None:
|
||||
raise EmptyResponseException(
|
||||
"Error communicating with server[%s]: "
|
||||
"empty response" % self.server)
|
||||
return response.decode('utf-8')
|
||||
except HTTPError as e:
|
||||
|
||||
return self._response_handler(
|
||||
self._request(req)).text
|
||||
|
||||
except req_exc.HTTPError as e:
|
||||
# Jenkins's funky authentication means its nigh impossible to
|
||||
# distinguish errors.
|
||||
if e.code in [401, 403, 500]:
|
||||
# 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
|
||||
if e.response.status_code in [401, 403, 500]:
|
||||
raise JenkinsException(
|
||||
'Error in request. ' +
|
||||
'Possibly authentication failed [%s]: %s' % (
|
||||
e.code, e.msg)
|
||||
e.response.status_code, e.response.reason)
|
||||
)
|
||||
elif e.code == 404:
|
||||
elif e.response.status_code == 404:
|
||||
raise NotFoundException('Requested item could not be found')
|
||||
else:
|
||||
raise
|
||||
except socket.timeout as e:
|
||||
except req_exc.Timeout as e:
|
||||
raise TimeoutException('Error in request: %s' % (e))
|
||||
except URLError as e:
|
||||
# python 2.6 compatibility to ensure same exception raised
|
||||
@ -476,15 +523,15 @@ class Jenkins(object):
|
||||
'''
|
||||
folder_url, short_name = self._get_job_folder(name)
|
||||
try:
|
||||
response = self.jenkins_open(Request(
|
||||
self._build_url(BUILD_INFO, locals())
|
||||
response = self.jenkins_open(requests.Request(
|
||||
'GET', self._build_url(BUILD_INFO, locals())
|
||||
))
|
||||
if response:
|
||||
return json.loads(response)
|
||||
else:
|
||||
raise JenkinsException('job[%s] number[%d] does not exist'
|
||||
% (name, number))
|
||||
except HTTPError:
|
||||
except (req_exc.HTTPError, NotFoundException):
|
||||
raise JenkinsException('job[%s] number[%d] does not exist'
|
||||
% (name, number))
|
||||
except ValueError:
|
||||
@ -502,7 +549,7 @@ class Jenkins(object):
|
||||
{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}
|
||||
'''
|
||||
return json.loads(self.jenkins_open(
|
||||
Request(self._build_url(Q_INFO))
|
||||
requests.Request('GET', self._build_url(Q_INFO))
|
||||
))['items']
|
||||
|
||||
def cancel_queue(self, id):
|
||||
@ -514,8 +561,9 @@ class Jenkins(object):
|
||||
# https://issues.jenkins-ci.org/browse/JENKINS-21311
|
||||
try:
|
||||
self.jenkins_open(
|
||||
Request(self._build_url(CANCEL_QUEUE, locals()), b'',
|
||||
headers={'Referer': self.server}))
|
||||
requests.Request(
|
||||
'POST', self._build_url(CANCEL_QUEUE, locals()),
|
||||
headers={'Referer': self.server}))
|
||||
except NotFoundException:
|
||||
# Exception is expected; cancel_queue() is a best-effort
|
||||
# mechanism, so ignore it
|
||||
@ -546,9 +594,9 @@ class Jenkins(object):
|
||||
url += query
|
||||
try:
|
||||
return json.loads(self.jenkins_open(
|
||||
Request(self._build_url(url))
|
||||
requests.Request('GET', self._build_url(INFO))
|
||||
))
|
||||
except (HTTPError, BadStatusLine):
|
||||
except (req_exc.HTTPError, BadStatusLine):
|
||||
raise BadHTTPException("Error communicating with server[%s]"
|
||||
% self.server)
|
||||
except ValueError:
|
||||
@ -570,7 +618,9 @@ class Jenkins(object):
|
||||
|
||||
"""
|
||||
try:
|
||||
response = self.jenkins_open(Request(self._build_url(WHOAMI_URL)))
|
||||
response = self.jenkins_open(requests.Request(
|
||||
'GET', self._build_url(WHOAMI_URL)
|
||||
))
|
||||
if response is None:
|
||||
raise EmptyResponseException(
|
||||
"Error communicating with server[%s]: "
|
||||
@ -578,7 +628,7 @@ class Jenkins(object):
|
||||
|
||||
return json.loads(response)
|
||||
|
||||
except (HTTPError, BadStatusLine):
|
||||
except (req_exc.HTTPError, BadStatusLine):
|
||||
raise BadHTTPException("Error communicating with server[%s]"
|
||||
% self.server)
|
||||
|
||||
@ -595,21 +645,13 @@ class Jenkins(object):
|
||||
|
||||
"""
|
||||
try:
|
||||
request = Request(self._build_url(''))
|
||||
request.add_header('X-Jenkins', '0.0')
|
||||
response = urlopen(request, timeout=self.timeout)
|
||||
if response is None:
|
||||
raise EmptyResponseException(
|
||||
"Error communicating with server[%s]: "
|
||||
"empty response" % self.server)
|
||||
request = requests.Request('GET', self._build_url(''))
|
||||
request.headers['X-Jenkins'] = '0.0'
|
||||
response = self._response_handler(self._request(request))
|
||||
|
||||
if six.PY2:
|
||||
return response.info().getheader('X-Jenkins')
|
||||
return response.headers['X-Jenkins']
|
||||
|
||||
if six.PY3:
|
||||
return response.getheader('X-Jenkins')
|
||||
|
||||
except (HTTPError, BadStatusLine):
|
||||
except (req_exc.HTTPError, BadStatusLine):
|
||||
raise BadHTTPException("Error communicating with server[%s]"
|
||||
% self.server)
|
||||
|
||||
@ -711,8 +753,8 @@ class Jenkins(object):
|
||||
|
||||
try:
|
||||
plugins_info_json = json.loads(self.jenkins_open(
|
||||
Request(self._build_url(PLUGIN_INFO, locals()))))
|
||||
except (HTTPError, BadStatusLine):
|
||||
requests.Request('GET', self._build_url(PLUGIN_INFO, locals()))))
|
||||
except (req_exc.HTTPError, BadStatusLine):
|
||||
raise BadHTTPException("Error communicating with server[%s]"
|
||||
% self.server)
|
||||
except ValueError:
|
||||
@ -758,7 +800,7 @@ class Jenkins(object):
|
||||
"""
|
||||
|
||||
if view_name:
|
||||
return self._get_view_jobs(view_name=view_name)
|
||||
return self._get_view_jobs(name=view_name)
|
||||
else:
|
||||
return self.get_all_jobs(folder_depth=folder_depth)
|
||||
|
||||
@ -848,8 +890,9 @@ class Jenkins(object):
|
||||
raise JenkinsException('copy[%s to %s] failed, source and destination '
|
||||
'folder must be the same' % (from_name, to_name))
|
||||
|
||||
self.jenkins_open(Request(
|
||||
self._build_url(COPY_JOB, locals()), b''))
|
||||
self.jenkins_open(requests.Request(
|
||||
'POST', self._build_url(COPY_JOB, locals())
|
||||
))
|
||||
self.assert_job_exists(to_name, 'create[%s] failed')
|
||||
|
||||
def rename_job(self, from_name, to_name):
|
||||
@ -868,8 +911,9 @@ class Jenkins(object):
|
||||
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))
|
||||
self.jenkins_open(Request(
|
||||
self._build_url(RENAME_JOB, locals()), b''))
|
||||
self.jenkins_open(requests.Request(
|
||||
'POST', self._build_url(RENAME_JOB, locals())
|
||||
))
|
||||
self.assert_job_exists(to_name, 'rename[%s] failed')
|
||||
|
||||
def delete_job(self, name):
|
||||
@ -878,8 +922,9 @@ class Jenkins(object):
|
||||
:param name: Name of Jenkins job, ``str``
|
||||
'''
|
||||
folder_url, short_name = self._get_job_folder(name)
|
||||
self.jenkins_open(Request(
|
||||
self._build_url(DELETE_JOB, locals()), b''))
|
||||
self.jenkins_open(requests.Request(
|
||||
'POST', self._build_url(DELETE_JOB, locals())
|
||||
))
|
||||
if self.job_exists(name):
|
||||
raise JenkinsException('delete[%s] failed' % (name))
|
||||
|
||||
@ -889,8 +934,9 @@ class Jenkins(object):
|
||||
:param name: Name of Jenkins job, ``str``
|
||||
'''
|
||||
folder_url, short_name = self._get_job_folder(name)
|
||||
self.jenkins_open(Request(
|
||||
self._build_url(ENABLE_JOB, locals()), b''))
|
||||
self.jenkins_open(requests.Request(
|
||||
'POST', self._build_url(ENABLE_JOB, locals())
|
||||
))
|
||||
|
||||
def disable_job(self, name):
|
||||
'''Disable Jenkins job.
|
||||
@ -900,8 +946,9 @@ class Jenkins(object):
|
||||
:param name: Name of Jenkins job, ``str``
|
||||
'''
|
||||
folder_url, short_name = self._get_job_folder(name)
|
||||
self.jenkins_open(Request(
|
||||
self._build_url(DISABLE_JOB, locals()), b''))
|
||||
self.jenkins_open(requests.Request(
|
||||
'POST', self._build_url(DISABLE_JOB, locals())
|
||||
))
|
||||
|
||||
def set_next_build_number(self, name, number):
|
||||
'''Set a job's next build number.
|
||||
@ -924,9 +971,9 @@ class Jenkins(object):
|
||||
>>> 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')))
|
||||
self.jenkins_open(requests.Request(
|
||||
'POST', self._build_url(SET_JOB_BUILD_NUMBER, locals()),
|
||||
data=("nextBuildNumber=%d" % number).encode('utf-8')))
|
||||
|
||||
def job_exists(self, name):
|
||||
'''Check whether a job exists
|
||||
@ -984,9 +1031,11 @@ class Jenkins(object):
|
||||
raise JenkinsException('job[%s] already exists' % (name))
|
||||
|
||||
try:
|
||||
self.jenkins_open(Request(
|
||||
self._build_url(CREATE_JOB, locals()),
|
||||
config_xml.encode('utf-8'), DEFAULT_HEADERS))
|
||||
self.jenkins_open(requests.Request(
|
||||
'POST', self._build_url(CREATE_JOB, locals()),
|
||||
data=config_xml.encode('utf-8'),
|
||||
headers=DEFAULT_HEADERS
|
||||
))
|
||||
except NotFoundException:
|
||||
raise JenkinsException('Cannot create job[%s] because folder '
|
||||
'for the job does not exist' % (name))
|
||||
@ -999,7 +1048,7 @@ class Jenkins(object):
|
||||
:returns: job configuration (XML format)
|
||||
'''
|
||||
folder_url, short_name = self._get_job_folder(name)
|
||||
request = Request(self._build_url(CONFIG_JOB, locals()))
|
||||
request = requests.Request('GET', self._build_url(CONFIG_JOB, locals()))
|
||||
return self.jenkins_open(request)
|
||||
|
||||
def reconfig_job(self, name, config_xml):
|
||||
@ -1012,8 +1061,11 @@ class Jenkins(object):
|
||||
'''
|
||||
folder_url, short_name = self._get_job_folder(name)
|
||||
reconfig_url = self._build_url(CONFIG_JOB, locals())
|
||||
self.jenkins_open(Request(reconfig_url, config_xml.encode('utf-8'),
|
||||
DEFAULT_HEADERS))
|
||||
self.jenkins_open(requests.Request(
|
||||
'POST', reconfig_url,
|
||||
data=config_xml.encode('utf-8'),
|
||||
headers=DEFAULT_HEADERS
|
||||
))
|
||||
|
||||
def build_job_url(self, name, parameters=None, token=None):
|
||||
'''Get URL to trigger build job.
|
||||
@ -1044,8 +1096,8 @@ class Jenkins(object):
|
||||
:param parameters: parameters for job, or ``None``, ``dict``
|
||||
:param token: Jenkins API token
|
||||
'''
|
||||
return self.jenkins_open(Request(
|
||||
self.build_job_url(name, parameters, token), b''))
|
||||
return self.jenkins_open(requests.Request(
|
||||
'POST', self.build_job_url(name, parameters, token)))
|
||||
|
||||
def run_script(self, script):
|
||||
'''Execute a groovy script on the jenkins master.
|
||||
@ -1061,8 +1113,10 @@ class Jenkins(object):
|
||||
Plugin:mailer, Plugin:jquery, Plugin:antisamy-markup-formatter,
|
||||
Plugin:maven-plugin, Plugin:pam-auth]'
|
||||
'''
|
||||
return self.jenkins_open(Request(self._build_url(SCRIPT_TEXT),
|
||||
"script=".encode('utf-8') + quote(script).encode('utf-8')))
|
||||
return self.jenkins_open(
|
||||
requests.Request(
|
||||
'POST', self._build_url(SCRIPT_TEXT),
|
||||
data="script=".encode('utf-8') + quote(script).encode('utf-8')))
|
||||
|
||||
def install_plugin(self, name, include_dependencies=True):
|
||||
'''Install a plugin and its dependencies from the Jenkins public
|
||||
@ -1104,8 +1158,9 @@ class Jenkins(object):
|
||||
:param number: Jenkins build number for the job, ``int``
|
||||
'''
|
||||
folder_url, short_name = self._get_job_folder(name)
|
||||
self.jenkins_open(Request(
|
||||
self._build_url(STOP_BUILD, locals()), b''))
|
||||
self.jenkins_open(requests.Request(
|
||||
'POST', self._build_url(STOP_BUILD, locals())
|
||||
))
|
||||
|
||||
def get_running_builds(self):
|
||||
'''Return list of running builds.
|
||||
@ -1164,10 +1219,11 @@ class Jenkins(object):
|
||||
:returns: List of nodes, ``[ { str: str, str: bool} ]``
|
||||
'''
|
||||
try:
|
||||
nodes_data = json.loads(self.jenkins_open(Request(self._build_url(NODE_LIST))))
|
||||
nodes_data = json.loads(self.jenkins_open(
|
||||
requests.Request('GET', self._build_url(NODE_LIST))))
|
||||
return [{'name': c["displayName"], 'offline': c["offline"]}
|
||||
for c in nodes_data["computer"]]
|
||||
except (HTTPError, BadStatusLine):
|
||||
except (req_exc.HTTPError, BadStatusLine):
|
||||
raise BadHTTPException("Error communicating with server[%s]"
|
||||
% self.server)
|
||||
except ValueError:
|
||||
@ -1182,13 +1238,14 @@ class Jenkins(object):
|
||||
:returns: Dictionary of node info, ``dict``
|
||||
'''
|
||||
try:
|
||||
response = self.jenkins_open(Request(
|
||||
self._build_url(NODE_INFO, locals())))
|
||||
response = self.jenkins_open(requests.Request(
|
||||
'GET', self._build_url(NODE_INFO, locals())
|
||||
))
|
||||
if response:
|
||||
return json.loads(response)
|
||||
else:
|
||||
raise JenkinsException('node[%s] does not exist' % name)
|
||||
except HTTPError:
|
||||
except (req_exc.HTTPError, NotFoundException):
|
||||
raise JenkinsException('node[%s] does not exist' % name)
|
||||
except ValueError:
|
||||
raise JenkinsException("Could not parse JSON info for node[%s]"
|
||||
@ -1224,8 +1281,9 @@ class Jenkins(object):
|
||||
:param name: Name of Jenkins node, ``str``
|
||||
'''
|
||||
self.get_node_info(name)
|
||||
self.jenkins_open(Request(
|
||||
self._build_url(DELETE_NODE, locals()), b''))
|
||||
self.jenkins_open(requests.Request(
|
||||
'POST', self._build_url(DELETE_NODE, locals())
|
||||
))
|
||||
if self.node_exists(name):
|
||||
raise JenkinsException('delete[%s] failed' % (name))
|
||||
|
||||
@ -1238,8 +1296,9 @@ class Jenkins(object):
|
||||
info = self.get_node_info(name)
|
||||
if info['offline']:
|
||||
return
|
||||
self.jenkins_open(Request(
|
||||
self._build_url(TOGGLE_OFFLINE, locals()), b''))
|
||||
self.jenkins_open(requests.Request(
|
||||
'POST', self._build_url(TOGGLE_OFFLINE, locals())
|
||||
))
|
||||
|
||||
def enable_node(self, name):
|
||||
'''Enable a node
|
||||
@ -1250,8 +1309,9 @@ class Jenkins(object):
|
||||
if not info['offline']:
|
||||
return
|
||||
msg = ''
|
||||
self.jenkins_open(Request(
|
||||
self._build_url(TOGGLE_OFFLINE, locals()), b''))
|
||||
self.jenkins_open(requests.Request(
|
||||
'POST', self._build_url(TOGGLE_OFFLINE, locals())
|
||||
))
|
||||
|
||||
def create_node(self, name, numExecutors=2, nodeDescription=None,
|
||||
remoteFS='/var/lib/jenkins', labels=None, exclusive=False,
|
||||
@ -1296,9 +1356,9 @@ class Jenkins(object):
|
||||
'json': json.dumps(inner_params)
|
||||
}
|
||||
|
||||
self.jenkins_open(Request(
|
||||
self._build_url(CREATE_NODE, locals()),
|
||||
urlencode(params).encode('utf-8')))
|
||||
self.jenkins_open(requests.Request(
|
||||
'POST', self._build_url(CREATE_NODE, locals()), data=params)
|
||||
)
|
||||
|
||||
self.assert_node_exists(name, 'create[%s] failed')
|
||||
|
||||
@ -1308,7 +1368,7 @@ class Jenkins(object):
|
||||
:param name: Jenkins node name, ``str``
|
||||
'''
|
||||
get_config_url = self._build_url(CONFIG_NODE, locals())
|
||||
return self.jenkins_open(Request(get_config_url))
|
||||
return self.jenkins_open(requests.Request('GET', get_config_url))
|
||||
|
||||
def reconfig_node(self, name, config_xml):
|
||||
'''Change the configuration for an existing node.
|
||||
@ -1317,7 +1377,11 @@ class Jenkins(object):
|
||||
:param config_xml: New XML configuration, ``str``
|
||||
'''
|
||||
reconfig_url = self._build_url(CONFIG_NODE, locals())
|
||||
self.jenkins_open(Request(reconfig_url, config_xml.encode('utf-8'), DEFAULT_HEADERS))
|
||||
self.jenkins_open(requests.Request(
|
||||
'POST', reconfig_url,
|
||||
data=config_xml.encode('utf-8'),
|
||||
headers=DEFAULT_HEADERS
|
||||
))
|
||||
|
||||
def get_build_console_output(self, name, number):
|
||||
'''Get build console text.
|
||||
@ -1328,15 +1392,15 @@ class Jenkins(object):
|
||||
'''
|
||||
folder_url, short_name = self._get_job_folder(name)
|
||||
try:
|
||||
response = self.jenkins_open(Request(
|
||||
self._build_url(BUILD_CONSOLE_OUTPUT, locals())
|
||||
response = self.jenkins_open(requests.Request(
|
||||
'GET', self._build_url(BUILD_CONSOLE_OUTPUT, locals())
|
||||
))
|
||||
if response:
|
||||
return response
|
||||
else:
|
||||
raise JenkinsException('job[%s] number[%d] does not exist'
|
||||
% (name, number))
|
||||
except HTTPError:
|
||||
except (req_exc.HTTPError, NotFoundException):
|
||||
raise JenkinsException('job[%s] number[%d] does not exist'
|
||||
% (name, number))
|
||||
|
||||
@ -1358,7 +1422,7 @@ class Jenkins(object):
|
||||
|
||||
return folder_url, short_name
|
||||
|
||||
def _get_view_jobs(self, view_name):
|
||||
def _get_view_jobs(self, name):
|
||||
'''Get list of jobs on the view specified.
|
||||
|
||||
Each job is a dictionary with 'name', 'url', 'color' and 'fullname'
|
||||
@ -1374,18 +1438,18 @@ class Jenkins(object):
|
||||
'''
|
||||
|
||||
try:
|
||||
response = self.jenkins_open(Request(
|
||||
self._build_url(VIEW_JOBS, {u'name': view_name})
|
||||
response = self.jenkins_open(requests.Request(
|
||||
'GET', self._build_url(VIEW_JOBS, locals())
|
||||
))
|
||||
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)
|
||||
raise JenkinsException('view[%s] does not exist' % name)
|
||||
except NotFoundException:
|
||||
raise JenkinsException('view[%s] does not exist' % name)
|
||||
except ValueError:
|
||||
raise JenkinsException(
|
||||
'Could not parse JSON info for view[%s]' % view_name)
|
||||
'Could not parse JSON info for view[%s]' % name)
|
||||
|
||||
for job_dict in jobs:
|
||||
job_dict.update({u'fullname': job_dict[u'name']})
|
||||
@ -1403,8 +1467,8 @@ class Jenkins(object):
|
||||
:returns: Name of view or None
|
||||
'''
|
||||
try:
|
||||
response = self.jenkins_open(Request(
|
||||
self._build_url(VIEW_NAME, locals())))
|
||||
response = self.jenkins_open(requests.Request(
|
||||
'GET', self._build_url(VIEW_NAME, locals())))
|
||||
except NotFoundException:
|
||||
return None
|
||||
else:
|
||||
@ -1425,7 +1489,7 @@ class Jenkins(object):
|
||||
:throws: :class:`JenkinsException` whenever the view does not exist
|
||||
'''
|
||||
if not self.view_exists(name):
|
||||
raise JenkinsException(exception_message % name)
|
||||
raise NotFoundException(exception_message % name)
|
||||
|
||||
def view_exists(self, name):
|
||||
'''Check whether a view exists
|
||||
@ -1450,8 +1514,8 @@ class Jenkins(object):
|
||||
|
||||
:param name: Name of Jenkins view, ``str``
|
||||
'''
|
||||
self.jenkins_open(Request(
|
||||
self._build_url(DELETE_VIEW, locals()), b''
|
||||
self.jenkins_open(requests.Request(
|
||||
'POST', self._build_url(DELETE_VIEW, locals())
|
||||
))
|
||||
if self.view_exists(name):
|
||||
raise JenkinsException('delete[%s] failed' % (name))
|
||||
@ -1465,9 +1529,11 @@ class Jenkins(object):
|
||||
if self.view_exists(name):
|
||||
raise JenkinsException('view[%s] already exists' % (name))
|
||||
|
||||
self.jenkins_open(Request(
|
||||
self._build_url(CREATE_VIEW, locals()),
|
||||
config_xml.encode('utf-8'), DEFAULT_HEADERS))
|
||||
self.jenkins_open(requests.Request(
|
||||
'POST', self._build_url(CREATE_VIEW, locals()),
|
||||
data=config_xml.encode('utf-8'),
|
||||
headers=DEFAULT_HEADERS
|
||||
))
|
||||
self.assert_view_exists(name, 'create[%s] failed')
|
||||
|
||||
def reconfig_view(self, name, config_xml):
|
||||
@ -1479,8 +1545,11 @@ class Jenkins(object):
|
||||
:param config_xml: New XML configuration, ``str``
|
||||
'''
|
||||
reconfig_url = self._build_url(CONFIG_VIEW, locals())
|
||||
self.jenkins_open(Request(reconfig_url, config_xml.encode('utf-8'),
|
||||
DEFAULT_HEADERS))
|
||||
self.jenkins_open(requests.Request(
|
||||
'POST', reconfig_url,
|
||||
data=config_xml.encode('utf-8'),
|
||||
headers=DEFAULT_HEADERS
|
||||
))
|
||||
|
||||
def get_view_config(self, name):
|
||||
'''Get configuration of existing Jenkins view.
|
||||
@ -1488,7 +1557,7 @@ class Jenkins(object):
|
||||
:param name: Name of Jenkins view, ``str``
|
||||
:returns: view configuration (XML format)
|
||||
'''
|
||||
request = Request(self._build_url(CONFIG_VIEW, locals()))
|
||||
request = requests.Request('GET', self._build_url(CONFIG_VIEW, locals()))
|
||||
return self.jenkins_open(request)
|
||||
|
||||
def get_promotion_name(self, name, job_name):
|
||||
@ -1504,8 +1573,8 @@ class Jenkins(object):
|
||||
'''
|
||||
folder_url, short_name = self._get_job_folder(job_name)
|
||||
try:
|
||||
response = self.jenkins_open(Request(
|
||||
self._build_url(PROMOTION_NAME, locals())))
|
||||
response = self.jenkins_open(requests.Request(
|
||||
'GET', self._build_url(PROMOTION_NAME, locals())))
|
||||
except NotFoundException:
|
||||
return None
|
||||
else:
|
||||
@ -1549,13 +1618,13 @@ class Jenkins(object):
|
||||
'''
|
||||
folder_url, short_name = self._get_job_folder(job_name)
|
||||
try:
|
||||
response = self.jenkins_open(Request(
|
||||
self._build_url(PROMOTION_INFO, locals())))
|
||||
response = self.jenkins_open(requests.Request(
|
||||
'GET', self._build_url(PROMOTION_INFO, locals())))
|
||||
if response:
|
||||
return json.loads(response)
|
||||
else:
|
||||
raise JenkinsException('job[%s] does not exist' % job_name)
|
||||
except HTTPError:
|
||||
except req_exc.HTTPError:
|
||||
raise JenkinsException('job[%s] does not exist' % job_name)
|
||||
except ValueError:
|
||||
raise JenkinsException("Could not parse JSON info for "
|
||||
@ -1578,8 +1647,8 @@ class Jenkins(object):
|
||||
: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''
|
||||
self.jenkins_open(requests.Request(
|
||||
'POST', self._build_url(DELETE_PROMOTION, locals())
|
||||
))
|
||||
if self.promotion_exists(name, job_name):
|
||||
raise JenkinsException('delete[%s] from job[%s] failed' %
|
||||
@ -1597,9 +1666,9 @@ class Jenkins(object):
|
||||
% (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.jenkins_open(requests.Request(
|
||||
'POST', self._build_url(CREATE_PROMOTION, locals()),
|
||||
data=config_xml.encode('utf-8'), headers=DEFAULT_HEADERS))
|
||||
self.assert_promotion_exists(name, job_name, 'create[%s] at '
|
||||
'job[%s] failed')
|
||||
|
||||
@ -1614,8 +1683,11 @@ class Jenkins(object):
|
||||
'''
|
||||
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))
|
||||
self.jenkins_open(requests.Request(
|
||||
'POST', reconfig_url,
|
||||
data=config_xml.encode('utf-8'),
|
||||
headers=DEFAULT_HEADERS
|
||||
))
|
||||
|
||||
def get_promotion_config(self, name, job_name):
|
||||
'''Get configuration of existing Jenkins promotion.
|
||||
@ -1625,7 +1697,8 @@ class Jenkins(object):
|
||||
:returns: promotion configuration (XML format)
|
||||
'''
|
||||
folder_url, short_name = self._get_job_folder(job_name)
|
||||
request = Request(self._build_url(CONFIG_PROMOTION, locals()))
|
||||
request = requests.Request(
|
||||
'GET', self._build_url(CONFIG_PROMOTION, locals()))
|
||||
return self.jenkins_open(request)
|
||||
|
||||
def quiet_down(self):
|
||||
@ -1634,7 +1707,7 @@ class Jenkins(object):
|
||||
No new builds will be started allowing running builds to complete
|
||||
prior to shutdown of the server.
|
||||
'''
|
||||
request = Request(self._build_url(QUIET_DOWN))
|
||||
request = requests.Request('POST', self._build_url(QUIET_DOWN))
|
||||
self.jenkins_open(request)
|
||||
info = self.get_info()
|
||||
if not info['quietingDown']:
|
||||
|
@ -1,121 +0,0 @@
|
||||
# Copyright (C) 2015 OpenStack Foundation
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import logging
|
||||
import re
|
||||
|
||||
import kerberos
|
||||
from six.moves.urllib import error, request
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HTTPNegotiateHandler(request.BaseHandler):
|
||||
handler_order = 490 # before Digest auth
|
||||
|
||||
def __init__(self, max_tries=5):
|
||||
self.krb_context = None
|
||||
self.tries = 0
|
||||
self.max_tries = max_tries
|
||||
self.re_extract_auth = re.compile('.*?Negotiate\s*([^,]*)', re.I)
|
||||
|
||||
def http_error_401(self, req, fp, code, msg, headers):
|
||||
logger.debug("INSIDE http_error_401")
|
||||
try:
|
||||
try:
|
||||
krb_req = self._extract_krb_value(headers)
|
||||
except ValueError:
|
||||
# Negotiate header not found or a similar error
|
||||
# we can't handle this, let the next handler have a go
|
||||
return None
|
||||
|
||||
if not krb_req:
|
||||
# First reply from server (no neg value)
|
||||
self.tries = 0
|
||||
krb_req = ""
|
||||
else:
|
||||
if self.tries > self.max_tries:
|
||||
raise error.HTTPError(
|
||||
req.get_full_url(), 401, "Negotiate auth failed",
|
||||
headers, None)
|
||||
|
||||
self.tries += 1
|
||||
try:
|
||||
krb_resp = self._krb_response(req.host, krb_req)
|
||||
|
||||
req.add_unredirected_header('Authorization',
|
||||
"Negotiate %s" % krb_resp)
|
||||
|
||||
resp = self.parent.open(req, timeout=req.timeout)
|
||||
self._authenticate_server(resp.headers)
|
||||
return resp
|
||||
|
||||
except kerberos.GSSError as err:
|
||||
try:
|
||||
msg = err.args[1][0]
|
||||
except Exception:
|
||||
msg = "Negotiate auth failed"
|
||||
logger.debug(msg)
|
||||
return None # let the next handler (if any) have a go
|
||||
|
||||
finally:
|
||||
if self.krb_context is not None:
|
||||
kerberos.authGSSClientClean(self.krb_context)
|
||||
self.krb_context = None
|
||||
|
||||
def _krb_response(self, host, krb_val):
|
||||
logger.debug("INSIDE _krb_response")
|
||||
|
||||
_dummy, self.krb_context = kerberos.authGSSClientInit("HTTP@%s" % host)
|
||||
kerberos.authGSSClientStep(self.krb_context, krb_val)
|
||||
response = kerberos.authGSSClientResponse(self.krb_context)
|
||||
|
||||
logger.debug("kerb auth successful")
|
||||
|
||||
return response
|
||||
|
||||
def _authenticate_server(self, headers):
|
||||
logger.debug("INSIDE _authenticate_server")
|
||||
try:
|
||||
val = self._extract_krb_value(headers)
|
||||
except ValueError:
|
||||
logger.critical("Server authentication failed."
|
||||
"Auth value couldn't be extracted from headers.")
|
||||
return None
|
||||
if not val:
|
||||
logger.critical("Server authentication failed."
|
||||
"Empty 'Negotiate' value.")
|
||||
return None
|
||||
|
||||
kerberos.authGSSClientStep(self.krb_context, val)
|
||||
|
||||
def _extract_krb_value(self, headers):
|
||||
logger.debug("INSIDE _extract_krb_value")
|
||||
header = headers.get('www-authenticate', None)
|
||||
|
||||
if header is None:
|
||||
msg = "www-authenticate header not found"
|
||||
logger.debug(msg)
|
||||
raise ValueError(msg)
|
||||
|
||||
if "negotiate" in header.lower():
|
||||
matches = self.re_extract_auth.search(header)
|
||||
if matches:
|
||||
return matches.group(1)
|
||||
else:
|
||||
return ""
|
||||
else:
|
||||
msg = "Negotiate not in www-authenticate header (%s)" % header
|
||||
logger.debug(msg)
|
||||
raise ValueError(msg)
|
@ -1,3 +1,4 @@
|
||||
six>=1.3.0
|
||||
pbr>=0.8.2
|
||||
multi_key_dict
|
||||
requests
|
||||
|
@ -1,9 +1,10 @@
|
||||
coverage>=3.6
|
||||
hacking>=0.5.6,<0.11
|
||||
kerberos>=1.2.4
|
||||
mock<1.1
|
||||
unittest2
|
||||
python-subunit
|
||||
requests-mock>=1.4.0
|
||||
requests-kerberos
|
||||
sphinx>=1.2,<1.3.0
|
||||
testrepository
|
||||
testscenarios
|
||||
|
@ -1,6 +1,5 @@
|
||||
import sys
|
||||
|
||||
from six.moves.urllib.request import build_opener
|
||||
from testscenarios import TestWithScenarios
|
||||
|
||||
import jenkins
|
||||
@ -25,7 +24,8 @@ class JenkinsTestBase(TestWithScenarios, unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(JenkinsTestBase, self).setUp()
|
||||
self.opener = build_opener()
|
||||
# TODO(darragh) would be useful if this could be mocked
|
||||
jenkins.requests_kerberos = None
|
||||
|
||||
self.j = jenkins.Jenkins(self.base_url, 'test', 'test')
|
||||
|
||||
@ -35,17 +35,4 @@ class JenkinsTestBase(TestWithScenarios, unittest.TestCase):
|
||||
def _check_requests(self, requests):
|
||||
|
||||
for req in requests:
|
||||
self._check_request(req[0][0])
|
||||
|
||||
def _check_request(self, request):
|
||||
|
||||
# taken from opener.open() in request
|
||||
# attribute request.type is only set automatically for python 3
|
||||
# requests, must use request.get_type() for python 2.7
|
||||
protocol = request.type or request.get_type()
|
||||
|
||||
# check that building the request doesn't throw any exception
|
||||
meth_name = protocol + "_request"
|
||||
for processor in self.opener.process_request.get(protocol, []):
|
||||
meth = getattr(processor, meth_name)
|
||||
request = meth(request)
|
||||
req[0][0].prepare()
|
||||
|
@ -1,8 +1,11 @@
|
||||
import functools
|
||||
import json
|
||||
from multiprocessing import Process
|
||||
from multiprocessing import Queue
|
||||
import traceback
|
||||
|
||||
from mock import Mock
|
||||
import requests
|
||||
from six.moves import socketserver
|
||||
|
||||
|
||||
@ -73,3 +76,31 @@ class NullServer(socketserver.TCPServer):
|
||||
socketserver.TCPServer.__init__(
|
||||
self, server_address, socketserver.BaseRequestHandler,
|
||||
*args, **kwargs)
|
||||
|
||||
|
||||
def build_response_mock(status_code, json_body=None, headers=None, **kwargs):
|
||||
real_response = requests.Response()
|
||||
real_response.status_code = status_code
|
||||
|
||||
text = None
|
||||
if json_body is not None:
|
||||
text = json.dumps(json_body)
|
||||
if headers is not {}:
|
||||
real_response.headers['content-length'] = len(text)
|
||||
|
||||
if headers is not None:
|
||||
for k, v in headers.items():
|
||||
real_response.headers[k] = v
|
||||
|
||||
for k, v in kwargs.items():
|
||||
setattr(real_response, k, v)
|
||||
|
||||
response = Mock(wraps=real_response, autospec=True)
|
||||
if text:
|
||||
response.text = text
|
||||
|
||||
# for some reason, wraps cannot handle attributes which are dicts
|
||||
# and accessed by key
|
||||
response.headers = real_response.headers
|
||||
|
||||
return response
|
||||
|
@ -14,7 +14,7 @@ class JenkinsBuildJobTest(JenkinsJobsTestBase):
|
||||
|
||||
build_info = self.j.build_job(u'Test Job')
|
||||
|
||||
self.assertEqual(jenkins_mock.call_args[0][0].get_full_url(),
|
||||
self.assertEqual(jenkins_mock.call_args[0][0].url,
|
||||
self.make_url('job/Test%20Job/build'))
|
||||
self.assertEqual(build_info, {'foo': 'bar'})
|
||||
self._check_requests(jenkins_mock.call_args_list)
|
||||
@ -27,7 +27,7 @@ class JenkinsBuildJobTest(JenkinsJobsTestBase):
|
||||
|
||||
build_info = self.j.build_job(u'a Folder/Test Job')
|
||||
|
||||
self.assertEqual(jenkins_mock.call_args[0][0].get_full_url(),
|
||||
self.assertEqual(jenkins_mock.call_args[0][0].url,
|
||||
self.make_url('job/a%20Folder/job/Test%20Job/build'))
|
||||
self.assertEqual(build_info, {'foo': 'bar'})
|
||||
self._check_requests(jenkins_mock.call_args_list)
|
||||
@ -40,7 +40,7 @@ class JenkinsBuildJobTest(JenkinsJobsTestBase):
|
||||
|
||||
build_info = self.j.build_job(u'TestJob', token='some_token')
|
||||
|
||||
self.assertEqual(jenkins_mock.call_args[0][0].get_full_url(),
|
||||
self.assertEqual(jenkins_mock.call_args[0][0].url,
|
||||
self.make_url('job/TestJob/build?token=some_token'))
|
||||
self.assertEqual(build_info, {'foo': 'bar'})
|
||||
self._check_requests(jenkins_mock.call_args_list)
|
||||
@ -53,7 +53,7 @@ class JenkinsBuildJobTest(JenkinsJobsTestBase):
|
||||
|
||||
build_info = self.j.build_job(u'a Folder/TestJob', token='some_token')
|
||||
|
||||
self.assertEqual(jenkins_mock.call_args[0][0].get_full_url(),
|
||||
self.assertEqual(jenkins_mock.call_args[0][0].url,
|
||||
self.make_url('job/a%20Folder/job/TestJob/build?token=some_token'))
|
||||
self.assertEqual(build_info, {'foo': 'bar'})
|
||||
self._check_requests(jenkins_mock.call_args_list)
|
||||
@ -69,8 +69,8 @@ class JenkinsBuildJobTest(JenkinsJobsTestBase):
|
||||
parameters={'when': 'now', 'why': 'because I felt like it'},
|
||||
token='some_token')
|
||||
|
||||
self.assertTrue('token=some_token' in jenkins_mock.call_args[0][0].get_full_url())
|
||||
self.assertTrue('when=now' in jenkins_mock.call_args[0][0].get_full_url())
|
||||
self.assertTrue('why=because+I+felt+like+it' in jenkins_mock.call_args[0][0].get_full_url())
|
||||
self.assertTrue('token=some_token' in jenkins_mock.call_args[0][0].url)
|
||||
self.assertTrue('when=now' in jenkins_mock.call_args[0][0].url)
|
||||
self.assertTrue('why=because+I+felt+like+it' in jenkins_mock.call_args[0][0].url)
|
||||
self.assertEqual(build_info, {'foo': 'bar'})
|
||||
self._check_requests(jenkins_mock.call_args_list)
|
||||
|
@ -11,7 +11,7 @@ class JenkinsGetJobConfigTest(JenkinsJobsTestBase):
|
||||
self.j.get_job_config(u'Test Job')
|
||||
|
||||
self.assertEqual(
|
||||
jenkins_mock.call_args[0][0].get_full_url(),
|
||||
jenkins_mock.call_args[0][0].url,
|
||||
self.make_url('job/Test%20Job/config.xml'))
|
||||
self._check_requests(jenkins_mock.call_args_list)
|
||||
|
||||
@ -20,6 +20,6 @@ class JenkinsGetJobConfigTest(JenkinsJobsTestBase):
|
||||
self.j.get_job_config(u'a folder/Test Job')
|
||||
|
||||
self.assertEqual(
|
||||
jenkins_mock.call_args[0][0].get_full_url(),
|
||||
jenkins_mock.call_args[0][0].url,
|
||||
self.make_url('job/a%20folder/job/Test%20Job/config.xml'))
|
||||
self._check_requests(jenkins_mock.call_args_list)
|
||||
|
@ -18,7 +18,7 @@ class JenkinsCopyJobTest(JenkinsJobsTestBase):
|
||||
self.j.copy_job(u'Test Job', u'Test Job_2')
|
||||
|
||||
self.assertEqual(
|
||||
jenkins_mock.call_args_list[0][0][0].get_full_url(),
|
||||
jenkins_mock.call_args_list[0][0][0].url,
|
||||
self.make_url('createItem?name=Test%20Job_2&mode=copy&from=Test%20Job'))
|
||||
self.assertTrue(self.j.job_exists('Test Job_2'))
|
||||
self._check_requests(jenkins_mock.call_args_list)
|
||||
@ -34,7 +34,7 @@ class JenkinsCopyJobTest(JenkinsJobsTestBase):
|
||||
self.j.copy_job(u'a Folder/Test Job', u'a Folder/Test Job_2')
|
||||
|
||||
self.assertEqual(
|
||||
jenkins_mock.call_args_list[0][0][0].get_full_url(),
|
||||
jenkins_mock.call_args_list[0][0][0].url,
|
||||
self.make_url('job/a%20Folder/createItem?name=Test%20Job_2'
|
||||
'&mode=copy&from=Test%20Job'))
|
||||
self.assertTrue(self.j.job_exists('a Folder/Test Job_2'))
|
||||
@ -50,7 +50,7 @@ class JenkinsCopyJobTest(JenkinsJobsTestBase):
|
||||
with self.assertRaises(jenkins.JenkinsException) as context_manager:
|
||||
self.j.copy_job(u'TestJob', u'TestJob_2')
|
||||
self.assertEqual(
|
||||
jenkins_mock.call_args_list[0][0][0].get_full_url(),
|
||||
jenkins_mock.call_args_list[0][0][0].url,
|
||||
self.make_url('createItem?name=TestJob_2&mode=copy&from=TestJob'))
|
||||
self.assertEqual(
|
||||
str(context_manager.exception),
|
||||
@ -67,7 +67,7 @@ class JenkinsCopyJobTest(JenkinsJobsTestBase):
|
||||
with self.assertRaises(jenkins.JenkinsException) as context_manager:
|
||||
self.j.copy_job(u'a Folder/TestJob', u'a Folder/TestJob_2')
|
||||
self.assertEqual(
|
||||
jenkins_mock.call_args_list[0][0][0].get_full_url(),
|
||||
jenkins_mock.call_args_list[0][0][0].url,
|
||||
self.make_url('job/a%20Folder/createItem?name=TestJob_2&mode=copy'
|
||||
'&from=TestJob'))
|
||||
self.assertEqual(
|
||||
|
@ -18,7 +18,7 @@ class JenkinsCreateJobTest(JenkinsJobsTestBase):
|
||||
self.j.create_job(u'Test Job', self.config_xml)
|
||||
|
||||
self.assertEqual(
|
||||
jenkins_mock.call_args_list[1][0][0].get_full_url(),
|
||||
jenkins_mock.call_args_list[1][0][0].url,
|
||||
self.make_url('createItem?name=Test%20Job'))
|
||||
self._check_requests(jenkins_mock.call_args_list)
|
||||
|
||||
@ -33,7 +33,7 @@ class JenkinsCreateJobTest(JenkinsJobsTestBase):
|
||||
self.j.create_job(u'a Folder/Test Job', self.config_xml)
|
||||
|
||||
self.assertEqual(
|
||||
jenkins_mock.call_args_list[1][0][0].get_full_url(),
|
||||
jenkins_mock.call_args_list[1][0][0].url,
|
||||
self.make_url('job/a%20Folder/createItem?name=Test%20Job'))
|
||||
self._check_requests(jenkins_mock.call_args_list)
|
||||
|
||||
@ -47,7 +47,7 @@ class JenkinsCreateJobTest(JenkinsJobsTestBase):
|
||||
with self.assertRaises(jenkins.JenkinsException) as context_manager:
|
||||
self.j.create_job(u'TestJob', self.config_xml)
|
||||
self.assertEqual(
|
||||
jenkins_mock.call_args_list[0][0][0].get_full_url(),
|
||||
jenkins_mock.call_args_list[0][0][0].url,
|
||||
self.make_url('job/TestJob/api/json?tree=name'))
|
||||
self.assertEqual(
|
||||
str(context_manager.exception),
|
||||
@ -64,7 +64,7 @@ class JenkinsCreateJobTest(JenkinsJobsTestBase):
|
||||
with self.assertRaises(jenkins.JenkinsException) as context_manager:
|
||||
self.j.create_job(u'a Folder/TestJob', self.config_xml)
|
||||
self.assertEqual(
|
||||
jenkins_mock.call_args_list[0][0][0].get_full_url(),
|
||||
jenkins_mock.call_args_list[0][0][0].url,
|
||||
self.make_url('job/a%20Folder/job/TestJob/api/json?tree=name'))
|
||||
self.assertEqual(
|
||||
str(context_manager.exception),
|
||||
@ -82,10 +82,10 @@ class JenkinsCreateJobTest(JenkinsJobsTestBase):
|
||||
with self.assertRaises(jenkins.JenkinsException) as context_manager:
|
||||
self.j.create_job(u'TestJob', self.config_xml)
|
||||
self.assertEqual(
|
||||
jenkins_mock.call_args_list[0][0][0].get_full_url(),
|
||||
jenkins_mock.call_args_list[0][0][0].url,
|
||||
self.make_url('job/TestJob/api/json?tree=name'))
|
||||
self.assertEqual(
|
||||
jenkins_mock.call_args_list[1][0][0].get_full_url(),
|
||||
jenkins_mock.call_args_list[1][0][0].url,
|
||||
self.make_url('createItem?name=TestJob'))
|
||||
self.assertEqual(
|
||||
str(context_manager.exception),
|
||||
@ -103,10 +103,10 @@ class JenkinsCreateJobTest(JenkinsJobsTestBase):
|
||||
with self.assertRaises(jenkins.JenkinsException) as context_manager:
|
||||
self.j.create_job(u'a Folder/TestJob', self.config_xml)
|
||||
self.assertEqual(
|
||||
jenkins_mock.call_args_list[0][0][0].get_full_url(),
|
||||
jenkins_mock.call_args_list[0][0][0].url,
|
||||
self.make_url('job/a%20Folder/job/TestJob/api/json?tree=name'))
|
||||
self.assertEqual(
|
||||
jenkins_mock.call_args_list[1][0][0].get_full_url(),
|
||||
jenkins_mock.call_args_list[1][0][0].url,
|
||||
self.make_url('job/a%20Folder/createItem?name=TestJob'))
|
||||
self.assertEqual(
|
||||
str(context_manager.exception),
|
||||
|