When troubleshooting issues in Adobe Experience Manager (AEM), the first step is often to identify which code version is deployed for the affected projects. However, OSGi bundle versions only provide a partial picture, lacking crucial details like the exact branch used. This becomes especially problematic when managing multiple tenants in the same environment or comparing code across different environments, which requires tedious navigation through CI/CD tool histories like Jenkins.
The Solution: Unlocking Git Build Transparency
To streamline development workflows and issue resolution, it’s essential to implement a solution that clearly displays vital Git information, including branch names and commit IDs. By accurately tracking and recording this data, teams like Dev/QA can verify code integrity before starting their tasks, ensuring a smoother and more efficient experience.
Power of git-commit-id plugin
The purpose of the git-commit-id plugin is to generate a property file containing Git build information every time a Maven build is executed. This file can then be utilized in our backend systems and exposed for tracking purposes.
Now, let’s delve into the steps for implementing and exposing this build information.
Integration of git-commit-id Plugin in POM.xml
We begin by adding and configuring the git-commit-id plugin in our project’s POM.xml file. This plugin will be responsible for generating the necessary property file containing Git build details during the Maven build process.
Development of Custom OSGi Service
Next, create a custom OSGi service within our Adobe Experience Manager (AEM) project. This service is designed to read the generated property file and extract the relevant build information. By doing so, we ensure that this data is readily available within our AEM environment.
Implementation of Sling Servlet
To expose the build information retrieved by the OSGi service, we developed a Sling Servlet. This servlet leverages the OSGi service to access the Git build details and then makes this information accessible through a designated endpoint. Through this endpoint, other components of our AEM project or external systems can easily access and utilize the build information as needed.
Let’s start with adding POM changes for the plugin.
Under core bundle POM in the plugin sections of build add an entry for git-commit-id plugin.
<plugin> <groupId>pl.project13.maven</groupId> <artifactId>git-commit-id-plugin</artifactId> <version>2.2.4</version> <executions> <execution> <id>get-the-git-infos</id> <goals> <goal>revision</goal> </goals> </execution> </executions> <configuration> <dotGitDirectory>${project.basedir}/.git</dotGitDirectory> <prefix>git</prefix> <verbose>false</verbose> <generateGitPropertiesFile>true</generateGitPropertiesFile> <generateGitPropertiesFilename>src/main/resources/my-project-git-response </generateGitPropertiesFilename> <format>json</format> <dateFormat>yyyy-MM-dd-HH-mm</dateFormat> <gitDescribe> <skip>false</skip> <always>false</always> <dirty>-dirty</dirty> </gitDescribe> <includeOnlyProperties> <includeOnlyProperty>git.branch</includeOnlyProperty> <includeOnlyProperty>git.build.time</includeOnlyProperty> <includeOnlyProperty>git.build.version</includeOnlyProperty> <includeOnlyProperty>git.commit.id</includeOnlyProperty> <includeOnlyProperty>git.commit.time</includeOnlyProperty> </includeOnlyProperties> </configuration> </plugin>
It gives us the ability to configure lots of options based on our needs and we can include the properties. Currently, we’ve only considered the git branch which will help us identify the branch name, the time it takes to complete the build process, the git build version, the commit ID, and the timestamp of the exact commit.
Since the file will be created under src/main/resources we want to make sure the resources entry is added in the build section. If not, we need to make sure the following entry exists there.
<resources> <resource> <directory>src/main/resources</directory> <filtering>true</filtering> </resource> </resources>
Now let’s create a bean class representing our Build Info.
@Data public class BuildInformation { @SerializedName("git.branch") private String branch; @SerializedName("git.build.time") private String buildTime; @SerializedName("git.build.version") private String buildVersion; @SerializedName("git.commit.id") private String commitId; @SerializedName("git.commit.id.abbrev") private String commitIdAbbrev; @SerializedName("git.commit.id.describe") private String commitIdDescribe; @SerializedName("git.commit.id.describe-short") private String commitIdDescribeShort; @SerializedName("git.commit.time") private String commitTime; private String aemVersion; private List<String> runModes; }
Our Service interface will look like this:
public interface BuildInformationService { BuildInformation getBuildInformation(); }
Finally, our Implementation class will look like this:
@Component(service = BuildInformationService.class) @ServiceDescription("BuildInformationService Implementation.") @Slf4j public class BuildInformationServiceImpl implements BuildInformationService { @Reference private ProductInfoProvider productInfoProvider; @Reference private SlingSettingsService slingSettingsService; public static final String GIT_INFO_FILE_PATH = "my-project-git-response.json"; private BuildInformation buildInformation; @Activate public void activate() { buildInformation = new BuildInformation(); try { final var inputStream = this.getClass().getClassLoader().getResourceAsStream(GIT_INFO_FILE_PATH); final var jsonString = IOUtils.toString(inputStream, StandardCharsets.UTF_8); final var gson = new Gson(); buildInformation = gson.fromJson(jsonString, BuildInformation.class); var productInfo = productInfoProvider.getProductInfo(); final var version = productInfo.getVersion().toString(); // Set additional fields not part of JSON response buildInformation.setAemVersion(version); buildInformation.setRunModes(slingSettingsService.getRunModes()); } catch (Exception e) { log.error("Error while generating build information"); } } @Override public BuildInformation getBuildInformation() { return buildInformation; } }
We are just reading the properties file created by the plugin and setting up the object.
ProductInfo provider API will help to get the AEM Version while the Sling setting service provides run modes information.
Now let’s create a sample servlet that will utilize this service and will print the information.
import com.google.gson.Gson; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.SlingHttpServletResponse; import org.apache.sling.api.servlets.SlingSafeMethodsServlet; import org.apache.sling.servlets.annotations.SlingServletPaths; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; import org.osgi.service.component.propertytypes.ServiceDescription; import javax.servlet.Servlet; import javax.servlet.ServletException; import java.io.IOException; @Component(service = Servlet.class) @ServiceDescription("This servlet will provide Build Information") @SlingServletPaths(BuildInformationServlet.BUILD_INFO_PATH) public class BuildInformationServlet extends SlingSafeMethodsServlet { public static final String BUILD_INFO_PATH = "/bin/my-project/buildinfo"; @Reference private BuildInformationService buildInformationService; @Override protected void doGet(final SlingHttpServletRequest request,final SlingHttpServletResponse response) throws ServletException, IOException { response.setContentType("application/json"); response.setCharacterEncoding("UTF-8"); final var writer = response.getWriter(); final var gson = new Gson(); final var jsonString = gson.toJson(buildInformationService.getBuildInformation()); writer.print(jsonString); writer.flush(); } }
After deploying the code and opening the service end point it will give response like this:
{ "git.branch": "feature/PROJECT-1234", "git.build.time": "2024-04-03-09-37", "git.build.version": "2.22.0-65-SNAPSHOT", "git.commit.id": "1fa29afb73e5b0cbfc90a2bb33db28741d98eec0", "git.commit.id.abbrev": "1fa29af", "git.commit.id.describe": "1fa29af-dirty", "git.commit.id.describe-short": "1fa29af-dirty", "git.commit.time": "2024-04-01-18-04", "aemVersion": "6.5.17.0", "runModes": [ "s7connect", "crx3", "author", "samplecontent", "crx3tar" ] }
Now that our Service is up and running, we can leverage it anywhere in our application. Let’s create a reusable component for displaying build information, powered by our trusty Sling model. This component can tap into the Service to fetch the necessary build details.
How to Include Additional Build Details
Want to include additional build details, such as Jenkins job names and build numbers? Easy! We can pass these attributes from Jenkins via command-line arguments. Then, we store these values in properties files, making them easily consumable by our Sling model, just like our Git information file.
The benefit of this solution is that it’s flexible, scalable, and reusable across multiple projects in a multitenant development environment.
Make sure to follow our Adobe blog for more Adobe solution tips and tricks!
Informative blog!!!!!!
This is useful blog for all