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.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.