In a typical CI/CD pipeline, code is build, code is deployed, code is tested. In our specific scenario the code is built through Jenkins and maven on a Jenkins build slave, then the build artifacts are uploaded to an artifact repository. Through an Infrastructure-as-code tool, like Chef, the code is deployed to a test environment that is separate from the Jenkins build slave, and then the code is functionally tested. This often present a challenge if we use a tool like Maven because we need to be able to map the artifacts built back to the job that they were built in, so that we can ensure the code that was built will be the code deployed, tested, and eventually promoted up. The easiest way to do this is to version the build artifacts with the BUILD_ID Jenkins variable (the build number).

In gradle, this is a very simple task; however, maven purposely makes this a difficult task. In a typical maven project, you have the parent or root pom then each subproject is it’s own module, that inherits from the root pom. My first attempt at dynamically versioning artifacts started with replacing all of the versions with 1.0.${artifact.version} where artifact.version was set as a property with a default value of 0. In Jenkins, I would build from the root pom with something like this mvn install -Dartifact.version=${BUILD__ID}. This worked fine for Jenkins as we always wanted to build from the root pom. However, with these changes, it became impossible for individual modules.

Not wanting to inhibit development any more than I already had and wanting to find a more complete solution, I decided to not add anything to their pom, and that we needed something completely on our side that would switch out the versions. I chose to write a groovy script that would switch out the version with a dynamic version in the poms. While this could be written in a tool like sed or perl, but I chose groovy because I believe it’s a little easier to read and maintain.

In my groovy script, I define an XmlSlurper to read in the pom file. I use the new XmlSlurper(false,false) constructor because the default constructor leaves in line endings, which could lead to incorrect XML being written later. Next, I include a release flag, so if it is a release artifact it will be correctly versioned. The next flag that we have is the root flag. In maven, the version is located in a different tag for the root pom than in a modules pom — which this block takes care of. Also in this block, the value of the XML tag gets replaced with the dynamic version. The rest of the program writes out the XML with the new version back to the same pom.

Now when Jenkins runs the build, we would run this on each pom in the repository. For example, if we want to sub a dynamic version into the root pom, and it is going to be a release artifact, we would run this script like this:
groovy /path/to/script true true

 


Here’s my solution:

import groovy.xml.XmlUtil

def pom = new XmlSlurper(false,false).parse(new File(args[0]))
def value
def release = args[1].toBoolean()
if(release){
        value = '1.0.${artifact.version)'
} else {
        value = '1.0.${artifact.version}-SNAPSHOT'
}
def root = args[2].toBoolean()
if(root){
        pom.each{
                it.version.replaceBody value
        }
}else{
        pom.each{
                it.parent.version.replaceBody value
        }

}
def xml = XmlUtil.serialize(pom)
def newFile = new File(args[0])
xmlOutput = new FileWriter(newFile)
printer = new XmlNodePrinter(new PrintWriter(xmlOutput))

printer.preserveWhitespace = true
newFile.write(xml)

Leave a comment

Your email address will not be published. Required fields are marked *

X