Getting the right Jenkins build number using Python
One of the jobs in our CI pipeline is responsible for compiling, building and packing the code. The artifacts of the job is a directory on our storage with the build number and all the artifacts that are related to this build number.
For example: //storage/build_1000, //storage/build_1001 and etc.
There are other jobs that are triggered by a scheduler that takes the artifacts of the latest job and runs some tests on it.
The build job can run on multiple git branches so we wanted to create a mechanism that will allow the “testing” job take the latest build of a specific branch and not the latest job that was successful.
Unfortunately, I didn’t find a way in Jenkins to do that so I used some python code. Jenkins has a nice RESTful API that is wrapped in an easy to use python library:
1 | pip install python-jenkins |
For convenience, each one of the build jobs has the branch name as part of the display name, e.g. “#1000 (origin/master)”
The script looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | #! /usr/bin/python import re import sys import jenkins import logging from optparse import OptionParser logger = logging.getLogger() hdlr = logging.StreamHandler(sys.stderr) formatter = logging.Formatter('%(asctime)s %(levelname)s\t%(message)s') hdlr.setFormatter(formatter) logger.addHandler(hdlr) logger.setLevel(logging.DEBUG) def connect(server, port, username, password): logger.info("Connecting to Jenkins at %s:%d (user: %s)" % (server, port, username)) try: return jenkins.Jenkins("http://%s:%d" % (server, port), username=username, password=password) except Exception as e: logger.error("Connection failed: %s" % e.message) return None def check_build(conn, number): logger.info("Checking build number %d" % number) info = conn.get_build_info(name=options.job, number=number) if info["building"]: logger.info("Build %d is still in progress - skipping" % number) return False if info["result"] != "SUCCESS": logger.info("Build %d result is %s - skipping" % (number, info["result"])) return False branch = re.findall("\((.*)\)", info["displayName"])[0] if branch != options.branch: logger.info("Build %d is on branch %s - skipping" % (number, branch)) return False logger.info("Found build %d" % number) print("%d" % number) return True if __name__ == "__main__": parser = OptionParser() parser.add_option("", "--server", dest="server", default="10.0.0.3", help="Jenkins server ip address") parser.add_option("", "--port", dest="port", default=8080, type="int", help="Jenkins server port") parser.add_option("", "--username", dest="username", default="automation", help="Jenkins user name") parser.add_option("", "--password", dest="password", default="password", help="Jenkins password") parser.add_option("-j", "--job", dest="job", default="", type="string", help="Jenkins job name") parser.add_option("-b", "--branch", dest="branch", default="", type="string", help="Git branch for the job") parser.add_option("-n", "--number", dest="number", type="int", help="Build number as a hint") (options, args) = parser.parse_args() conn = connect(options.server, options.port, options.username, options.password) if conn == None: sys.exit(2) if options.number: logger.info("Checking given build number %d" % options.number) found = check_build(conn, options.number) if found: sys.exit(0) else: logger.warn("The given build is not in the right branch - not using it.") logger.info("Looking for last successful job %s on branch %s" % (options.job, options.branch)) logger.info("Getting builds for job %s" % options.job) job = conn.get_job_info(options.job) builds = job["builds"] for b in builds: found = check_build(conn, b["number"]) if found: sys.exit(0) logger.error("Did not found any suitable build.") sys.exit(1) |
The execution is simple as well:
1 | ./get_latest_successful_build.py --job "Builder" --branch "origin/master" |
We can give the latest successful build as an hint and the script will check it first but it’s an optimization and not mandatory.
Run example from our CI lab (censored a bit):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | + ./tools/get_latest_successful_build.py -j Builder -b origin/master -n 14151 2017-06-27 12:30:10,417 INFO Connecting to Jenkins at 10.0.0.3:8080 (user: automation) 2017-06-27 12:30:10,417 INFO Checking given build number 14151 2017-06-27 12:30:10,418 INFO Checking build number 14151 2017-06-27 12:30:10,687 INFO Build 14151 is on branch origin/feature1 - skipping 2017-06-27 12:30:10,688 WARNING The given build is not in the right branch - not using it. 2017-06-27 12:30:10,688 INFO Looking for last successful job Builder on branch origin/master 2017-06-27 12:30:10,688 INFO Getting builds for job Builder 2017-06-27 12:30:10,822 INFO Checking build number 14154 2017-06-27 12:30:10,978 INFO Build 14154 is still in progress - skipping 2017-06-27 12:30:10,978 INFO Checking build number 14153 2017-06-27 12:30:11,132 INFO Build 14153 is still in progress - skipping 2017-06-27 12:30:11,132 INFO Checking build number 14152 2017-06-27 12:30:11,335 INFO Found build 14152 |
Feel free using and modifying this script.
It can be downloaded from my github repository.
– Alexander