Hello community,
here is the log from the commit of package python-python-jenkins for
openSUSE:Factory checked in at 2018-12-12 17:29:26
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-python-jenkins (Old)
and /work/SRC/openSUSE:Factory/.python-python-jenkins.new.28833 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-python-jenkins"
Wed Dec 12 17:29:26 2018 rev:7 rq:657235 version:1.4.0
Changes:
--------
---
/work/SRC/openSUSE:Factory/python-python-jenkins/python-python-jenkins.changes
2018-09-07 15:39:48.290510336 +0200
+++
/work/SRC/openSUSE:Factory/.python-python-jenkins.new.28833/python-python-jenkins.changes
2018-12-12 17:29:27.714802628 +0100
@@ -1,0 +2,12 @@
+Tue Dec 11 15:07:10 UTC 2018 - Thomas Bechtold <[email protected]>
+
+- update to 1.4.0:
+ * Update min tox version to 2.0
+ * Request multiple folder levels at once in get\_all\_jobs
+ * Replace build\_jobs\_list\_responses with actual Jenkins responses
+ * Clean up job/folder path handling
+ * Test requested URLs in test\_getall
+ * Make jjb-tox-cross-jenkins-job-builder voting
+ * Allow adding extra HTTP headers to Jenkins requests
+
+-------------------------------------------------------------------
Old:
----
python-jenkins-1.2.1.tar.gz
New:
----
python-jenkins-1.4.0.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-python-jenkins.spec ++++++
--- /var/tmp/diff_new_pack.7C42JO/_old 2018-12-12 17:29:28.406801751 +0100
+++ /var/tmp/diff_new_pack.7C42JO/_new 2018-12-12 17:29:28.406801751 +0100
@@ -13,13 +13,13 @@
# license that conforms to the Open Source Definition (Version 1.9)
# published by the Open Source Initiative.
-# Please submit bugfixes or comments via http://bugs.opensuse.org/
+# Please submit bugfixes or comments via https://bugs.opensuse.org/
#
%{?!python_module:%define python_module() python-%{**} python3-%{**}}
Name: python-python-jenkins
-Version: 1.2.1
+Version: 1.4.0
Release: 0
Summary: Python bindings for the remote Jenkins API
License: BSD-3-Clause
++++++ python-jenkins-1.2.1.tar.gz -> python-jenkins-1.4.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-jenkins-1.2.1/.zuul.yaml
new/python-jenkins-1.4.0/.zuul.yaml
--- old/python-jenkins-1.2.1/.zuul.yaml 2018-08-24 19:43:33.000000000 +0200
+++ new/python-jenkins-1.4.0/.zuul.yaml 2018-11-19 02:55:24.000000000 +0100
@@ -7,7 +7,6 @@
- openstack/python-jenkins
- openstack-infra/jenkins-job-builder
voting: true
- failure-message: WARNING
- project:
templates:
@@ -29,3 +28,4 @@
- openstack-tox-py27
- openstack-tox-py35
- openstack-tox-py36
+ - jjb-tox-cross-jenkins-job-builder
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-jenkins-1.2.1/AUTHORS
new/python-jenkins-1.4.0/AUTHORS
--- old/python-jenkins-1.2.1/AUTHORS 2018-08-24 19:45:47.000000000 +0200
+++ new/python-jenkins-1.4.0/AUTHORS 2018-11-19 02:56:08.000000000 +0100
@@ -1,5 +1,6 @@
Abhijeet Kasurde <[email protected]>
Adam Gandelman <[email protected]>
+Aigars Mahinovs <[email protected]>
Akshat Tandon <[email protected]>
Alexandre Conrad <[email protected]>
Aliaksandr Buhayeu <[email protected]>
@@ -19,6 +20,7 @@
Darragh Bailey <[email protected]>
Darragh Bailey <[email protected]>
David Strauss <[email protected]>
+Dennis Dmitriev <[email protected]>
Dong Ma <[email protected]>
Eduardo Gonzalez <[email protected]>
Emilien Macchi <[email protected]>
@@ -57,9 +59,9 @@
Thanh Ha <[email protected]>
Tomas Janousek <[email protected]>
ZhangHongtao <[email protected]>
-Zuul <[email protected]>
grahamlyons <[email protected]>
huang.zhiping <[email protected]>
+joelee <[email protected]>
lvxianguo <[email protected]>
melissaml <[email protected]>
mhuin <[email protected]>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-jenkins-1.2.1/ChangeLog
new/python-jenkins-1.4.0/ChangeLog
--- old/python-jenkins-1.2.1/ChangeLog 2018-08-24 19:45:47.000000000 +0200
+++ new/python-jenkins-1.4.0/ChangeLog 2018-11-19 02:56:08.000000000 +0100
@@ -1,6 +1,21 @@
CHANGES
=======
+1.4.0
+-----
+
+* Update min tox version to 2.0
+
+1.3.0
+-----
+
+* Request multiple folder levels at once in get\_all\_jobs
+* Replace build\_jobs\_list\_responses with actual Jenkins responses
+* Clean up job/folder path handling
+* Test requested URLs in test\_getall
+* Make jjb-tox-cross-jenkins-job-builder voting
+* Allow adding extra HTTP headers to Jenkins requests
+
1.2.1
-----
@@ -12,12 +27,14 @@
* Avoid empty body failure on HEAD requests
* Fix item being ignored in get\_info
+* Add folder credential support
* detect and respect http redirects
1.1.0
-----
* Fix run\_script method
+* Check for 'Location' header in the response
* Adopt use of pre-commit hooks
* Adds support for executing Groovy scripts on jenkins nodes
* Allow use of unicode job names
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-jenkins-1.2.1/PKG-INFO
new/python-jenkins-1.4.0/PKG-INFO
--- old/python-jenkins-1.2.1/PKG-INFO 2018-08-24 19:45:47.000000000 +0200
+++ new/python-jenkins-1.4.0/PKG-INFO 2018-11-19 02:56:08.000000000 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 1.2
Name: python-jenkins
-Version: 1.2.1
+Version: 1.4.0
Summary: Python bindings for the remote Jenkins API
Home-page: http://git.openstack.org/cgit/openstack/python-jenkins
Author: Ken Conley
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-jenkins-1.2.1/jenkins/__init__.py
new/python-jenkins-1.4.0/jenkins/__init__.py
--- old/python-jenkins-1.2.1/jenkins/__init__.py 2018-08-24
19:43:52.000000000 +0200
+++ new/python-jenkins-1.4.0/jenkins/__init__.py 2018-11-19
02:55:24.000000000 +0100
@@ -62,6 +62,7 @@
from six.moves.http_client import BadStatusLine
from six.moves.urllib.error import URLError
from six.moves.urllib.parse import quote, urlencode, urljoin, urlparse
+import xml.etree.ElementTree as ET
from jenkins import plugins
@@ -96,7 +97,8 @@
PLUGIN_INFO = 'pluginManager/api/json?depth=%(depth)s'
CRUMB_URL = 'crumbIssuer/api/json'
WHOAMI_URL = 'me/api/json?depth=%(depth)s'
-JOBS_QUERY = '?tree=jobs[url,color,name,jobs]'
+JOBS_QUERY = '?tree=%s'
+JOBS_QUERY_TREE = 'jobs[url,color,name,%s]'
JOB_INFO = '%(folder_url)sjob/%(short_name)s/api/json?depth=%(depth)s'
JOB_NAME = '%(folder_url)sjob/%(short_name)s/api/json?tree=name'
ALL_BUILDS =
'%(folder_url)sjob/%(short_name)s/api/json?tree=allBuilds[number,url]'
@@ -141,6 +143,14 @@
DELETE_PROMOTION =
'%(folder_url)sjob/%(short_name)s/promotion/process/%(name)s/doDelete'
CREATE_PROMOTION =
'%(folder_url)sjob/%(short_name)s/promotion/createProcess?name=%(name)s'
CONFIG_PROMOTION =
'%(folder_url)sjob/%(short_name)s/promotion/process/%(name)s/config.xml'
+LIST_CREDENTIALS =
'%(folder_url)sjob/%(short_name)s/credentials/store/folder/' \
+ 'domain/%(domain_name)s/api/json?tree=credentials[id]'
+CREATE_CREDENTIAL =
'%(folder_url)sjob/%(short_name)s/credentials/store/folder/' \
+ 'domain/%(domain_name)s/createCredentials'
+CONFIG_CREDENTIAL =
'%(folder_url)sjob/%(short_name)s/credentials/store/folder/' \
+ 'domain/%(domain_name)s/credential/%(name)s/config.xml'
+CREDENTIAL_INFO = '%(folder_url)sjob/%(short_name)s/credentials/store/folder/'
\
+
'domain/%(domain_name)s/credential/%(name)s/api/json?depth=0'
QUIET_DOWN = 'quietDown'
# for testing only
@@ -321,6 +331,14 @@
self.timeout = timeout
self._session = WrappedSession()
+ extra_headers = os.environ.get("JENKINS_API_EXTRA_HEADERS", "")
+ if extra_headers:
+ logging.warning("JENKINS_API_EXTRA_HEADERS adds these HTTP
headers: %s", extra_headers.split("\n"))
+ for token in extra_headers.split("\n"):
+ if ":" in token:
+ header, value = token.split(":", 1)
+ self._session.headers[header] = value.strip()
+
if os.getenv('PYTHONHTTPSVERIFY', '1') == '0':
logging.debug('PYTHONHTTPSVERIFY=0 detected so we will '
'disable requests library SSL verification to keep '
@@ -457,17 +475,21 @@
raise JenkinsException(
"Could not parse JSON info for job[%s]" % name)
- def get_job_info_regex(self, pattern, depth=0, folder_depth=0):
+ def get_job_info_regex(self, pattern, depth=0, folder_depth=0,
+ folder_depth_per_request=10):
'''Get a list of jobs information that contain names which match the
regex pattern.
:param pattern: regex pattern, ``str``
:param depth: JSON depth, ``int``
:param folder_depth: folder level depth to search ``int``
+ :param folder_depth_per_request: Number of levels to fetch at once,
+ ``int``. See :func:`get_all_jobs`.
:returns: List of jobs info, ``list``
'''
result = []
- jobs = self.get_all_jobs(folder_depth)
+ jobs = self.get_all_jobs(folder_depth=folder_depth,
+
folder_depth_per_request=folder_depth_per_request)
for job in jobs:
if re.search(pattern, job['name']):
result.append(self.get_job_info(job['name'], depth=depth))
@@ -513,6 +535,7 @@
headers = response.headers
if (headers.get('content-length') is None and
headers.get('transfer-encoding') is None and
+ headers.get('location') is None and
(response.content is None or len(response.content) <= 0)):
# response body should only exist if one of these is provided
raise EmptyResponseException(
@@ -924,7 +947,7 @@
return plugins_data
- def get_jobs(self, folder_depth=0, view_name=None):
+ def get_jobs(self, folder_depth=0, folder_depth_per_request=10,
view_name=None):
"""Get list of jobs.
Each job is a dictionary with 'name', 'url', 'color' and 'fullname'
@@ -937,6 +960,8 @@
:param folder_depth: Number of levels to search, ``int``. By default
0, which will limit search to toplevel. None disables the limit.
+ :param folder_depth_per_request: Number of levels to fetch at once,
+ ``int``. See :func:`get_all_jobs`.
: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.
@@ -958,9 +983,10 @@
if view_name:
return self._get_view_jobs(name=view_name)
else:
- return self.get_all_jobs(folder_depth=folder_depth)
+ return self.get_all_jobs(folder_depth=folder_depth,
+
folder_depth_per_request=folder_depth_per_request)
- def get_all_jobs(self, folder_depth=None):
+ def get_all_jobs(self, folder_depth=None, folder_depth_per_request=10):
"""Get list of all jobs recursively to the given folder depth.
Each job is a dictionary with 'name', 'url', 'color' and 'fullname'
@@ -968,65 +994,57 @@
:param folder_depth: Number of levels to search, ``int``. By default
None, which will search all levels. 0 limits to toplevel.
+ :param folder_depth_per_request: Number of levels to fetch at once,
+ ``int``. By default 10, which is usually enough to fetch all jobs
+ using a single request and still easily fits into an HTTP request.
: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.
+ On instances with many folders it would not be efficient to fetch
+ each folder separately, hence `folder_depth_per_request` levels
+ are fetched at once using the ``tree`` query parameter::
+
+ ?tree=jobs[url,color,name,jobs[...,jobs[...,jobs[...,jobs]]]]
- Example::
+ If there are more folder levels than the query asks for, Jenkins
+ returns empty [#]_ objects at the deepest level::
- server.run_script(\"\"\"
- import groovy.json.JsonBuilder;
+ {"name": "folder", "url": "...", "jobs": [{}, {}, ...]}
- // 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()
- \"\"\")
+ This makes it possible to detect when additional requests are
+ needed.
+ .. [#] Actually recent Jenkins includes a ``_class`` field
+ everywhere, but it's missing the requested fields.
"""
- jobs_list = []
+ jobs_query = 'jobs'
+ for _ in range(folder_depth_per_request):
+ jobs_query = JOBS_QUERY_TREE % jobs_query
+ jobs_query = JOBS_QUERY % jobs_query
- jobs = [(0, "", self.get_info(query=JOBS_QUERY)['jobs'])]
+ 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:
+ path = root + [job[u'name']]
# 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']])
+ job[u'fullname'] = '/'.join(path)
jobs_list.append(job)
- if 'jobs' in job: # folder
+ if 'jobs' in job and isinstance(job['jobs'], list): # 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']))
+ children = job['jobs']
+ # once folder_depth_per_request is reached, Jenkins
+ # returns empty objects
+ if any('url' not in child for child in job['jobs']):
+ url_path = ''.join(['/job/' + p for p in path])
+ children = self.get_info(url_path,
+ query=jobs_query)['jobs']
+ jobs.append((lvl + 1, path, children))
return jobs_list
def copy_job(self, from_name, to_name):
@@ -1145,22 +1163,6 @@
'''Get the number of jobs on the Jenkins server
:returns: Total number of jobs, ``int``
-
- .. 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)"
- " })")
-
'''
return len(self.get_all_jobs())
@@ -1274,6 +1276,12 @@
'''
response = self.jenkins_request(requests.Request(
'POST', self.build_job_url(name, parameters, token)))
+
+ if 'Location' not in response.headers:
+ raise EmptyResponseException(
+ "Header 'Location' not found in "
+ "response from server[%s]" % self.server)
+
location = response.headers['Location']
# location is a queue item, eg. "http://jenkins/queue/item/25/"
if location.endswith('/'):
@@ -1924,6 +1932,189 @@
'GET', self._build_url(CONFIG_PROMOTION, locals()))
return self.jenkins_open(request)
+ def _get_tag_text(self, name, xml):
+ '''Get text of tag from xml
+
+ :param name: XML tag name, ``str``
+ :param xml: XML configuration, ``str``
+ :returns: Text of tag, ``str``
+ :throws: :class:`JenkinsException` whenever tag does not exist
+ or has invalidated text
+ '''
+ tag = ET.fromstring(xml).find(name)
+ try:
+ text = tag.text.strip()
+ if text:
+ return text
+ raise JenkinsException("tag[%s] is invalidated" % name)
+ except AttributeError:
+ raise JenkinsException("tag[%s] is invalidated" % name)
+
+ def assert_folder(self, name, exception_message='job[%s] is not a folder'):
+ '''Raise an exception if job is not Cloudbees Folder
+
+ :param name: Name of job, ``str``
+ :param exception_message: Message to use for the exception.
+ :throws: :class:`JenkinsException` whenever the job is
+ not Cloudbees Folder
+ '''
+ if not self.is_folder(name):
+ raise JenkinsException(exception_message % name)
+
+ def is_folder(self, name):
+ '''Check whether a job is Cloudbees Folder
+
+ :param name: Job name, ``str``
+ :returns: ``True`` if job is folder, ``False`` otherwise
+ '''
+ return 'com.cloudbees.hudson.plugins.folder.Folder' \
+ == self.get_job_info(name)['_class']
+
+ def assert_credential_exists(self, name, folder_name, domain_name='_',
+ exception_message='credential[%s] does not '
+ 'exist in the domain[%s] of [%s]'):
+ '''Raise an exception if credential does not exist in domain of folder
+
+ :param name: Name of credential, ``str``
+ :param folder_name: Folder name, ``str``
+ :param domain_name: Domain name, default is '_', ``str``
+ :param exception_message: Message to use for the exception.
+ Formatted with ``name``, ``domain_name``,
+ and ``folder_name``
+ :throws: :class:`JenkinsException` whenever the credentail
+ does not exist in domain of folder
+ '''
+ if not self.credential_exists(name, folder_name, domain_name):
+ raise JenkinsException(exception_message
+ % (name, domain_name, folder_name))
+
+ def credential_exists(self, name, folder_name, domain_name='_'):
+ '''Check whether a credentail exists in domain of folder
+
+ :param name: Name of credentail, ``str``
+ :param folder_name: Folder name, ``str``
+ :param domain_name: Domain name, default is '_', ``str``
+ :returns: ``True`` if credentail exists, ``False`` otherwise
+ '''
+ try:
+ return self.get_credential_info(name, folder_name,
+ domain_name)['id'] == name
+ except JenkinsException:
+ return False
+
+ def get_credential_info(self, name, folder_name, domain_name='_'):
+ '''Get credential information dictionary in domain of folder
+
+ :param name: Name of credentail, ``str``
+ :param folder_name: folder_name, ``str``
+ :param domain_name: Domain name, default is '_', ``str``
+ :returns: Dictionary of credential info, ``dict``
+ '''
+ self.assert_folder(folder_name)
+ folder_url, short_name = self._get_job_folder(folder_name)
+ try:
+ response = self.jenkins_open(requests.Request(
+ 'GET', self._build_url(CREDENTIAL_INFO, locals())
+ ))
+ if response:
+ return json.loads(response)
+ else:
+ raise JenkinsException('credential[%s] does not exist '
+ 'in the domain[%s] of [%s]'
+ % (name, domain_name, folder_name))
+ except (req_exc.HTTPError, NotFoundException):
+ raise JenkinsException('credential[%s] does not exist '
+ 'in the domain[%s] of [%s]'
+ % (name, domain_name, folder_name))
+ except ValueError:
+ raise JenkinsException(
+ 'Could not parse JSON info for credential[%s] '
+ 'in the domain[%s] of [%s]'
+ % (name, domain_name, folder_name)
+ )
+
+ def get_credential_config(self, name, folder_name, domain_name='_'):
+ '''Get configuration of credential in domain of folder.
+
+ :param name: Name of credentail, ``str``
+ :param folder_name: Folder name, ``str``
+ :param domain_name: Domain name, default is '_', ``str``
+ :returns: Credential configuration (XML format)
+ '''
+ self.assert_folder(folder_name)
+ folder_url, short_name = self._get_job_folder(folder_name)
+ return self.jenkins_open(requests.Request(
+ 'GET', self._build_url(CONFIG_CREDENTIAL, locals())
+ ))
+
+ def create_credential(self, folder_name, config_xml,
+ domain_name='_'):
+ '''Create credentail in domain of folder
+
+ :param folder_name: Folder name, ``str``
+ :param config_xml: New XML configuration, ``str``
+ :param domain_name: Domain name, default is '_', ``str``
+ '''
+ folder_url, short_name = self._get_job_folder(folder_name)
+ name = self._get_tag_text('id', config_xml)
+ if self.credential_exists(name, folder_name, domain_name):
+ raise JenkinsException('credential[%s] already exists '
+ 'in the domain[%s] of [%s]'
+ % (name, domain_name, folder_name))
+
+ self.jenkins_open(requests.Request(
+ 'POST', self._build_url(CREATE_CREDENTIAL, locals()),
+ data=config_xml.encode('utf-8'),
+ headers=DEFAULT_HEADERS
+ ))
+ self.assert_credential_exists(name, folder_name, domain_name,
+ 'create[%s] failed in the '
+ 'domain[%s] of [%s]')
+
+ def delete_credential(self, name, folder_name, domain_name='_'):
+ '''Delete credential from domain of folder
+
+ :param name: Name of credentail, ``str``
+ :param folder_name: Folder name, ``str``
+ :param domain_name: Domain name, default is '_', ``str``
+ '''
+ folder_url, short_name = self._get_job_folder(folder_name)
+ self.jenkins_open(requests.Request(
+ 'DELETE', self._build_url(CONFIG_CREDENTIAL, locals())
+ ))
+ if self.credential_exists(name, folder_name, domain_name):
+ raise JenkinsException('delete credential[%s] from '
+ 'domain[%s] of [%s] failed'
+ % (name, domain_name, folder_name))
+
+ def reconfig_credential(self, folder_name, config_xml, domain_name='_'):
+ '''Reconfig credential with new config in domain of folder
+
+ :param folder_name: Folder name, ``str``
+ :param config_xml: New XML configuration, ``str``
+ :param domain_name: Domain name, default is '_', ``str``
+ '''
+ folder_url, short_name = self._get_job_folder(folder_name)
+ name = self._get_tag_text('id', config_xml)
+ self.assert_credential_exists(name, folder_name, domain_name)
+ self.jenkins_open(requests.Request(
+ 'POST', self._build_url(CONFIG_CREDENTIAL, locals())
+ ))
+
+ def list_credentials(self, folder_name, domain_name='_'):
+ '''List credentials in domain of folder
+
+ :param folder_name: Folder name, ``str``
+ :param domain_name: Domain name, default is '_', ``str``
+ :returns: Credentials list, ``list``
+ '''
+ self.assert_folder(folder_name)
+ folder_url, short_name = self._get_job_folder(folder_name)
+ response = self.jenkins_open(requests.Request(
+ 'GET', self._build_url(LIST_CREDENTIALS, locals())
+ ))
+ return json.loads(response)['credentials']
+
def quiet_down(self):
'''Prepare Jenkins for shutdown.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/python-jenkins-1.2.1/python_jenkins.egg-info/PKG-INFO
new/python-jenkins-1.4.0/python_jenkins.egg-info/PKG-INFO
--- old/python-jenkins-1.2.1/python_jenkins.egg-info/PKG-INFO 2018-08-24
19:45:47.000000000 +0200
+++ new/python-jenkins-1.4.0/python_jenkins.egg-info/PKG-INFO 2018-11-19
02:56:08.000000000 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 1.2
Name: python-jenkins
-Version: 1.2.1
+Version: 1.4.0
Summary: Python bindings for the remote Jenkins API
Home-page: http://git.openstack.org/cgit/openstack/python-jenkins
Author: Ken Conley
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/python-jenkins-1.2.1/python_jenkins.egg-info/SOURCES.txt
new/python-jenkins-1.4.0/python_jenkins.egg-info/SOURCES.txt
--- old/python-jenkins-1.2.1/python_jenkins.egg-info/SOURCES.txt
2018-08-24 19:45:47.000000000 +0200
+++ new/python-jenkins-1.4.0/python_jenkins.egg-info/SOURCES.txt
2018-11-19 02:56:08.000000000 +0100
@@ -33,6 +33,7 @@
tests/base.py
tests/helper.py
tests/test_build.py
+tests/test_credential.py
tests/test_info.py
tests/test_jenkins.py
tests/test_jenkins_sockets.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/python-jenkins-1.2.1/python_jenkins.egg-info/pbr.json
new/python-jenkins-1.4.0/python_jenkins.egg-info/pbr.json
--- old/python-jenkins-1.2.1/python_jenkins.egg-info/pbr.json 2018-08-24
19:45:47.000000000 +0200
+++ new/python-jenkins-1.4.0/python_jenkins.egg-info/pbr.json 2018-11-19
02:56:08.000000000 +0100
@@ -1 +1 @@
-{"git_version": "93515ae", "is_release": true}
\ No newline at end of file
+{"git_version": "7166f87", "is_release": true}
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-jenkins-1.2.1/tests/base.py
new/python-jenkins-1.4.0/tests/base.py
--- old/python-jenkins-1.2.1/tests/base.py 2018-08-24 19:43:52.000000000
+0200
+++ new/python-jenkins-1.4.0/tests/base.py 2018-11-19 02:55:24.000000000
+0100
@@ -43,3 +43,9 @@
for req in requests:
req[0][0].prepare()
+
+ def got_request_urls(self, mock):
+ return [
+ call[0][0].url.split('?')[0]
+ for call in mock.call_args_list
+ ]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-jenkins-1.2.1/tests/helper.py
new/python-jenkins-1.4.0/tests/helper.py
--- old/python-jenkins-1.2.1/tests/helper.py 2018-08-24 19:43:33.000000000
+0200
+++ new/python-jenkins-1.4.0/tests/helper.py 2018-11-19 02:55:24.000000000
+0100
@@ -78,14 +78,15 @@
*args, **kwargs)
-def build_response_mock(status_code, json_body=None, headers=None, **kwargs):
+def build_response_mock(status_code, json_body=None, headers=None,
+ add_content_length=True, **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 {}:
+ if add_content_length and headers is not {}:
real_response.headers['content-length'] = len(text)
if headers is not None:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-jenkins-1.2.1/tests/jobs/base.py
new/python-jenkins-1.4.0/tests/jobs/base.py
--- old/python-jenkins-1.2.1/tests/jobs/base.py 2018-08-24 19:43:33.000000000
+0200
+++ new/python-jenkins-1.4.0/tests/jobs/base.py 2018-11-19 02:55:24.000000000
+0100
@@ -1,6 +1,3 @@
-import copy
-import json
-
from tests.base import JenkinsTestBase
@@ -16,54 +13,72 @@
class JenkinsGetJobsTestBase(JenkinsJobsTestBase):
jobs_in_folder = [
- [
- {'name': 'my_job1'},
- {'name': 'my_folder1', 'jobs': None},
- {'name': 'my_job2'}
- ],
+ {'jobs': [
+ {'name': 'my_job1', 'color': 'blue', 'url': 'http://...'},
+ {'name': 'my_folder1', 'url': 'http://...', 'jobs': [{}, {}]},
+ {'name': 'my_job2', 'color': 'blue', 'url': 'http://...'},
+ {'name': 'job', 'url': 'http://...', 'jobs': [{}]}
+ ]},
# my_folder1 jobs
- [
- {'name': 'my_job3'},
- {'name': 'my_job4'}
- ]
+ {'jobs': [
+ {'name': 'my_job3', 'color': 'blue', 'url': 'http://...'},
+ {'name': 'my_job4', 'color': 'blue', 'url': 'http://...'}
+ ]},
+ # "job" folder jobs
+ {'jobs': [
+ {'name': 'my_job', 'color': 'blue', 'url': 'http://...'}
+ ]}
]
- jobs_in_multiple_folders = copy.deepcopy(jobs_in_folder)
- jobs_in_multiple_folders[1].insert(
- 0, {'name': 'my_folder2', 'jobs': None})
- jobs_in_multiple_folders.append(
+ jobs_in_multiple_folders = [
+ {'jobs': [
+ {'name': 'my_job1', 'color': 'blue', 'url': 'http://...'},
+ {'name': 'my_folder1', 'url': 'http://...', 'jobs': [{}, {}, {}]},
+ {'name': 'my_job2', 'color': 'blue', 'url': 'http://...'}
+ ]},
+ # my_folder1 jobs
+ {'jobs': [
+ {'name': 'my_folder2', 'url': 'http://...', 'jobs': [{}, {}]},
+ {'name': 'my_job3', 'color': 'blue', 'url': 'http://...'},
+ {'name': 'my_job4', 'color': 'blue', 'url': 'http://...'}
+ ]},
# my_folder1/my_folder2 jobs
- [
- {'name': 'my_job1'},
- {'name': 'my_job2'}
- ]
- )
-
- jobs_in_unsafe_name_folders = copy.deepcopy(jobs_in_folder)
- jobs_in_unsafe_name_folders[1].insert(
- 0, {'name': 'my spaced folder', 'jobs': None})
- jobs_in_unsafe_name_folders.append(
- # my_folder1/my\ spaced\ folder jobs
- [
- {'name': 'my job 5'}
- ]
- )
-
-
-def build_jobs_list_responses(jobs_list, server_url):
- responses = []
- for jobs in jobs_list:
- get_jobs_response = []
- for job in jobs:
- job_json = {
- u'url': u'%s/job/%s' % (server_url.rstrip('/'), job['name']),
- u'name': job['name'],
- u'color': u'blue'
- }
- if 'jobs' in job:
- job_json[u'jobs'] = "null"
- get_jobs_response.append(job_json)
+ {'jobs': [
+ {'name': 'my_job1', 'color': 'blue', 'url': 'http://...'},
+ {'name': 'my_job2', 'color': 'blue', 'url': 'http://...'}
+ ]}
+ ]
- responses.append(json.dumps({u'jobs': get_jobs_response}))
+ jobs_in_unsafe_name_folders = [
+ {'jobs': [
+ {'name': 'my_job1', 'color': 'blue', 'url': 'http://...'},
+ {'name': 'my_folder1', 'url': 'http://...', 'jobs': [{}, {}]},
+ {'name': 'my_job2', 'color': 'blue', 'url': 'http://...'}
+ ]},
+ # my_folder1 jobs
+ {'jobs': [
+ {'name': 'my spaced folder', 'url': 'http://...', 'jobs': [{}]},
+ {'name': 'my_job3', 'color': 'blue', 'url': 'http://...'},
+ {'name': 'my_job4', 'color': 'blue', 'url': 'http://...'}
+ ]},
+ # my_folder1/my\ spaced\ folder jobs
+ {'jobs': [
+ {'name': 'my job 5', 'color': 'blue', 'url': 'http://...'}
+ ]}
+ ]
- return responses
+ jobs_in_folder_deep_query = [
+ {'jobs': [
+ {'name': 'top_folder', 'url': 'http://...', 'jobs': [
+ {'name': 'middle_folder', 'url': 'http://...', 'jobs': [
+ {'name': 'bottom_folder', 'url': 'http://...',
+ 'jobs': [{}, {}]}
+ ]}
+ ]}
+ ]},
+ # top_folder/middle_folder/bottom_folder jobs
+ {'jobs': [
+ {'name': 'my_job1', 'color': 'blue', 'url': 'http://...'},
+ {'name': 'my_job2', 'color': 'blue', 'url': 'http://...'}
+ ]}
+ ]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-jenkins-1.2.1/tests/jobs/test_build.py
new/python-jenkins-1.4.0/tests/jobs/test_build.py
--- old/python-jenkins-1.2.1/tests/jobs/test_build.py 2018-08-24
19:43:33.000000000 +0200
+++ new/python-jenkins-1.4.0/tests/jobs/test_build.py 2018-11-19
02:55:24.000000000 +0100
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
from mock import patch
+import jenkins
from six.moves.urllib.parse import quote
from tests.helper import build_response_mock
from tests.jobs.base import JenkinsJobsTestBase
@@ -20,6 +21,49 @@
self.assertEqual(queue_id, 25)
@patch('jenkins.requests.Session.send', autospec=True)
+ def test_assert_no_location(self, session_send_mock):
+ session_send_mock.return_value = build_response_mock(302, {})
+
+ with self.assertRaises(jenkins.EmptyResponseException) as context_mgr:
+ self.j.build_job(u'Test Job')
+
+ self.assertEqual(
+ str(context_mgr.exception),
+ "Header 'Location' not found in response from server[{0}]".format(
+ self.make_url('')))
+
+ @patch.object(jenkins.Jenkins, 'maybe_add_crumb')
+ @patch('jenkins.requests.Session.send', autospec=True)
+ def test_simple_no_content_lenght(self, session_send_mock,
+ maybe_add_crumb_mock):
+ maybe_add_crumb_mock.return_value = None
+ session_send_mock.return_value = build_response_mock(
+ 201, None, add_content_length=False,
+ headers={'Location': self.make_url('/queue/item/25/')})
+
+ queue_id = self.j.build_job(u'Test Job')
+
+ self.assertEqual(session_send_mock.call_args[0][1].url,
+ self.make_url('job/Test%20Job/build'))
+ self.assertEqual(queue_id, 25)
+
+ @patch.object(jenkins.Jenkins, 'maybe_add_crumb')
+ @patch('jenkins.requests.Session.send', autospec=True)
+ def test_assert_no_content_lenght_no_location(self, session_send_mock,
+ maybe_add_crumb_mock):
+ maybe_add_crumb_mock.return_value = None
+ session_send_mock.return_value = build_response_mock(
+ 201, None, add_content_length=False)
+
+ with self.assertRaises(jenkins.EmptyResponseException) as context_mgr:
+ self.j.build_job(u'Test Job')
+
+ self.assertEqual(
+ str(context_mgr.exception),
+ 'Error communicating with server[{0}]: empty response'.format(
+ self.make_url('')))
+
+ @patch('jenkins.requests.Session.send', autospec=True)
def test_in_folder(self, session_send_mock):
session_send_mock.return_value = build_response_mock(
302, {}, headers={'Location': self.make_url('/queue/item/25/')})
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-jenkins-1.2.1/tests/jobs/test_get.py
new/python-jenkins-1.4.0/tests/jobs/test_get.py
--- old/python-jenkins-1.2.1/tests/jobs/test_get.py 2018-08-24
19:43:33.000000000 +0200
+++ new/python-jenkins-1.4.0/tests/jobs/test_get.py 2018-11-19
02:55:24.000000000 +0100
@@ -3,7 +3,6 @@
import jenkins
from tests.helper import build_response_mock
-from tests.jobs.base import build_jobs_list_responses
from tests.jobs.base import JenkinsGetJobsTestBase
@@ -19,7 +18,7 @@
job_info_to_return = {u'jobs': jobs}
jenkins_mock.return_value = json.dumps(job_info_to_return)
- job_info = self.j.get_jobs()
+ job_info = self.j.get_jobs(folder_depth_per_request=1)
jobs[u'fullname'] = jobs[u'name']
self.assertEqual(job_info, [jobs])
@@ -30,14 +29,12 @@
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_folders_simple(self, jenkins_mock):
- response = build_jobs_list_responses(
- self.jobs_in_folder, self.make_url(''))
- jenkins_mock.side_effect = iter(response)
+ jenkins_mock.side_effect = map(json.dumps, self.jobs_in_folder)
jobs_info = self.j.get_jobs()
expected_fullnames = [
- u"my_job1", u"my_folder1", u"my_job2"
+ u"my_job1", u"my_folder1", u"my_job2", u"job"
]
self.assertEqual(len(expected_fullnames), len(jobs_info))
got_fullnames = [job[u"fullname"] for job in jobs_info]
@@ -45,15 +42,13 @@
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_folders_additional_level(self, jenkins_mock):
- response = build_jobs_list_responses(
- self.jobs_in_folder, self.make_url(''))
- jenkins_mock.side_effect = iter(response)
+ jenkins_mock.side_effect = map(json.dumps, self.jobs_in_folder)
jobs_info = self.j.get_jobs(folder_depth=1)
expected_fullnames = [
- u"my_job1", u"my_folder1", u"my_job2",
- u"my_folder1/my_job3", u"my_folder1/my_job4"
+ u"my_job1", u"my_folder1", u"my_job2", u"job",
+ u"my_folder1/my_job3", u"my_folder1/my_job4", u"job/my_job"
]
self.assertEqual(len(expected_fullnames), len(jobs_info))
got_fullnames = [job[u"fullname"] for job in jobs_info]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-jenkins-1.2.1/tests/jobs/test_getall.py
new/python-jenkins-1.4.0/tests/jobs/test_getall.py
--- old/python-jenkins-1.2.1/tests/jobs/test_getall.py 2018-08-24
19:43:33.000000000 +0200
+++ new/python-jenkins-1.4.0/tests/jobs/test_getall.py 2018-11-19
02:55:24.000000000 +0100
@@ -1,7 +1,7 @@
+import json
from mock import patch
import jenkins
-from tests.jobs.base import build_jobs_list_responses
from tests.jobs.base import JenkinsGetJobsTestBase
@@ -9,25 +9,30 @@
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_simple(self, jenkins_mock):
- response = build_jobs_list_responses(
- self.jobs_in_folder, 'http://example.com/')
- jenkins_mock.side_effect = iter(response)
+ jenkins_mock.side_effect = map(json.dumps, self.jobs_in_folder)
jobs_info = self.j.get_all_jobs()
expected_fullnames = [
- u"my_job1", u"my_folder1", u"my_job2",
- u"my_folder1/my_job3", u"my_folder1/my_job4"
+ u"my_job1", u"my_folder1", u"my_job2", u"job",
+ u"my_folder1/my_job3", u"my_folder1/my_job4", u"job/my_job"
]
self.assertEqual(len(expected_fullnames), len(jobs_info))
got_fullnames = [job[u"fullname"] for job in jobs_info]
self.assertEqual(expected_fullnames, got_fullnames)
+ expected_request_urls = [
+ self.make_url('api/json'),
+ self.make_url('job/my_folder1/api/json'),
+ self.make_url('job/job/api/json')
+ ]
+ self.assertEqual(expected_request_urls,
+ self.got_request_urls(jenkins_mock))
+
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_multi_level(self, jenkins_mock):
- response = build_jobs_list_responses(
- self.jobs_in_multiple_folders, 'http://example.com/')
- jenkins_mock.side_effect = iter(response)
+ jenkins_mock.side_effect = map(
+ json.dumps, self.jobs_in_multiple_folders)
jobs_info = self.j.get_all_jobs()
@@ -44,11 +49,18 @@
for job in jobs_info
if job['name'] == u"my_job1"]))
+ expected_request_urls = [
+ self.make_url('api/json'),
+ self.make_url('job/my_folder1/api/json'),
+ self.make_url('job/my_folder1/job/my_folder2/api/json')
+ ]
+ self.assertEqual(expected_request_urls,
+ self.got_request_urls(jenkins_mock))
+
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_folders_depth(self, jenkins_mock):
- response = build_jobs_list_responses(
- self.jobs_in_multiple_folders, 'http://example.com/')
- jenkins_mock.side_effect = iter(response)
+ jenkins_mock.side_effect = map(
+ json.dumps, self.jobs_in_multiple_folders)
jobs_info = self.j.get_all_jobs(folder_depth=1)
@@ -60,11 +72,17 @@
got_fullnames = [job[u"fullname"] for job in jobs_info]
self.assertEqual(expected_fullnames, got_fullnames)
+ expected_request_urls = [
+ self.make_url('api/json'),
+ self.make_url('job/my_folder1/api/json')
+ ]
+ self.assertEqual(expected_request_urls,
+ self.got_request_urls(jenkins_mock))
+
@patch.object(jenkins.Jenkins, 'jenkins_open')
def test_unsafe_chars(self, jenkins_mock):
- response = build_jobs_list_responses(
- self.jobs_in_unsafe_name_folders, 'http://example.com/')
- jenkins_mock.side_effect = iter(response)
+ jenkins_mock.side_effect = map(
+ json.dumps, self.jobs_in_unsafe_name_folders)
jobs_info = self.j.get_all_jobs()
@@ -76,3 +94,36 @@
self.assertEqual(len(expected_fullnames), len(jobs_info))
got_fullnames = [job[u"fullname"] for job in jobs_info]
self.assertEqual(expected_fullnames, got_fullnames)
+
+ expected_request_urls = [
+ self.make_url('api/json'),
+ self.make_url('job/my_folder1/api/json'),
+ self.make_url('job/my_folder1/job/my%20spaced%20folder/api/json')
+ ]
+ self.assertEqual(expected_request_urls,
+ self.got_request_urls(jenkins_mock))
+
+ @patch.object(jenkins.Jenkins, 'jenkins_open')
+ def test_deep_query(self, jenkins_mock):
+ jenkins_mock.side_effect = map(
+ json.dumps, self.jobs_in_folder_deep_query)
+
+ jobs_info = self.j.get_all_jobs()
+
+ expected_fullnames = [
+ u"top_folder",
+ u"top_folder/middle_folder",
+ u"top_folder/middle_folder/bottom_folder",
+ u"top_folder/middle_folder/bottom_folder/my_job1",
+ u"top_folder/middle_folder/bottom_folder/my_job2"
+ ]
+ self.assertEqual(len(expected_fullnames), len(jobs_info))
+ got_fullnames = [job[u"fullname"] for job in jobs_info]
+ self.assertEqual(expected_fullnames, got_fullnames)
+
+ expected_request_urls = [
+ self.make_url('api/json'),
+
self.make_url('job/top_folder/job/middle_folder/job/bottom_folder/api/json')
+ ]
+ self.assertEqual(expected_request_urls,
+ self.got_request_urls(jenkins_mock))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-jenkins-1.2.1/tests/test_credential.py
new/python-jenkins-1.4.0/tests/test_credential.py
--- old/python-jenkins-1.2.1/tests/test_credential.py 1970-01-01
01:00:00.000000000 +0100
+++ new/python-jenkins-1.4.0/tests/test_credential.py 2018-11-19
02:55:24.000000000 +0100
@@ -0,0 +1,353 @@
+import json
+from mock import patch
+
+import jenkins
+from tests.base import JenkinsTestBase
+
+
+class JenkinsCredentialTestBase(JenkinsTestBase):
+ config_xml =
"""<com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl>
+ <scope>GLOBAL</scope>
+ <id>Test Credential</id>
+ <username>Test-User</username>
+ <password>secret123</password>
+
</com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl>"""
+
+
+class JenkinsGetTagTextTest(JenkinsCredentialTestBase):
+
+ def test_simple(self):
+ name_to_return = self.j._get_tag_text('id', self.config_xml)
+ self.assertEqual('Test Credential', name_to_return)
+
+ def test_failed(self):
+ with self.assertRaises(jenkins.JenkinsException) as context_manager:
+ self.j._get_tag_text('id', '<xml></xml>')
+ self.assertEqual(str(context_manager.exception),
+ 'tag[id] is invalidated')
+
+ with self.assertRaises(jenkins.JenkinsException) as context_manager:
+ self.j._get_tag_text('id', '<xml><id></id></xml>')
+ self.assertEqual(str(context_manager.exception),
+ 'tag[id] is invalidated')
+
+ with self.assertRaises(jenkins.JenkinsException) as context_manager:
+ self.j._get_tag_text('id', '<xml><id> </id></xml>')
+ self.assertEqual(str(context_manager.exception),
+ 'tag[id] is invalidated')
+
+
+class JenkinsIsFolderTest(JenkinsCredentialTestBase):
+
+ @patch.object(jenkins.Jenkins, 'jenkins_open')
+ def test_is_folder(self, jenkins_mock):
+ jenkins_mock.side_effect = [
+ json.dumps({'_class':
'com.cloudbees.hudson.plugins.folder.Folder'}),
+ ]
+ self.assertTrue(self.j.is_folder('Test Folder'))
+
+ @patch.object(jenkins.Jenkins, 'jenkins_open')
+ def test_is_not_folder(self, jenkins_mock):
+ jenkins_mock.side_effect = [
+ json.dumps({'_class':
'org.jenkinsci.plugins.workflow.job.WorkflowJob'}),
+ ]
+ self.assertFalse(self.j.is_folder('Test Job'))
+
+
+class JenkinsAssertFolderTest(JenkinsCredentialTestBase):
+
+ @patch.object(jenkins.Jenkins, 'jenkins_open')
+ def test_is_folder(self, jenkins_mock):
+ jenkins_mock.side_effect = [
+ json.dumps({'_class':
'com.cloudbees.hudson.plugins.folder.Folder'}),
+ ]
+ self.j.assert_folder('Test Folder')
+
+ @patch.object(jenkins.Jenkins, 'jenkins_open')
+ def test_is_not_folder(self, jenkins_mock):
+ jenkins_mock.side_effect = [
+ json.dumps({'_class':
'org.jenkinsci.plugins.workflow.job.WorkflowJob'}),
+ ]
+ with self.assertRaises(jenkins.JenkinsException) as context_manager:
+ self.j.assert_folder('Test Job')
+ self.assertEqual(str(context_manager.exception),
+ 'job[Test Job] is not a folder')
+
+
+class JenkinsAssertCredentialTest(JenkinsCredentialTestBase):
+
+ @patch.object(jenkins.Jenkins, 'jenkins_open')
+ def test_credential_missing(self, jenkins_mock):
+ jenkins_mock.side_effect = [
+ json.dumps({'_class':
'com.cloudbees.hudson.plugins.folder.Folder'}),
+ jenkins.NotFoundException()
+ ]
+
+ with self.assertRaises(jenkins.JenkinsException) as context_manager:
+ self.j.assert_credential_exists('NonExistent', 'TestFoler')
+ self.assertEqual(
+ str(context_manager.exception),
+ 'credential[NonExistent] does not exist'
+ ' in the domain[_] of [TestFoler]')
+ self._check_requests(jenkins_mock.call_args_list)
+
+ @patch.object(jenkins.Jenkins, 'jenkins_open')
+ def test_credential_exists(self, jenkins_mock):
+ jenkins_mock.side_effect = [
+ json.dumps({'_class':
'com.cloudbees.hudson.plugins.folder.Folder'}),
+ json.dumps({'id': 'ExistingCredential'})
+ ]
+ self.j.assert_credential_exists('ExistingCredential', 'TestFoler')
+ self._check_requests(jenkins_mock.call_args_list)
+
+
+class JenkinsCredentialExistsTest(JenkinsCredentialTestBase):
+
+ @patch.object(jenkins.Jenkins, 'jenkins_open')
+ def test_credential_missing(self, jenkins_mock):
+ jenkins_mock.side_effect = [
+ json.dumps({'_class':
'com.cloudbees.hudson.plugins.folder.Folder'}),
+ jenkins.NotFoundException()
+ ]
+
+ self.assertEqual(self.j.credential_exists('NonExistent', 'TestFolder'),
+ False)
+ self._check_requests(jenkins_mock.call_args_list)
+
+ @patch.object(jenkins.Jenkins, 'jenkins_open')
+ def test_credential_exists(self, jenkins_mock):
+ jenkins_mock.side_effect = [
+ json.dumps({'_class':
'com.cloudbees.hudson.plugins.folder.Folder'}),
+ json.dumps({'id': 'ExistingCredential'})
+ ]
+
+ self.assertEqual(self.j.credential_exists('ExistingCredential',
+ 'TestFolder'),
+ True)
+ self._check_requests(jenkins_mock.call_args_list)
+
+
+class JenkinsGetCredentialInfoTest(JenkinsCredentialTestBase):
+
+ @patch.object(jenkins.Jenkins, 'jenkins_open')
+ def test_simple(self, jenkins_mock):
+ credential_info_to_return = {'id': 'ExistingCredential'}
+ jenkins_mock.side_effect = [
+ json.dumps({'_class':
'com.cloudbees.hudson.plugins.folder.Folder'}),
+ json.dumps(credential_info_to_return)
+ ]
+
+ credential_info = self.j.get_credential_info('ExistingCredential',
'TestFolder')
+
+ self.assertEqual(credential_info, credential_info_to_return)
+ self.assertEqual(
+ jenkins_mock.call_args[0][0].url,
+ self.make_url('job/TestFolder/credentials/store/folder/'
+
'domain/_/credential/ExistingCredential/api/json?depth=0'))
+ self._check_requests(jenkins_mock.call_args_list)
+
+ @patch.object(jenkins.Jenkins, 'jenkins_open')
+ def test_nonexistent(self, jenkins_mock):
+ jenkins_mock.side_effect = [
+ json.dumps({'_class':
'com.cloudbees.hudson.plugins.folder.Folder'}),
+ None,
+ ]
+
+ with self.assertRaises(jenkins.JenkinsException) as context_manager:
+ self.j.get_credential_info('NonExistent', 'TestFolder')
+
+ self.assertEqual(
+ str(context_manager.exception),
+ 'credential[NonExistent] does not exist '
+ 'in the domain[_] of [TestFolder]')
+
+ @patch.object(jenkins.Jenkins, 'jenkins_open')
+ def test_invalid_json(self, jenkins_mock):
+ jenkins_mock.side_effect = [
+ json.dumps({'_class':
'com.cloudbees.hudson.plugins.folder.Folder'}),
+ '{invalid_json}'
+ ]
+
+ with self.assertRaises(jenkins.JenkinsException) as context_manager:
+ self.j.get_credential_info('NonExistent', 'TestFolder')
+
+ self.assertEqual(
+ str(context_manager.exception),
+ 'Could not parse JSON info for credential[NonExistent]'
+ ' in the domain[_] of [TestFolder]')
+
+
+class JenkinsGetCredentialConfigTest(JenkinsCredentialTestBase):
+
+ @patch.object(jenkins.Jenkins, 'jenkins_open')
+ def test_encodes_credential_name(self, jenkins_mock):
+ jenkins_mock.side_effect = [
+ json.dumps({'_class':
'com.cloudbees.hudson.plugins.folder.Folder'}),
+ None,
+ ]
+ self.j.get_credential_config(u'Test Credential', u'Test Folder')
+
+ self.assertEqual(
+ jenkins_mock.call_args_list[1][0][0].url,
+ self.make_url('job/Test%20Folder/credentials/store/folder/domain/'
+ '_/credential/Test%20Credential/config.xml'))
+ self._check_requests(jenkins_mock.call_args_list)
+
+
+class JenkinsCreateCredentialTest(JenkinsCredentialTestBase):
+
+ @patch.object(jenkins.Jenkins, 'jenkins_open')
+ def test_simple(self, jenkins_mock):
+ jenkins_mock.side_effect = [
+ json.dumps({'_class':
'com.cloudbees.hudson.plugins.folder.Folder'}),
+ jenkins.NotFoundException(),
+ None,
+ json.dumps({'_class':
'com.cloudbees.hudson.plugins.folder.Folder'}),
+ json.dumps({'id': 'Test Credential'}),
+ ]
+
+ self.j.create_credential('Test Folder', self.config_xml)
+ self.assertEqual(
+ jenkins_mock.call_args_list[1][0][0].url,
+ self.make_url('job/Test%20Folder/credentials/store/folder/'
+
'domain/_/credential/Test%20Credential/api/json?depth=0'))
+
+ self.assertEqual(
+ jenkins_mock.call_args_list[2][0][0].url,
+ self.make_url('job/Test%20Folder/credentials/store/folder/'
+ 'domain/_/createCredentials'))
+
+ self.assertEqual(
+ jenkins_mock.call_args_list[4][0][0].url,
+ self.make_url('job/Test%20Folder/credentials/store/folder/'
+
'domain/_/credential/Test%20Credential/api/json?depth=0'))
+ self._check_requests(jenkins_mock.call_args_list)
+
+ @patch.object(jenkins.Jenkins, 'jenkins_open')
+ def test_already_exists(self, jenkins_mock):
+ jenkins_mock.side_effect = [
+ json.dumps({'_class':
'com.cloudbees.hudson.plugins.folder.Folder'}),
+ json.dumps({'id': 'Test Credential'}),
+ ]
+
+ with self.assertRaises(jenkins.JenkinsException) as context_manager:
+ self.j.create_credential('Test Folder', self.config_xml)
+ self.assertEqual(
+ jenkins_mock.call_args_list[1][0][0].url,
+ self.make_url('job/Test%20Folder/credentials/store/folder/'
+
'domain/_/credential/Test%20Credential/api/json?depth=0'))
+
+ self.assertEqual(
+ str(context_manager.exception),
+ 'credential[Test Credential] already exists'
+ ' in the domain[_] of [Test Folder]')
+ self._check_requests(jenkins_mock.call_args_list)
+
+ @patch.object(jenkins.Jenkins, 'jenkins_open')
+ def test_failed(self, jenkins_mock):
+ jenkins_mock.side_effect = [
+ json.dumps({'_class':
'com.cloudbees.hudson.plugins.folder.Folder'}),
+ jenkins.NotFoundException(),
+ None,
+ json.dumps({'_class':
'com.cloudbees.hudson.plugins.folder.Folder'}),
+ None,
+ ]
+
+ with self.assertRaises(jenkins.JenkinsException) as context_manager:
+ self.j.create_credential('Test Folder', self.config_xml)
+ self.assertEqual(
+ jenkins_mock.call_args_list[1][0][0].url,
+ self.make_url('job/Test%20Folder/credentials/store/folder/'
+
'domain/_/credential/Test%20Credential/api/json?depth=0'))
+ self.assertEqual(
+ jenkins_mock.call_args_list[2][0][0].url,
+ self.make_url('job/Test%20Folder/credentials/store/'
+ 'folder/domain/_/createCredentials'))
+ self.assertEqual(
+ jenkins_mock.call_args_list[4][0][0].url,
+ self.make_url('job/Test%20Folder/credentials/store/folder/'
+
'domain/_/credential/Test%20Credential/api/json?depth=0'))
+ self.assertEqual(
+ str(context_manager.exception),
+ 'create[Test Credential] failed in the domain[_] of [Test Folder]')
+ self._check_requests(jenkins_mock.call_args_list)
+
+
+class JenkinsDeleteCredentialTest(JenkinsCredentialTestBase):
+
+ @patch.object(jenkins.Jenkins, 'jenkins_open')
+ def test_simple(self, jenkins_mock):
+ jenkins_mock.side_effect = [
+ True,
+ json.dumps({'_class':
'com.cloudbees.hudson.plugins.folder.Folder'}),
+ jenkins.NotFoundException(),
+ ]
+
+ self.j.delete_credential(u'Test Credential', 'TestFolder')
+
+ self.assertEqual(
+ jenkins_mock.call_args_list[0][0][0].url,
+ self.make_url('job/TestFolder/credentials/store/folder/domain/'
+ '_/credential/Test%20Credential/config.xml'))
+ self._check_requests(jenkins_mock.call_args_list)
+
+ @patch.object(jenkins.Jenkins, 'jenkins_open')
+ def test_failed(self, jenkins_mock):
+ jenkins_mock.side_effect = [
+ json.dumps({'id': 'ExistingCredential'}),
+ json.dumps({'_class':
'com.cloudbees.hudson.plugins.folder.Folder'}),
+ json.dumps({'id': 'ExistingCredential'})
+ ]
+
+ with self.assertRaises(jenkins.JenkinsException) as context_manager:
+ self.j.delete_credential(u'ExistingCredential', 'TestFolder')
+ self.assertEqual(
+ jenkins_mock.call_args_list[0][0][0].url,
+ self.make_url('job/TestFolder/credentials/store/folder/'
+ 'domain/_/credential/ExistingCredential/config.xml'))
+ self.assertEqual(
+ str(context_manager.exception),
+ 'delete credential[ExistingCredential] from '
+ 'domain[_] of [TestFolder] failed')
+ self._check_requests(jenkins_mock.call_args_list)
+
+
+class JenkinsReconfigCredentialTest(JenkinsCredentialTestBase):
+
+ @patch.object(jenkins.Jenkins, 'jenkins_open')
+ def test_simple(self, jenkins_mock):
+ jenkins_mock.side_effect = [
+ json.dumps({'_class':
'com.cloudbees.hudson.plugins.folder.Folder'}),
+ json.dumps({'id': 'Test Credential'}),
+ None
+ ]
+
+ self.j.reconfig_credential(u'Test Folder', self.config_xml)
+
+ self.assertEqual(
+ jenkins_mock.call_args_list[1][0][0].url,
+ self.make_url('job/Test%20Folder/credentials/store/folder/domain/'
+ '_/credential/Test%20Credential/api/json?depth=0'))
+ self.assertEqual(
+ jenkins_mock.call_args_list[2][0][0].url,
+ self.make_url('job/Test%20Folder/credentials/store/folder/domain/'
+ '_/credential/Test%20Credential/config.xml'))
+ self._check_requests(jenkins_mock.call_args_list)
+
+
+class JenkinsListCredentialConfigTest(JenkinsCredentialTestBase):
+
+ @patch.object(jenkins.Jenkins, 'jenkins_open')
+ def test_simple(self, jenkins_mock):
+ credentials_to_return = [{'id': 'Test Credential'}]
+ jenkins_mock.side_effect = [
+ json.dumps({'_class':
'com.cloudbees.hudson.plugins.folder.Folder'}),
+ json.dumps({'credentials': [{'id': 'Test Credential'}]}),
+ ]
+ credentials = self.j.list_credentials(u'Test Folder')
+ self.assertEqual(credentials, credentials_to_return)
+ self.assertEqual(
+ jenkins_mock.call_args_list[1][0][0].url,
+ self.make_url('job/Test%20Folder/credentials/store/folder/domain/'
+ '_/api/json?tree=credentials[id]'))
+ self._check_requests(jenkins_mock.call_args_list)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-jenkins-1.2.1/tests/test_jenkins.py
new/python-jenkins-1.4.0/tests/test_jenkins.py
--- old/python-jenkins-1.2.1/tests/test_jenkins.py 2018-08-24
19:43:52.000000000 +0200
+++ new/python-jenkins-1.4.0/tests/test_jenkins.py 2018-11-19
02:55:24.000000000 +0100
@@ -120,6 +120,34 @@
self.assertFalse('.crumb' in request.headers)
+class JenkinsMaybeAddHeaders(JenkinsTestBase):
+ @patch('jenkins.requests.Session.send', autospec=True)
+ def test_simple(self, session_send_mock):
+ session_send_mock.return_value = build_response_mock(
+ 404, reason="Not Found")
+ request = jenkins.requests.Request('GET',
'http://example.com/job/TestJob')
+
+ with patch.dict('os.environ', {}):
+ j = jenkins.Jenkins(self.base_url, 'test', 'test')
+ request = j._session.prepare_request(request)
+
+ self.assertEqual(request.headers, self.j._session.headers)
+ self.assertNotIn("X-Auth", request.headers)
+
+ @patch('jenkins.requests.Session.send', autospec=True)
+ def test_add_header(self, session_send_mock):
+ session_send_mock.return_value = build_response_mock(
+ 404, reason="Not Found")
+ request = jenkins.requests.Request('GET',
'http://example.com/job/TestJob')
+
+ with patch.dict('os.environ', {"JENKINS_API_EXTRA_HEADERS": "X-Auth:
123\nX-Key: 234"}):
+ j = jenkins.Jenkins(self.base_url, 'test', 'test')
+ request = j._session.prepare_request(request)
+
+ self.assertEqual(request.headers["X-Auth"], "123")
+ self.assertEqual(request.headers["X-Key"], "234")
+
+
class JenkinsOpenTest(JenkinsTestBase):
@patch('jenkins.requests.Session.send', autospec=True)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-jenkins-1.2.1/tox.ini
new/python-jenkins-1.4.0/tox.ini
--- old/python-jenkins-1.2.1/tox.ini 2018-08-24 19:43:33.000000000 +0200
+++ new/python-jenkins-1.4.0/tox.ini 2018-11-19 02:55:24.000000000 +0100
@@ -1,5 +1,5 @@
[tox]
-minversion = 1.6
+minversion = 2.0
skipsdist = True
envlist = py{34,27,35}, linters, pypy