Publishing POM files in Maven Central leveraging on Amazon Route53.
Introduction.
One of the problems I faced when working on Monorepos was the errors produced when building internal Maven projects isolatedly. For example, in our previous tutorial, I needed to provide the entire project using the SAM CLI because the internal Lambda function needs the parent POM file to compile and generate the native Linux executable. On the other hand, I need the parent POM because I want to control common properties in all Maven projects, like Java versions and libraries. I wouldn’t say I like the idea that Maven projects within Monorepo use different frameworks or versions of them.
In this tutorial, I’ll show you how to publish a parent POM file in the Maven Central repository so your Open Source dependencies will be accessible when compiling a Maven child project without needing the parent POM file.
Tooling.
To complete this guide, you’ll need the following tools:
- Git.
- AWS CLI (version 2.11.+)
- AWS SAM CLI (version 1.97.+)
- OpenJDK (version 21.+). You can use SDKMAN to install it.
- Apache Maven (version 3.9.+). You can use SDKMAN to install it.
- Docker engine. You can use Docker Desktop or Colima.
- Java IDE. You can use IntelliJ.
Note: You can download the project’s source code from my GitHub repository, which contains all the configurations made in this tutorial.
Maven Central Repository.
The first thing you must do is to create your account in the Maven Central Repository before deploying artifacts to it.
Then, go to your Namespaces and create a new one for your OSS project. It’s recommended to use a reverse DNS. Our project is called Hiperium City, and I own the <hiperium.city> domain name, I used it to create the namespace:
Copy the verification key value and follow the next instructions.
Amazon Route53.
Go to your Hosted Zones and select your domain name:
Add a new record. Select TXT in the record type list, and paste the Verification Key in the value field:
You can change the Time-to-Live (TTL) value to fit your needs. As you can see, this value is evaluated in seconds.
After creating this record, you must see the following message on your main Hosted Zone page:
The message indicates that I must wait 60 seconds for Route53 to propagate this value in the authoritative DNS servers. But I chose 300 seconds in my record’s TTL, so I must wait at least 5 minutes before continuing.
You can use the following command to validate if your domain name contains your TXT register:
dig TXT hiperium.city
If so, continue to the following section.
Maven Central Validation.
Back to your Namespaces tab option in your Maven Central Repository account and click the <Verify Namespace> button. Accept the popup message, and you must see a yellow message indicating the verification is pending:
After another few minutes, reload the page to see if the domain name was verified. You must see a green message with the text “Verified” like this:
It’s time to deploy our POM files into the Maven Central Repository.
Required POM definitions.
I don’t have an entire package to deploy into Maven Central. I only need to publish the parent POM files for Cities and Tasks projects. You can go to my Git repository to find a folder called <java>, which contains the parent POMs I will publish.
Before deploying your POM file, verify you have the required tags: coordinates (GAV), name, description, URL, license, SCM, and developer information. Your POM must look like this:
<parent>
<groupId>city.hiperium</groupId>
<artifactId>parent-pom</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>city-functions-parent</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<name>${project.groupId}:${project.artifactId}</name>
<description>Parent POM for Lambda functions in the City Admin project.</description>
<url>https://github.com/hiperium/city-commons</url>
<licenses>
<license>
<name>GNU Affero General Public License v3.0</name>
<url>https://www.gnu.org/licenses/agpl-3.0.html</url>
<distribution>repo</distribution>
</license>
</licenses>
<developers>
<developer>
<name>Andres Solorzano</name>
<email>contact@hiperium.com</email>
<organization>Hiperium Inc.</organization>
<organizationUrl>https://hiperium.com</organizationUrl>
</developer>
</developers>
<scm>
<url>https://github.com/hiperium/city-commons</url>
<connection>scm:git:git://github.com/hiperium/city-commons.git</connection>
<developerConnection>scm:git:ssh://git@github.com/hiperium/city-commons.git</developerConnection>
</scm>
In the <build> section, you must add the Sonatype and GPG plugins before deploying artifacts to the Maven Central Repository:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.sonatype.central</groupId>
<artifactId>central-publishing-maven-plugin</artifactId>
<version>0.5.0</version>
<extensions>true</extensions>
<configuration>
<publishingServerId>central</publishingServerId>
</configuration>
</plugin>
</plugins>
</build>
Then, go to your account settings in the Maven Central Repository and generate a User Token to validate your credentials when deploying:
Copy your credentials in the <servers> section inside your Maven <settings.xml> file:
<server>
<id>central</id>
<username><!-- your token username --></username>
<password><!-- your token password --></password>
</server>
Finally, execute the following Maven command to deploy the POM file:
./mvnw deploy -f city-data-function/pom.xml
I got the following error concerning a GPG key to sign the artifact:
If you get the same error as I do, you must create a GPG key and perform additional configurations to the previous ones.
GnuPG.
I run the GPG command as follows to know the version I’m using:
Then, execute the following command to create a new GPG key.
gpg --gen-key
The command will ask for your name and email to generate a new key. It also asks for a passphrase to complete the procedure:
Verify your GPG key was effectively created with the following command:
gpg --list-keys
The value of your key ID is the last 8 digits in the text shown in the <pub> row. You can also verify this by executing the following command that lists the keys in a hexadecimal format:
gpg --list-signatures --keyid-format 0xshort
Add the key value shown in the <sig 3> row in your <settings.xml> file using a new profile:
<profile>
<id>cental</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<gpg.keyname><!-- your GPG key--></gpg.keyname>
</properties>
</profile>
Note the definition of the <gpg.keyname> property variable. This must be used when updating your POM file concerning the GPG plugin as follows:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
<configuration>
<keyname>${gpg.keyname}</keyname>
<gpgArguments>
<arg>--pinentry-mode</arg>
<arg>loopback</arg>
</gpgArguments>
</configuration>
</execution>
</executions>
</plugin>
The <gpgArguments> block lets you enter the key’s passphrase from the terminal. This is useful if you don’t have an interactive one.
If you don’t want to enter your passphrase every time you wish to perform a Maven deployment, add a server configuration in your <settings.xml> file:
IMPORTANT: Maven will warn you when using this method because you expose sensitive information in a known file.
<server>
<id>gpg.passphrase</id>
<passphrase><!-- your GPG passphrase --></passphrase>
</server>
Then, modify your <maven-gpg-plugin> configuration as follows:
<configuration>
<keyname>${gpg.keyname}</keyname>
<passphrase>${gpg.passphrase}</passphrase>
</configuration>
With this alternative, you don’t need to enter the passphrase every time you execute a Maven deployment command.
It’s essential to publish your GPG public key to a GPG keyserver so the Maven Central Repository can verify the signature of the files you’re trying to publish. Execute the following command to perform this requirement:
gpg --keyserver keyserver.ubuntu.com \
--send-keys <your-public-key>
If you get an error like this: “gpg: keyserver send failed: No keyserver available,” try to execute the following commands to use a standard server resolver in the <dirmngr> process, which is part of the GPG command:
gpgconf --kill dirmngr
dirmngr --daemon --standard-resolver
Then, try to execute the GPG command to export your public key. You can verify if your key was sent to the GPG server executing the following:
gpg --keyserver keyserver.ubuntu.com \
--recv-keys <your-public-key>
You must see something like the following message:
You have configured and published a new GPG key, which Maven Central must use to validate your signed files before publishing.
Maven Central deployment.
Try to execute the Maven deploy command again to see the results. The command will deploy your artifact(s) to the Maven Central Repository and wait for its validation:
After a few seconds, you must see the following output if everything is ok:
Then, go to your Deployments option in your Sonatype account and verify if a deployment needs validation before proceeding with the publishing:
If you agree, click the <Publish> button to accept the procedure. Then, you must see that your deployment is publishing:
You can click the <Refresh> button to verify your successful publishing. After some minutes, you will see the following status:
This means your Java files were published successfully in the Maven Central Repository. Therefore, you can easily reference these POM files from your internal projects.
New Code Structure.
As I said in the introduction, the idea is to modify the project we used in my previous tutorial. So, let’s start deleting the parent POMs because they are currently deployed in Maven Central, so we don’t need them locally. The structure of the Lambda project must be as follows:
I moved the SAM config files from the root directory to the <city-data-function> directory. There is no patent POM file in the root directory, so I also moved the Maven Wrapper artifacts into the <functions> directory because we have Java projects in that directory.
Now, we can build our Lambda function using SAM as usual, and Maven can resolve POM files from Maven Central from the builder container. So first, delete the <city> directory from your Maven local directory:
rm -rf ~/.m2/repository/city/
Then, build the Lamba function using the following Maven command from inside the <functions> directory:
./mvnw clean compile -f functions/pom.xml
Notice that Maven tries to download the required dependencies from the Central Repository:
It’s working!! Now, let’s try to build our Lambda function using AWS SAM, as we did in the previous tutorial.
SAM Build & Deployment.
Let’s execute the following command from the project’s root directory to use the automated deployment:
./start.sh
Select option 2, which executes the SAM build and then the SAM deployment commands:
The build process will be performed inside a Docker container managed by SAM. It doesn’t matter if you have the Hiperium dependencies in your Maven local repository because the builder container will try to download the required Maven dependencies from scratch. As we have our parent POMs in the Central Repository, this won’t be a problem:
As you can see, the builds succeed, meaning that all dependencies were downloaded in the first place.
Our automated script then deploys the native Linux executable to AWS and loads test data to our DynamoDB table on AWS:
Open a new terminal tab/window and execute the following command project’s root directory to retrieve the Lambda function’s logs in real time:
sam logs -n "CityDataFunction" \
--stack-name "city-data-function-dev" \
--tail \
--profile "your-aws-profile"
Return to your original terminal tab/window and invoke your Lambda function deployed in AWS using the following command:
aws lambda invoke \
--function-name "city-data-function" \
--payload file://functions/city-data-function/src/test/resources/requests/lambda-valid-id-request.json \
--cli-binary-format raw-in-base64-out \
--profile "your-aws-profile" response.json
Then, go to your Lamba’s logs terminal tab/window, and you must see an output like this:
Finally, print the content of the <response.json> to validate the response. You must see a content like the following:
And that’s it!! Now, we have deployed our Lambda function to AWS using SAM. More importantly, our parent POMs are deployed in the Maven Central Repository, so we don’t need to specify them to the build tools to avoid compilation errors.
I hope this article has been helpful to you, and I’ll see you in the next one.
Thanks for reading.