#!/usr/bin/env python3 # -*- coding: utf-8 -*- # vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 ############################################################################### # OpenLP - Open Source Lyrics Projection # # --------------------------------------------------------------------------- # # Copyright (c) 2008-2017 OpenLP Developers # # --------------------------------------------------------------------------- # # This program is free software; you can redistribute it and/or modify it # # under the terms of the GNU General Public License as published by the Free # # Software Foundation; version 2 of the License. # # # # This program is distributed in the hope that it will be useful, but WITHOUT # # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # # more details. # # # # You should have received a copy of the GNU General Public License along # # with this program; if not, write to the Free Software Foundation, Inc., 59 # # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### """ This script helps to trigger builds of branches. To use it you have to install the jenkins-webapi package: pip3 install jenkins-webapi You probably want to create an alias. Add this to your ~/.bashrc file and then logout and login (to apply the alias): alias ci="python3 ./scripts/jenkins_script.py TOKEN" You can look up the token in the Branch-01-Pull job configuration or ask in IRC. """ import os import re import time from argparse import ArgumentParser from subprocess import Popen, PIPE from jenkins import Jenkins JENKINS_URL = 'https://ci.openlp.io/' REPO_REGEX = r'(.*/+)(~.*)' class OpenLPJobs(object): """ This class holds any jobs we have on jenkins and we actually need in this script. """ Branch_Pull = 'Branch-01-Pull' Branch_Functional = 'Branch-02-Functional-Tests' Branch_Interface = 'Branch-03-Interface-Tests' Branch_PEP = 'Branch-04a-Code_Analysis' Branch_Coverage = 'Branch-04b-Test_Coverage' Branch_Pylint = 'Branch-04c-Code_Analysis2' Branch_AppVeyor = 'Branch-05-AppVeyor-Tests' Jobs = [Branch_Pull, Branch_Functional, Branch_Interface, Branch_PEP, Branch_Coverage, Branch_Pylint, Branch_AppVeyor] class Colour(object): """ This class holds values which can be used to print coloured text. """ RED_START = '\033[1;31m' RED_END = '\033[1;m' GREEN_START = '\033[1;32m' GREEN_END = '\033[1;m' class JenkinsTrigger(object): """ A class to trigger builds on Jenkins and print the results. :param token: The token we need to trigger the build. If you do not have this token, ask in IRC. """ def __init__(self, username, password, can_use_colour): """ Create the JenkinsTrigger instance. """ self.build_numbers = {} self.can_use_colour = can_use_colour and not os.name.startswith('nt') self.repo_name = get_repo_name() self.server = Jenkins(JENKINS_URL, username=username, password=password) def fetch_build_numbers(self): """ Get the next build number from all the jobs """ for job_name in OpenLPJobs.Jobs: job_info = self.server.get_job_info(job_name) self.build_number[job_name] = job_info['nextBuildNumber'] def trigger_build(self): """ Ask our jenkins server to build the "Branch-01-Pull" job. """ bzr = Popen(('bzr', 'whoami'), stdout=PIPE, stderr=PIPE) raw_output, error = bzr.communicate() # We just want the name (not the email). name = ' '.join(raw_output.decode().split()[:-1]) cause = 'Build triggered by %s (%s)' % (name, self.repo_name) self.server.build_job(OpenLPJobs.Branch_Pull, {'BRANCH_NAME': self.repo_name, 'cause': cause}) def print_output(self): """ Print the status information of the build triggered. """ print('Add this to your merge proposal:') print('--------------------------------') bzr = Popen(('bzr', 'revno'), stdout=PIPE, stderr=PIPE) raw_output, error = bzr.communicate() revno = raw_output.decode().strip() print('%s (revision %s)' % (get_repo_name(), revno)) for job in OpenLPJobs.Jobs: if not self.__print_build_info(job): print('Stopping after failure') break def open_browser(self): """ Opens the browser. """ url = self.jenkins_instance.job(OpenLPJobs.Branch_Pull).info['url'] # Open the url Popen(('xdg-open', url), stderr=PIPE) def _get_build_info(self, job_name, build_number): """ Get the build info from the server. This method will check the queue and wait for the build. """ queue_info = self.server.get_queue_info() tries = 0 while queue_info and tries < 50: tries += 1 time.sleep(0.5) queue_info = self.server.get_queue_info() if tries >= 50: raise Exception('Build has not started yet, it may be stuck in the queue.') return self.server.get_build_info(job_name, build_number) def __print_build_info(self, job_name): """ This helper method prints the job information of the given ``job_name`` :param job_name: The name of the job we want the information from. For example *Branch-01-Pull*. Use the class variables from the :class:`OpenLPJobs` class. """ build_info = self._get_build_info(job_name, self.build_number[job_name]) print('{} ... '.format(build_info['url']), end='', flush=True) is_success = False while build_info['building'] is True: time.sleep(0.5) build_info = self.server.get_build_info(job_name, self.build_number[job_name]) result_string = build_info['result'] if self.can_use_colour and result_string == 'SUCCESS': # Make 'SUCCESS' green. result_string = '%s%s%s' % (Colour.GREEN_START, result_string, Colour.GREEN_END) is_success = True elif self.can_use_colour: # Make 'FAILURE' red. result_string = '%s%s%s' % (Colour.RED_START, result_string, Colour.RED_END) print('[{}]'.format(result_string)) return is_success def get_repo_name(): """ This returns the name of branch of the working directory. For example it returns *lp:~googol/openlp/render*. """ # Run the bzr command. bzr = Popen(('bzr', 'info'), stdout=PIPE, stderr=PIPE) raw_output, error = bzr.communicate() # Detect any errors if error: print('This is not a branch.') return # Clean the output. raw_output = raw_output.decode() output_list = list(map(str.strip, raw_output.split('\n'))) # Determine the branch's name repo_name = '' for line in output_list: # Check if it is api branch. if 'push branch' in line: match = re.match(REPO_REGEX, line) if match: repo_name = 'lp:%s' % match.group(2) break elif 'checkout of branch' in line: match = re.match(REPO_REGEX, line) if match: repo_name = 'lp:%s' % match.group(2) break return repo_name.strip('/') def main(): """ Run the script """ parser = ArgumentParser() parser.add_argument('-d', '--disable-output', action='store_false', default=True, help='Disable output') parser.add_argument('-b', '--open-browser', action='store_true', default=False, help='Opens the jenkins page in your browser') parser.add_argument('-c', '--enable-colour', action='store_false', default=True, help='Enable coloured output. Disabled on Windows') parser.add_argument('-u', '--username', required=True, help='Your Jenkins username') parser.add_argument('-p', '--password', required=True, help='Your Jenkins password or personal token') args = parser.parse_args() if not get_repo_name(): print('Not a branch. Have you pushed it to launchpad? Did you cd to the branch?') return jenkins_trigger = JenkinsTrigger(username=args.username, password=args.password) jenkins_trigger.trigger_build() # Open the browser before printing the output. if args.open_browser: jenkins_trigger.open_browser() if not args.disable_output: jenkins_trigger.print_output() if __name__ == '__main__': main()