In Neoteric, we value software craftsmanship as high as making our customers happy with the software we provide. We strive for high code quality. We do that by following the best practices, holding on to sensible test coverage and keeping up with latest technologies. However, not everything is still as good as we want it to be. The biggest issue which affected us was the lack of releasing and versioning capability of our modules and products. That’s the problem I struggled with recently and I’d like to share the solution we have adopted in the company.
As of the technical background: We use Git as our version control system, Maven as a build tool and Jenkins as an integration platform. So far, we mainly had one master branch where most of the development took place. Feature branches were spawned occasionally. All artifacts were using the same 1.0.0 version, which obviously introduced a lot of hassle and pain. We wanted to put an end to that and start doing things like they should be done.
We were attracted to the Git Flow branching model. Some of us already used it, to some extent, in front-end development. Hence, it felt natural to adopt it on the back-end side as well. The aim was to make use of this branching model as pleasant as possible and leave coping with modules versioning to Jenkins.
I started to play around with Jenkins and Git and what I didn’t like about their cooperation in the first place (one job per Git repository) was the lack of clear feedback. I couldn’t tell which exact branch has just been built. What’s more, it looked like it would stop me from fully utilizing Git Flow potential in terms of releasing and versioning. Hence, the first step emerged:
1. Keeping separate jobs per branch
How can we achieve that? Of course, we can manually create and drop Jenkins jobs per each feature, hotfix and release branches. Any volunteers…? I thought so. Not without a reason, it is said that a programmer’s biggest virtue is laziness. I had to find a way to automate that. When I googled, I was surprised. I couldn’t find an easy solution to that (or didn’t google enough). Even if found, it very often introduced a lot of dirty scripting and workarounds. The tool, which seemed to be the most elegant and closest to our needs, is Jenkins Build Per Branch by Entagen. It is a Groovy script, which you invoke from a Jenkins job as a Gradle task. However, it looks to be more useful in a different type of branching convention (GitHub Flow).
As I really liked the idea behind it, I decided to work on Git Flow variation of the script: Jenkins Build Per Git Flow Branch. Pretty much everything (genesis, installation, usage) is described on the GitHub project page, so it’s better to check it out before reading further.
Ok, so Jenkins provides really nice Git Flow job management now. We need to figure out how to utilize that for Maven artifacts releasing.
2. Maven releasing
Back in the day, when working with SVN, there was a Maven Release Plugin (and M2 Release Jenkins plugin) to accomplish the releasing part. It wasn’t great, but when configured properly, it did the job pretty well. I started to dig in on how to make releasing work with Git Flow. It appears that it is still possible using Maven Release Plugin, but it can be tedious and troublesome work. It seems that a much better solution exists. It is a plugin created by Jonatan Doklovic – Maven JGit Flow Plugin. He has already done a good job explaining it in his blog post and describing the advantages over the old approach.
3. Combining the ingredients
It looks like we have all the necessary ingredients, so let’s start cooking.
Two things are required in our pom.xml build file. Firstly, we need to set up the artifact repository as usual:
Note: Snapshot repository address won’t be used in our example, we decided to store only final releases. Secondly, we need to configure the JGitFlow Plugin:
We want to make the plugin doing the pushes for us and we need to make sure that we follow the ‚dash’ suffix (i.e. release-) branch naming convention (as explained in the Jenkins tool readme). EnableSshAgent value is set for connectivity purposes.
As stated previously, the ground point is that Jenkins handles modules building and releasing. One benefit is a guarantee of same compilator version among the builds. What’s more, some of our developers work on Windows OS, hence they are not able to build .deb packages out of the box (and we use them to distribute our services). Again, Jenkins to the rescue!
I think that the most sensible way of explaining the concept is going through the whole flow step by step. And I will try to accomplish that:
b) feature branches
So, let’s assume that master and development jobs are already set up (we will cover them in a while) and I want to start working on a new feature. After I make sure that my project has clear Git status (nothing left to be committed etc.), I can execute JGitFlow Maven goal: mvn jgitflow:feature-start
Shortly after providing the feature branch name in the Maven interactive console, two things will happen: the created feature branch will be pushed to the remote repository by JGitFlow plugin and during Jenkins Git Flow per Branch (I’ll abbreviate it to JGFPB) sync job execution, it will create an actual job based on the feature job template. How should the template look like? In our case, it’s the simplest scenario you can imagine. We only want to execute maven clean install. Let’s look at the example:
Currently, we are using two sets of templates: one for ordinary jars and the other for debian packages. As you can see, I’m going to focus on the first one. The latter is a topic for the next blog post. The remaining part of this screenshot is the fact that I added the startOnCreate parameter as I want to build the project as soon as it shows up. That will probably be the case in most situations.
Nothing fancy here as well. At this point, ‚Repository URL‚ value is irrelevant. It will be substituted for the Git URL by the Jenkins Sync Job. The same applies to ‚Branches to build‚. And yes, we are using GitLab
Maven clean install. Nothing more, nothing less.
If you’d like to perform Sonar builds out of your feature jobs, you just need to add Sonar post-build-step and provide any value in the ‚branch’ field. JGFPB tool will do its job and replace the branch accordingly (as it does with Git URL and branch to build).
Are you ready to deliver your feature? Just finish it by JGitFlow: mvn jgitflow:feature-finish and pick the feature you want to finish in the Maven interactive console. That’s it. The plugin will merge it into development, delete the branch and the Sync Job will remove the Jenkins feature job when executed. As you see, working with features is pretty simple.
Now it’s time for the key player – the development job. One of the two ‚fixed’ jobs (along with master). As Git Flow branching model suggests it should always contain production-ready like codebase. Hence, it should be responsible for initiating the release process. To do that, we can incorporate Release Jenkins plugin. It will enable us to start a customizable job with different execution than the usual build. Here are most important bits of development job configuration:
- We obviously want to check out development branch.
- We should prune stale remote branches. Otherwise, Jenkins (JGitFlow plugin to be precise) wouldn’t allow us to do more than one release.
- We also need to make Jenkins Git plugin check out the project to a specific local branch.
- Now the release configuration. We are happy with the defaults of Release Plugin. When we perform the release, we are asked for releaseVersion and developmentVersion. The convention is that for normal releases we should increment MINOR version (you can read more on the semver site). When provided, GitFlow will automatically pick those values up and use them.
- Here we specify the behavior of the release itself. What we want to do is to perform the jgitflow:release-start goal of JGitFlow Plugin. -X is a debug flag. With debug enabled, we can thoroughly trace the steps of the Maven goal execution.
- By checking out to a specific location (instead of a default – temporary one) we are able to clean up after the release initialization. We are checking out again to a development branch (Plugin left Jenkins in a newly created release branch) and deleting the release branch locally. We won’t need it, because the release job will be created from a remote branch.
- Last thing to do here is to trigger the synchronization job after the release initalization is complete, so we wouldn’t have to wait for a cron trigger execution.
- For a normal build, everything is pretty ordinary. We run clean install goals. Don’t mind the profile at the moment, it is just for building Debian package out of the generated jar.
- We like to have Sonar feedback on the development branch. In Sonar advanced options, we provide development as a branch to build.
A few words of explanation before we dive into the release job configuration:
Git-Flow recommends using release branching for so called ‚release hardening’, where you can fix all bugs found in UAT and prepare release metadata. We found it to be an excessive effort (at least for now) and decided to skip the release phase in a way that it starts and immediately finishes the release branch. This way we still stay flexible, if we’d like to extend the release cycle.
That being said, let’s analyze the Release branch Jenkins template:
- We definitely want to start the release job as soon as it appears.
- Again, the same things apply to Git configuration, the URL and branch will be replaced by the script.
- The last thing is to run jgitflow:release-finish goal. We set the debug mode on to have better understanding of what is going on. And yet again, don’t mind the profile.
When build is completed, we should have:
- our artefact release version done and deployed to the Nexus repository
- development branch updated with latest snapshot version.
- Release branch deleted by the JGitFlow plugin
- Release Jenkins job deleted by the JGFPB
That covers almost every aspect of the releasing cycle. The last outstanding part are hotfixes. To perform such, we should run jgitflow:hotfix-start Maven goal. It will create and push a new hotfix branch (after specifying its version in the interactive console). For most cases, you should just increment the PATCH version. Hotfix branch will be built against its template:
There is nothing here you haven’t seen already. The most notable difference is the fact that hotfix template has releasing capability enabled (but still, it’s exactly the same as you saw in development branch config). Maven goal for releasing hotfix branch is jgitflow:hotfix-finish. Usage of hotfix branch is easy. When you’re done pushing your fixes, you execute release build, specifying release and development versions. When hotfix is done, artifactshould be deployed to Nexus, branch, and job should be removed, and all hotfix changes merged to development and master branches. Reminder: do not forget to delete the hotfix branch locally and to git fetch prune, as the hotfix remote branch is no longer available.
That concludes all parts of making Jenkins speak Git Flow. The solution is not without flaws but works quite well for us. We have a clear vision of the project status and progress without the burden of managing Jenkins jobs manually. A few things to consider would be: making Jenkins automatically pick version numbers from pom.xml itself and how to avoid unnecessary waiting for sync job to create project jobs.
In the next post, I’ll show you how we build and handle our services as Debian packages using Nexus as a native Debian repository. Stay tuned!