Continuous Delivery to Maven Central with Travis CI

Having used continuous delivery in my professional life for years, I would never want to miss it, even with my private projects. Recently, I had the challenge of deploying an artifact to maven central. I implemented this before using Jenkins. However, there’s no way (I know of) to get a Jenkins As A Service for free for OSS projects. So it was convenient to use TravisCI.

Surprisingly, I didn’t find a turnkey solution online. So I had to come up with my own solution. Here’s how it goes:

language: java
jdk:
- openjdk8

script:
  - |
    cat << EOF > settings.xml
    <settings>
      <servers>
        <server>
          <id>ossrh</id>
          <username>\${env.OSSRH_USER}</username>
          <password>\${env.OSSRH_TOKEN}</password>
        </server>
      </servers>
    </settings>
    EOF

- TMP_KEY="$(mktemp)"
- echo "${PK_BASE64}" | base64 -d > "${TMP_KEY}"
- export PGP_SECRETKEY="keyfile:${TMP_KEY}"

- ./mvnw clean package

deploy:
skip_cleanup: true
provider: script
script: bash ./mvnw -Prelease -s settings.xml deploy -DskipTests
on:
branch: master

cache:
directories:
- '$HOME/.m2/repository'
- '$HOME/.sonar/cache'

This requires four environment variables defined in your travis-ci project and a profile in your pom.xml:

  • OSSRH_USER – Username to Nexus
  • OSSRH_TOKEN – Access Token to Nexus
  • PGP_PASSPHRASE in the format literal:PASSPHRASE
  • PK_BASE64 – Content of your PK, base64 encoded, create with e.g. base64 -w0 pk.asc

Details will be described in the following paragraphs.

Preface

If you haven read a guide on how to deploy to maven central, do it now. The requirements, keys, signing, etc. are not within the scope of this article.

Repository credentials / settings.xml

  • The OSSRH_USER and OSSRH_TOKEN are needed for authenticating against the nexus repository where the artifacts are deployed. While you could use your username and password, I recommend to create an access token, though. It could be revoked more easily.
  • Credentials are passed to maven via settings.xml. In order to make this process secure, we create a settings.xml in the travis.yaml and tell it to read the actual credentials from the environment. We need to escape the $ because it is not supposed to be interpreted by the shell that writes the settings.xml, but by maven when reading the settings.xml
  • When calling maven, we tell it to use the local settings.xml via the -s parameter.

Signing artifacts

For signing the artifacts, kohsuke’s pgp-maven-plugin (from 2014 😲) still seems the easiest way for headless use such as in CI environments.
First, we have to specify the plugin in the pom.xml. My example pom.xml shows how.

We then can easily pass the passphrase for the private key to the plugins using the env var: PGP_PASSPHRASE. Note that we can’t just copy the plain passphrase, we have to tell the plugin, that’s its a literal, by using the format literal:PASSPHRASE.

For the private key itself, there are two challenges:

  1. How do we get the key into travis?
  2. How do we get the key into maven?

How do we get the key into travis?

Unfortunately, other than Jenkins, travis does not seem to provide a way of uploading secret files. If I get the docs right, I could encrypt the private key and push it into my repo. Has anything ever felt so wrong as pushing a private key into a public repository? So I tried loading it into an env var. Here the docs tell me to “escape any Bash special characters”, which is quite a challenge for a private key. Workaround: Base64 encode the whole thing before pasting it into travis.

How do we get the key into maven?

Now that the private key is ready in an env var, it turns out that the plugin’s sole open issue (opened 2018) is that it can’t read a private key from an env var.
So, while base64 decoding the private key, it is pasted into a temporary file, which is then passed to the plugin via an env var.

Epilogue

Only 20 lines of yaml to continuously deliver artifacts to maven central, including signing and all. Not so bad, is it?

Please keep in mind that the version within your pom.xml decides where the artifact is deployed to:

  • If it ends in -SNAPSHOT it is deployed to the SNAPSHOT repository
  • Otherwise, it’s deployed to the releases repository and promoted to maven central.

Generating a hard-coded build number/version name in your Java app

TLDR;

It’s now possible to easily generate a version number for Java apps during the build as a static final field without any runtime dependencies using the annotation processor of the cloudogu/versionName library.


While developing and operating apps with Java for more than a decade, I found one of the recurring problems when working with (or debugging) an app is the matching of the app to the corresponding source code revision/commit/version. So it’s usually a good idea to add a version name, commit id, or build number to your app. I wrote about this topic multiple times and authored a library (now cloudogu/versionName) to automate this task. I thought I’d be through with this topic. Thouroughly. Honestly.

However, while getting started with GraalVM native images, I struck me that the cloudogu/versionName library has shortcomings: It reads the version name from a file (MANIFEST.MF or properties file) within the jar. GraalVM native images are not packaged as jar, though. While we could of course copy the file manually, it made me wonder if a reading a read-only value from a text files isn’t the proper solution. Why don’t we hard-code this into a Java constant? After all, this feels more read-only than a text file and is much easier to read. Also, there’s no more risk of IOExceptions, different behaviours in JDKs, or environments (jar/war). But obviously the writing of the version name field (i.e. generating code) has to be automated.

I sat and thought how this could be realized. The official way of generating code with Java are annotation processors.  From a user perspective an implementation that uses byte code manipulation could be easier to use. OTOH this would require lots of vodoo. If you’re interested in the amount of voodoo, I recommend reading this classic: Project Lombok – Trick Explained.

So, annotations processors it is! Now, I’m happy to announce that the new version 2.1.0 of cloudogu/versionName contains an annotation processor, that generates a Version.NAME constant. This has another neat side effect: at runtime, there is no longer a need to include the library itself. All is done at compile time, zero dependencies at runtime!

All we have to do is add the dependency during compilation (e.g. using Maven’s provided scope) and set the version we want to create as a compiler argument. When using a build tool, it’s easy to generate the version name or build number as described in one the previous articles, and then pass them on to the compiler. For example with Maven:

<dependency>
    <groupId>com.cloudogu.versionName</groupId>
    <artifactId>processor</artifactId>
       <version>2.1.0</version>
   </dependency>
    <!-- This dependency is only needed during compile time and should not be packaged into final JAR -->
    <scope>provided</scope>
</dependency>
 
<!--- .. -->
 
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.8.0</version>
    <configuration>
        <compilerArgs>
            <arg>-AversionName=${versionName}</arg>
        </compilerArgs>
    </configuration>
</plugin>

As it is an annotation processor, we finally have to add an annotation @VersionName to a package or class for the annotation processor to hook in the compilation process. This will cause the processor to create a class Version containing a static final field NAME, during compilation. In the application, this can be read like any other field without the risk of any failures at runtime. Why didn’t I think of this before!
Here’s an example for switiching from the regular versionName library to the annotation processor.

One last side note: The annotation processor’s features are verified using unit tests, which is more complex as it might seem with the compilation process and all. Fortunately, I could reuse the approach of the Shiro Static Permission library (by my colleage @cloudogu, Sebastian Sdorra) that uses Google’s compile-testing library. Unit tests for annotation processors, how awesome is that?!

Maven: Create a simple build number

It’s always a good idea to have means to integrate a build number within your application. It makes it easy to find out which exact versions are deployed on your stages our what version you’re users are running in case of third level support.

The task of creating unique build numbers can easily be automated using maven.

A build number can contain all kind of information, like the application version, a sequential number, SCM revision or a time stamp.

From my point of view, using the time stamp when creating a unique build number is essential: It is unique per se (it is unlikely that the same system is build twice in one second) and it is generated by maven without depending on any plugins. In addition, it is easy to understand, like “Did operations really deploy the new version of the application, yet? No, the build number still shows that it’s the one from last week!”.

So let’s get to buisness: How to generate build numbers within a JAR and how to read it? Does it also work with WARs?

JAR

As mentioned before, maven provides a built-in mechanism for creating a timestamp. You can just customize the formatting and use it for your build number within your pom.xml like so:

	<properties>
		<maven.build.timestamp.format>yyyyMMddHHmmss</maven.build.timestamp.format>
		<buildNumber>v.${project.version} build ${maven.build.timestamp}</buildNumber>
	</properties>

Please note that using the property maven.build.timestamp directly within your filtered file is not possible due to this bug.

The build number must no be made accessible to your application. There are at least two solutions at hand: Write the build number to your manifest or create a properties file.

Manifest

pom.xml

	<build>
		<!-- Add build number to manifest -->
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-jar-plugin</artifactId>
				<configuration>
					<archive>
						<manifestEntries>
							<build>${buildNumber}</build>
						</manifestEntries>
						<manifest>
							<mainClass>info.schnatterer.Main</mainClass>
						</manifest>
					</archive>
				</configuration>
			</plugin>
		</plugins>
	</build>

And that’s how you can access it from your application:

	private String getBuildNumberFromManifest() throws IOException {
		InputStream manifestStream = getClass().getClassLoader()
				.getResourceAsStream("META-INF/MANIFEST.MF");
		if (manifestStream != null) {
			Manifest manifest = new Manifest(manifestStream);
			Attributes attributes = manifest.getMainAttributes();
			return attributes.getValue("build");
		}
		return null;
	}

Properties

Alternatively, create use a properties file:

build.properties

buildNumber=${buildNumber}

pom.xml

	<build>
		<resources>
			<resource>
				<!-- Filter for build number -->
				<directory>src/main/resources</directory>
				<filtering>true</filtering>
				<includes>
					<include>build.properties</include>
				</includes>
			</resource>
		</resources>
	</build>

And that’s how you can access it from your application:

	private String getBuildNumberFromProps() throws IOException {
		String propertiesName = "/build.properties";

		InputStream propertiesStream = Main.class
				.getResourceAsStream(propertiesName);
		if (propertiesStream != null) {
			Properties pros = new Properties();
			pros.load(propertiesStream);

			return pros.getProperty("buildNumber");
		}
		return null;
	}

The output looks something like this:

v.0.0.1-SNAPSHOT build 20140225211328

Note: You might want to check the returned value for null or have the methods return empty Strings in order to avoid NullPointerExceptions or the word null on your GUI.

WAR

If you’re building a web application, you should also be able to use the manifest or the properties file as described above. Your maven set up might be a bit different, though (see Maven War plugin – WAR Manifest Customization).

You have another alternative that makes creating the build number even easier: You can “stamp” the build number directly into interpreted documents like (X)HTML or js files.

For example when using JSF, you could write the build number directly into the footer template.

Add this to your page:

<h:outputText styleClass="version" value="${buildNumber}"/>

And this to your pom.xml

	<properties>
		<maven.build.timestamp.format>yyyy-MM-dd HH:mm:ss</maven.build.timestamp.format>
		<buildNumber>v.${project.version} build ${maven.build.timestamp}</buildNumber>
	</properties>

	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-war-plugin</artifactId>
				<version>2.3</version>
				<configuration>
					<!-- Filter for build number -->
					<webResources>
						<resource>
							<directory>src/main/webapp/WEB-INF/templates</directory>
							<targetPath>WEB-INF/templates</targetPath>
							<filtering>true</filtering>
							<includes>
								<include>yourFooterTemplate.xhtml</include>
							</includes>
						</resource>
					</webResources>
				</configuration>
			</plugin>
		</plugins>
	</build>

And that’s it. No parsing necessary in code, as maven will insert the build number in your web page directly.

Further Reading

If you’re planning on creating a more complex build number (SCM, sequential numbers, etc.), I suggest you have a look at the Build Number Maven Plugin.