class JenkinsAPI(object): def __init__(self): self.__server = Jenkins(settings.JENKINS.get('URI')) def get_all_jobs(self): return self.__server.get_all_jobs() def get_next_build_number(self, name): return self.__server.get_job_info(name)['nextBuildNumber'] def build_job(self, name, parameters=None): return self.__server.build_job(name=name, parameters=parameters) def get_build_info(self, name, build_number): try: return self.__server.get_build_info(name, build_number) except Exception as e: logger.exception(e) return None def get_build_console_output(self, name, number): try: return self.__server.get_build_console_output(name, number) except JenkinsException as e: logger.exception(e) return None def download_package(self, package_url, name, build_number): URI = settings.JENKINS.get('URI') download_url = '{}/job/{}/{}/artifact/{}'.format( URI, name, build_number, package_url) logger.debug(download_url) local_filename = download_url.split('/')[-1] code_path = os.path.join(settings.DEPLOY.get('CODE_PATH'), 'packages') local_full_filename = os.path.join(code_path, local_filename) # with requests.get(url, stream=True, # auth=HTTPBasicAuth("zhoujinliang", "117c911a35acf51e428e29f3ccb363f53f")) as r: with requests.get(download_url, stream=True) as r: r.raise_for_status() with open(local_full_filename, 'wb') as f: for chunk in r.iter_content(chunk_size=8192): if chunk: # filter out keep-alive new chunks f.write(chunk) return local_full_filename def cancel_build(self, name, queue_id, build_number): try: self.__server.cancel_queue(queue_id) self.__server.stop_build(name, build_number) except Exception as e: logger.error(e) def test(self): return self.__server.cancel_queue(3590) # # return self.__server.stop_build('devops', 98) return self.__server.get_queue_info()
class JenkinsJobMonitor(): def __init__(self, url, username, password, *args, **argvs): self._attach_server(url, username, password, *args, **argvs) def _attach_server(self, url, username, password, *args, **argvs): self.server = Jenkins(url=url, username=username, password=password, *args, **argvs) def _is_stuck(self, item): if item['pending'] == False and item['stuck'] == True: print( "Cancel 'stuck' job [name : {name}] [id : {job_id} [why: {why}]]" .format(name=item['task']['name'], job_id=item['id'], why=item['why'])) return True else: return False def _cancel_jobs(self, build_id): self.server.cancel_queue(build_id) def cancel_stuck_jobs(self): if self.server.get_queue_info(): for item in self.server.get_queue_info(): if self._is_stuck(item): self._cancel_jobs(item['id']) else: self.poll_flag = False def _is_timeout(self): pass def cancel_timeout_jobs(self): pass def poll_stuck(self): self.poll_flag = True while self.poll_flag: time.sleep(10) # Timer maybe better. self.cancel_stuck_jobs() def poll_timeout(self): pass
class JenkinsBot(BotPlugin): """Basic Err integration with Jenkins CI""" min_err_version = '1.2.1' # max_err_version = '4.0.3' def get_configuration_template(self): return CONFIG_TEMPLATE def configure(self, configuration): if configuration is not None and configuration != {}: config = dict(chain(CONFIG_TEMPLATE.items(), configuration.items())) else: config = CONFIG_TEMPLATE super(JenkinsBot, self).configure(config) return def check_configuration(self, configuration): self.log.debug(configuration) for c, v in configuration.items(): if c == 'URL': if not validators.url(v): raise ValidationException('JENKINS_URL is not a well formed URL') elif c in ['USERNAME', 'PASSWORD', 'RECEIVE_NOTIFICATION']: if len(v) == 0 or not isinstance(v, str): raise ValidationException("{} is a required string config setting".format(c)) elif c in ['CHATROOMS_NOTIFICATION']: if not isinstance(v, tuple): raise ValidationException("{} should be of type tuple".format(c)) return def connect_to_jenkins(self): """Connect to a Jenkins instance using configuration.""" self.log.debug('Connecting to Jenkins ({0})'.format( self.config['URL'])) self.jenkins = Jenkins(url=self.config['URL'], username=self.config['USERNAME'], password=self.config['PASSWORD']) return def broadcast(self, mess): """Shortcut to broadcast a message to all elligible chatrooms.""" chatrooms = (self.config['CHATROOMS_NOTIFICATION'] if self.config['CHATROOMS_NOTIFICATION'] else self.bot_config.CHATROOM_PRESENCE) for room in chatrooms: self.send(self.build_identifier(room), mess) return @webhook(r'/jenkins/notification') def handle_notification(self, incoming_request): if not self.config['RECEIVE_NOTIFICATION']: return 'Notification handling is disabled.' self.log.debug(repr(incoming_request)) self.broadcast(self.format_notification(incoming_request)) return @botcmd def jenkins_list(self, mess, args): """List all jobs, optionally filter them using a search term.""" self.connect_to_jenkins() return self.format_jobs([job for job in self.jenkins.get_jobs(folder_depth=None) if args.lower() in job['fullname'].lower()]) @botcmd def jenkins_running(self, mess, args): """List all running jobs.""" self.connect_to_jenkins() jobs = [job for job in self.jenkins.get_jobs() if 'anime' in job['color']] return self.format_running_jobs(jobs) @botcmd(split_args_with=None) def jenkins_param(self, mess, args): """List Parameters for a given job.""" if len(args) == 0: return 'What Job would you like the parameters for?' self.connect_to_jenkins() job = self.jenkins.get_job_info(args[0]) if job['actions'][1] != {}: job_param = job['actions'][1]['parameterDefinitions'] elif job['actions'][0] != {}: job_param = job['actions'][0]['parameterDefinitions'] else: job_param = [] return self.format_params(job_param) @botcmd(split_args_with=None) def jenkins_build(self, mess, args): """Build a Jenkins Job with the given parameters Example: !jenkins build test_project FOO:bar """ if len(args) == 0: # No Job name return 'What job would you like to build?' self.connect_to_jenkins() params = self.build_parameters(args[1:]) # Is it a parameterized job ? job = self.jenkins.get_job_info(args[0]) if job['actions'][0] == {} and job['actions'][1] == {}: self.jenkins.build_job(args[0]) else: self.jenkins.build_job(args[0], params) running_job = self.search_job(args[0]) return 'Your job should begin shortly: {0}'.format( self.format_jobs(running_job)) @botcmd(split_args_with=None) def build(self, mess, args): """Shortcut for jenkins_build""" return self.jenkins_build(mess, args) @botcmd def jenkins_unqueue(self, msg, args): """Cancel a queued job. Example !jenkins unqueue foo """ self.connect_to_jenkins() try: queue = self.jenkins.get_queue_info() job = next((job for job in queue if job['task']['name'].lower() == args.lower()), None) if job: self.jenkins.cancel_queue(job['id']) return 'Unqueued job {0}'.format(job['task']['name']) else: return 'Could not find job {0}, but found the following: {1}'.format( args, ', '.join(job['task']['name'] for job in queue)) except JenkinsException as e: return 'Oops, {0}'.format(e) @botcmd(split_args_with=None) def jenkins_createjob(self, mess, args): """Create a Jenkins Job. Example: !jenkins createjob pipeline foo [email protected]:foo/bar.git """ if len(args) < 2: # No Job type or name return 'Oops, I need a type and a name for your new job.' if args[0] not in ('pipeline', 'multibranch'): return 'I\'m sorry, I can only create `pipeline` and \ `multibranch` jobs.' self.connect_to_jenkins() try: if args[0] == 'pipeline': self.jenkins.create_job( args[1], JENKINS_JOB_TEMPLATE_PIPELINE.format(repository=args[2])) elif args[0] == 'multibranch': repository = args[2].rsplit('/', maxsplit=2)[-2:] self.jenkins.create_job( args[1], JENKINS_JOB_TEMPLATE_MULTIBRANCH.format( repo_owner=repository[0].split(':')[-1], repo_name=repository[1].strip('.git'))) except JenkinsException as e: return 'Oops, {0}'.format(e) return 'Your job has been created: {0}/job/{1}'.format( self.config['URL'], args[1]) @botcmd(split_args_with=None) def jenkins_deletejob(self, mess, args): """Delete a Jenkins Job. Example: !jenkins deletejob foo """ if len(args) < 1: # No job name return 'Oops, I need the name of the job you want me to delete.' self.connect_to_jenkins() try: self.jenkins.delete_job(args[0]) except JenkinsException as e: return 'Oops, {0}'.format(e) return 'Your job has been deleted.' @botcmd(split_args_with=None) def jenkins_enablejob(self, mess, args): """Enable a Jenkins Job. Example: !jenkins enablejob foo """ if len(args) < 1: # No job name return 'Oops, I need the name of the job you want me to enable.' self.connect_to_jenkins() try: self.jenkins.enable_job(args[0]) except JenkinsException as e: return 'Oops, {0}'.format(e) return 'Your job has been enabled.' @botcmd(split_args_with=None) def jenkins_disablejob(self, mess, args): """Disable a Jenkins Job. Example: !jenkins disablejob foo """ if len(args) < 1: # No job name return 'Oops, I need the name of the job you want me to disable.' self.connect_to_jenkins() try: self.jenkins.disable_job(args[0]) except JenkinsException as e: return 'Oops, {0}'.format(e) return 'Your job has been disabled.' @botcmd(split_args_with=None) def jenkins_createnode(self, mess, args): """Create a Jenkins Node with a JNLP Launcher with optionnal labels. Example: !jenkins createnode runner-foo-laptop /home/foo # without labels Example: !jenkins createnode runner-bar-laptop /home/bar linux docker # with labels """ if len(args) < 1: # No node name return 'Oops, I need a name and a working dir for your new node.' self.connect_to_jenkins() try: self.jenkins.create_node( name=args[0], remoteFS=args[1], labels=' '.join(args[2:]), exclusive=True, launcher=LAUNCHER_JNLP) except JenkinsException as e: return 'Oops, {0}'.format(e) return 'Your node has been created: {0}/computer/{1}'.format( self.config['URL'], args[0]) @botcmd(split_args_with=None) def jenkins_deletenode(self, mess, args): """Delete a Jenkins Node. Example: !jenkins deletenode runner-foo-laptop """ if len(args) < 1: # No node name return 'Oops, I need the name of the node you want me to delete.' self.connect_to_jenkins() try: self.jenkins.delete_node(args[0]) except JenkinsException as e: return 'Oops, {0}'.format(e) return 'Your node has been deleted.' @botcmd(split_args_with=None) def jenkins_enablenode(self, mess, args): """Enable a Jenkins Node. Example: !jenkins enablenode runner-foo-laptop """ if len(args) < 1: # No node name return 'Oops, I need the name of the node you want me to enable.' self.connect_to_jenkins() try: self.jenkins.enable_node(args[0]) except JenkinsException as e: return 'Oops, {0}'.format(e) return 'Your node has been enabled.' @botcmd(split_args_with=None) def jenkins_disablenode(self, mess, args): """Disable a Jenkins Node. Example: !jenkins disablenode runner-foo-laptop """ if len(args) < 1: # No node name return 'Oops, I need the name of the node you want me to disable.' self.connect_to_jenkins() try: self.jenkins.disable_node(args[0]) except JenkinsException as e: return 'Oops, {0}'.format(e) return 'Your node has been disabled.' def search_job(self, search_term): self.log.debug('Querying Jenkins for job "{0}"'.format(search_term)) return [job for job in self.jenkins.get_jobs(folder_depth=None) if search_term.lower() == job['fullname'].lower()] def format_running_jobs(self, jobs): if len(jobs) == 0: return 'No running jobs.' jobs_info = [self.jenkins.get_job_info(job['name']) for job in jobs] return '\n\n'.join(['%s (%s)\n%s' % ( job['name'], job['lastBuild']['url'], job['healthReport'][0]['description']) for job in jobs_info]).strip() @staticmethod def format_jobs(jobs): if len(jobs) == 0: return 'No jobs found.' max_length = max([len(job['fullname']) for job in jobs]) return '\n'.join( ['%s (%s)' % (job['fullname'].ljust(max_length), job['url']) for job in jobs]).strip() @staticmethod def format_params(job): """Format job parameters.""" if len(job) == 0: return 'This job is not parameterized.' PARAM_TEMPLATE = Template("""{% for p in params %}Type: {{p.type}} Description: {{p.description}} Default Value: {{p.defaultParameterValue.value}} Parameter Name: {{p.name}} {% endfor %}""") return PARAM_TEMPLATE.render({'params': job}) @staticmethod def format_notification(body): body['fullname'] = body.get('fullname', body['name']) NOTIFICATION_TEMPLATE = Template("""Build #{{build.number}} \ {{build.status}} for Job {{fullname}} ({{build.full_url}}) {% if build.scm %}Based on {{build.scm.url}}/commit/{{build.scm.commit}} \ ({{build.scm.branch}}){% endif %}""") return NOTIFICATION_TEMPLATE.render(body) @staticmethod def build_parameters(params): if len(params) > 0: return {param.split(':')[0]: param.split(':')[1] for param in params} return {'': ''}
class JenkinsBot(BotPlugin): """Basic Err integration with Jenkins CI""" min_err_version = '1.2.1' # max_err_version = '4.0.3' def get_configuration_template(self): return CONFIG_TEMPLATE def configure(self, configuration): if configuration is not None and configuration != {}: config = dict(chain(CONFIG_TEMPLATE.items(), configuration.items())) else: config = CONFIG_TEMPLATE super(JenkinsBot, self).configure(config) return def check_configuration(self, configuration): self.log.debug(configuration) for c, v in configuration.items(): if c == 'URL': if not validators.url(v): raise ValidationException( 'JENKINS_URL is not a well formed URL') elif c in ['USERNAME', 'PASSWORD', 'RECEIVE_NOTIFICATION']: if len(v) == 0 or not isinstance(v, str): raise ValidationException( "{} is a required string config setting".format(c)) elif c in ['CHATROOMS_NOTIFICATION']: if not isinstance(v, tuple): raise ValidationException( "{} should be of type tuple".format(c)) return def connect_to_jenkins(self): """Connect to a Jenkins instance using configuration.""" self.log.debug('Connecting to Jenkins ({0})'.format( self.config['URL'])) self.jenkins = Jenkins(url=self.config['URL'], username=self.config['USERNAME'], password=self.config['PASSWORD']) return def broadcast(self, mess): """Shortcut to broadcast a message to all elligible chatrooms.""" chatrooms = (self.config['CHATROOMS_NOTIFICATION'] if self.config['CHATROOMS_NOTIFICATION'] else self.bot_config.CHATROOM_PRESENCE) for room in chatrooms: self.send(self.build_identifier(room), mess) return @webhook(r'/jenkins/notification') def handle_notification(self, incoming_request): if not self.config['RECEIVE_NOTIFICATION']: return 'Notification handling is disabled.' self.log.debug(repr(incoming_request)) self.broadcast(self.format_notification(incoming_request)) return @botcmd def jenkins_list(self, mess, args): """List all jobs, optionally filter them using a search term.""" self.connect_to_jenkins() return self.format_jobs([ job for job in self.jenkins.get_jobs(folder_depth=None) if args.lower() in job['fullname'].lower() ]) @botcmd def jenkins_running(self, mess, args): """List all running jobs.""" self.connect_to_jenkins() jobs = [ job for job in self.jenkins.get_jobs() if 'anime' in job['color'] ] return self.format_running_jobs(jobs) @botcmd(split_args_with=None) def jenkins_param(self, mess, args): """List Parameters for a given job.""" if len(args) == 0: return 'What Job would you like the parameters for?' self.connect_to_jenkins() job = self.jenkins.get_job_info(args[0]) if job['actions'][1] != {}: job_param = job['actions'][1]['parameterDefinitions'] elif job['actions'][0] != {}: job_param = job['actions'][0]['parameterDefinitions'] else: job_param = [] return self.format_params(job_param) @botcmd(split_args_with=None) def jenkins_build(self, mess, args): """Build a Jenkins Job with the given parameters Example: !jenkins build test_project FOO:bar """ if len(args) == 0: # No Job name return 'What job would you like to build?' self.connect_to_jenkins() params = self.build_parameters(args[1:]) # Is it a parameterized job ? job = self.jenkins.get_job_info(args[0]) if job['actions'][0] == {} and job['actions'][1] == {}: self.jenkins.build_job(args[0]) else: self.jenkins.build_job(args[0], params) running_job = self.search_job(args[0]) return 'Your job should begin shortly: {0}'.format( self.format_jobs(running_job)) @botcmd(split_args_with=None) def build(self, mess, args): """Shortcut for jenkins_build""" return self.jenkins_build(mess, args) @botcmd def jenkins_unqueue(self, msg, args): """Cancel a queued job. Example !jenkins unqueue foo """ self.connect_to_jenkins() try: queue = self.jenkins.get_queue_info() job = next((job for job in queue if job['task']['name'].lower() == args.lower()), None) if job: self.jenkins.cancel_queue(job['id']) return 'Unqueued job {0}'.format(job['task']['name']) else: return 'Could not find job {0}, but found the following: {1}'.format( args, ', '.join(job['task']['name'] for job in queue)) except JenkinsException as e: return 'Oops, {0}'.format(e) @botcmd(split_args_with=None) def jenkins_createjob(self, mess, args): """Create a Jenkins Job. Example: !jenkins createjob pipeline foo [email protected]:foo/bar.git """ if len(args) < 2: # No Job type or name return 'Oops, I need a type and a name for your new job.' if args[0] not in ('pipeline', 'multibranch'): return 'I\'m sorry, I can only create `pipeline` and \ `multibranch` jobs.' self.connect_to_jenkins() try: if args[0] == 'pipeline': self.jenkins.create_job( args[1], JENKINS_JOB_TEMPLATE_PIPELINE.format(repository=args[2])) elif args[0] == 'multibranch': repository = args[2].rsplit('/', maxsplit=2)[-2:] self.jenkins.create_job( args[1], JENKINS_JOB_TEMPLATE_MULTIBRANCH.format( repo_owner=repository[0].split(':')[-1], repo_name=repository[1].strip('.git'))) except JenkinsException as e: return 'Oops, {0}'.format(e) return 'Your job has been created: {0}/job/{1}'.format( self.config['URL'], args[1]) @botcmd(split_args_with=None) def jenkins_deletejob(self, mess, args): """Delete a Jenkins Job. Example: !jenkins deletejob foo """ if len(args) < 1: # No job name return 'Oops, I need the name of the job you want me to delete.' self.connect_to_jenkins() try: self.jenkins.delete_job(args[0]) except JenkinsException as e: return 'Oops, {0}'.format(e) return 'Your job has been deleted.' @botcmd(split_args_with=None) def jenkins_enablejob(self, mess, args): """Enable a Jenkins Job. Example: !jenkins enablejob foo """ if len(args) < 1: # No job name return 'Oops, I need the name of the job you want me to enable.' self.connect_to_jenkins() try: self.jenkins.enable_job(args[0]) except JenkinsException as e: return 'Oops, {0}'.format(e) return 'Your job has been enabled.' @botcmd(split_args_with=None) def jenkins_disablejob(self, mess, args): """Disable a Jenkins Job. Example: !jenkins disablejob foo """ if len(args) < 1: # No job name return 'Oops, I need the name of the job you want me to disable.' self.connect_to_jenkins() try: self.jenkins.disable_job(args[0]) except JenkinsException as e: return 'Oops, {0}'.format(e) return 'Your job has been disabled.' @botcmd(split_args_with=None) def jenkins_createnode(self, mess, args): """Create a Jenkins Node with a JNLP Launcher with optionnal labels. Example: !jenkins createnode runner-foo-laptop /home/foo # without labels Example: !jenkins createnode runner-bar-laptop /home/bar linux docker # with labels """ if len(args) < 1: # No node name return 'Oops, I need a name and a working dir for your new node.' self.connect_to_jenkins() try: self.jenkins.create_node(name=args[0], remoteFS=args[1], labels=' '.join(args[2:]), exclusive=True, launcher=LAUNCHER_JNLP) except JenkinsException as e: return 'Oops, {0}'.format(e) return 'Your node has been created: {0}/computer/{1}'.format( self.config['URL'], args[0]) @botcmd(split_args_with=None) def jenkins_deletenode(self, mess, args): """Delete a Jenkins Node. Example: !jenkins deletenode runner-foo-laptop """ if len(args) < 1: # No node name return 'Oops, I need the name of the node you want me to delete.' self.connect_to_jenkins() try: self.jenkins.delete_node(args[0]) except JenkinsException as e: return 'Oops, {0}'.format(e) return 'Your node has been deleted.' @botcmd(split_args_with=None) def jenkins_enablenode(self, mess, args): """Enable a Jenkins Node. Example: !jenkins enablenode runner-foo-laptop """ if len(args) < 1: # No node name return 'Oops, I need the name of the node you want me to enable.' self.connect_to_jenkins() try: self.jenkins.enable_node(args[0]) except JenkinsException as e: return 'Oops, {0}'.format(e) return 'Your node has been enabled.' @botcmd(split_args_with=None) def jenkins_disablenode(self, mess, args): """Disable a Jenkins Node. Example: !jenkins disablenode runner-foo-laptop """ if len(args) < 1: # No node name return 'Oops, I need the name of the node you want me to disable.' self.connect_to_jenkins() try: self.jenkins.disable_node(args[0]) except JenkinsException as e: return 'Oops, {0}'.format(e) return 'Your node has been disabled.' def search_job(self, search_term): self.log.debug('Querying Jenkins for job "{0}"'.format(search_term)) return [ job for job in self.jenkins.get_jobs(folder_depth=None) if search_term.lower() == job['fullname'].lower() ] def format_running_jobs(self, jobs): if len(jobs) == 0: return 'No running jobs.' jobs_info = [self.jenkins.get_job_info(job['name']) for job in jobs] return '\n\n'.join([ '%s (%s)\n%s' % (job['name'], job['lastBuild']['url'], job['healthReport'][0]['description']) for job in jobs_info ]).strip() @staticmethod def format_jobs(jobs): if len(jobs) == 0: return 'No jobs found.' max_length = max([len(job['fullname']) for job in jobs]) return '\n'.join([ '%s (%s)' % (job['fullname'].ljust(max_length), job['url']) for job in jobs ]).strip() @staticmethod def format_params(job): """Format job parameters.""" if len(job) == 0: return 'This job is not parameterized.' PARAM_TEMPLATE = Template("""{% for p in params %}Type: {{p.type}} Description: {{p.description}} Default Value: {{p.defaultParameterValue.value}} Parameter Name: {{p.name}} {% endfor %}""") return PARAM_TEMPLATE.render({'params': job}) @staticmethod def format_notification(body): body['fullname'] = body.get('fullname', body['name']) NOTIFICATION_TEMPLATE = Template("""Build #{{build.number}} \ {{build.status}} for Job {{fullname}} ({{build.full_url}}) {% if build.scm %}Based on {{build.scm.url}}/commit/{{build.scm.commit}} \ ({{build.scm.branch}}){% endif %}""") return NOTIFICATION_TEMPLATE.render(body) @staticmethod def build_parameters(params): if len(params) > 0: return { param.split(':')[0]: param.split(':')[1] for param in params } return {'': ''}
class JenkinsNotifier(BotPlugin): """JenkinsBot is an Err plugin to manage Jenkins CI jobs from your chat platform like Slack.""" status = {'blue':'SUCCESS', 'blue_anime':'IN PROGRESS','red': 'FAILED', 'red_anime': 'IN PROGRESS', 'disabled': 'DISABLED', 'aborted':'ABORTED', 'notbuilt': 'NOTBUILT', 'yellow': 'UNSTABLE'} failedjobsstring = " " def __init__(self, bot, name): self.jenkins = Jenkins(JENKINS_URL, JENKINS_USERNAME, JENKINS_TOKEN) super().__init__(bot, name) @botcmd(split_args_with=None) def jn_build(self, msg, args): """Build the job specified by jobName. You can add params!""" # Params are passed like "key1=value1 key2=value2" params = {} #try: # for arg in args[1:]: # params[arg.split('=', 1)[0]] = arg.split('=', 1)[1] #except IndexError: # return "I don'G like that params! Try with this format: key1=value1 key2=value2..." jobName = ''.join([args[0],' ',args[1]]) #TODO handle jobname with spaces in it, space is cosidered argument splitter try: self.jenkins.build_job(jobName, params) except NotFoundException: return ' '.join(["Sorry, I can't find the job. Typo maybe?"," ARGS=",jobName]) return ' '.join(["The job", args[0].strip(), "has been sent to the queue to be built."]) @botcmd def jn_cancel(self, msg, args): """Cancel a job in the queue by jobId.""" try: self.jenkins.cancel_queue(args.strip()) except NotFoundException: return "Sorry, I can't find the job. Maybe the ID does not exist." return "Job canceled from the queue." @botcmd def jn_list(self, msg, args): """List Jenkins jobs. You can filter with strings.""" self.send(msg.to, "I'm getting the jobs list from Jenkins...") search_term = args.strip().lower() jobs = [job for job in self.jenkins.get_jobs() if search_term.lower() in job['name'].lower()] return self.format_jobs(jobs) @botcmd def jn_status(self, msg, args): """List Jenkins jobs with their current status.""" self.send(msg.to, "I'm getting the jobs with status Jenkins...") search_term = args.strip().lower() jobs = [job for job in self.jenkins.get_jobs() if search_term.lower() in job['fullname'].lower()] return self.format_job_status(jobs) @botcmd def jn_describe(self, msg, args): """Describe the job specified by jobName.""" try: job = self.jenkins.get_job_info(args.strip()) except NotFoundException: return "Sorry, I can't find the job. Typo maybe?" return ''.join([ 'Name: ', job['name'], '\n', 'URL: ', job['url'], '\n', 'Description: ', 'None' if job['description'] is None else job['description'], '\n', 'Next Build Number: ', str('None' if job['nextBuildNumber'] is None else job['nextBuildNumber']), '\n', 'Last Successful Build Number: ', str('None' if job['lastBuild'] is None else job['lastBuild']['number']), '\n', 'Last Successful Build URL: ', 'None' if job['lastBuild'] is None else job['lastBuild']['url'], '\n' ]) @botcmd def jn_running(self, msg, args): """List running jobs.""" self.send(msg.to, "I will ask for the current running builds list!") jobs = self.jenkins.get_running_builds() return self.format_running_jobs(jobs) @botcmd(split_args_with=None) def jn_stop(self, msg, args): """Stop the building job specified by jobName and jobNumber.""" try: int(args[1].strip()) except ValueError: return "You have to specify the jobNumber: \"!jenkins stop <jobName> <jobNumber>" try: self.jenkins.stop_build(args[0].strip(), int(args[1].strip())) except NotFoundException: return "Sorry, I can't find the job. Typo maybe?" return ' '.join(["The job", args[0].strip(), "has been stopped."]) @botcmd def jn_queue(self, msg, args): """List jobs in queue.""" self.send(msg.to, "Getting the job queue...") jobs = self.jenkins.get_queue_info() return self.format_queue_jobs(jobs) @botcmd def jn_msgtimer(self, msg, args): """Sends messages at fix intervals.""" yield "Starting timer" self.start_poller(5, self.my_callback) self.send(msg.to, "Boo! Bet you weren't expecting me, were you?", ) def my_callback(self): self.log.info('I am called every 5sec') self.send(self.build_identifier("#errbottestchannel"), "I am called every 5sec", ) @botcmd def jn_failed(self, msg, args): """List Jenkins jobs with failed status.""" self.send(msg.to, "I'm getting the failed jobs ...") failedJobs = [] search_term = args.strip().lower() jobs = [job for job in self.jenkins.get_jobs() if search_term.lower() in job['fullname'].lower()] for job in jobs: if self.status[job['color']] == 'FAILED': failedJobs.append(job) return self.format_job_status(failedJobs) # SUPPORT FUNCTIONS START HERE def format_jobs(self, jobs): """Format jobs list""" if len(jobs) == 0: return "I haven't found any job." max_length = max([len(job['name']) for job in jobs]) return '\n'.join(['%s (%s)' % (job['name'].ljust(max_length), job['url']) for job in jobs]).strip() def format_queue_jobs(self, jobs): """Format queue jobs list""" if len(jobs) == 0: return "It seems that there is not jobs in queue." return '\n'.join(['%s - %s (%s)' % (str(job['id']), job['task']['name'], job['task']['url']) for job in jobs]).strip() def format_running_jobs(self, jobs): """Format running jobs list""" if len(jobs) == 0: return "There is no running jobs!" return '\n'.join(['%s - %s (%s) - %s' % (str(job['number']), job['name'], job['url'], job['executor']) for job in jobs]).strip() @botcmd def jn_queue(self, msg, args): """List jobs in queue.""" self.send(msg.to, "Getting the job queue...") jobs = self.jenkins.get_queue_info() return self.format_queue_jobs(jobs) def format_jobs(self, jobs): """Format jobs list""" if len(jobs) == 0: return "I haven't found any job." max_length = max([len(job['name']) for job in jobs]) return '\n'.join(['%s (%s)' % (job['name'].ljust(max_length), job['url']) for job in jobs]).strip() def format_queue_jobs(self, jobs): """Format queue jobs list""" if len(jobs) == 0: return "It seems that there is not jobs in queue." return '\n'.join(['%s - %s (%s)' % (str(job['id']), job['task']['name'], job['task']['url']) for job in jobs]).strip() def format_running_jobs(self, jobs): """Format running jobs list""" if len(jobs) == 0: return "There is no running jobs!" return '\n'.join(['%s - %s (%s) - %s' % (str(job['number']), job['name'], job['url'], job['executor']) for job in jobs]).strip() def format_job_status(self, jobs): """Format job status""" if len(jobs) == 0: return "there are no jobs to return" return '\n'.join(['%s (%s)' % (job['fullname'], self.status[job['color']]) for job in jobs]).strip()
class JenkinsBot(BotPlugin): """JenkinsBot is an Err plugin to manage Jenkins CI jobs from your chat platform like Slack.""" def __init__(self, bot): self.jenkins = Jenkins(JENKINS_URL, JENKINS_USERNAME, JENKINS_PASSWORD) super().__init__(bot) @botcmd(split_args_with=None) def jenkins_build(self, msg, args): """Build the job specified by jobName. You can add params!""" # Params are passed like "key1=value1 key2=value2" params = {} try: for arg in args[1:]: params[arg.split('=', 1)[0]] = arg.split('=', 1)[1] except IndexError: return "I don't like that params! Try with this format: key1=value1 key2=value2..." try: self.jenkins.build_job(args[0].strip(), params) except NotFoundException: return "Sorry, I can't find the job. Typo maybe?" return ' '.join([ "The job", args[0].strip(), "has been sent to the queue to be built." ]) @botcmd def jenkins_cancel(self, msg, args): """Cancel a job in the queue by jobId.""" try: self.jenkins.cancel_queue(args.strip()) except NotFoundException: return "Sorry, I can't find the job. Maybe the ID does not exist." return "Job canceled from the queue." @botcmd def jenkins_list(self, msg, args): """List Jenkins jobs. You can filter with strings.""" self.send(msg.to, "I'm getting the jobs list from Jenkins...") search_term = args.strip().lower() jobs = [ job for job in self.jenkins.get_jobs() if search_term.lower() in job['name'].lower() ] return self.format_jobs(jobs) @botcmd def jenkins_describe(self, msg, args): """Describe the job specified by jobName.""" try: job = self.jenkins.get_job_info(args.strip()) except NotFoundException: return "Sorry, I can't find the job. Typo maybe?" return ''.join([ 'Name: ', job['name'], '\n', 'URL: ', job['url'], '\n', 'Description: ', 'None' if job['description'] is None else job['description'], '\n', 'Next Build Number: ', str('None' if job['nextBuildNumber'] is None else job['nextBuildNumber']), '\n', 'Last Successful Build Number: ', str('None' if job['lastBuild'] is None else job['lastBuild'] ['number']), '\n', 'Last Successful Build URL: ', 'None' if job['lastBuild'] is None else job['lastBuild']['url'], '\n' ]) @botcmd def jenkins_running(self, msg, args): """List running jobs.""" self.send(msg.to, "I will ask for the current running builds list!") jobs = self.jenkins.get_running_builds() return self.format_running_jobs(jobs) @botcmd(split_args_with=None) def jenkins_stop(self, msg, args): """Stop the building job specified by jobName and jobNumber.""" try: int(args[1].strip()) except ValueError: return "You have to specify the jobNumber: \"!jenkins stop <jobName> <jobNumber>" try: self.jenkins.stop_build(args[0].strip(), int(args[1].strip())) except NotFoundException: return "Sorry, I can't find the job. Typo maybe?" return ' '.join(["The job", args[0].strip(), "has been stopped."]) @botcmd def jenkins_queue(self, msg, args): """List jobs in queue.""" self.send(msg.to, "Getting the job queue...") jobs = self.jenkins.get_queue_info() return self.format_queue_jobs(jobs) def format_jobs(self, jobs): """Format jobs list""" if len(jobs) == 0: return "I haven't found any job." max_length = max([len(job['name']) for job in jobs]) return '\n'.join([ '%s (%s)' % (job['name'].ljust(max_length), job['url']) for job in jobs ]).strip() def format_queue_jobs(self, jobs): """Format queue jobs list""" if len(jobs) == 0: return "It seems that there is not jobs in queue." return '\n'.join([ '%s - %s (%s)' % (str(job['id']), job['task']['name'], job['task']['url']) for job in jobs ]).strip() def format_running_jobs(self, jobs): """Format running jobs list""" if len(jobs) == 0: return "There is no running jobs!" return '\n'.join([ '%s - %s (%s) - %s' % (str(job['number']), job['name'], job['url'], job['executor']) for job in jobs ]).strip()