def __delitem__(self, shortName): if re.match('.*@.*', shortName): real_shortName = re.compile('(.*)@(.*)').search(shortName).group(1) raise ValueError( ("Plugin shortName can't contain version. '%s' should be '%s'") % (shortName, real_shortName) ) if shortName not in self: raise KeyError( 'Plugin with ID "%s" not found, cannot uninstall' % shortName) if self[shortName].deleted: raise JenkinsAPIException( 'Plugin "%s" already marked for uninstall. ' 'Restart jenkins for uninstall to complete.') params = { 'Submit': 'OK', 'json': {} } url = ('%s/pluginManager/plugin/%s/doUninstall' % (self.jenkins_obj.baseurl, shortName)) self.jenkins_obj.requester.post_and_confirm_status( url, params={}, data=urlencode(params) ) self.poll() if not self[shortName].deleted: raise JenkinsAPIException( "Problem uninstalling plugin '%s'." % shortName)
def __setitem__(self, description, credential): """ Creates Credential in Jenkins using username, password and description Description must be unique in Jenkins instance because it is used to find Credential later. If description already exists - this method is going to update existing Credential :param str description: Credential description :param tuple credential_tuple: (username, password, description) tuple. """ if description not in self: params = credential.get_attributes() url = ('%s/createCredentials' % self.baseurl) else: raise JenkinsAPIException('Updating credentials is not supported ' 'by jenkinsapi') try: self.jenkins.requester.post_and_confirm_status( url, params={}, data=urlencode(params)) except JenkinsAPIException as jae: raise JenkinsAPIException('Latest version of Credentials ' 'plugin is required to be able ' 'to create/update credentials. ' 'Original exception: %s' % str(jae)) self.poll() self.credentials = self._data['credentials'] if description not in self: raise JenkinsAPIException('Problem creating/updating credential.')
def get_data(self, url, params=None, tree=None): try: return ast.literal_eval(self.get_raw_data(url, params, tree)) except Exception: logging.exception('Inappropriate content found at %s', url) raise JenkinsAPIException('Inappropriate content found at %s' % url)
def create(self, job_name, config): """ Create a job :param jobname: name of new job, str :param config: configuration of new job, xml :return: new Job obj """ if job_name in self: return self[job_name] params = {'name': job_name} try: if isinstance( config, unicode): # pylint: disable=undefined-variable config = str(config) except NameError: # Python3 already a str pass self.jenkins.requester.post_xml_and_confirm_status( self.jenkins.get_create_url(), data=config, params=params ) self.jenkins.poll() if job_name not in self: raise JenkinsAPIException('Cannot create job %s' % job_name) return self[job_name]
def __delitem__(self, description): params = {'Submit': 'OK', 'json': {}} url = ('%s/credential-store/domain/_/credential/%s/doDelete' % (self.jenkins.baseurl, self[description].credential_id)) try: self.jenkins.requester.post_and_confirm_status( url, params={}, data=urlencode(params)) except JenkinsAPIException as jae: raise JenkinsAPIException('Latest version of Credentials ' 'required to be able to create ' 'credentials. Original exception: %s' % str(jae)) self.poll() self.credentials = self._data['credentials'] if description in self: raise JenkinsAPIException('Problem deleting credential.')
def create(self, job_name, config): """ Create a job :param str jobname: Name of new job :param str config: XML configuration of new job :returns Job: new Job object """ if job_name in self: return self[job_name] if config is None or len(config) == 0: raise JenkinsAPIException('Job XML config cannot be empty') params = {'name': job_name} config = str(config) # cast unicode in case of Python 2 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 = [] return self[job_name]
def post_and_confirm_status( self, url, params=None, data=None, files=None, headers=None, valid=None, allow_redirects=True): valid = valid or self.VALID_STATUS_CODES if not headers and not files: headers = {'Content-Type': 'application/x-www-form-urlencoded'} assert data is not None, "Post messages must have data" response = self.post_url( url, params, data, files, headers, allow_redirects) if response.status_code not in valid: raise JenkinsAPIException( 'Operation failed. url={0}, data={1}, headers={2}, ' 'status={3}, text={4}'.format( response.url, data, headers, response.status_code, response.text.encode('UTF-8') ) ) return response
def create(self, job_name, config): """ Create a job :param str jobname: Name of new job :param str config: XML configuration of new job :returns Job: new Job object """ if job_name in self: return self[job_name] if config is None or len(config) == 0: 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 = [] return self[job_name]
def get_data(self, url, params=None): requester = self.get_jenkins_obj().requester response = requester.get_url(url, params) try: return eval(response.text) except Exception: log.exception('Inappropriate content found at %s', url) raise JenkinsAPIException('Cannot parse %s' % response.content)
def __delitem__(self, shortName): if shortName not in self: raise KeyError('Plugin with ID "%s" not found, cannot uninstall' % shortName) if self[shortName].deleted: raise JenkinsAPIException( 'Plugin "%s" already marked for uninstall. ' 'Restart jenkins for uninstall to complete.') params = {'Submit': 'OK', 'json': {}} url = ('%s/pluginManager/plugin/%s/doUninstall' % (self.jenkins_obj.baseurl, shortName)) self.jenkins_obj.requester.post_and_confirm_status( url, params={}, data=urlencode(params)) self.poll() if not self[shortName].deleted: raise JenkinsAPIException("Problem uninstalling plugin '%s'." % shortName)
def upload_config(self, config_xml): """ Uploads config_xml to the config.xml for the node. """ if self.name == 'master': raise JenkinsAPIException('master node does not have config.xml') self.jenkins.requester.post_and_confirm_status( "%(baseurl)s/config.xml" % self.__dict__, data=config_xml)
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 get_and_confirm_status(self, url, params=None, headers=None, valid=None): valid = valid or self.VALID_STATUS_CODES response = self.get_url(url, params, headers) if response.status_code not in valid: if response.status_code == 405: # POST required raise PostRequired('POST required for url {0}'.format(url)) else: raise JenkinsAPIException('Operation failed. url={0}, headers={1}, status={2}, text={3}'.format( response.url, headers, response.status_code, response.text.encode('UTF-8'))) return response
def get_data(self, url, params=None): requester = self.get_jenkins_obj().requester response = requester.get_url(url, params) if response.status_code != 200: response.raise_for_status() try: return ast.literal_eval(response.text) except Exception: logging.exception('Inappropriate content found at %s', url) raise JenkinsAPIException('Cannot parse %s' % response.content)
def update_center_install_status(self): """ Jenkins 2.x specific """ url = "%s/updateCenter/installStatus" % self.jenkins_obj.baseurl status = self.jenkins_obj.requester.get_url(url) if status.status_code == 404: raise JenkinsAPIException( 'update_center_install_status not available for Jenkins 1.X') return status.json()
def load_config(self): """ Loads the config.xml for the node allowing it to be re-queried without generating new requests. """ if self.name == 'master': raise JenkinsAPIException('master node does not have config.xml') self._config = self.get_config() self._get_config_element_tree()
def iteritems(self): for item in self._data['computer']: nodename = item['displayName'] if nodename.lower() == 'master': nodeurl = '%s/(%s)' % (self.baseurl, nodename) else: nodeurl = '%s/%s' % (self.baseurl, nodename) try: yield item['displayName'], Node(self.jenkins, nodeurl, nodename, node_dict={}) except Exception: raise JenkinsAPIException('Unable to iterate nodes')
def itervalues(self): """ Return an iterator over the container's nodes. Using itervalues() while creating nodes may raise a RuntimeError or fail to iterate over all entries. """ for item in self._data['computer']: try: yield self._make_node(item['displayName']) except Exception: raise JenkinsAPIException('Unable to iterate nodes')
def get_and_confirm_status(self, url, params=None, headers=None, valid=None): valid = valid or self.VALID_STATUS_CODES response = self.get_url(url, params, headers) if not response.status_code in valid: raise JenkinsAPIException( 'Operation failed. url={0}, headers={1}, status={2}, text={3}'. format(response.url, headers, response.status_code, response.text.encode('UTF-8'))) return response
def post_and_confirm_status(self, url, params=None, data=None, files=None, headers=None, valid=None): valid = valid or self.VALID_STATUS_CODES assert isinstance(data, ( str, dict)), \ "Unexpected type of parameter 'data': %s. Expected (str, dict)" % type(data) if not headers and not files: headers = {'Content-Type': 'application/x-www-form-urlencoded'} response = self.post_url(url, params, data, files, headers) if response.status_code not in valid: raise JenkinsAPIException('Operation failed. url={0}, data={1}, headers={2}, status={3}, text={4}'.format( response.url, data, headers, response.status_code, response.text.encode('UTF-8'))) return response
def get_credentials(self): """ Return credentials """ if 'credentials' not in self.plugins: raise JenkinsAPIException('Credentials plugin not installed') if int(self.plugins['credentials'].version[0:1]) == 1: url = '%s/credential-store/domain/_/' % self.baseurl return Credentials(url, self) else: url = '%s/credentials/store/system/domain/_/' % self.baseurl return Credentials2x(url, self)
def _wait_until_plugin_installed(self, plugin, maxwait=120, interval=1): for _ in range(maxwait, 0, -interval): self.poll() if self._plugin_has_finished_installation(plugin): return if plugin.shortName in self: return True # for Jenkins 1.X time.sleep(interval) if self.jenkins_obj.version.startswith('2'): raise JenkinsAPIException("Problem installing plugin '%s'." % plugin.shortName) else: log.warning( "Plugin '%s' not found in loaded plugins." "You may need to restart Jenkins.", plugin.shortName)
def get_console(self): """ Return the current state of the text console. """ url = "%s/consoleText" % self.baseurl content = self.job.jenkins.requester.get_url(url).content # This check was made for Python 3.x # In this version content is a bytes string # By contract this function must return string if isinstance(content, str): return content elif isinstance(content, bytes): return content.decode('ISO-8859-1') else: raise JenkinsAPIException('Unknown content type for console')
def get_data(self, url, params=None, tree=None): requester = self.get_jenkins_obj().requester if tree: if not params: params = {'tree': tree} else: params.update({'tree': tree}) 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 '') response.raise_for_status() try: return ast.literal_eval(response.text) except Exception: logging.exception('Inappropriate content found at %s', url) raise JenkinsAPIException('Cannot parse %s' % response.content)
def create(self, view_name, view_type=LIST_VIEW, config=None): """ Create a view :param view_name: name of new view, str :param view_type: type of the view, one of the constants in Views, str :param config: XML configuration of the new view :return: new View obj or None if view was not created """ log.info('Creating "%s" view "%s"', view_type, view_name) if view_name in self: log.warning('View "%s" already exists', view_name) return self[view_name] url = '%s/createView' % self.jenkins.baseurl if view_type == self.CATEGORIZED_VIEW: if config is None or len(config) == 0: raise JenkinsAPIException( 'Job XML config cannot be empty for CATEGORIZED_VIEW') params = {'name': view_name} self.jenkins.requester.post_xml_and_confirm_status(url, data=config, params=params) else: headers = {'Content-Type': 'application/x-www-form-urlencoded'} data = { "name": view_name, "mode": view_type, "Submit": "OK", "json": json.dumps({ "name": view_name, "mode": view_type }) } self.jenkins.requester.post_and_confirm_status(url, data=data, headers=headers) self.poll() return self[view_name]
def update_job(self, jobname, config): """ Update a job :param jobname: name of job, str :param config: new configuration of job, xml :return: updated Job obj """ if self.has_job(jobname): if isinstance(config, unicode): config = str(config) self.requester.post_xml_and_confirm_status('%s/job/%s/config.xml' % (self.baseurl, jobname), data=config) self.poll() if (not self.has_job(jobname) and self.jobs[jobname].get_config() != config): raise JenkinsAPIException('Cannot update job %s' % jobname) else: raise UnknownJob(jobname) return self[jobname]
def create(self, job_name, config): """ Create a job :param jobname: name of new job, str :param config: configuration of new job, xml :return: new Job obj """ if job_name in self: return self[job_name] params = {'name': job_name} if isinstance(config, unicode): config = str(config) self.jenkins.requester.post_xml_and_confirm_status( self.jenkins.get_create_url(), data=config, params=params) self.jenkins.poll() if job_name not in self: raise JenkinsAPIException('Cannot create job %s' % job_name) return self[job_name]
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 run_groovy_script(self, script): """ Runs the requested groovy script on the Jenkins server returning the result as text. Raises a JenkinsAPIException if the returned HTTP response code from the POST request is not 200 OK. Example: server = Jenkins(...) script = 'println "Hello world!"' result = server.run_groovy_script(script) print(result) # will print "Hello world!" """ url = "%s/scriptText" % self.baseurl data = urlencode({'script': script}) response = self.requester.post_and_confirm_status(url, data=data) if response.status_code != 200: raise JenkinsAPIException('Unexpected response %d.' % response.status_code) return response.text
def create(self, job_name, config): """ Create a job :param str job_name: Name of new job :param str config: XML configuration of new job :returns Job: new Job object """ if job_name in self: return self[job_name] if config is None or len(config) == 0: 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 ) # Call above would fail if Jenkins is unhappy # If Jenkins is happy - we don't poll it, but insert job into # internal cache self._data.append({ 'name': job_name, 'color': 'notbuilt' }) return self[job_name]