logo-StatusNeo
d

WE ARE EXPERTS IN TECHNOLOGY

Let’s Work Together

Image Alt

StatusNeo

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:

  1. The version of the jar files are hardcoded inside the pom.xml
  2. 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:

  1. 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.
  2. 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:

  1. GitHub Actions is setup to be triggered on Releases in Git
  2. Actions trigger the creation of Ubuntu container for execution of the CI/CD Task
  3. GitHub Actions checks out default branch of the repository which has version hardcoded inside the pom.xml file (say X- SNAPSHOT)
  4. Maven Release Prepare plugin is invoked and it modifies the source code base
  5. 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 "nishkarshraj000@gmail.com"
        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.

Principal DevOps Evangelist and Consultant at StatusNeo

Add Comment