Continuous integration with Jenkins
Buttle
The question of whether developers should check their work into a version control system (VCS) such as subversion or Git often and early is the subject of heated discussions. Opponents claim that it can make the work of other developers difficult because it is not always possible to ensure that the checked-in code or the software will work or can even be built. In contrast, the proponents claim that new code becomes increasingly difficult to integrate, the larger and more extensive the new commit is. Small, but numerous code snippets, they say, ensure that a potential error is identified at an early stage, and thus quickly resolved.
But, of course, the whole thing can only work if someone actually checks all these new code snippets regularly, as soon as they appear in the repository. Only then is it ensured that errors are fixable in a timely manner. So, why not simply employ someone who does nothing all day except build newly checked-in code and identify possible errors? This new resource wouldn't even ask for a paycheck, because I'm talking here about Jenkins, the butler – some readers may know him as Hudson.
Jenkins [1] is a web-based Java application that runs in any modern servlet container, such as Tomcat or JBoss. However, the tool also includes Winstone in its own, minimal container. Thus, you don't need to install an additional web container if you don't already have one. Jenkins supports a whole range of build tools and integrates seamlessly into version control systems such as Subversion. Thanks to its modular structure, Jenkins's feature set is extensible. For instance, plugins exist for integrating other build tools or version control systems.
Regularly querying the configured VCS helps Jenkins very quickly discover whether new code has been checked in to the repository. If this is the case, the code is checked out and Jenkins calls the configured build tool to compile the new code. Additionally, Jenkins is also capable of running other actions against the new code. This includes, for example, building new RPM packages on the basis of the new code.
If you install the Chuck Norris plugin, you get a thumbs up if the build is a success (Figure 1); otherwise, Jenkins just issues a simple notification. Jenkins keeps the build results, known as artifacts, on request. This feature is quite useful if you don't want to overwrite them in the next build process. You can then let Jenkins call automatic testing tools, such as JUnit, to make sure the new build provides the desired results in terms of functionally. Jenkins also shows you possible errors directly and, on request, can also notify by email or RSS feed.
To use Jenkins, you first need to integrate the appropriate software repository:
# sudo wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat/jenkins.repo # sudo rpm --import http://pkg.jenkins-ci.org/redhat/jenkins-ci.org.key # sudo yum install -y jenkins # sudo /etc/init.d/jenkins start
For non RPM-based distributions, a war archive is available on the Jenkins project website.
Jenkins then listens to incoming requests on port 8080. If you now access the page in your web browser, you will see a selection menu on the left side. To create a new project, select New Job. Then, you can use the Build a freestyle software project (Figure 2) menu item to access the actual configuration dialog. You need to specify the version control system, the build tool, and optionally the testing tool here. To create RPM packages automatically, which is what it's all about, I use the simple script in Listing 1. If you add this script as the build tool in Jenkins, it will trigger the desired RPM build.
Listing 1: build.sh
¤¤nonumber 01 tar cfz foo.tar.gz foo/* --exclude="*.spec" --exclude ".svn" --exclude="build.sh" 02 03 for dir in BUILD RPMS SOURCES SPECS SRPMS 04 do 05 [[ -d $dir ]] && rm -Rf $dir 06 mkdir $dir 07 done 08 09 cp foo/foo.spec SPECS/ 10 mv foo.tar.gz SOURCES/ 11 rpmbuild -ba SPECS/foo.spec
The whole thing assumes that the Subversion checkout takes place in a subfolder named foo
. You can pass this information into Jenkins. In the folder, you should have the RPM spec file, the build script and, of course, all the files to be integrated into the RPM. To create a clean SRPM, an archive is built from the sources and bundled into the SRPM. In the project folder below /var/lib/Jenkins/workspace/
, the for
loop then generates the corresponding RPM build structure.
To make sure the call to rpmbuild
actually works, the tool needs to know that its build environment is now in the Jenkins project folder. Although you can specify this in the call using the topdir
macro, adding this information to the spec file is a more elegant approach. To do so, I simply define two new macros in the header of the file; I can then use them in the further course of the spec files:
%define _topdir %(echo `pwd`) %define BUILD_NUMBER %(echo $SVN_REVISION)
BUILD_NUMBER
relates to the SVN revision. Jenkins kindly provides a variable for this. Jenkins understands a lot of variables, and the documentation [2] provides a complete list. I can then simply use the BUILD_NUMBER
as the RPM revision in the RPM spec file. The header in the spec file can thus contain, for example, the following lines:
Name : foo Summary : foo application Version : 42 Release : %{?BUILD_NUMBER}
Calling Build Now
or checking into the repository again initiates the build. If everything works correctly, you should have a newly built RPM package in the RPMS subfolder below the Jenkins project directory. Jenkins provides a number of different URLs with which you can reference, for example, the last RPM package to be created. A URL of this type might look like:
http://localhost:8080/job/foo-jenkins-project/lastSuccessfulBuild/
Jenkins posts a list of all URLs and RSS feeds on the overview page of the project. Here you also will find a corresponding chart with a build trend for the project. I hope you see sunshine here for the most part.
At this point, you have many options for further actions. For example, you could automatically upload the RPM you just created to a Spacewalk server and deploy it on the test systems from there. Jenkins offers a wide variety of opportunities here – and, thanks to many plugins, its range of applications is very extensive.