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:
- Spring From the Trenches: Injecting Property Values Into Configuration Beans describes why you should inject property values into configuration beans and helps you to do it.
- Spring From the Trenches: Returning Runtime Configuration as JSON describes how you can write the runtime configuration of a web application to a log file and return it as JSON.
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:
- Add the Maven Git Commit Id plugin to our build.
- Ensure that the revision goal of the Maven Git Commit Id plugin is invoked at the initialize phase of the default lifecycle.
- 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:
- Add a static final Logger field to the GitProperties class and create a new Logger object by using the LoggerFactory class.
- 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.
- 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:
- Add a final GitProperties field to the PropertiesController class.
- Inject the GitProperties bean into the created controller bean by using constructor injection.
- 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).
Reference: | Spring From the Trenches: Returning Git Commit Information as JSON from our JCG partner Petri Kainulainen at the Petri Kainulainen blog. |