Enterprise Java

Spring From the Trenches: Returning Git Commit Information as JSON

There are situations when we must know the exact version of our web application that is deployed to a remote server. For example, a customer might want to know if we have already deployed a bug fix to the server X.

We can, of course, try to find an answer to that question by using the “traditional” method. The problem is that:
 
 
 

  • No one cannot remember who updated the server X or when it was updated.
  • The person who updated it cannot remember which was the last commit that was included in the build.

In other words, we are screwed. We can try to test if the bug is still present at the server X, but this doesn’t really help us because our bug fix might not work.

This blog post describes how we can solve this problem. Let’s start by extracting the build-time state of our Git repository.

If you use Spring Boot, you should use the Spring Boot Actuator. It helps you to publish information about the state of your Git repository.

If you have not read the following blog posts, you should read them before you continue reading this blog post:

Extracting the Build-Time State of Our Git Repository

We can extract the build-time state of our Git repository by using the Maven Git Commit Id plugin. Let’s find out how we can configure the Maven Git Commit Id plugin and add the extracted information to a properties file.

First, we need to configure the location of our resource directory and ensure that the property placeholders found from our properties files are replaced with the actual property values. We can do this by adding the following XML to the build section of our pom.xml file:

<resources>
	<resource>
		<directory>src/main/resources</directory>
		<filtering>true</filtering>
		<includes>
			<include>**/*.properties</include>
		</includes>
	</resource>
</resources>

Second, we need to configure the Maven Git Commit Id plugin. We can do this by following these steps:

  1. Add the Maven Git Commit Id plugin to our build.
  2. Ensure that the revision goal of the Maven Git Commit Id plugin is invoked at the initialize phase of the default lifecycle.
  3. Configure the location of the .git directory.

We need to add the following XML to the plugins section of the pom.xml file:

<plugin>
	<groupId>pl.project13.maven</groupId>
	<artifactId>git-commit-id-plugin</artifactId>
	<version>2.1.13</version>
	<!--
		 Ensure that the revision goal is invoked during the initialize
		 phase.
	-->
	<executions>
		<execution>
			<goals>
				<goal>revision</goal>
			</goals>
		</execution>
	</executions>
	<configuration>
		<!--
			Configure the location of the .git directory.
		-->
		<dotGitDirectory>${project.basedir}/../.git</dotGitDirectory>
	</configuration>
</plugin>

If you are not happy with the default configuration of the Maven Git Commit Id plugin, you should take a closer look at its README:

  • Using the plugin provides a commented XML configuration file that describes the configuration of the Maven Git Commit Id plugin.
  • Configuration options in depth describes every configuration option of the Maven Git Commit Id plugin.

Third, we need to create the properties file that contains the information which is extracted from our Git repository. The application.properties file looks as follows:

git.tags=${git.tags}
git.branch=${git.branch}
git.dirty=${git.dirty}
git.remote.origin.url=${git.remote.origin.url}

git.commit.id=${git.commit.id}
git.commit.id.abbrev=${git.commit.id.abbrev}
git.commit.id.describe=${git.commit.id.describe}
git.commit.id.describe-short=${git.commit.id.describe-short}
git.commit.user.name=${git.commit.user.name}
git.commit.user.email=${git.commit.user.email}
git.commit.message.full=${git.commit.message.full}
git.commit.message.short=${git.commit.message.short}
git.commit.time=${git.commit.time}

git.build.user.name=${git.build.user.name}
git.build.user.email=${git.build.user.email}
git.build.time=${git.build.time}

We have now configured the Maven Git Commit Id plugin. When we compile our project, the property placeholders found from the application.properties file are replaced with the actual property values that are extracted from our Git repository.

The application.properties file found from the target/classes directory looks as follows:

git.tags=
git.branch=master
git.dirty=true
git.remote.origin.url=git@github.com:pkainulainen/spring-from-the-trenches.git

git.commit.id=1bdfe9cf22b550a3ebe170f60df165e5c26448f9
git.commit.id.abbrev=1bdfe9c
git.commit.id.describe=1bdfe9c-dirty
git.commit.id.describe-short=1bdfe9c-dirty
git.commit.user.name=Petri Kainulainen
git.commit.user.email=petri.kainulainen@gmail.com
git.commit.message.full=Declare PropertySourcesPlaceholderConfigurer in a static @Bean method
git.commit.message.short=Declare PropertySourcesPlaceholderConfigurer in a static @Bean method
git.commit.time=16.04.2015 @ 23:35:23 EEST

git.build.user.name=Petri Kainulainen
git.build.user.email=petri.kainulainen@gmail.com
git.build.time=18.04.2015 @ 17:07:55 EEST

If you don’t want to create the properties file, the Maven Git Commit Id plugin can generate one for you.

Let’s move on and find out how we can inject the Git commit information into properties beans.

Injecting the Git Commit Information Into Properties Beans

We need to create three properties bean classes that are described in the following:

  • The BuildProperties class contains information about the person who started the build.
  • The CommitProperties class contains information about the latest commit that is included in the build.
  • The GitProperties class contains a few “common” properties such as branch, tags, and remoteOriginUrl. It also contains a references to BuildProperties and CommitProperties objects.

Properties beans are similar than the configuration beans that were described in
my earlier blog post. The reason why I used a different suffix for these classes is that they aren’t part of the configuration of our web application. They simply contain information that is written to a log file and returned as JSON.

Also, if you don’t know why you should inject property values into special bean classes and how you can do it, you should read my blog post that answers to both questions.

First, we need to create the BuildProperties class. This class has the final time, userEmail, and userName fields. The actual field values are injected into these fields by using constructor injection. The source code of the BuildProperties class looks as follows:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class BuildProperties {

    private final String time;
    private final String userEmail;
    private final String userName;

    @Autowired
    public BuildProperties(@Value("${git.build.time}") String time,
                           @Value("${git.build.user.email}") String userEmail,
                           @Value("${git.build.user.name}") String userName) {
        this.time = time;
        this.userEmail = userEmail;
        this.userName = userName;
    }
	
    //Getters are omitted for the sake of clarity
}

Second, we need to create the CommitProperties class. This class has the final describe, describeShort, fullMessage, id, idAbbrev, shortMessage, time, userEmail, and userName fields. The actual property values are injected into these fields by using constructor injection. The source code of the CommitProperties class looks as follows:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class CommitProperties {

    private final String describe;
    private final String describeShort;
    private final String fullMessage;
    private final String id;
    private final String idAbbrev;
    private final String shortMessage;
    private final String time;
    private final String userEmail;
    private final String userName;

    @Autowired
    public CommitProperties(@Value("${git.commit.id.describe}") String describe,
                            @Value("${git.commit.id.describe-short}") String describeShort,
                            @Value("${git.commit.message.full}") String fullMessage,
                            @Value("${git.commit.id}") String id,
                            @Value("${git.commit.id.abbrev}") String idAbbrev,
                            @Value("${git.commit.message.short}") String shortMessage,
                            @Value("${git.commit.time}") String time,
                            @Value("${git.commit.user.email}") String userEmail,
                            @Value("${git.commit.user.name}") String userName) {
        this.describe = describe;
        this.describeShort = describeShort;
        this.fullMessage = fullMessage;
        this.id = id;
        this.idAbbrev = idAbbrev;
        this.shortMessage = shortMessage;
        this.time = time;
        this.userEmail = userEmail;
        this.userName = userName;
    }

    //Getters are omitted for the sake of clarity
}

Third, we need to create the GitProperties class. This class has the final branch, build, commit, dirty, remoteOriginUrl, and tags fields. The actual field values (or objects) are injected into these fields by using constructor injection. The source code of the GitProperties class looks as follows:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class GitProperties {

    private String branch;
    private final BuildProperties build;
    private final CommitProperties commit;
    private final boolean dirty;
    private final String remoteOriginUrl;
    private final String tags;

    @Autowired
    public GitProperties(@Value("${git.branch}") String branch,
                         BuildProperties build,
                         CommitProperties commit,
                         @Value("${git.dirty}") boolean dirty,
                         @Value("${git.remote.origin.url}") String remoteOriginUrl,
                         @Value("${git.tags}") String tags) {
        this.branch = branch;
        this.build = build;
        this.commit = commit;
        this.dirty = dirty;
        this.remoteOriginUrl = remoteOriginUrl;
        this.tags = tags;
    }

    //Getters are omitted for the sake of clarity
}

Let’s move on and write the Git commit information to a log file.

Writing the Git Commit Information to a Log File

Our next step is to write the Git commit information information to a log file. Let’s find out how we can do that.

First, we have to add toString() methods to BuildProperties, CommitProperties, and GitProperties classes and implement these methods by using the ToStringBuilder class.

The source code of the BuildProperties class looks as follows:

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class BuildProperties {

    private final String time;
    private final String userEmail;
    private final String userName;

    @Autowired
    public BuildProperties(@Value("${git.build.time}") String time,
                           @Value("${git.build.user.email}") String userEmail,
                           @Value("${git.build.user.name}") String userName) {
        this.time = time;
        this.userEmail = userEmail;
        this.userName = userName;
    }

    //Getters are omitted for the sake of clarity

    @Override
    public String toString() {
        return new ToStringBuilder(this)
                .append("time", this.time)
                .append("userEmail", this.userEmail)
                .append("userName", this.userName)
                .toString();
    }
}

The source code of the CommitProperties class looks as follows:

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class CommitProperties {

    private final String describe;
    private final String describeShort;
    private final String fullMessage;
    private final String id;
    private final String idAbbrev;
    private final String shortMessage;
    private final String time;
    private final String userEmail;
    private final String userName;

    @Autowired
    public CommitProperties(@Value("${git.commit.id.describe}") String describe,
                            @Value("${git.commit.id.describe-short}") String describeShort,
                            @Value("${git.commit.message.full}") String fullMessage,
                            @Value("${git.commit.id}") String id,
                            @Value("${git.commit.id.abbrev}") String idAbbrev,
                            @Value("${git.commit.message.short}") String shortMessage,
                            @Value("${git.commit.time}") String time,
                            @Value("${git.commit.user.email}") String userEmail,
                            @Value("${git.commit.user.name}") String userName) {
        this.describe = describe;
        this.describeShort = describeShort;
        this.fullMessage = fullMessage;
        this.id = id;
        this.idAbbrev = idAbbrev;
        this.shortMessage = shortMessage;
        this.time = time;
        this.userEmail = userEmail;
        this.userName = userName;
    }

    //Getters are omitted for the sake of clarity

    @Override
    public String toString() {
        return new ToStringBuilder(this)
                .append("describe", this.describe)
                .append("describeShort", this.describeShort)
                .append("fullMessage", this.fullMessage)
                .append("id", this.id)
                .append("idAbbrev", this.idAbbrev)
                .append("shortMessage", this.shortMessage)
                .append("time", this.time)
                .append("userEmail", this.userEmail)
                .append("userName", this.userName)
                .toString();
    }
}

The source code of the GitProperties class looks as follows:

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class GitProperties {

    private String branch;
    private final BuildProperties build;
    private final CommitProperties commit;
    private final boolean dirty;
    private final String remoteOriginUrl;
    private final String tags;

    @Autowired
    public GitProperties(@Value("${git.branch}") String branch,
                         BuildProperties build,
                         CommitProperties commit,
                         @Value("${git.dirty}") boolean dirty,
                         @Value("${git.remote.origin.url}") String remoteOriginUrl,
                         @Value("${git.tags}") String tags) {
        this.branch = branch;
        this.build = build;
        this.commit = commit;
        this.dirty = dirty;
        this.remoteOriginUrl = remoteOriginUrl;
        this.tags = tags;
    }

    //Getters are omitted for the sake of clarity

    @Override
    public String toString() {
        return new ToStringBuilder(this)
                .append("branch", this.branch)
                .append("build", this.build)
                .append("commit", this.commit)
                .append("dirty", this.dirty)
                .append("remoteOriginUrl", this.remoteOriginUrl)
                .append("tags", this.tags)
                .toString();
    }
}

Second, we have to write the Git commit information to a log file when our application is started. We can do this by following these steps:

  1. Add a static final Logger field to the GitProperties class and create a new Logger object by using the LoggerFactory class.
  2. Add a writeGitCommitInformationToLog() method to the GitProperties class and annotate it with the @PostConstruct annotation. This ensures that the Spring container invokes this method after it has injected the dependencies of the created bean object into it.
  3. Implement the writeGitCommitInformationToLog() method by writing the Git commit information to a log file.

After we have made these changes, the source code of the GitProperties class looks as follows:

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

@Component
public class GitProperties {

    private static final Logger LOGGER = LoggerFactory.getLogger(GitProperties.class);

    private String branch;

    private final BuildProperties build;

    private final CommitProperties commit;

    private final boolean dirty;

    private final String remoteOriginUrl;

    private final String tags;

    @Autowired
    public GitProperties(@Value("${git.branch}") String branch,
                         BuildProperties build,
                         CommitProperties commit,
                         @Value("${git.dirty}") boolean dirty,
                         @Value("${git.remote.origin.url}") String remoteOriginUrl,
                         @Value("${git.tags}") String tags) {
        this.branch = branch;
        this.build = build;
        this.commit = commit;
        this.dirty = dirty;
        this.remoteOriginUrl = remoteOriginUrl;
        this.tags = tags;
    }

    //Getters are omitted for the sake of clarity

    @Override
    public String toString() {
        return new ToStringBuilder(this)
                .append("branch", this.branch)
                .append("build", this.build)
                .append("commit", this.commit)
                .append("dirty", this.dirty)
                .append("remoteOriginUrl", this.remoteOriginUrl)
                .append("tags", this.tags)
                .toString();
    }

    @PostConstruct
    public void writeGitCommitInformationToLog() {
        LOGGER.info("Application was built by using the Git commit: {}", this);
    }
}

When we start our web application, we should find the following information from its log file:

INFO  - GitProperties - Application was built by using the Git commit: net.petrikainulainen.spring.trenches.config.GitProperties@47044f7d[
	branch=master,
	build=net.petrikainulainen.spring.trenches.config.BuildProperties@7b14c61[
		time=19.04.2015 @ 00:47:37 EEST,
		userEmail=petri.kainulainen@gmail.com,
		userName=Petri Kainulainen
	],
	commit=net.petrikainulainen.spring.trenches.config.CommitProperties@8fcc534[
		describe=1bdfe9c-dirty,
		describeShort=1bdfe9c-dirty,
		fullMessage=Declare PropertySourcesPlaceholderConfigurer in a static @Bean method,
		id=1bdfe9cf22b550a3ebe170f60df165e5c26448f9,
		idAbbrev=1bdfe9c,
		shortMessage=Declare PropertySourcesPlaceholderConfigurer in a static @Bean method,
		time=16.04.2015 @ 23:35:23 EEST,
		userEmail=petri.kainulainen@gmail.com,
		userName=Petri Kainulainen
	],
	dirty=true,
	remoteOriginUrl=git@github.com:pkainulainen/spring-from-the-trenches.git,
	tags=
]

That information is written into a single line, but I formatted it a bit because I wanted to make it easier to read.

Let’s find out how we can return the Git commit information as JSON.

Returning the Git Commit Information as JSON

Earlier we created a controller class that returns the runtime configuration of a web application as JSON. Let’s modify this class to return the Git commit information as JSON. We can do this by following these steps:

  1. Add a final GitProperties field to the PropertiesController class.
  2. Inject the GitProperties bean into the created controller bean by using constructor injection.
  3. Create a controller method that processes GET requests send to the url ‘/version’ and implement it by returning the GitProperties object.

The source code of the PropertiesController looks as follows:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
final class PropertiesController {

    private final ApplicationProperties applicationProperties;
    private final GitProperties gitProperties;

    @Autowired
    PropertiesController(ApplicationProperties applicationProperties, 
						 GitProperties gitProperties) {
        this.applicationProperties = applicationProperties;
        this.gitProperties = gitProperties;
    }

    @RequestMapping(value = "/config", method = RequestMethod.GET)
    ApplicationProperties getAppConfiguration() {
        return applicationProperties;
    }

    @RequestMapping(value = "/version", method = RequestMethod.GET)
    GitProperties getVersion() {
        return gitProperties;
    }
}

When we send a GET request to the url ‘/version’, our controller method returns the following JSON:

{
	"branch":"master",
	"build":{
		"time":"19.04.2015 @ 00:47:37 EEST",
		"userEmail":"petri.kainulainen@gmail.com",
		"userName":"Petri Kainulainen"
	},
	"commit":{
		"describe":"1bdfe9c-dirty",
		"describeShort":"1bdfe9c-dirty",
		"fullMessage":"Declare PropertySourcesPlaceholderConfigurer in a static @Bean method",
		"id":"1bdfe9cf22b550a3ebe170f60df165e5c26448f9",
		"idAbbrev":"1bdfe9c",
		"shortMessage":"Declare PropertySourcesPlaceholderConfigurer in a static @Bean method",
		"time":"16.04.2015 @ 23:35:23 EEST",
		"userEmail":"petri.kainulainen@gmail.com",
		"userName":"Petri Kainulainen"
	},
	"dirty":true,
	"remoteOriginUrl":"git@github.com:pkainulainen/spring-from-the-trenches.git",
	"tags":""
}

We shouldn’t allow everyone to access the Git commit information of our application. If this would be a real life application, we should ensure that only administrators can access this information.

Let’s move on and summarize what we learned from this blog post.

Summary

This blog post has taught us three things:

  • We can extract the build-time state from a Git repository by using the Maven Git Commit Id plugin.
  • We can write the Git commit information to a log file by overriding the toString() methods of the properties bean classes and writing the property values of these beans to a log file after the property values have been injected into them.
  • We can return the Git commit information as JSON by creating a controller method that returns the “root” properties bean object (GitProperties).

Petri Kainulainen

Petri is passionate about software development and continuous improvement. He is specialized in software development with the Spring Framework and is the author of Spring Data book.
Subscribe
Notify of
guest

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

0 Comments
Inline Feedbacks
View all comments
Back to top button