From: Alfredo Deza Date: Mon, 26 Oct 2015 12:11:50 +0000 (-0400) Subject: we should specify the builds need with ansible, not in library X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=c6dc907b3591abfe32dadad1fecb153d25744194;p=ceph-build.git we should specify the builds need with ansible, not in library Signed-off-by: Alfredo Deza --- diff --git a/ansible/library/jenkins.py b/ansible/library/jenkins.py deleted file mode 100644 index 4a934f868..000000000 --- a/ansible/library/jenkins.py +++ /dev/null @@ -1,854 +0,0 @@ -#!/usr/bin/env python -# Software License Agreement (BSD License) -# -# Copyright (c) 2010, Willow Garage, Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following -# disclaimer in the documentation and/or other materials provided -# with the distribution. -# * Neither the name of Willow Garage, Inc. nor the names of its -# contributors may be used to endorse or promote products derived -# from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# 'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. -# -# Authors: -# Ken Conley -# James Page -# Tully Foote -# Matthew Gertner - -############################################################################### -# THIS IS A VENDORED VERSION OF python-jenkins v. 4.7 -# why: https://bugs.launchpad.net/python-jenkins/+bug/1500898 -# we can't pick and choose what we need on distros. Sorry Ken. -############################################################################### - -''' -.. module:: jenkins - :platform: Unix, Windows - :synopsis: Python API to interact with Jenkins - -See examples at :doc:`example` -''' - -import base64 -import json -import re -import socket - -import six -from six.moves.http_client import BadStatusLine -from six.moves.urllib.error import HTTPError -from six.moves.urllib.error import URLError -from six.moves.urllib.parse import quote, urlencode -from six.moves.urllib.request import Request, urlopen - -LAUNCHER_SSH = 'hudson.plugins.sshslaves.SSHLauncher' -LAUNCHER_COMMAND = 'hudson.slaves.CommandLauncher' -LAUNCHER_JNLP = 'hudson.slaves.JNLPLauncher' -LAUNCHER_WINDOWS_SERVICE = 'hudson.os.windows.ManagedWindowsServiceLauncher' -DEFAULT_HEADERS = {'Content-Type': 'text/xml; charset=utf-8'} - -# REST Endpoints -INFO = 'api/json' -PLUGIN_INFO = 'pluginManager/api/json?depth=%(depth)s' -CRUMB_URL = 'crumbIssuer/api/json' -JOB_INFO = 'job/%(name)s/api/json?depth=%(depth)s' -JOB_NAME = 'job/%(name)s/api/json?tree=name' -Q_INFO = 'queue/api/json?depth=0' -CANCEL_QUEUE = 'queue/cancelItem?id=%(id)s' -CREATE_JOB = 'createItem?name=%(name)s' # also post config.xml -CONFIG_JOB = 'job/%(name)s/config.xml' -DELETE_JOB = 'job/%(name)s/doDelete' -ENABLE_JOB = 'job/%(name)s/enable' -DISABLE_JOB = 'job/%(name)s/disable' -COPY_JOB = 'createItem?name=%(to_name)s&mode=copy&from=%(from_name)s' -RENAME_JOB = 'job/%(from_name)s/doRename?newName=%(to_name)s' -BUILD_JOB = 'job/%(name)s/build' -STOP_BUILD = 'job/%(name)s/%(number)s/stop' -BUILD_WITH_PARAMS_JOB = 'job/%(name)s/buildWithParameters' -BUILD_INFO = 'job/%(name)s/%(number)d/api/json?depth=%(depth)s' -BUILD_CONSOLE_OUTPUT = 'job/%(name)s/%(number)d/consoleText' -NODE_LIST = 'computer/api/json' -CREATE_NODE = 'computer/doCreateItem?%s' -DELETE_NODE = 'computer/%(name)s/doDelete' -NODE_INFO = 'computer/%(name)s/api/json?depth=%(depth)s' -NODE_TYPE = 'hudson.slaves.DumbSlave$DescriptorImpl' -TOGGLE_OFFLINE = 'computer/%(name)s/toggleOffline?offlineMessage=%(msg)s' -CONFIG_NODE = 'computer/%(name)s/config.xml' - -# for testing only -EMPTY_CONFIG_XML = ''' - - false - - - true - false - false - - false - - - -''' - -# for testing only -RECONFIG_XML = ''' - - false - - - true - false - false - - false - - - export FOO=bar - - - - -''' - - -class JenkinsException(Exception): - '''General exception type for jenkins-API-related failures.''' - pass - - -class NotFoundException(JenkinsException): - '''A special exception to call out the case of receiving a 404.''' - pass - - -class EmptyResponseException(JenkinsException): - '''A special exception to call out the case receiving an empty response.''' - pass - - -class BadHTTPException(JenkinsException): - '''A special exception to call out the case of a broken HTTP response.''' - pass - - -def auth_headers(username, password): - '''Simple implementation of HTTP Basic Authentication. - - Returns the 'Authentication' header value. - ''' - auth = '%s:%s' % (username, password) - if isinstance(auth, six.text_type): - auth = auth.encode('utf-8') - return b'Basic ' + base64.b64encode(auth) - - -class Jenkins(object): - - def __init__(self, url, username=None, password=None, - timeout=socket._GLOBAL_DEFAULT_TIMEOUT): - '''Create handle to Jenkins instance. - - All methods will raise :class:`JenkinsException` on failure. - - :param username: Server username, ``str`` - :param password: Server password, ``str`` - :param url: URL of Jenkins server, ``str`` - :param timeout: Server connection timeout in secs (default: not set), ``int`` - ''' - if url[-1] == '/': - self.server = url - else: - self.server = url + '/' - if username is not None and password is not None: - self.auth = auth_headers(username, password) - else: - self.auth = None - self.crumb = None - self.timeout = timeout - - def _get_encoded_params(self, params): - for k, v in params.items(): - if k in ["name", "to_name", "from_name", "msg"]: - params[k] = quote(v) - return params - - def maybe_add_crumb(self, req): - # We don't know yet whether we need a crumb - if self.crumb is None: - try: - response = self.jenkins_open(Request( - self.server + CRUMB_URL), add_crumb=False) - except (NotFoundException, EmptyResponseException): - self.crumb = False - else: - self.crumb = json.loads(response) - if self.crumb: - req.add_header(self.crumb['crumbRequestField'], self.crumb['crumb']) - - def get_job_info(self, name, depth=0): - '''Get job information dictionary. - - :param name: Job name, ``str`` - :param depth: JSON depth, ``int`` - :returns: dictionary of job information - ''' - try: - response = self.jenkins_open(Request( - self.server + JOB_INFO % self._get_encoded_params(locals()))) - if response: - return json.loads(response) - else: - raise JenkinsException('job[%s] does not exist' % name) - except HTTPError: - raise JenkinsException('job[%s] does not exist' % name) - except ValueError: - raise JenkinsException( - "Could not parse JSON info for job[%s]" % name) - - def get_job_info_regex(self, pattern, depth=0): - '''Get a list of jobs information that contain names which match the - regex pattern. - - :param pattern: regex pattern, ``str`` - :param depth: JSON depth, ``int`` - :returns: List of jobs info, ``list`` - ''' - result = [] - jobs = self.get_jobs() - for job in jobs: - if re.search(pattern, job['name']): - result.append(self.get_job_info(job['name'], depth=depth)) - - return result - - def get_job_name(self, name): - '''Return the name of a job using the API. - - That is roughly an identity method which can be used to quickly verify - a job exist or is accessible without causing too much stress on the - server side. - - :param name: Job name, ``str`` - :returns: Name of job or None - ''' - try: - response = self.jenkins_open( - Request(self.server + JOB_NAME % - self._get_encoded_params(locals()))) - except NotFoundException: - return None - else: - actual = json.loads(response)['name'] - if actual != name: - raise JenkinsException( - 'Jenkins returned an unexpected job name %s ' - '(expected: %s)' % (actual, name)) - return actual - - def debug_job_info(self, job_name): - '''Print out job info in more readable format.''' - for k, v in self.get_job_info(job_name).items(): - print(k, v) - - def jenkins_open(self, req, add_crumb=True): - '''Utility routine for opening an HTTP request to a Jenkins server. - - This should only be used to extends the :class:`Jenkins` API. - ''' - try: - if self.auth: - req.add_header('Authorization', self.auth) - if add_crumb: - self.maybe_add_crumb(req) - response = urlopen(req, timeout=self.timeout).read() - if response is None: - raise EmptyResponseException( - "Error communicating with server[%s]: " - "empty response" % self.server) - return response.decode('utf-8') - except HTTPError as e: - # Jenkins's funky authentication means its nigh impossible to - # distinguish errors. - if e.code in [401, 403, 500]: - # six.moves.urllib.error.HTTPError provides a 'reason' - # attribute for all python version except for ver 2.6 - # Falling back to HTTPError.msg since it contains the - # same info as reason - raise JenkinsException( - 'Error in request. ' + - 'Possibly authentication failed [%s]: %s' % ( - e.code, e.msg) - ) - elif e.code == 404: - raise NotFoundException('Requested item could not be found') - else: - raise - except URLError as e: - raise JenkinsException('Error in request: %s' % (e.reason)) - - def get_build_info(self, name, number, depth=0): - '''Get build information dictionary. - - :param name: Job name, ``str`` - :param name: Build number, ``int`` - :param depth: JSON depth, ``int`` - :returns: dictionary of build information, ``dict`` - - Example:: - - >>> j = Jenkins() - >>> next_build_number = j.get_job_info('build_name')['nextBuildNumber'] - >>> output = j.build_job('build_name') - >>> from time import sleep; sleep(10) - >>> build_info = j.get_build_info('build_name', next_build_number) - >>> print(build_info) - {u'building': False, u'changeSet': {u'items': [{u'date': u'2011-12-19T18:01:52.540557Z', u'msg': u'test', u'revision': 66, u'user': u'unknown', u'paths': [{u'editType': u'edit', u'file': u'/branches/demo/index.html'}]}], u'kind': u'svn', u'revisions': [{u'module': u'http://eaas-svn01.i3.level3.com/eaas', u'revision': 66}]}, u'builtOn': u'', u'description': None, u'artifacts': [{u'relativePath': u'dist/eaas-87-2011-12-19_18-01-57.war', u'displayPath': u'eaas-87-2011-12-19_18-01-57.war', u'fileName': u'eaas-87-2011-12-19_18-01-57.war'}, {u'relativePath': u'dist/eaas-87-2011-12-19_18-01-57.war.zip', u'displayPath': u'eaas-87-2011-12-19_18-01-57.war.zip', u'fileName': u'eaas-87-2011-12-19_18-01-57.war.zip'}], u'timestamp': 1324317717000, u'number': 87, u'actions': [{u'parameters': [{u'name': u'SERVICE_NAME', u'value': u'eaas'}, {u'name': u'PROJECT_NAME', u'value': u'demo'}]}, {u'causes': [{u'userName': u'anonymous', u'shortDescription': u'Started by user anonymous'}]}, {}, {}, {}], u'id': u'2011-12-19_18-01-57', u'keepLog': False, u'url': u'http://eaas-jenkins01.i3.level3.com:9080/job/build_war/87/', u'culprits': [{u'absoluteUrl': u'http://eaas-jenkins01.i3.level3.com:9080/user/unknown', u'fullName': u'unknown'}], u'result': u'SUCCESS', u'duration': 8826, u'fullDisplayName': u'build_war #87'} - ''' - try: - response = self.jenkins_open(Request( - self.server + BUILD_INFO % self._get_encoded_params(locals()))) - if response: - return json.loads(response) - else: - raise JenkinsException('job[%s] number[%d] does not exist' - % (name, number)) - except HTTPError: - raise JenkinsException('job[%s] number[%d] does not exist' - % (name, number)) - except ValueError: - raise JenkinsException( - 'Could not parse JSON info for job[%s] number[%d]' - % (name, number) - ) - - def get_queue_info(self): - ''':returns: list of job dictionaries, ``[dict]`` - - Example:: - >>> j = Jenkins() - >>> queue_info = j.get_queue_info() - >>> print(queue_info[0]) - {u'task': {u'url': u'http://your_url/job/my_job/', u'color': u'aborted_anime', u'name': u'my_job'}, u'stuck': False, u'actions': [{u'causes': [{u'shortDescription': u'Started by timer'}]}], u'buildable': False, u'params': u'', u'buildableStartMilliseconds': 1315087293316, u'why': u'Build #2,532 is already in progress (ETA:10 min)', u'blocked': True} - ''' - return json.loads(self.jenkins_open( - Request(self.server + Q_INFO) - ))['items'] - - def cancel_queue(self, id): - '''Cancel a queued build. - - :param id: Jenkins job id number for the build, ``int`` - ''' - # Jenkins seems to always return a 404 when using this REST endpoint - # https://issues.jenkins-ci.org/browse/JENKINS-21311 - try: - self.jenkins_open( - Request(self.server + CANCEL_QUEUE % locals(), b'', - headers={'Referer': self.server})) - except NotFoundException: - # Exception is expected; cancel_queue() is a best-effort - # mechanism, so ignore it - pass - - def get_info(self): - """Get information on this Master. - - This information includes job list and view information. - - :returns: dictionary of information about Master, ``dict`` - - Example:: - - >>> j = Jenkins() - >>> info = j.get_info() - >>> jobs = info['jobs'] - >>> print(jobs[0]) - {u'url': u'http://your_url_here/job/my_job/', u'color': u'blue', - u'name': u'my_job'} - - """ - try: - return json.loads(self.jenkins_open( - Request(self.server + INFO))) - except (HTTPError, BadStatusLine): - raise BadHTTPException("Error communicating with server[%s]" - % self.server) - except ValueError: - raise JenkinsException("Could not parse JSON info for server[%s]" - % self.server) - - def get_version(self): - """Get the version of this Master. - - :returns: This master's version number ``str`` - - Example:: - - >>> j = Jenkins() - >>> info = j.get_version() - >>> print info - >>> 1.541 - - """ - try: - request = Request(self.server) - request.add_header('X-Jenkins', '0.0') - response = urlopen(request, timeout=self.timeout) - if response is None: - raise EmptyResponseException( - "Error communicating with server[%s]: " - "empty response" % self.server) - - if six.PY2: - return response.info().getheader('X-Jenkins') - - if six.PY3: - return response.getheader('X-Jenkins') - - except (HTTPError, BadStatusLine): - raise BadHTTPException("Error communicating with server[%s]" - % self.server) - - def get_plugins_info(self, depth=2): - """Get all installed plugins information on this Master. - - This method retrieves information about each plugin that is installed - on master. - - :param depth: JSON depth, ``int`` - :returns: info on all plugins ``[dict]`` - - Example:: - - >>> j = Jenkins() - >>> info = j.get_plugins_info() - >>> print(info) - [{u'backupVersion': None, u'version': u'0.0.4', u'deleted': False, - u'supportsDynamicLoad': u'MAYBE', u'hasUpdate': True, - u'enabled': True, u'pinned': False, u'downgradable': False, - u'dependencies': [], u'url': - u'http://wiki.jenkins-ci.org/display/JENKINS/Gearman+Plugin', - u'longName': u'Gearman Plugin', u'active': True, u'shortName': - u'gearman-plugin', u'bundled': False}, ..] - - """ - try: - plugins_info = json.loads(self.jenkins_open( - Request(self.server + PLUGIN_INFO % locals()))) - return plugins_info['plugins'] - except (HTTPError, BadStatusLine): - raise BadHTTPException("Error communicating with server[%s]" - % self.server) - except ValueError: - raise JenkinsException("Could not parse JSON info for server[%s]" - % self.server) - - def get_plugin_info(self, name, depth=2): - """Get an installed plugin information on this Master. - - This method retrieves information about a speicifc plugin. - The passed in plugin name (short or long) must be an exact match. - - :param name: Name (short or long) of plugin, ``str`` - :param depth: JSON depth, ``int`` - :returns: a specific plugin ``dict`` - - Example:: - - >>> j = Jenkins() - >>> info = j.get_plugin_info("Gearman Plugin") - >>> print(info) - {u'backupVersion': None, u'version': u'0.0.4', u'deleted': False, - u'supportsDynamicLoad': u'MAYBE', u'hasUpdate': True, - u'enabled': True, u'pinned': False, u'downgradable': False, - u'dependencies': [], u'url': - u'http://wiki.jenkins-ci.org/display/JENKINS/Gearman+Plugin', - u'longName': u'Gearman Plugin', u'active': True, u'shortName': - u'gearman-plugin', u'bundled': False} - - """ - try: - plugins_info = json.loads(self.jenkins_open( - Request(self.server + PLUGIN_INFO % self._get_encoded_params(locals())))) - for plugin in plugins_info['plugins']: - if plugin['longName'] == name or plugin['shortName'] == name: - return plugin - except (HTTPError, BadStatusLine): - raise BadHTTPException("Error communicating with server[%s]" - % self.server) - except ValueError: - raise JenkinsException("Could not parse JSON info for server[%s]" - % self.server) - - def get_jobs(self): - """Get list of jobs running. - - Each job is a dictionary with 'name', 'url', and 'color' keys. - - :returns: list of jobs, ``[ { str: str} ]`` - """ - return self.get_info()['jobs'] - - def copy_job(self, from_name, to_name): - '''Copy a Jenkins job - - :param from_name: Name of Jenkins job to copy from, ``str`` - :param to_name: Name of Jenkins job to copy to, ``str`` - ''' - self.jenkins_open(Request( - self.server + COPY_JOB % self._get_encoded_params(locals()), - b'')) - self.assert_job_exists(to_name, 'create[%s] failed') - - def rename_job(self, from_name, to_name): - '''Rename an existing Jenkins job - - :param from_name: Name of Jenkins job to rename, ``str`` - :param to_name: New Jenkins job name, ``str`` - ''' - self.jenkins_open(Request( - self.server + RENAME_JOB % self._get_encoded_params(locals()), - b'')) - self.assert_job_exists(to_name, 'rename[%s] failed') - - def delete_job(self, name): - '''Delete Jenkins job permanently. - - :param name: Name of Jenkins job, ``str`` - ''' - self.jenkins_open(Request( - self.server + DELETE_JOB % self._get_encoded_params(locals()), - b'')) - if self.job_exists(name): - raise JenkinsException('delete[%s] failed' % (name)) - - def enable_job(self, name): - '''Enable Jenkins job. - - :param name: Name of Jenkins job, ``str`` - ''' - self.jenkins_open(Request( - self.server + ENABLE_JOB % self._get_encoded_params(locals()), - b'')) - - def disable_job(self, name): - '''Disable Jenkins job. - - To re-enable, call :meth:`Jenkins.enable_job`. - - :param name: Name of Jenkins job, ``str`` - ''' - self.jenkins_open(Request( - self.server + DISABLE_JOB % self._get_encoded_params(locals()), - b'')) - - def job_exists(self, name): - '''Check whether a job exists - - :param name: Name of Jenkins job, ``str`` - :returns: ``True`` if Jenkins job exists - ''' - if self.get_job_name(name) == name: - return True - - def jobs_count(self): - '''Get the number of jobs on the Jenkins server - - :returns: Total number of jobs, ``int`` - ''' - return len(self.get_jobs()) - - def assert_job_exists(self, name, - exception_message='job[%s] does not exist'): - '''Raise an exception if a job does not exist - - :param name: Name of Jenkins job, ``str`` - :param exception_message: Message to use for the exception. Formatted - with ``name`` - :throws: :class:`JenkinsException` whenever the job does not exist - ''' - if not self.job_exists(name): - raise JenkinsException(exception_message % name) - - def create_job(self, name, config_xml): - '''Create a new Jenkins job - - :param name: Name of Jenkins job, ``str`` - :param config_xml: config file text, ``str`` - ''' - if self.job_exists(name): - raise JenkinsException('job[%s] already exists' % (name)) - - self.jenkins_open(Request( - self.server + CREATE_JOB % self._get_encoded_params(locals()), - config_xml.encode('utf-8'), DEFAULT_HEADERS)) - self.assert_job_exists(name, 'create[%s] failed') - - def get_job_config(self, name): - '''Get configuration of existing Jenkins job. - - :param name: Name of Jenkins job, ``str`` - :returns: job configuration (XML format) - ''' - request = Request(self.server + CONFIG_JOB % self._get_encoded_params(locals())) - return self.jenkins_open(request) - - def reconfig_job(self, name, config_xml): - '''Change configuration of existing Jenkins job. - - To create a new job, see :meth:`Jenkins.create_job`. - - :param name: Name of Jenkins job, ``str`` - :param config_xml: New XML configuration, ``str`` - ''' - reconfig_url = self.server + CONFIG_JOB % self._get_encoded_params(locals()) - self.jenkins_open(Request(reconfig_url, config_xml.encode('utf-8'), - DEFAULT_HEADERS)) - - def build_job_url(self, name, parameters=None, token=None): - '''Get URL to trigger build job. - - Authenticated setups may require configuring a token on the server - side. - - :param parameters: parameters for job, or None., ``dict`` - :param token: (optional) token for building job, ``str`` - :returns: URL for building job - ''' - if parameters: - if token: - parameters['token'] = token - return (self.server + BUILD_WITH_PARAMS_JOB % self._get_encoded_params(locals()) + - '?' + urlencode(parameters)) - elif token: - return (self.server + BUILD_JOB % self._get_encoded_params(locals()) + - '?' + urlencode({'token': token})) - else: - return self.server + BUILD_JOB % self._get_encoded_params(locals()) - - def build_job(self, name, parameters=None, token=None): - '''Trigger build job. - - :param name: name of job - :param parameters: parameters for job, or ``None``, ``dict`` - :param token: Jenkins API token - ''' - return self.jenkins_open(Request( - self.build_job_url(name, parameters, token), b'')) - - def stop_build(self, name, number): - '''Stop a running Jenkins build. - - :param name: Name of Jenkins job, ``str`` - :param number: Jenkins build number for the job, ``int`` - ''' - self.jenkins_open(Request(self.server + STOP_BUILD % self._get_encoded_params(locals())), b'') - - def get_nodes(self): - '''Get a list of nodes connected to the Master - - Each node is a dict with keys 'name' and 'offline' - - :returns: List of nodes, ``[ { str: str, str: bool} ]`` - ''' - try: - nodes_data = json.loads(self.jenkins_open(Request(self.server + NODE_LIST))) - return [{'name': c["displayName"], 'offline': c["offline"]} - for c in nodes_data["computer"]] - except (HTTPError, BadStatusLine): - raise BadHTTPException("Error communicating with server[%s]" - % self.server) - except ValueError: - raise JenkinsException("Could not parse JSON info for server[%s]" - % self.server) - - def get_node_info(self, name, depth=0): - '''Get node information dictionary - - :param name: Node name, ``str`` - :param depth: JSON depth, ``int`` - :returns: Dictionary of node info, ``dict`` - ''' - try: - response = self.jenkins_open(Request( - self.server + NODE_INFO % self._get_encoded_params(locals()))) - if response: - return json.loads(response) - else: - raise JenkinsException('node[%s] does not exist' % name) - except HTTPError: - raise JenkinsException('node[%s] does not exist' % name) - except ValueError: - raise JenkinsException("Could not parse JSON info for node[%s]" - % name) - - def node_exists(self, name): - '''Check whether a node exists - - :param name: Name of Jenkins node, ``str`` - :returns: ``True`` if Jenkins node exists - ''' - try: - self.get_node_info(name) - return True - except JenkinsException: - return False - - def assert_node_exists(self, name, - exception_message='node[%s] does not exist'): - '''Raise an exception if a node does not exist - - :param name: Name of Jenkins node, ``str`` - :param exception_message: Message to use for the exception. Formatted - with ``name`` - :throws: :class:`JenkinsException` whenever the node does not exist - ''' - if not self.node_exists(name): - raise JenkinsException(exception_message % name) - - def delete_node(self, name): - '''Delete Jenkins node permanently. - - :param name: Name of Jenkins node, ``str`` - ''' - self.get_node_info(name) - self.jenkins_open(Request( - self.server + DELETE_NODE % self._get_encoded_params(locals()), - b'')) - if self.node_exists(name): - raise JenkinsException('delete[%s] failed' % (name)) - - def disable_node(self, name, msg=''): - '''Disable a node - - :param name: Jenkins node name, ``str`` - :param msg: Offline message, ``str`` - ''' - info = self.get_node_info(name) - if info['offline']: - return - self.jenkins_open(Request( - self.server + TOGGLE_OFFLINE % self._get_encoded_params(locals()), - b'')) - - def enable_node(self, name): - '''Enable a node - - :param name: Jenkins node name, ``str`` - ''' - info = self.get_node_info(name) - if not info['offline']: - return - msg = '' - self.jenkins_open(Request( - self.server + TOGGLE_OFFLINE % self._get_encoded_params(locals()), - b'')) - - def create_node(self, name, numExecutors=2, nodeDescription=None, - remoteFS='/var/lib/jenkins', labels=None, exclusive=False, - launcher=LAUNCHER_COMMAND, launcher_params={}): - '''Create a node - - :param name: name of node to create, ``str`` - :param numExecutors: number of executors for node, ``int`` - :param nodeDescription: Description of node, ``str`` - :param remoteFS: Remote filesystem location to use, ``str`` - :param labels: Labels to associate with node, ``str`` - :param exclusive: Use this node for tied jobs only, ``bool`` - :param launcher: The launch method for the slave, ``jenkins.LAUNCHER_COMMAND``, ``jenkins.LAUNCHER_SSH``, ``jenkins.LAUNCHER_JNLP``, ``jenkins.LAUNCHER_WINDOWS_SERVICE`` - :param launcher_params: Additional parameters for the launcher, ``dict`` - ''' - if self.node_exists(name): - raise JenkinsException('node[%s] already exists' % (name)) - - mode = 'NORMAL' - if exclusive: - mode = 'EXCLUSIVE' - - launcher_params['stapler-class'] = launcher - - inner_params = { - 'name': name, - 'nodeDescription': nodeDescription, - 'numExecutors': numExecutors, - 'remoteFS': remoteFS, - 'labelString': labels, - 'mode': mode, - 'type': NODE_TYPE, - 'retentionStrategy': { - 'stapler-class': - 'hudson.slaves.RetentionStrategy$Always' - }, - 'nodeProperties': {'stapler-class-bag': 'true'}, - 'launcher': launcher_params - } - - params = { - 'name': name, - 'type': NODE_TYPE, - 'json': json.dumps(inner_params) - } - - self.jenkins_open(Request( - self.server + CREATE_NODE % urlencode(params)), b'') - - self.assert_node_exists(name, 'create[%s] failed') - - def get_node_config(self, name): - '''Get the configuration for a node. - - :param name: Jenkins node name, ``str`` - ''' - get_config_url = self.server + CONFIG_NODE % self._get_encoded_params(locals()) - return self.jenkins_open(Request(get_config_url)) - - def reconfig_node(self, name, config_xml): - '''Change the configuration for an existing node. - - :param name: Jenkins node name, ``str`` - :param config_xml: New XML configuration, ``str`` - ''' - reconfig_url = self.server + CONFIG_NODE % self._get_encoded_params(locals()) - self.jenkins_open(Request(reconfig_url, config_xml.encode('utf-8'), DEFAULT_HEADERS)) - - def get_build_console_output(self, name, number): - '''Get build console text. - - :param name: Job name, ``str`` - :param name: Build number, ``int`` - :returns: Build console output, ``str`` - ''' - try: - response = self.jenkins_open(Request( - self.server + BUILD_CONSOLE_OUTPUT % self._get_encoded_params(locals()))) - if response: - return response - else: - raise JenkinsException('job[%s] number[%d] does not exist' - % (name, number)) - except HTTPError: - raise JenkinsException('job[%s] number[%d] does not exist' - % (name, number))