Continuous Release process allows an artifact to be continuously releasable and promotable to the upper environment.
The topic has been covered by a large amount of articles, so why this new one? Continuous release of an Application (eg. executable jar, war) is indeed well covered but not the continuous release of the dependencies (eg. jar library) of an Application.
In this article we will cover both topics and we will get ride of the release pipeline I describe in my previous article Jenkins Workflow - Pipeline de release
to
rely solely on a slightly modified continuous integration pipeline. I will end the article with a description of the Continuous Integration pressure paradigm.
Because a non snapshot build must be reproducible no modification are allowed once a project is released. This include no modification of the project and no modification of the dependencies. Thus project version must be fixed and version dependencies too
We have two projects: Application and Dependency. Developments of Application and Dependency are coupled for an iteration because Application needs a feature of Dependency.
1
2
3
4
5
<project>
<groupId>org.nlab.article.release</groupId>
<artifactId>application</artifactId>
<version>1.0.0-SNAPSHOT</version>
</project>
1
2
3
4
5
<project>
<groupId>org.nlab.article.release</groupId>
<artifactId>dependency</artifactId>
<version>1.2.0-SNAPSHOT</version>
</project>
Application depends on Dependency:
1
2
3
4
5
<dependency>
<groupId>org.nlab.article.release</groupId>
<artifactId>dependency</artifactId>
<version>1.2.0-SNAPSHOT</version>
</dependency>
The target process is a slightly modified continuous integration pipeline:
This process does not involve the common release steps: no need to remove snapshot qualifier, commit and wait for the jenkins pipeline to finish. The usual continuous integration pipeline is self sufficient and generate a releasable artifact.
To fix the version I will use the build number for three reasons:
Before compilation we fix the version using the versions:set
goal:
1
mvn build-helper:parse-version versions:set -DnewVersion=${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}-${BUILD_NUMBER}
Where BUILD_NUMBER can be a number coming from the SCM or the Jenkins Job build number. A build must have a BUILD_NUMBER greater than the previous builds.
build-helper:parse-version
is a convenient method to extract the version component.
Application and Dependency versions are now fixed.
versions:use-releases
: searches the pom for all -SNAPSHOT versions which have been released and replaces them with the corresponding release version.
Only update version using version without qualifier/build number. Not suitable.
versions:use-latest-releases
: searches the pom for all versions which have been a newer version and replaces them with the latest version.
Update version using latest release. Update scope is configurable (eg. allowIncrementalUpdates
.
May be useful if continuous release is done using the incremental version.
versions:resolve-ranges
: finds dependencies using version ranges and resolves the range to the specific version being used.
Suitable if Dependency version is defined as a range.
versions:update-properties
: updates properties defined in a project so that they correspond to the latest available version of specific dependencies. This can be useful if a suite of dependencies must all be locked to one version.
Suitable if Dependency version property is defined as a range. As we see, this goal add another level of flexibility.
Note: All goals does not supports Maven properties defined in the root pom and used in a module. For this case dependencyManagement
may be used in the root pom.
The only suitable option is the versions:resolve-ranges
goal. Application must depend on Dependency using a
version range instead of a SNAPSHOT.
In our case the range is [1.2.0,1.2.0-99999]
:
1
2
3
4
5
<dependency>
<groupId>org.nlab.article.release</groupId>
<artifactId>dependency</artifactId>
<version>[1.2.0,1.2.0-99999]</version>
</dependency>
The Application now depends only on deployed version of Dependency and no longer depends on a SNAPSHOT dependency.
To fix the range we call the versions:resolve-ranges
goal:
1
#> mvn versions:resolve-ranges
The dependency is now:
1
2
3
4
5
<dependency>
<groupId>org.nlab.article.release</groupId>
<artifactId>dependency</artifactId>
<version>1.2.0-123</version>
</dependency>
The dependency version is now fixed.
The Continuous Integration Pressure concept is simple: When a new version of Dependency is deployed it must trigger the pipeline of Application in order to test the integration of Dependency into Application
The concept could go further, Application must test the integration prior to integrating the new version of Dependency. If the test failed, the integration should be reviewed but must not block the developers or the pipeline.
On the CI side, a new version of Dependency will automatically trigger the integration test of all Application that depend on it.
There are different levels:
On the CI side, a new version of Dependency will automatically trigger the Application pipeline. As Application is using a range, the build will use the latest build number. No modification of the above process is needed.
The drawback of SNAPSHOT and version range is their volatile nature. A build or the tests of Application may failed because of a new version of Dependency. A fixed version resolves this issue.
On the CI side, a new version of Dependency will automatically trigger the Application integration pipelines. This integration pipeline :
It involves some modification of the process.
if the incremental or minor part of the version is used to do continuous release, the Dependency version could be updated using versions:use-latest-releases
and its properties (eg. allowIncrementalUpdates
). That’s it the version is updated.
Vincent Latombe on the CastCodeur mailing list suggests me this process (in french).
If a range is used it involves more configuration. We should retain the range somewhere in the POM to be able to fix the Dependency version.
For this purpose we use the versions:update-properties
goal using the properties
property which allows to
add restrictions that apply to specific properties.
We create a dependency.version
maven property which holds the current version of Dependency.
Dependency version is set to this property.
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
<properties>
<dependency.version>1.2.0-123</dependency.version>
</properties>
<dependencies>
<dependency>
<groupId>org.nlab.article.release</groupId>
<artifactId>dependency</artifactId>
<version>${dependency.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>versions-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<properties>
<property>
<name>dependency.version</name>
<version>[1.2.0,1.2.0-9999]</version>
</property>
</properties>
</configuration>
</plugin>
</plugins>
</build>
When we execute the goal:
1
mvn versions:update-properties
The versions plugin updates dependency.version
using the provided restrictions range.
We get continuous release for application and dependencies with continuous integration pressure. Every artifact is releasable without the burden of a release process. The same pipeline can be used to do CI and to generate a promotable artifact.