A Jenkins meta-plugin for defining a build in python sourced from version control
Basic motivation: A build definition sourced from version control
This plugin allows you to define your Jenkins build using a python script sourced from your build job's workspace, rather than configured using Jenkins' web UI. The plugin injects the Jenkins build context into the python script to allow the python script to communicate with Jenkins and perform build actions.
It's hard to describe how cool this is.
-
Backed up and versioned
Your build definition is as well backed up as is your source code--and versioned to boot!
-
Empowering to Jenkins consumers and admins
You can lock down job configuration while still empowering developers to modify, for example, which files get archived, where test reports are gathered from, and that all from code changes without ever having to login to Jenkins!
-
Lightweight templating
This does not get you all the way to a truly "templated" build, but it does get much of the way there, since it can get you down to one build step that can be the same everywhere: "run the jenkins-build.py thing".
You no longer have to have slightly different job configurations per branch. If there is a new place for test reports or a new artifact to archive, just update the python build script on that branch!
Enough talk! Let's seen an example!
from pyjenkins import *
class TheBuild(PyJenkinsBuild):
def run(self):
# read a remote file
version = read_remote_file('the-version.txt')
# set the display name for this build
build.setDisplayName(version)
# execute the build
built = False
if is_windows():
built = execute(['cmd', '/c', 'build.bat'])
else:
built = execute(['bash', 'build.sh'])
# report results always
report_tests( '**/test-reports/*-TEST.xml' )
if not built:
logger.println('Whoops. It broked.')
return Result.FAILURE
# archive artifacts on build success
if is_windows():
# fails if artifact is not present
archive_artifacts( 'build/foo.exe' )
# fails if not one of the patterns is not present
archive_artifacts( 'build/setup.msi,foo/setup.exe' )
else:
# fails if artifact is not present
archive_artifacts( 'build/foo' )
# fails if not one of the patterns is not present
archive_artifacts( 'build/setup.rpm,build/setup.deb' )
# return success
return Result.SUCCESS
register_pybuild( TheBuild() )
All you need to tell jenkins is:
One of the downsides of using a scripting language (i.e. python) to describe the build as opposed to something more declarative (like XML) is that it makes it appear that the python script is actually running on the remote node.
It is not. The plugin runs on the master.
It took a bit of a mindshift for me to get at first. Nevertheless, it is still quite powerful to have build logic expressed in python. This world already has far too much XML in it anyway, and I didn't want to add to the mess. You'll just have to remember to use the Jenkins API to run commands, read remote files, etc., rather than trying to do it directly.
The full PyJenkins API is available in pyjenkins.py.
Step 1: Create a class that implements the PyJenkinsBuild interface
Step 2: Make sure the run
method returns an instance of Result.
(This class is made available in python as pyjenkins.Result
.)
Step 3: Call pyjenkins.register_pybuild()
with an instance of this class.
register_pybuild
: Registers your python class with the Java plugin so it knows what to run
report_tests
: Takes a file pattern as an argument (e.g. report_tests('test-reports/\*\*/\*-TEST.xml')
)
and runs all these files through the JUnit XML report parser.
archive_artifacts
: Takes a file pattern of build artifacts that should be archived by Jenkins.
This will fail the build if the artifact is not present. If a comma-separated
list is provided, as in archive_artifacts('\*.rpm,\*.deb')
, it will only fail if none of the
patterns match.
execute
: Takes an array of strings -- the command to execute along with
its arguments. Example: execute(['bash', 'echo', 'hello there!'])
is_windows
: True if the Jenkins node the job is running on is Windows.
remote_file
: Takes a relative path to the job's workspace and returns a FilePath
object that represents the target file on the Jenkins remote node.
read_remote_file
: A shortcut for remote_file(file).readToString()
Development environment should be really simple to setup; all you need is a JDK and maven.
-
Run tests:
mvn test
-
Package:
mvn package -DskipTests
-
Upload target/jenkins-python-api-plugin.hpi to your Jenkins instance!