Hello community,

here is the log from the commit of package python-jenkinsapi for 
openSUSE:Leap:15.2 checked in at 2020-04-20 12:55:17
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Leap:15.2/python-jenkinsapi (Old)
 and      /work/SRC/openSUSE:Leap:15.2/.python-jenkinsapi.new.2738 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-jenkinsapi"

Mon Apr 20 12:55:17 2020 rev:12 rq:795462 version:0.3.11

Changes:
--------
--- /work/SRC/openSUSE:Leap:15.2/python-jenkinsapi/python-jenkinsapi.changes    
2020-03-09 18:06:45.848859173 +0100
+++ 
/work/SRC/openSUSE:Leap:15.2/.python-jenkinsapi.new.2738/python-jenkinsapi.changes
  2020-04-20 12:55:38.152742341 +0200
@@ -1,0 +2,35 @@
+Fri Apr 17 06:51:42 UTC 2020 - [email protected]
+
+- use pytest -k instead of removing tests
+
+-------------------------------------------------------------------
+Thu Apr 16 12:13:33 UTC 2020 - [email protected]
+
+- version update to 0.3.11
+  0.3.11
+  ------
+  * jobs.py: don't yield jobs twice (#740)
+  * Add debug log message in JenkinsBase.process\_job\_folder to follow 
execution progress (#738)
+  * don't faile when node\_descriprion is missing (#737)
+  * Fix safe exit test (#736)
+  * don't stale feature requests or "help wanted"
+  * change label for stale issues
+  * Fixed idle state not being refreshed (#575)
+  * Simple plugins (#735)
+  0.3.10
+  ------
+  * Add method for streaming build logs (#722)
+  * add instructions for Stale bot
+  * Fixed problems with changeSets (#717)
+  * Create Node by providing predetermined configuration (#730)
+  * Fix plugin versions (#734)
+  * Updates Requester to use a Session, maintaining cookies (#727)
+  * Feature/multibranch pipeline job (#715)
+  * Fix failures caused by plugins (#718)
+  * Fix for build.get\_artifacts() (#712)
+  * Add safe exit (#700)
+  * simplify crumb usage (#704)
+  * generate new api token for logged in user (#706)
+  * ignore dot files (#705)
+
+-------------------------------------------------------------------

Old:
----
  jenkinsapi-0.3.9.tar.gz

New:
----
  jenkinsapi-0.3.11.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-jenkinsapi.spec ++++++
--- /var/tmp/diff_new_pack.wUT7au/_old  2020-04-20 12:55:38.440742793 +0200
+++ /var/tmp/diff_new_pack.wUT7au/_new  2020-04-20 12:55:38.444742799 +0200
@@ -1,7 +1,7 @@
 #
 # spec file for package python-jenkinsapi
 #
-# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany.
+# Copyright (c) 2020 SUSE LLC
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -18,13 +18,13 @@
 
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 Name:           python-jenkinsapi
-Version:        0.3.9
+Version:        0.3.11
 Release:        0
 Summary:        A Python API for accessing resources on a Jenkins continuous 
integration server
 License:        MIT
 Group:          Development/Languages/Python
-Url:            https://github.com/salimfadhley/jenkinsapi
-Source:         
https://pypi.io/packages/source/j/jenkinsapi/jenkinsapi-%{version}.tar.gz
+URL:            https://github.com/salimfadhley/jenkinsapi
+Source:         
https://files.pythonhosted.org/packages/source/j/jenkinsapi/jenkinsapi-%{version}.tar.gz
 BuildRequires:  %{python_module pbr}
 BuildRequires:  %{python_module pytz}
 BuildRequires:  %{python_module requests}
@@ -33,7 +33,15 @@
 BuildRequires:  python-rpm-macros
 Requires:       python-pytz >= 2014.4
 Requires:       python-requests >= 2.3.0
+Requires:       python-six >= 1.10.0
 BuildArch:      noarch
+# SECTION test requirements
+BuildRequires:  %{python_module astroid >= 1.4.8}
+BuildRequires:  %{python_module mock}
+BuildRequires:  %{python_module pytest-mock}
+BuildRequires:  %{python_module pytest}
+BuildRequires:  %{python_module requests-kerberos}
+# /SECTION
 %python_subpackages
 
 %description
@@ -78,8 +86,14 @@
 %postun
 %python_uninstall_alternative jenkins_invoke
 
+%check
+# jenkinsapi_tests/unittests/test_view.py
+# E       fixture '_view_poll' not found
+# jddenkinsapi_tests/unittests/test_plugins.py
+# E           ConnectionError: HTTPSConnectionPool(host='updates.jenkins.io...
+%pytest jenkinsapi_tests/unittests jenkinsapi_tests/test_utils -k 'not 
(test_view or test_plugins)'
+
 %files %{python_files}
-%defattr(-,root,root,-)
 %doc README.rst
 %license license.txt
 %python_alternative %{_bindir}/jenkins_invoke

++++++ jenkinsapi-0.3.9.tar.gz -> jenkinsapi-0.3.11.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jenkinsapi-0.3.9/.github/stale.yml 
new/jenkinsapi-0.3.11/.github/stale.yml
--- old/jenkinsapi-0.3.9/.github/stale.yml      1970-01-01 01:00:00.000000000 
+0100
+++ new/jenkinsapi-0.3.11/.github/stale.yml     2019-10-21 13:43:49.000000000 
+0200
@@ -0,0 +1,64 @@
+# Configuration for probot-stale - https://github.com/probot/stale
+
+# Number of days of inactivity before an Issue or Pull Request becomes stale
+daysUntilStale: 60
+
+# Number of days of inactivity before an Issue or Pull Request with the stale 
label is closed.
+# Set to false to disable. If disabled, issues still need to be closed 
manually, but will remain marked as stale.
+daysUntilClose: 7
+
+# Only issues or pull requests with all of these labels are check if stale. 
Defaults to `[]` (disabled)
+onlyLabels: []
+
+# Issues or Pull Requests with these labels will never be considered stale. 
Set to `[]` to disable
+exemptLabels:
+  - pinned
+  - security
+  - "[Status] Maybe Later"
+  - "feature request"
+  - "help wanted"
+  - "improvement request"
+
+# Set to true to ignore issues in a project (defaults to false)
+exemptProjects: false
+
+# Set to true to ignore issues in a milestone (defaults to false)
+exemptMilestones: false
+
+# Set to true to ignore issues with an assignee (defaults to false)
+exemptAssignees: false
+
+# Label to use when marking as stale
+staleLabel: stale
+
+# Comment to post when marking as stale. Set to `false` to disable
+markComment: >
+  This issue has been automatically marked as stale because it has not had
+  recent activity. It will be closed if no further activity occurs. Thank you
+  for your contributions.
+
+# Comment to post when removing the stale label.
+# unmarkComment: >
+#   Your comment here.
+
+# Comment to post when closing a stale Issue or Pull Request.
+closeComment: >
+  Closed due to inactivity
+
+# Limit the number of actions per hour, from 1-30. Default is 30
+limitPerRun: 30
+
+# Limit to only `issues` or `pulls`
+# only: issues
+
+# Optionally, specify configuration settings that are specific to just 
'issues' or 'pulls':
+# pulls:
+#   daysUntilStale: 30
+#   markComment: >
+#     This pull request has been automatically marked as stale because it has 
not had
+#     recent activity. It will be closed if no further activity occurs. Thank 
you
+#     for your contributions.
+
+# issues:
+#   exemptLabels:
+#     - confirmed
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jenkinsapi-0.3.9/AUTHORS 
new/jenkinsapi-0.3.11/AUTHORS
--- old/jenkinsapi-0.3.9/AUTHORS        2019-04-13 02:46:41.000000000 +0200
+++ new/jenkinsapi-0.3.11/AUTHORS       2019-10-31 13:00:21.000000000 +0100
@@ -16,6 +16,7 @@
 Bradley Harris <[email protected]>
 Brian Weber <[email protected]>
 Chris Gavin <[email protected]>
+Chris Maes <[email protected]>
 Christophe Bliard <[email protected]>
 Christophe de Vienne <[email protected]>
 Cleber J Santos <[email protected]>
@@ -42,6 +43,7 @@
 Jason Swager <[email protected]>
 Jeremy Kao <[email protected]>
 Jingjing Duan <[email protected]>
+Jiri Polansky <[email protected]>
 Joao Vale <[email protected]>
 John Zeller <[email protected]>
 Jon Passki <[email protected]>
@@ -61,6 +63,7 @@
 Lars Hupfeldt <[email protected]>
 Lars Wirzenius <[email protected]>
 Leshist <[email protected]>
+Lucas Cimon <[email protected]>
 Marius van den Beek <[email protected]>
 Matt Hootman <[email protected]>
 Mickaël Schoentgen <[email protected]>
@@ -72,6 +75,7 @@
 Pablo Fabregat <[email protected]>
 Paul Belanger <[email protected]>
 Petr Kocandrle <[email protected]>
+Przemysław Pietras <[email protected]>
 Rafael Eyng <[email protected]>
 Ramon van Alteren <[email protected]>
 Robert Collins <[email protected]>
@@ -90,6 +94,7 @@
 Sorin Sbarnea <[email protected]>
 Sorin Sbarnea <[email protected]>
 Stephen Chu <[email protected]>
+Steve Bussetti <[email protected]>
 Steve Lounsbury <[email protected]>
 Sudharshan S <[email protected]>
 Sudharshan S <[email protected]>
@@ -110,6 +115,7 @@
 andrewgy8 <[email protected]>
 bbgobie <[email protected]>
 benjaminkemper <[email protected]>
+chrismaes87 <[email protected]>
 ddavydov <[email protected]>
 elhostis <[email protected]>
 hokadiri <[email protected]>
@@ -119,11 +125,14 @@
 jpiron <[email protected]>
 jwhitworth <[email protected]>
 kavehv <[email protected]>
+kennedy <[email protected]>
 kworr <[email protected]>
 lechat <[email protected]>
 lechat <[email protected]>
+leon-gh <[email protected]>
 lphoward <[email protected]>
 luciali <[email protected]>
+maxime-viargues-serato <[email protected]>
 mdear <[email protected]>
 mthak <[email protected]>
 mthakkar <[email protected]>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jenkinsapi-0.3.9/ChangeLog 
new/jenkinsapi-0.3.11/ChangeLog
--- old/jenkinsapi-0.3.9/ChangeLog      2019-04-13 02:46:41.000000000 +0200
+++ new/jenkinsapi-0.3.11/ChangeLog     2019-10-31 13:00:21.000000000 +0100
@@ -1,6 +1,35 @@
 CHANGES
 =======
 
+0.3.11
+------
+
+* jobs.py: don't yield jobs twice (#740)
+* Add debug log message in JenkinsBase.process\_job\_folder to follow 
execution progress (#738)
+* don't faile when node\_descriprion is missing (#737)
+* Fix safe exit test (#736)
+* don't stale feature requests or "help wanted"
+* change label for stale issues
+* Fixed idle state not being refreshed (#575)
+* Simple plugins (#735)
+
+0.3.10
+------
+
+* Add method for streaming build logs (#722)
+* add instructions for Stale bot
+* Fixed problems with changeSets (#717)
+* Create Node by providing predetermined configuration (#730)
+* Fix plugin versions (#734)
+* Updates Requester to use a Session, maintaining cookies (#727)
+* Feature/multibranch pipeline job (#715)
+* Fix failures caused by plugins (#718)
+* Fix for build.get\_artifacts() (#712)
+* Add safe exit (#700)
+* simplify crumb usage (#704)
+* generate new api token for logged in user (#706)
+* ignore dot files (#705)
+
 0.3.9
 -----
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jenkinsapi-0.3.9/PKG-INFO 
new/jenkinsapi-0.3.11/PKG-INFO
--- old/jenkinsapi-0.3.9/PKG-INFO       2019-04-13 02:46:42.000000000 +0200
+++ new/jenkinsapi-0.3.11/PKG-INFO      2019-10-31 13:00:22.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: jenkinsapi
-Version: 0.3.9
+Version: 0.3.11
 Summary: A Python API for accessing resources on a Jenkins 
continuous-integration server.
 Home-page: UNKNOWN
 Author: Salim Fadhley, Aleksey Maksimov
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jenkinsapi-0.3.9/examples/how_to/use_crumbs.py 
new/jenkinsapi-0.3.11/examples/how_to/use_crumbs.py
--- old/jenkinsapi-0.3.9/examples/how_to/use_crumbs.py  2018-11-13 
01:11:17.000000000 +0100
+++ new/jenkinsapi-0.3.11/examples/how_to/use_crumbs.py 2019-10-18 
14:46:09.000000000 +0200
@@ -1,17 +1,9 @@
 """
-Example of using CrumbRequester - when CSRF protection is enable in Jenkins
+Example of using CrumbRequester - when CSRF protection is enabled in Jenkins
 """
 from jenkinsapi.jenkins import Jenkins
-from jenkinsapi.utils.crumb_requester import CrumbRequester
 
-jenkins = Jenkins(
-    'http://localhost:8080', username='admin', password='password',
-    requester=CrumbRequester(
-        baseurl='http://localhost:8080',
-        username='admin',
-        password='password'
-    )
-)
+jenkins = Jenkins('http://localhost:8080', username='admin', 
password='password', useCrumb=True)
 
 for job_name in jenkins.jobs:
-    print job_name
+    print(job_name)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jenkinsapi-0.3.9/jenkinsapi/build.py 
new/jenkinsapi-0.3.11/jenkinsapi/build.py
--- old/jenkinsapi-0.3.9/jenkinsapi/build.py    2018-11-13 01:11:17.000000000 
+0100
+++ new/jenkinsapi-0.3.11/jenkinsapi/build.py   2019-10-18 16:28:28.000000000 
+0200
@@ -82,16 +82,13 @@
         return self._data["builtOn"]
 
     def get_revision(self):
-        vcs = self._data['changeSet']['kind'] or 'git'
-        return getattr(self, '_get_%s_rev' % vcs, lambda: None)()
+        return getattr(self, '_get_%s_rev' % self._get_vcs(), lambda: None)()
 
     def get_revision_branch(self):
-        vcs = self._data['changeSet']['kind'] or 'git'
-        return getattr(self, '_get_%s_rev_branch' % vcs, lambda: None)()
+        return getattr(self, '_get_%s_rev_branch' % self._get_vcs(), lambda: 
None)()
 
     def get_repo_url(self):
-        vcs = self._data['changeSet']['kind'] or 'git'
-        return getattr(self, '_get_%s_repo_url' % vcs, lambda: None)()
+        return getattr(self, '_get_%s_repo_url' % self._get_vcs(), lambda: 
None)()
 
     def get_params(self):
         """
@@ -147,6 +144,18 @@
                 return self._data['changeSets']['items']
         return []
 
+    def _get_vcs(self):
+        """
+        Returns a string VCS.
+        By default, 'git' will be used.
+        """
+        vcs = 'git'
+        if 'changeSet' in self._data and 'kind' in self._data['changeSet']:
+            vcs = self._data['changeSet']['kind'] or 'git'
+        elif 'changeSets' in self._data and 'kind' in self._data['changeSets']:
+            vcs = self._data['changeSets']['kind'] or 'git'
+        return vcs
+
     def _get_svn_rev(self):
         warnings.warn(
             "This untested function may soon be removed from Jenkinsapi "
@@ -222,7 +231,7 @@
 
     def get_artifact_dict(self):
         return dict(
-            (af.filename, af) for af in self.get_artifacts()
+            (af.relative_path, af) for af in self.get_artifacts()
         )
 
     def get_upstream_job_name(self):
@@ -494,6 +503,27 @@
         else:
             raise JenkinsAPIException('Unknown content type for console')
 
+    def stream_logs(self, interval=0):
+        """
+        Return generator which streams parts of text console.
+        """
+        url = "%s/logText/progressiveText" % self.baseurl
+        size = 0
+        more_data = True
+        while more_data:
+            resp = self.job.jenkins.requester.get_url(url, params={'start': 
size})
+            content = resp.content
+            if content:
+                if isinstance(content, str):
+                    yield content
+                elif isinstance(content, bytes):
+                    yield content.decode('ISO-8859-1')
+                else:
+                    raise JenkinsAPIException('Unknown content type for 
console')
+            size = resp.headers['X-Text-Size']
+            more_data = resp.headers.get('X-More-Data')
+            sleep(interval)
+
     def get_estimated_duration(self):
         """
         Return the estimated build duration (in seconds) or none.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jenkinsapi-0.3.9/jenkinsapi/jenkins.py 
new/jenkinsapi-0.3.11/jenkinsapi/jenkins.py
--- old/jenkinsapi-0.3.9/jenkinsapi/jenkins.py  2019-04-12 15:45:38.000000000 
+0200
+++ new/jenkinsapi-0.3.11/jenkinsapi/jenkins.py 2019-10-21 15:49:10.000000000 
+0200
@@ -22,12 +22,14 @@
 from jenkinsapi.nodes import Nodes
 from jenkinsapi.plugins import Plugins
 from jenkinsapi.plugin import Plugin
+from jenkinsapi.utils.requester import Requester
 from jenkinsapi.views import Views
 from jenkinsapi.queue import Queue
 from jenkinsapi.fingerprint import Fingerprint
 from jenkinsapi.jenkinsbase import JenkinsBase
-from jenkinsapi.utils.requester import Requester
 from jenkinsapi.custom_exceptions import JenkinsAPIException
+from jenkinsapi.utils.crumb_requester import CrumbRequester
+
 
 log = logging.getLogger(__name__)
 
@@ -42,7 +44,8 @@
             self, baseurl,
             username=None, password=None,
             requester=None, lazy=False,
-            ssl_verify=True, cert=None, timeout=10):
+            ssl_verify=True, cert=None,
+            timeout=10, useCrumb=False):
         """
         :param baseurl: baseurl for jenkins instance including port, str
         :param username: username for jenkins auth, str
@@ -51,14 +54,23 @@
         """
         self.username = username
         self.password = password
-        self.requester = requester or Requester(
-            username,
-            password,
-            baseurl=baseurl,
-            ssl_verify=ssl_verify,
-            cert=cert,
-            timeout=timeout
-        )
+        if requester is None:
+            if useCrumb:
+                requester = CrumbRequester
+            else:
+                requester = Requester
+
+            self.requester = requester(
+                username,
+                password,
+                baseurl=baseurl,
+                ssl_verify=ssl_verify,
+                cert=cert,
+                timeout=timeout
+            )
+        else:
+            self.requester = requester
+
         self.requester.timeout = timeout
         self.lazy = lazy
         self.jobs_container = None
@@ -172,6 +184,12 @@
         """
         return self.jobs.create(jobname, xml)
 
+    def create_multibranch_pipeline_job(self, jobname, xml, block=True, 
delay=60):
+        """
+        :return: list of new Job objects
+        """
+        return self.jobs.create_multibranch_pipeline(jobname, xml, block, 
delay)
+
     def copy_job(self, jobname, newjobname):
         return self.jobs.copy(jobname, newjobname)
 
@@ -335,6 +353,17 @@
         }
         return self.nodes.create_node(name, node_dict)
 
+    def create_node_with_config(self, name, config):
+        """
+        Create a new slave node with specific configuration.
+        Config should be resemble the output of node.get_node_attributes()
+        :param str name: name of slave
+        :param dict config: Node attributes for Jenkins API request to create 
node
+            (See function output Node.get_node_attributes())
+        :return: node obj
+        """
+        return self.nodes.create_node_with_config(name=name, config=config)
+
     def get_plugins_url(self, depth):
         # This only ever needs to work on the base object
         return '%s/pluginManager/api/python?depth=%i' % (self.baseurl, depth)
@@ -477,6 +506,68 @@
                 # so Jenkins is likely available
                 time.sleep(1)
 
+    def safe_exit(self, wait_for_exit=True, max_wait=360):
+        """ restarts jenkins when no jobs are running, except for pipeline 
jobs """
+        # NB: unlike other methods, the value of resp.status_code
+        # here can be 503 even when everything is normal
+        url = '%s/safeExit' % (self.baseurl,)
+        valid = self.requester.VALID_STATUS_CODES + [503, 500]
+        resp = self.requester.post_and_confirm_status(url, data='',
+                                                      valid=valid)
+        if wait_for_exit:
+            self._wait_for_exit(max_wait=max_wait)
+        return resp
+
+    def _wait_for_exit(self, max_wait=360):
+        # We need to make sure all non pipeline jobs have finished,
+        # and that jenkins is unavailable
+        self.__jenkins_is_unresponsive(max_wait=max_wait)
+
+    def __jenkins_is_unresponsive(self, max_wait=360):
+        # Blocks until jenkins returns ConnectionError or JenkinsAPIException
+        # Default wait is one hour
+        is_alive = True
+        wait = 0
+        while is_alive and wait < max_wait:
+            try:
+                self.requester.get_and_confirm_status(
+                    self.baseurl, valid=[200])
+                time.sleep(1)
+                wait += 1
+                is_alive = True
+            except (ConnectionError, JenkinsAPIException):
+                # Jenkins is finally down
+                is_alive = False
+                return True
+            except HTTPError:
+                # This is a return code that is not 503,
+                # so Jenkins is likely available, and we need to wait
+                time.sleep(1)
+                wait += 1
+                is_alive = True
+
+    def quiet_down(self):
+        """ Put Jenkins in a Quiet mode, preparation for restart. no new 
builds  started"""
+        # 
https://support.cloudbees.com/hc/en-us/articles/216118748-How-to-Start-Stop-or-Restart-your-Instance-
+        # NB: unlike other methods, the value of resp.status_code
+        # here can be 503 even when everything is normal
+        url = '%s/quietDown' % (self.baseurl,)
+        valid = self.requester.VALID_STATUS_CODES + [503, 500]
+        resp = self.requester.post_and_confirm_status(url, data='',
+                                                      valid=valid)
+        return resp
+
+    def cancel_quiet_down(self):
+        """  Cancel the effect of the quiet-down command """
+        # 
https://support.cloudbees.com/hc/en-us/articles/216118748-How-to-Start-Stop-or-Restart-your-Instance-
+        # NB: unlike other methods, the value of resp.status_code
+        # here can be 503 even when everything is normal
+        url = '%s/cancelQuietDown' % (self.baseurl,)
+        valid = self.requester.VALID_STATUS_CODES + [503, 500]
+        resp = self.requester.post_and_confirm_status(url, data='',
+                                                      valid=valid)
+        return resp
+
     @property
     def plugins(self):
         return self.get_plugins()
@@ -528,10 +619,24 @@
     def credentials_by_id(self):
         return self.get_credentials(CredentialsById)
 
+    @property
+    def is_quieting_down(self):
+        url = '%s/api/python?tree=quietingDown' % (self.baseurl,)
+        data = self.get_data(url=url)
+        return data.get('quietingDown', False)
+
     def shutdown(self):
         url = "%s/exit" % self.baseurl
         self.requester.post_and_confirm_status(url, data='')
 
+    def generate_new_api_token(self, new_token_name='Token By jenkinsapi 
python'):
+        subUrl = 
'/me/descriptorByName/jenkins.security.ApiTokenProperty/generateNewToken'
+        url = '%s%s' % (self.baseurl, subUrl)
+        data = urlencode({'newTokenName': new_token_name})
+        response = self.requester.post_and_confirm_status(url, data=data)
+        token = response.json()['data']['tokenValue']
+        return token
+
     def run_groovy_script(self, script):
         """
         Runs the requested groovy script on the Jenkins server returning the
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jenkinsapi-0.3.9/jenkinsapi/jenkinsbase.py 
new/jenkinsapi-0.3.11/jenkinsapi/jenkinsbase.py
--- old/jenkinsapi-0.3.9/jenkinsapi/jenkinsbase.py      2019-04-12 
16:44:27.000000000 +0200
+++ new/jenkinsapi-0.3.11/jenkinsapi/jenkinsbase.py     2019-10-31 
12:58:24.000000000 +0100
@@ -9,6 +9,8 @@
 from jenkinsapi import config
 from jenkinsapi.custom_exceptions import JenkinsAPIException
 
+logger = logging.getLogger(__name__)
+
 
 class JenkinsBase(object):
 
@@ -77,13 +79,13 @@
 
         response = requester.get_url(url, params)
         if response.status_code != 200:
-            logging.error('Failed request at %s with params: %s %s',
-                          url, params, tree if tree else '')
+            logger.error('Failed request at %s with params: %s %s',
+                         url, params, tree if tree else '')
             response.raise_for_status()
         try:
             return ast.literal_eval(response.text)
         except Exception:
-            logging.exception('Inappropriate content found at %s', url)
+            logger.exception('Inappropriate content found at %s', url)
             raise JenkinsAPIException('Cannot parse %s' % response.content)
 
     def pprint(self):
@@ -101,6 +103,7 @@
         return jobs
 
     def process_job_folder(self, folder, folder_path):
+        logger.debug('Processing folder %s in %s', folder['name'], folder_path)
         folder_path += '/job/%s' % urlquote(folder['name'])
         data = self.get_data(self.python_api_url(folder_path),
                              tree='jobs[name,color]')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jenkinsapi-0.3.9/jenkinsapi/jobs.py 
new/jenkinsapi-0.3.11/jenkinsapi/jobs.py
--- old/jenkinsapi-0.3.9/jenkinsapi/jobs.py     2018-11-25 03:54:32.000000000 
+0100
+++ new/jenkinsapi-0.3.11/jenkinsapi/jobs.py    2019-10-31 12:58:24.000000000 
+0100
@@ -3,6 +3,7 @@
 interface for all of the jobs defined on a single Jenkins server.
 """
 import logging
+import time
 from jenkinsapi.job import Job
 from jenkinsapi.custom_exceptions import JenkinsAPIException, UnknownJob
 
@@ -92,9 +93,10 @@
         Iterate over the names & objects for all jobs
         """
         for job in self.itervalues():
-            yield job.name, job
             if job.name != job.get_full_name():
                 yield job.get_full_name(), job
+            else:
+                yield job.name, job
 
     def __contains__(self, job_name):
         """
@@ -109,12 +111,13 @@
         if not self._data:
             self._data = self.poll().get('jobs', [])
         for row in self._data:
-            yield row['name']
             if row['name'] != \
                 Job.get_full_name_from_url_and_baseurl(row['url'],
                                                        self.jenkins.baseurl):
                 yield Job.get_full_name_from_url_and_baseurl(
                     row['url'], self.jenkins.baseurl)
+            else:
+                yield row['name']
 
     def itervalues(self):
         """
@@ -162,6 +165,60 @@
 
         return self[job_name]
 
+    def create_multibranch_pipeline(self, job_name, config, block=True, 
delay=60):
+        """
+        Create a multibranch pipeline job
+
+        :param str jobname: Name of new job
+        :param str config: XML configuration of new job
+        :param block: block until scan is finished?
+        :param delay: max delay to wait for scan to finish (seconds)
+        :returns list of new Jobs after scan
+        """
+        if not config:
+            raise JenkinsAPIException('Job XML config cannot be empty')
+
+        params = {'name': job_name}
+        try:
+            if isinstance(config, unicode):  # pylint: 
disable=undefined-variable
+                config = str(config)
+        except NameError:
+            # Python2 already a str
+            pass
+        self.jenkins.requester.post_xml_and_confirm_status(
+            self.jenkins.get_create_url(),
+            data=config,
+            params=params
+        )
+        # Reset to get it refreshed from Jenkins
+        self._data = []
+
+        # Launch a first scan / indexing to discover the branches...
+        self.jenkins.requester.post_and_confirm_status(
+            '{}/job/{}/build'.format(self.jenkins.baseurl, job_name),
+            data='',
+            valid=[200, 302],  # expect 302 without redirects
+            allow_redirects=False)
+
+        start_time = time.time()
+        # redirect-url does not work with indexing;
+        # so the only workaround found is to parse the console output untill 
scan has finished.
+        scan_finished = False
+        while not scan_finished and block and time.time() < start_time + delay:
+            indexing_console_text = self.jenkins.requester.get_url(
+                '{}/job/{}/indexing/consoleText'.format(self.jenkins.baseurl, 
job_name))
+            if 
indexing_console_text.text.strip().split('\n')[-1].startswith('Finished:'):
+                scan_finished = True
+            time.sleep(1)
+
+        # now search for all jobs created; those who start with job_name + '/'
+        jobs = []
+        for name in self.jenkins.get_jobs_list():
+            if name.startswith(job_name + '/'):
+                jobs.append(self[name])
+
+        return jobs
+
     def copy(self, job_name, new_job_name):
         """
         Copy a job
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jenkinsapi-0.3.9/jenkinsapi/node.py 
new/jenkinsapi-0.3.11/jenkinsapi/node.py
--- old/jenkinsapi-0.3.9/jenkinsapi/node.py     2019-04-12 15:45:38.000000000 
+0200
+++ new/jenkinsapi-0.3.11/jenkinsapi/node.py    2019-10-31 12:58:24.000000000 
+0100
@@ -6,8 +6,9 @@
 
 import xml.etree.ElementTree as ET
 
+import time
 from jenkinsapi.jenkinsbase import JenkinsBase
-from jenkinsapi.custom_exceptions import PostRequired
+from jenkinsapi.custom_exceptions import PostRequired, TimeOut
 from jenkinsapi.custom_exceptions import JenkinsAPIException
 from six.moves.urllib.parse import quote as urlquote
 
@@ -171,7 +172,7 @@
             'type': 'hudson.slaves.DumbSlave$DescriptorImpl',
             'json': json.dumps({
                 'name': self.name,
-                'nodeDescription': na['node_description'],
+                'nodeDescription': na.get('node_description', ''),
                 'numExecutors': na['num_executors'],
                 'remoteFS': na['remote_fs'],
                 'labelString': na['labels'],
@@ -201,7 +202,7 @@
         return self._data['jnlpAgent']
 
     def is_idle(self):
-        return self._data['idle']
+        return self.poll(tree='idle')['idle']
 
     def set_online(self):
         """
@@ -469,6 +470,27 @@
         # no need to poll as the architecture will never change
         return self.get_monitor('ArchitectureMonitor', poll_monitor=False)
 
+    def block_until_idle(self, timeout, poll_time=5):
+        """
+        Blocks until the node become idle.
+        :param timeout: Time in second when the wait is aborted.
+        :param poll_time: Interval in seconds between each check.
+        :@raise TimeOut
+        """
+        start_time = time.time()
+        while not self.is_idle() and (time.time() - start_time) < timeout:
+            log.debug(
+                "Waiting for the node to become idle. Elapsed time: %s",
+                (time.time() - start_time)
+            )
+            time.sleep(poll_time)
+
+        if not self.is_idle():
+            raise TimeOut(
+                "The node has not become idle after {} minutes."
+                .format(timeout/60)
+            )
+
     def get_response_time(self):
         """
         Returns the node's average response time.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jenkinsapi-0.3.9/jenkinsapi/nodes.py 
new/jenkinsapi-0.3.11/jenkinsapi/nodes.py
--- old/jenkinsapi-0.3.9/jenkinsapi/nodes.py    2019-04-12 15:45:38.000000000 
+0200
+++ new/jenkinsapi-0.3.11/jenkinsapi/nodes.py   2019-10-18 14:46:09.000000000 
+0200
@@ -154,3 +154,25 @@
         self.jenkins.requester.post_and_confirm_status(url, data=data)
         self.poll()
         return self[name]
+
+    def create_node_with_config(self, name, config):
+        """
+        Create a new slave node with specific configuration.
+        Config should be resemble the output of node.get_node_attributes()
+        :param str name: name of slave
+        :param dict config: Node attributes for Jenkins API request to create 
node
+            (See function output Node.get_node_attributes())
+        :return: node obj
+        """
+        if name in self:
+            return self[name]
+
+        if not isinstance(config, dict):
+            return None
+        url = ('%s/computer/doCreateItem?%s'
+               % (self.jenkins.baseurl,
+                  urlencode(config)))
+        data = {'json': urlencode(config)}
+        self.jenkins.requester.post_and_confirm_status(url, data=data)
+        self.poll()
+        return self[name]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jenkinsapi-0.3.9/jenkinsapi/plugins.py 
new/jenkinsapi-0.3.11/jenkinsapi/plugins.py
--- old/jenkinsapi-0.3.9/jenkinsapi/plugins.py  2019-04-12 15:45:38.000000000 
+0200
+++ new/jenkinsapi-0.3.11/jenkinsapi/plugins.py 2019-10-21 15:49:10.000000000 
+0200
@@ -50,7 +50,7 @@
 
     @property
     def update_center_dict(self):
-        update_center = 'https://updates.jenkins-ci.org/update-center.json'
+        update_center = 'https://updates.jenkins.io/update-center.json'
         jsonp = requests.get(update_center).content.decode('utf-8')
         return json.loads(jsonp_to_json(jsonp))
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jenkinsapi-0.3.9/jenkinsapi/utils/requester.py 
new/jenkinsapi-0.3.11/jenkinsapi/utils/requester.py
--- old/jenkinsapi-0.3.9/jenkinsapi/utils/requester.py  2019-04-12 
15:45:38.000000000 +0200
+++ new/jenkinsapi-0.3.11/jenkinsapi/utils/requester.py 2019-10-18 
14:46:09.000000000 +0200
@@ -77,6 +77,7 @@
         self.ssl_verify = kwargs.get('ssl_verify', ssl_verify)
         self.cert = kwargs.get('cert', cert)
         self.timeout = kwargs.get('timeout', timeout)
+        self.session = requests.Session()
 
     def get_request_dict(
             self, params=None, data=None, files=None, headers=None, **kwargs):
@@ -140,7 +141,7 @@
             allow_redirects=allow_redirects,
             stream=stream
         )
-        return requests.get(self._update_url_scheme(url), **requestKwargs)
+        return self.session.get(self._update_url_scheme(url), **requestKwargs)
 
     def post_url(self, url, params=None, data=None, files=None,
                  headers=None, allow_redirects=True, **kwargs):
@@ -151,7 +152,7 @@
             headers=headers,
             allow_redirects=allow_redirects,
             **kwargs)
-        return requests.post(self._update_url_scheme(url), **requestKwargs)
+        return self.session.post(self._update_url_scheme(url), **requestKwargs)
 
     def post_xml_and_confirm_status(
             self, url, params=None, data=None, valid=None):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jenkinsapi-0.3.9/jenkinsapi.egg-info/PKG-INFO 
new/jenkinsapi-0.3.11/jenkinsapi.egg-info/PKG-INFO
--- old/jenkinsapi-0.3.9/jenkinsapi.egg-info/PKG-INFO   2019-04-13 
02:46:41.000000000 +0200
+++ new/jenkinsapi-0.3.11/jenkinsapi.egg-info/PKG-INFO  2019-10-31 
13:00:21.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: jenkinsapi
-Version: 0.3.9
+Version: 0.3.11
 Summary: A Python API for accessing resources on a Jenkins 
continuous-integration server.
 Home-page: UNKNOWN
 Author: Salim Fadhley, Aleksey Maksimov
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jenkinsapi-0.3.9/jenkinsapi.egg-info/SOURCES.txt 
new/jenkinsapi-0.3.11/jenkinsapi.egg-info/SOURCES.txt
--- old/jenkinsapi-0.3.9/jenkinsapi.egg-info/SOURCES.txt        2019-04-13 
02:46:41.000000000 +0200
+++ new/jenkinsapi-0.3.11/jenkinsapi.egg-info/SOURCES.txt       2019-10-31 
13:00:21.000000000 +0100
@@ -12,6 +12,7 @@
 test-requirements.txt
 tox.ini
 .github/ISSUE_TEMPLATE.md
+.github/stale.yml
 bin/pipelint
 doc/.gitignore
 doc/build.properties
@@ -110,6 +111,7 @@
 jenkinsapi_tests/systests/test_downstream_upstream.py
 jenkinsapi_tests/systests/test_env_vars.py
 jenkinsapi_tests/systests/test_executors.py
+jenkinsapi_tests/systests/test_generate_new_api_token.py
 jenkinsapi_tests/systests/test_invocation.py
 jenkinsapi_tests/systests/test_jenkins.py
 jenkinsapi_tests/systests/test_jenkins_artifacts.py
@@ -118,7 +120,9 @@
 jenkinsapi_tests/systests/test_parameterized_builds.py
 jenkinsapi_tests/systests/test_plugins.py
 jenkinsapi_tests/systests/test_queue.py
+jenkinsapi_tests/systests/test_quiet_down.py
 jenkinsapi_tests/systests/test_restart.py
+jenkinsapi_tests/systests/test_safe_exit.py
 jenkinsapi_tests/systests/test_scm.py
 jenkinsapi_tests/systests/test_views.py
 jenkinsapi_tests/systests/view_configs.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jenkinsapi-0.3.9/jenkinsapi.egg-info/pbr.json 
new/jenkinsapi-0.3.11/jenkinsapi.egg-info/pbr.json
--- old/jenkinsapi-0.3.9/jenkinsapi.egg-info/pbr.json   2019-04-13 
02:46:41.000000000 +0200
+++ new/jenkinsapi-0.3.11/jenkinsapi.egg-info/pbr.json  2019-10-31 
13:00:21.000000000 +0100
@@ -1 +1 @@
-{"git_version": "b4d62f1", "is_release": true}
\ No newline at end of file
+{"git_version": "dce4073", "is_release": true}
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jenkinsapi-0.3.9/jenkinsapi_tests/systests/conftest.py 
new/jenkinsapi-0.3.11/jenkinsapi_tests/systests/conftest.py
--- old/jenkinsapi-0.3.9/jenkinsapi_tests/systests/conftest.py  2019-04-12 
15:45:38.000000000 +0200
+++ new/jenkinsapi-0.3.11/jenkinsapi_tests/systests/conftest.py 2019-10-21 
13:39:57.000000000 +0200
@@ -13,30 +13,32 @@
 
 # Extra plugins required by the systests
 PLUGIN_DEPENDENCIES = [
-    "http://updates.jenkins-ci.org/latest/";
+    "http://updates.jenkins.io/latest/";
     "apache-httpcomponents-client-4-api.hpi",
-    "http://updates.jenkins-ci.org/latest/jsch.hpi";,
-    "http://updates.jenkins-ci.org/latest/trilead-api.hpi";,
-    "http://updates.jenkins-ci.org/latest/workflow-api.hpi";,
-    "http://updates.jenkins-ci.org/latest/display-url-api.hpi";,
-    "http://updates.jenkins-ci.org/latest/workflow-step-api.hpi";,
-    "http://updates.jenkins-ci.org/latest/workflow-scm-step.hpi";,
-    "http://updates.jenkins-ci.org/latest/icon-shim.hpi";,
-    "http://updates.jenkins-ci.org/latest/junit.hpi";,
-    "http://updates.jenkins-ci.org/latest/script-security.hpi";,
-    "http://updates.jenkins-ci.org/latest/matrix-project.hpi";,
-    "http://updates.jenkins-ci.org/latest/credentials.hpi";,
-    "http://updates.jenkins-ci.org/latest/ssh-credentials.hpi";,
-    "http://updates.jenkins-ci.org/latest/scm-api.hpi";,
-    "http://updates.jenkins-ci.org/latest/mailer.hpi";,
-    "http://updates.jenkins-ci.org/latest/git.hpi";,
-    "http://updates.jenkins-ci.org/latest/git-client.hpi";,
-    "https://updates.jenkins-ci.org/latest/nested-view.hpi";,
-    "https://updates.jenkins-ci.org/latest/ssh-slaves.hpi";,
-    "https://updates.jenkins-ci.org/latest/structs.hpi";,
-    "http://updates.jenkins-ci.org/latest/plain-credentials.hpi";,
-    "http://updates.jenkins-ci.org/latest/envinject.hpi";,
-    "http://updates.jenkins-ci.org/latest/envinject-api.hpi";
+    "http://updates.jenkins.io/latest/jsch.hpi";,
+    "http://updates.jenkins.io/download/plugins/trilead-api/1.0.5/";
+    "trilead-api.hpi",
+    "http://updates.jenkins.io/latest/workflow-api.hpi";,
+    "http://updates.jenkins.io/latest/display-url-api.hpi";,
+    "http://updates.jenkins.io/latest/workflow-step-api.hpi";,
+    "http://updates.jenkins.io/latest/workflow-scm-step.hpi";,
+    "http://updates.jenkins.io/latest/icon-shim.hpi";,
+    "http://updates.jenkins.io/download/plugins/junit/1.28/junit.hpi";,
+    "http://updates.jenkins.io/latest/script-security.hpi";,
+    "http://updates.jenkins.io/latest/matrix-project.hpi";,
+    "http://updates.jenkins.io/latest/credentials.hpi";,
+    "http://updates.jenkins.io/latest/ssh-credentials.hpi";,
+    "http://updates.jenkins.io/latest/scm-api.hpi";,
+    "http://updates.jenkins.io/latest/mailer.hpi";,
+    "http://updates.jenkins.io/latest/git.hpi";,
+    "http://updates.jenkins.io/latest/git-client.hpi";,
+    "https://updates.jenkins.io/latest/nested-view.hpi";,
+    "https://updates.jenkins.io/latest/ssh-slaves.hpi";,
+    "https://updates.jenkins.io/latest/structs.hpi";,
+    "http://updates.jenkins.io/latest/plain-credentials.hpi";,
+    "http://updates.jenkins.io/latest/envinject.hpi";,
+    "http://updates.jenkins.io/latest/envinject-api.hpi";,
+    "http://updates.jenkins.io/latest/jdk-tool.hpi";
 ]
 
 
@@ -122,7 +124,7 @@
 def jenkins(launched_jenkins):
     url = launched_jenkins.jenkins_url
 
-    jenkins_instance = Jenkins(url)
+    jenkins_instance = Jenkins(url, timeout=30)
 
     _delete_all_jobs(jenkins_instance)
     _delete_all_views(jenkins_instance)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jenkinsapi-0.3.9/jenkinsapi_tests/systests/test_generate_new_api_token.py 
new/jenkinsapi-0.3.11/jenkinsapi_tests/systests/test_generate_new_api_token.py
--- 
old/jenkinsapi-0.3.9/jenkinsapi_tests/systests/test_generate_new_api_token.py   
    1970-01-01 01:00:00.000000000 +0100
+++ 
new/jenkinsapi-0.3.11/jenkinsapi_tests/systests/test_generate_new_api_token.py  
    2019-10-18 14:46:09.000000000 +0200
@@ -0,0 +1,24 @@
+
+"""
+System tests for generation new api token for logged in user
+"""
+import pytest
+import logging
+from jenkinsapi.utils.crumb_requester import CrumbRequester
+
+
+log = logging.getLogger(__name__)
+
+
[email protected]_new_api_token
+def test_generate_new_api_token(jenkins_admin_admin):
+    jenkins_admin_admin.requester = CrumbRequester(
+        baseurl=jenkins_admin_admin.baseurl,
+        username=jenkins_admin_admin.username,
+        password=jenkins_admin_admin.password
+    )
+    jenkins_admin_admin.poll()
+
+    new_token = jenkins_admin_admin.generate_new_api_token()  # generate new 
token
+    log.info('newly generated token: %s', new_token)
+    assert new_token is not None
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jenkinsapi-0.3.9/jenkinsapi_tests/systests/test_jenkins.py 
new/jenkinsapi-0.3.11/jenkinsapi_tests/systests/test_jenkins.py
--- old/jenkinsapi-0.3.9/jenkinsapi_tests/systests/test_jenkins.py      
2018-11-26 14:23:11.000000000 +0100
+++ new/jenkinsapi-0.3.11/jenkinsapi_tests/systests/test_jenkins.py     
2019-10-18 14:46:09.000000000 +0200
@@ -206,7 +206,7 @@
 
 
 def test_install_delete_single_plugin_string(jenkins):
-    plugin_name = 'antisamy-markup-formatter'
+    plugin_name = 'simple-theme-plugin'
     plugin_version = 'latest'
     plugin = ('%s@%s') % (plugin_name, plugin_version)
 
@@ -223,7 +223,7 @@
 
 
 def test_install_delete_single_plugin_object(jenkins):
-    plugin_name = 'antisamy-markup-formatter'
+    plugin_name = 'simple-theme-plugin'
     plugin_version = 'latest'
     plugin = Plugin(('%s@%s') % (plugin_name, plugin_version))
 
@@ -240,7 +240,7 @@
 
 
 def test_install_delete_multiple_plugins_mix_string_object(jenkins):
-    plugin_one_name = 'antisamy-markup-formatter'
+    plugin_one_name = 'simple-theme-plugin'
     plugin_one_version = 'latest'
     plugin_one = ('%s@%s') % (plugin_one_name, plugin_one_version)
     plugin_two_name = 'docker-commons'
@@ -264,4 +264,4 @@
 def test_run_groovy_script(jenkins):
     expected_result = 'Hello world!'
     result = jenkins.run_groovy_script('print "%s"' % expected_result)
-    assert result == 'Hello world!'
+    assert result.strip() == 'Hello world!'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jenkinsapi-0.3.9/jenkinsapi_tests/systests/test_plugins.py 
new/jenkinsapi-0.3.11/jenkinsapi_tests/systests/test_plugins.py
--- old/jenkinsapi-0.3.9/jenkinsapi_tests/systests/test_plugins.py      
2017-12-14 14:59:25.000000000 +0100
+++ new/jenkinsapi-0.3.11/jenkinsapi_tests/systests/test_plugins.py     
2019-10-21 13:39:57.000000000 +0200
@@ -9,6 +9,16 @@
 log = logging.getLogger(__name__)
 
 
+def test_plugin_data(jenkins):
+    # It takes time to get plugins json from remote
+    timeout = jenkins.requester.timeout
+    jenkins.requester.timeout = 60
+    jenkins.plugins.check_updates_server()
+    jenkins.requester.timeout = timeout
+
+    assert 'mailer' in jenkins.plugins
+
+
 def test_get_missing_plugin(jenkins):
     plugins = jenkins.get_plugins()
     with pytest.raises(KeyError):
@@ -27,39 +37,38 @@
     plugins = jenkins.get_plugins(depth=2)
     _, plugin = next(plugins.iteritems())
 
+    assert isinstance(plugin, Plugin)
+
 
 def test_delete_inexistant_plugin(jenkins):
     with pytest.raises(KeyError):
-        plugins = jenkins.plugins
-
-        del plugins[random_string()]
+        del jenkins.plugins[random_string()]
 
 
 def test_install_uninstall_plugin(jenkins):
-    plugins = jenkins.plugins
-    plugin_name = 'async-http-client'
+    plugin_name = 'suppress-stack-trace'
 
     plugin_dict = {
         'shortName': plugin_name,
         'version': 'latest',
     }
-    plugins[plugin_name] = Plugin(plugin_dict)
+    jenkins.plugins[plugin_name] = Plugin(plugin_dict)
 
-    assert plugin_name in plugins
+    assert plugin_name in jenkins.plugins
 
-    plugin = plugins[plugin_name]
+    plugin = jenkins.get_plugins()[plugin_name]
     assert isinstance(plugin, Plugin)
     assert plugin.shortName == plugin_name
 
-    del plugins[plugin_name]
-    assert plugins[plugin_name].deleted
+    del jenkins.plugins[plugin_name]
+    assert jenkins.plugins[plugin_name].deleted
 
 
 def test_install_multiple_plugins(jenkins):
-    plugin_one_name = 'jenkins-cloudformation-plugin'
+    plugin_one_name = 'keyboard-shortcuts-plugin'
     plugin_one_version = 'latest'
     plugin_one = "@".join((plugin_one_name, plugin_one_version))
-    plugin_two = Plugin({'shortName': 'anything-goes-formatter', 'version': 
'latest'})
+    plugin_two = Plugin({'shortName': 'emotional-jenkins-plugin', 'version': 
'latest'})
 
     assert isinstance(plugin_two, Plugin)
 
@@ -70,23 +79,28 @@
     assert plugin_one_name in jenkins.plugins
     assert plugin_two.shortName in jenkins.plugins
 
+    del jenkins.plugins['keyboard-shortcuts-plugin']
+    del jenkins.plugins['emotional-jenkins-plugin']
+
 
 def test_downgrade_plugin(jenkins):
-    plugin_name = 'amazon-ecs'
-    plugin_version = '1.5'  # This is explicitly not the latest version
+    plugin_name = 'console-badge'
+    plugin_version = 'latest'
     plugin = Plugin({'shortName': plugin_name, 'version': plugin_version})
 
     assert isinstance(plugin, Plugin)
 
     # Need to restart when not installing the latest version
-    jenkins.install_plugins([plugin], restart=True, wait_for_reboot=True)
+    jenkins.install_plugins([plugin])
 
     installed_plugin = jenkins.plugins[plugin_name]
 
-    assert installed_plugin.version == '1.5'
+    assert installed_plugin.version == '1.1'
 
-    older_plugin = Plugin({'shortName': plugin_name, 'version': '1.4'})
+    older_plugin = Plugin({'shortName': plugin_name, 'version': '1.0'})
     jenkins.install_plugins([older_plugin], restart=True, wait_for_reboot=True)
     installed_older_plugin = jenkins.plugins[plugin_name]
 
-    assert installed_older_plugin.version == '1.4'
+    assert installed_older_plugin.version == '1.0'
+
+    del jenkins.plugins[plugin_name]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jenkinsapi-0.3.9/jenkinsapi_tests/systests/test_quiet_down.py 
new/jenkinsapi-0.3.11/jenkinsapi_tests/systests/test_quiet_down.py
--- old/jenkinsapi-0.3.9/jenkinsapi_tests/systests/test_quiet_down.py   
1970-01-01 01:00:00.000000000 +0100
+++ new/jenkinsapi-0.3.11/jenkinsapi_tests/systests/test_quiet_down.py  
2019-10-18 14:46:09.000000000 +0200
@@ -0,0 +1,27 @@
+
+"""
+System tests for setting jenkins in quietDown mode
+"""
+import pytest
+import logging
+
+
+log = logging.getLogger(__name__)
+
+
[email protected]_these_please
+def test_quiet_down_and_cancel_quiet_down(jenkins):
+    jenkins.poll()  # jenkins should be alive
+
+    jenkins.quiet_down()  # put Jenkins in quietDown mode
+    # is_quieting_down = jenkins.is_quieting_down
+    assert jenkins.is_quieting_down is True
+
+    jenkins.poll()  # jenkins should be alive
+
+    jenkins.cancel_quiet_down()  # leave quietDown mode
+
+    # is_quieting_down = jenkins_api['quietingDown']
+    assert jenkins.is_quieting_down is False
+
+    jenkins.poll()  # jenkins should be alive
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jenkinsapi-0.3.9/jenkinsapi_tests/systests/test_safe_exit.py 
new/jenkinsapi-0.3.11/jenkinsapi_tests/systests/test_safe_exit.py
--- old/jenkinsapi-0.3.9/jenkinsapi_tests/systests/test_safe_exit.py    
1970-01-01 01:00:00.000000000 +0100
+++ new/jenkinsapi-0.3.11/jenkinsapi_tests/systests/test_safe_exit.py   
2019-10-21 15:49:10.000000000 +0200
@@ -0,0 +1,44 @@
+"""
+System tests for `jenkinsapi.jenkins` module.
+"""
+import time
+import logging
+import pytest
+from jenkinsapi.build import Build
+from jenkinsapi_tests.test_utils.random_strings import random_string
+from jenkinsapi_tests.systests.job_configs import LONG_RUNNING_JOB
+
+
+log = logging.getLogger(__name__)
+
+
[email protected]_these_please
+def test_safe_exit(jenkins):
+    job_name = 'Bcreate_%s' % random_string()
+    job = jenkins.create_job(job_name, LONG_RUNNING_JOB)
+    qq = job.invoke()
+    time.sleep(3)
+    bn = qq.block_until_building(delay=3).get_number()
+    assert isinstance(bn, int)
+
+    build = qq.get_build()
+    assert isinstance(build, Build)
+    assert build.is_running()
+
+    # A job is now running and safe_exit should await running jobs
+    # Call, but wait only for 5 seconds then cancel exit
+    jenkins.safe_exit(wait_for_exit=False)
+    time.sleep(5)
+
+    jenkins.cancel_quiet_down()  # leave quietDown mode
+    assert jenkins.is_quieting_down is False
+
+    build.stop()
+    # if we call next line right away - Jenkins have no time to stop job
+    # so we wait a bit
+    while build.is_running():
+        time.sleep(0.5)
+
+    console = build.get_console()
+    assert isinstance(console, str)
+    assert 'Started by user' in console
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jenkinsapi-0.3.9/jenkinsapi_tests/unittests/test_jenkins.py 
new/jenkinsapi-0.3.11/jenkinsapi_tests/unittests/test_jenkins.py
--- old/jenkinsapi-0.3.9/jenkinsapi_tests/unittests/test_jenkins.py     
2019-04-12 15:45:38.000000000 +0200
+++ new/jenkinsapi-0.3.11/jenkinsapi_tests/unittests/test_jenkins.py    
2019-10-18 14:46:09.000000000 +0200
@@ -1,4 +1,5 @@
 import pytest
+from collections import namedtuple
 
 import jenkinsapi
 from jenkinsapi.plugins import Plugins
@@ -19,6 +20,34 @@
          'color': 'blue'},
     ]
 }
+MULTIBRANCH_JOBS_DATA = {
+    'jobs': [
+        {'name': 'multibranch-repo/master',
+         'url': 'http://localhost:8080/job/multibranch-repo/job/master',
+         'color': 'blue'},
+        {'name': 'multibranch-repo/develop',
+         'url': 'http://localhost:8080/job/multibranch-repo/job/develop',
+         'color': 'blue'},
+    ]
+}
+SCAN_MULTIBRANCH_PIPELINE_LOG = """
+Started by timer
+[Fri Jul 05 06:46:00 CEST 2019] Starting branch indexing...
+Connecting to https://stash.macq.eu using Jenkins/****** (jenkins-ldap)
+Repository type: Git
+Looking up internal/base for branches
+Checking branch master from internal/base
+      'Jenkinsfile' found
+    Met criteria
+No changes detected: master (still at 26d4d8a673f57a957fd5a23f5adfe0be02089294)
+
+  1 branches were processed
+Looking up internal/base for pull requests
+
+  0 pull requests were processed
+[Fri Jul 05 06:46:01 CEST 2019] Finished branch indexing. Indexing took 1.1 sec
+Finished: SUCCESS
+"""
 
 
 @pytest.fixture(scope='function')
@@ -112,6 +141,45 @@
     assert 'Job XML config cannot be empty' in str(ar.value)
 
 
+def test_create_multibranch_pipeline_job(mocker, monkeypatch):
+    def fake_jenkins_poll(cls, tree=None):  # pylint: disable=unused-argument
+        # return multibranch jobs and other jobs.
+        # create_multibranch_pipeline_job is supposed to filter out the 
MULTIBRANCH jobs
+        return {
+            'jobs': TWO_JOBS_DATA['jobs'] + MULTIBRANCH_JOBS_DATA['jobs']
+        }
+
+    def fake_job_poll(cls, tree=None):  # pylint: disable=unused-argument
+        return {}
+
+    monkeypatch.setattr(JenkinsBase, '_poll', fake_jenkins_poll)
+    monkeypatch.setattr(Jenkins, '_poll', fake_jenkins_poll)
+    monkeypatch.setattr(Job, '_poll', fake_job_poll)
+
+    mock_requester = Requester(username='foouser', password='foopassword')
+    mock_requester.post_xml_and_confirm_status = mocker.MagicMock(
+        return_value=''
+    )
+    mock_requester.post_and_confirm_status = mocker.MagicMock(
+        return_value=''
+    )
+    get_response = namedtuple('get_response', 'text')
+    mock_requester.get_url = mocker.MagicMock(
+        return_value=get_response(text=SCAN_MULTIBRANCH_PIPELINE_LOG)
+    )
+    jenkins = Jenkins('http://localhost:8080/',
+                      username='foouser', password='foopassword',
+                      requester=mock_requester)
+
+    jobs = jenkins.create_multibranch_pipeline_job("multibranch-repo", 
"multibranch-xml-content")
+
+    for idx, job_instance in enumerate(jobs):
+        assert job_instance.name == MULTIBRANCH_JOBS_DATA['jobs'][idx]['name']
+
+    # make sure we didn't get more jobs.
+    assert len(MULTIBRANCH_JOBS_DATA['jobs']) == len(jobs)
+
+
 def test_get_jenkins_obj_from_url(mocker, monkeypatch):
     def fake_jenkins_poll(cls, tree=None):  # pylint: disable=unused-argument
         return TWO_JOBS_DATA
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jenkinsapi-0.3.9/jenkinsapi_tests/unittests/test_requester.py 
new/jenkinsapi-0.3.11/jenkinsapi_tests/unittests/test_requester.py
--- old/jenkinsapi-0.3.9/jenkinsapi_tests/unittests/test_requester.py   
2019-04-12 15:45:38.000000000 +0200
+++ new/jenkinsapi-0.3.11/jenkinsapi_tests/unittests/test_requester.py  
2019-10-18 14:46:09.000000000 +0200
@@ -255,7 +255,7 @@
     def fake_get(*args, **kwargs):  # pylint: disable=unused-argument
         return 'SUCCESS'
 
-    monkeypatch.setattr(requests, 'get', fake_get)
+    monkeypatch.setattr(requests.Session, 'get', fake_get)
 
     req = Requester('foo', 'bar')
     response = req.get_url(
@@ -270,7 +270,7 @@
     def fake_post(*args, **kwargs):  # pylint: disable=unused-argument
         return 'SUCCESS'
 
-    monkeypatch.setattr(requests, 'post', fake_post)
+    monkeypatch.setattr(requests.Session, 'post', fake_post)
 
     req = Requester('foo', 'bar')
     response = req.post_url(
@@ -285,7 +285,7 @@
     def fake_post(*args, **kwargs):  # pylint: disable=unused-argument
         return 'SUCCESS'
 
-    monkeypatch.setattr(requests, 'post', fake_post)
+    monkeypatch.setattr(requests.Session, 'post', fake_post)
 
     req = Requester('foo', 'bar')
     with pytest.raises(AssertionError):
@@ -304,7 +304,7 @@
     def fake_post(*args, **kwargs):  # pylint: disable=unused-argument
         return FakeResponse()
 
-    monkeypatch.setattr(requests, 'post', fake_post)
+    monkeypatch.setattr(requests.Session, 'post', fake_post)
 
     req = Requester('foo', 'bar')
     ret = req.post_xml_and_confirm_status(
@@ -319,7 +319,7 @@
     def fake_post(*args, **kwargs):  # pylint: disable=unused-argument
         return 'SUCCESS'
 
-    monkeypatch.setattr(requests, 'post', fake_post)
+    monkeypatch.setattr(requests.Session, 'post', fake_post)
 
     req = Requester('foo', 'bar')
     with pytest.raises(AssertionError):
@@ -338,7 +338,7 @@
     def fake_post(*args, **kwargs):  # pylint: disable=unused-argument
         return FakeResponse()
 
-    monkeypatch.setattr(requests, 'post', fake_post)
+    monkeypatch.setattr(requests.Session, 'post', fake_post)
 
     req = Requester('foo', 'bar')
     ret = req.post_and_confirm_status(
@@ -359,7 +359,7 @@
     def fake_post(*args, **kwargs):  # pylint: disable=unused-argument
         return FakeResponse()
 
-    monkeypatch.setattr(requests, 'post', fake_post)
+    monkeypatch.setattr(requests.Session, 'post', fake_post)
 
     req = Requester('foo', 'bar')
     with pytest.raises(JenkinsAPIException):
@@ -378,7 +378,7 @@
     def fake_get(*args, **kwargs):  # pylint: disable=unused-argument
         return FakeResponse()
 
-    monkeypatch.setattr(requests, 'get', fake_get)
+    monkeypatch.setattr(requests.Session, 'get', fake_get)
 
     req = Requester('foo', 'bar')
     ret = req.get_and_confirm_status(
@@ -398,7 +398,7 @@
     def fake_get(*args, **kwargs):  # pylint: disable=unused-argument
         return FakeResponse()
 
-    monkeypatch.setattr(requests, 'get', fake_get)
+    monkeypatch.setattr(requests.Session, 'get', fake_get)
 
     req = Requester('foo', 'bar', baseurl='http://dummy')
     with pytest.raises(JenkinsAPIException):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jenkinsapi-0.3.9/jenkinsapi_utils/jenkins_launcher.py 
new/jenkinsapi-0.3.11/jenkinsapi_utils/jenkins_launcher.py
--- old/jenkinsapi-0.3.9/jenkinsapi_utils/jenkins_launcher.py   2019-04-12 
15:45:38.000000000 +0200
+++ new/jenkinsapi-0.3.11/jenkinsapi_utils/jenkins_launcher.py  2019-10-21 
15:49:10.000000000 +0200
@@ -65,10 +65,10 @@
     Launch jenkins
     """
     JENKINS_WEEKLY_WAR_URL = (
-        "http://mirrors.jenkins-ci.org/war/latest/jenkins.war";
+        "http://updates.jenkins.io/latest/jenkins.war";
     )
     JENKINS_LTS_WAR_URL = (
-        "http://mirrors.jenkins-ci.org/war-stable/latest/jenkins.war";
+        "https://updates.jenkins.io/stable/latest/jenkins.war";
     )
 
     def __init__(self, local_orig_dir, systests_dir, war_name, 
plugin_urls=None,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jenkinsapi-0.3.9/pylintrc 
new/jenkinsapi-0.3.11/pylintrc
--- old/jenkinsapi-0.3.9/pylintrc       2018-11-26 13:33:20.000000000 +0100
+++ new/jenkinsapi-0.3.11/pylintrc      2019-10-18 14:46:09.000000000 +0200
@@ -51,7 +51,7 @@
 # E1103: %s %r has no %r member (but some types could not be inferred) - fails 
to infer real members of types, e.g. in Celery
 # W0231: method from base class is not called - complains about not invoking 
empty __init__s in parents, which is annoying
 # R0921: abstract class not referenced, when in fact referenced from another 
egg
-disable=F0401,E0611,E1101,W0142,W0212,R0201,W0703,R0801,R0901,W0511,E1103,W0231,R0921,W0402,I0011,wrong-import-position,wrong-import-order,ungrouped-imports,redefined-variable-type,missing-docstring,redefined-outer-name,redefined-builtin,relative-import,c-extension-no-member,useless-object-inheritance,no-else-return,consider-using-in,consider-using-dict-comprehension,unnecessary-pass
+disable=F0401,E0611,E1101,W0142,W0212,R0201,W0703,R0801,R0901,W0511,E1103,W0231,R0921,W0402,I0011,wrong-import-position,wrong-import-order,ungrouped-imports,redefined-variable-type,missing-docstring,redefined-outer-name,redefined-builtin,relative-import,c-extension-no-member,useless-object-inheritance,no-else-return,consider-using-in,consider-using-dict-comprehension,unnecessary-pass,unnecessary-comprehension
 
 [REPORTS]
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jenkinsapi-0.3.9/setup.cfg 
new/jenkinsapi-0.3.11/setup.cfg
--- old/jenkinsapi-0.3.9/setup.cfg      2019-04-13 02:46:42.000000000 +0200
+++ new/jenkinsapi-0.3.11/setup.cfg     2019-10-31 13:00:22.000000000 +0100
@@ -51,7 +51,7 @@
 universal = 1
 
 [pycodestyle]
-exclude = .tox,doc/source/conf.py,build
+exclude = .tox,doc/source/conf.py,build,.venv,.eggs
 max-line-length = 99
 
 [egg_info]


Reply via email to