CI/CD with GitHub x Apache Maven
The Theory Of Evolution is applicable in the world of Software as well. You either adapt and be agile or you perish.
In today’s world, amazingly, Java is still predominantly used as one of the main programming languages irrespective of the fact that its first release happened 25 years ago. Java’s robustness, agility, portability, ability to scale and evolve keeps it relevant but as the project size grows, it becomes increasingly tedious to manually keep a track of all the dependencies and maintain hierarchy for the code base.
Enter Apache Maven. Maven is a build automation and dependency management tool for Java (and other) languages which keeps a track of dependency in a special Project Object Model file called pom.xml.
On the other hand, GitHub started as a popular Source Code Management tool to store repositories on cloud that are being version controlled by Git VCS. In recent years, GitHub is continuously evolving into domains of DevOps with its automation engine – GitHub Actions and also in the field of Artifact Management with GitHub Packages
The aim to the blog is to help understand how to automate Apache Maven Release process from building source to binary, modifying its version and deploying the artifacts in the Artifactory by using GitHub Actions and GitHub Packages.
1) Build Automation CI for Maven Projects
This guide assumes you already have an Apache Maven project hosted on a GitHub Repository with pom.xml (parent pom if multi-module project) on the root of the repository. If you don’t have a repository in hand, clone this template Apache Maven project with Spring Boot project.
Clone this repository locally and test whether the project is building fine or not with the following commands:
$ mvn clean install
$ java -jar target/<jar file name>
Let’s move back to GitHub and create our first GitHub Actions for Testing if this build passes successfully via CI as well.
Go to Actions tab for starter workflows made by the community.
name: Java CI with Maven
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 11
uses: actions/setup-java@v2
with:
java-version: '11'
distribution: 'adopt'
- name: Build with Maven
run: mvn clean install
The above YAML Syntax for the Actions has following details:
- Name of the CI Actions we are creating
- On implies trigger. The current Action gets triggered on Push and Pull Request events on the Main branch only
- Checkout functionality is used to give access to repository content to the server where this action is executing.
- Runs on implies the base OS image, here Ubuntu (Linux)
- Java 11 is used with AdoptJDK vendor.
- Build Job has a single task of executing maven clean install
Move back to the Actions tab to see the Logs of the CI workflow run.
2) Deploy Artifacts to GitHub Packages
To deploy the artifacts created inside the target/ folder after Maven Build is created, we should first add the Repository details inside the pom.xml file.
<distributionManagement>
<repository>
<id>mvn-repo</id>
<name>[GitHub username]</name>
<url>https://maven.pkg.github.com/[GitHub Username]/[GitHub Repository]</url>
</repository>
</distributionManagement>
To authenticate GitHub Package Registry with your Maven project, you have to add a server ID on your machines ~/.m2/settings.xml (MacOS / Linux) or C://Users/<Username>/.m2/settings.xml (Windows)
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
https://maven.apache.org/xsd/settings-1.0.0.xsd">
<servers>
<server>
<id>mvn-repo</id>
<username>[GitHub Username]</username>
<password>[GitHub Token]</password>
</server>
</servers>
</settings>
Let’s test this workflow on the local machine first
$ mvn deploy
After successful deployment, you can see the uploaded package on the right sidebar of your GitHub Repository.
Create another GitHub Actions for the Deployment Process.
name: Maven Package
on:
release:
types: [created]
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v2
- name: Set up JDK 11
uses: actions/setup-java@v2
with:
java-version: '11'
distribution: 'adopt'
server-id: mvn-repo
settings-path: ${{ github.workspace }}
- name: Build with Maven
run: mvn clean install
- name: Publish to GitHub Packages Apache Maven
run: mvn deploy -s $GITHUB_WORKSPACE/settings.xml
env:
GITHUB_TOKEN: ${{ github.token }}
This Actions has the following attributes:
- Triggered on GitHub Releases
- Builds the Jar files with Maven Clean install
- Deploys the jars present in the target. folder to GitHub Package Registry
To trigger this GitHub Actions, create a GitHub Release from the UI.
Let’s observe the logs of this Actions from the Actions Tab
Now, while this deployment works fine, there are two limitations to this approach:
- The version of the jar files are hardcoded inside the pom.xml
- The jars present inside the GitHub Package registry are SNAPSHOT versions which should not be the case.
To overcome these limitations, let’s incorporate the Maven Release Plugin that auto-increments version of the jar files inside the pom file and also deploys non-snapshot version jars by tagging them properly.
3) Release Automation with Maven Release Plugin
To overcome the limitations of `mvn deploy` – Maven Release Plugin was developed for correct versioning of Jar files and maintaining SNAPSHOTs and production level Jars separately.
Maven Release Plugin has two main commands:
- Prepare: Release prepare takes the default branch with X versioned Snapshot in pom.xml and increments it by 1. Also, it creates a tag X with the same jar without having the SNAPSHOT suffix.
- Perform: Perform task follows the prepare task. It takes the tagged X git tag and deploys the non-snapshot jar to the registry.
The workflow above is what we aim to achieve in the following steps:
- GitHub Actions is setup to be triggered on Releases in Git
- Actions trigger the creation of Ubuntu container for execution of the CI/CD Task
- GitHub Actions checks out default branch of the repository which has version hardcoded inside the pom.xml file (say X- SNAPSHOT)
- Maven Release Prepare plugin is invoked and it modifies the source code base
- Maven Release Perform plugin is invoked and it deploys the production level Jar files to Artifactory (here, GitHub Packages)
To perform Maven, Release we have to add the SCM configuration and add Release Plugin the pom.xml file.
<scm>
<developerConnection>scm:git:https://github.com/[Username]/[Repository]</developerConnection>
</scm>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
<version>3.0.0-M4</version>
<configuration>
<username>[GitHub Username]</username>
<password>[GitHub Token]</password>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
Let’s test the Release Process locally first.
$ mvn release:prepare –batch-mode
$ mvn release:perform
Now, if we go back to the GitHub Registry, we will see a non-snapshotted version of Jar file has been deployed. If you observe closely, [maven-release-plugin] has made the recent commits automatically by making updates in the pom file.
Let’s modify our GitHub Actions for deployment to use Maven Release Plugin
name: Maven Package
on:
release:
types: [created]
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: write
actions: write
repository-projects: write
packages: write
steps:
- uses: actions/checkout@v2
with:
ref: main
- name: Set up JDK 11
uses: actions/setup-java@v2
with:
java-version: '11'
distribution: 'adopt'
server-id: mvn-repo
settings-path: ${{ github.workspace }}
- name: Maven Release x GitHub Packages
run: |
git config --global user.name "NishkarshRaj"
git config --global user.email "[email protected]"
mvn release:prepare --batch-mode
mvn release:perform -s $GITHUB_WORKSPACE/settings.xml
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
Trigger this actions by creating another release:
Now if we see the package registry, we see that a newer version 1.0.0 -> 1.0.1 is released.
Note: Storing your credentials in Source Control is not a good practise. Please follow this StackOverflow Forum to setup this automation securely.
Add Comment
You must be logged in to post a comment.