Try managing Java libraries with AWS CodeArtifact

Introduction

AWS CodeArtifact, which was GA in June 2020, a person who wants a local repository to manage a common library for a large project, but does not bother to set up EC2 or a container for that purpose ... It seems to be very easy to use.

So, this time, I will actually touch AWS CodeArtifact to manage Java libraries. Since it's a big deal, I'll try running it with Maven and Gradle, and even try CI / CD of the library linked with CodeArtifact. You will always be accompanied by the Official User Guide (At the time of writing this article, it is still in Japanese. There is no manual)

Also, as stated in the user guide, please note that the CLI cannot be used unless it is updated to the latest version.

First, try creating a repository

Search for CodeArtifact from the service selection, go to the top screen, and press the" Create repository "button. Then, you will be asked for the name of the repository as shown below, so give it a textual name.

キャプチャ1.png

Next, create a domain. I'm not going to cross-account this time, so for the time being, select "this AWS account" and give it a textual domain name. Set a customer master key for security.

キャプチャ2.png

Do nothing on the confirmation screen and "create repository"!

キャプチャ3.png

This completes the creation. Next, let's connect. For connection settings, press "View connection procedure" on the details screen of the created repository.

キャプチャ4.png

When you select the package manager client, the following is displayed. For some reason, the hyphen of id is doubled. I don't know if it's something like this or a bug. A mystery.

キャプチャ5.png

Obtain the first token and save the long password that appears on the screen. The token expiration date is up to 12 hours and can be shortened, but it doesn't seem to be longer. A little inconvenient ...

Try using it in a Maven project

Let's create the following library in Maven project.

Library storage

Greeting.java

Greeting.java


package com.amazonaws.lambda.demo;

public class Greeting {
    private String greetings;
    private String name = null;
	private String version = null;

    public String getGreetings() {
        return greetings;
    }
	public void setGreetings(String greetings) {
        this.greetings = greetings;
    }
    public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getVersion() {
		return version;
	}
	public void setVersion(String version) {
		this.version = version;
	}

    public Greeting(String greetings, String name, String version) {
		this.greetings = greetings;
		
		if(name != null) {
			this.name = name;
		}
		if(version != null) {
			this.version = version;
		}
		else {
			this.version = "1.0";
		}
	}
}
pom.xml

pom.xml


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.amazonaws.lambda</groupId>
  <artifactId>Greeting</artifactId>
  <version>1.0.0</version>
  <packaging>jar</packaging>
  <name>Greeting</name>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.6.0</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
          <encoding>UTF-8</encoding>
          <forceJavacCompilerUse>true</forceJavacCompilerUse>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>3.2.1</version>
        <executions>
          <execution>
            <phase>package</phase>
            <goals>
              <goal>shade</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
  
  <distributionManagement>
    <repository>
      <id>test-domain-TestRepository</id>
      <name>test-domain-TestRepository</name>
      <url>[Repository URL]</url>
    </repository>
  </distributionManagement>
</project>
settings.xml

settings.xml


<?xml version="1.0" encoding="UTF-8"?>
<settings>
  <servers>
    <server>
      <id>test-domain-TestRepository</id>
      <username>aws</username>
      <password>[↑ Tokens paid out by acquiring tokens]</password>
    </server>
  </servers>
</settings>

Now, if you mvn deploy and SUCCESS with this ...

キャプチャ6.png

The repository has been added successfully!

Use of the library

The code on the user side should look like the following. setting.xml can be the same as ↑. It's supposed to work with Lambda, so it's doing something weird.

In the Lambda handler function, of the class defined in the ↑ library

Greeting greetingBody = new Greeting("Hello", "Taro", null);

Is using.

LambdaFunctionHandler.java

LambdaFunctionHandler.java


package com.amazonaws.lambda.demo;

import java.util.logging.Logger;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.LambdaLogger;
import com.amazonaws.services.lambda.runtime.RequestHandler;

import java.util.Map;
import java.util.HashMap;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class LambdaFunctionHandler implements RequestHandler<Map<String, Object> , Response> {
	private static final Logger LOG = Logger.getLogger(LambdaFunctionHandler.class.getName());

    @Override
    public Response handleRequest(Map<String, Object> input, Context context) {
    	LambdaLogger logger = context.getLogger();
    	logger.log("received: " + input);

        Greeting greetingBody = new Greeting("Hello", "Taro", null);
        
        // TODO: implement your handler	
        return new Response.Builder()
        		.setStatusCode(200)
        		.setHeaders(new HashMap<String, String>(){{put("Content-Type","application/json");}})
        		.setObjectBody(greetingBody)
        		.build();
    }

}

Response.java

Response.java


package com.amazonaws.lambda.demo;

import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Collections;
import java.util.Map;

//import org.apache.log4j.Logger;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.annotation.JsonInclude;


public class Response {
	private final int statusCode;
	private final Map<String, String> headers;
	private final boolean isBase64Encoded;
	private final String body;
		
	public int getStatusCode() {
		return statusCode;
	}

	public String getBody() {
		return body;
	}

	public Map<String, String> getHeaders() {
		return headers;
	}

	// API Gateway expects the property to be called "isBase64Encoded" => isIs
	public boolean isIsBase64Encoded() {
		return isBase64Encoded;
	}

	public static Builder builder() {
		return new Builder();
	}

	public static class Builder {
//		private static final Logger LOG = Logger.getLogger(Response.Builder.class);

		private static final ObjectMapper objectMapper = new ObjectMapper()
				.setSerializationInclusion(JsonInclude.Include.NON_NULL)
				.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);

		private int statusCode = 200;
		private Map<String, String> headers = Collections.emptyMap();
		private String rawBody;
		private Object objectBody;
		private byte[] binaryBody;
		private boolean base64Encoded;

		public Builder setStatusCode(int statusCode) {
			this.statusCode = statusCode;
			return this;
		}

		public Builder setHeaders(Map<String, String> headers) {
			this.headers = headers;
			return this;
		}

		/**
		 * Builds the {@link Response} using the passed raw body string.
		 */
		public Builder setRawBody(String rawBody) {
			this.rawBody = rawBody;
			return this;
		}

		/**
		 * Builds the {@link Response} using the passed object body
		 * converted to JSON.
		 */
		public Builder setObjectBody(Object objectBody) {
			this.objectBody = objectBody;
			return this;
		}

		/**
		 * Builds the {@link Response} using the passed binary body
		 * encoded as base64. {@link #setBase64Encoded(boolean)
		 * setBase64Encoded(true)} will be in invoked automatically.
		 */
		public Builder setBinaryBody(byte[] binaryBody) {
			this.binaryBody = binaryBody;
			setBase64Encoded(true);
			return this;
		}

		/**
		 * A binary or rather a base64encoded responses requires
		 * <ol>
		 * <li>"Binary Media Types" to be configured in API Gateway
		 * <li>a request with an "Accept" header set to one of the "Binary Media
		 * Types"
		 * </ol>
		 */
		public Builder setBase64Encoded(boolean base64Encoded) {
			this.base64Encoded = base64Encoded;
			return this;
		}

		public Response build() {
			String body = null;
			if (rawBody != null) {
				body = rawBody;
			} else if (objectBody != null) {
				try {
					body = objectMapper.writeValueAsString(objectBody);
				} catch (JsonProcessingException e) {
//					LOG.error("failed to serialize object", e);
					throw new RuntimeException(e);
				}
			} else if (binaryBody != null) {
				body = new String(Base64.getEncoder().encode(binaryBody), StandardCharsets.UTF_8);
			}
			return new Response(statusCode, headers, base64Encoded, body);
		}
	}
	
    public Response(int statusCode, Map<String, String> headers, boolean isBase64Encoded, String body) {
    	this.statusCode = statusCode;
    	this.headers = headers;
    	this.isBase64Encoded = isBase64Encoded;
        this.body = body;
    }
}

The key is the following part of pom.xml. Definition of library dependencies stored in CodeArtifact and connection settings.

    <dependency>
      <groupId>com.amazonaws.lambda</groupId>
      <artifactId>Greeting</artifactId>
      <version>1.0.0</version>
    </dependency>
  <profiles>
    <profile>
      <id>test-domain-TestRepository</id>
      <activation>
        <activeByDefault>true</activeByDefault>
      </activation>
      <repositories>
        <repository>
          <id>test-domain-TestRepository</id>
          <name>test-domain-TestRepository</name>
          <url>[Repository URL]</url>
        </repository>
      </repositories>
    </profile>
  </profiles>
pom.xml

pom.xml


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.amazonaws.lambda</groupId>
  <artifactId>LambdaTest3</artifactId>
  <version>1.0.0</version>
  <packaging>jar</packaging>
  <name>LambdaTest3</name>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <profiles>
    <profile>
      <id>test-domain-TestRepository</id>
      <activation>
        <activeByDefault>true</activeByDefault>
      </activation>
      <repositories>
        <repository>
          <id>test-domain-TestRepository</id>
          <name>test-domain-TestRepository</name>
          <url>[Repository URL]</url>
        </repository>
      </repositories>
    </profile>
  </profiles>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.6.0</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
          <encoding>UTF-8</encoding>
          <forceJavacCompilerUse>true</forceJavacCompilerUse>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>3.2.1</version>
        <executions>
          <execution>
            <phase>package</phase>
            <goals>
              <goal>shade</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>com.amazonaws</groupId>
        <artifactId>aws-java-sdk-bom</artifactId>
        <version>1.11.256</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>com.amazonaws</groupId>
      <artifactId>aws-lambda-java-events</artifactId>
      <version>1.3.0</version>
    </dependency>
    <dependency>
      <groupId>com.amazonaws</groupId>
      <artifactId>aws-lambda-java-core</artifactId>
      <version>1.1.0</version>
    </dependency>
    
    <dependency>
      <groupId>com.amazonaws.lambda</groupId>
      <artifactId>Greeting</artifactId>
      <version>1.0.0</version>
    </dependency>
  </dependencies>
</project>

Now, when you mvn package, the library is firmly imported and the build is completed. If you have changed settings.xml from the default path, be sure to specify the `` `-s``` option.

Try using it in a Gradle project

This time, I will try to port what I built with Maven with Gradle build. Do not change the contents of the Java source.

Library storage

Create the following build.gradle and `` `gradlew publish```.

build.gradle


plugins {
    id 'maven-publish'
    id 'java-library'
}

repositories {
    jcenter()
}

dependencies {
    testImplementation 'junit:junit:4.12'
}

publishing {
  publications {
      mavenJava(MavenPublication) {
          groupId = 'com.amazonaws.lambda'
          artifactId = 'Greeting'
          version = '1.0.1'
          from components.java
      }
  }
  repositories {
      maven {
          url '[Repository URL]'
          credentials {
              username "aws"
              password "[↑ Tokens paid out by acquiring tokens]"
          }
      }
  }
}

The following error is displayed, but the upload itself seems to be working.

> Task :publishMavenJavaPublicationToMavenRepository
Cannot upload checksum for module-maven-metadata.xml. Remote repository doesn't support sha-512. Error: Could not PUT 'https://test-domain-xxxxxxxxxxx.d.codeartifact.ap-northeast-1.amazonaws.com/maven/TestRepository/com/amazonaws/lambda/Greeting/maven-metadata.xml.sha512'. Received status code 400 from server: Bad Request

Use of the library

The library user sets build.gradle as follows. As with the library side, the Java source does not need to be changed.

To create FatJar, run it with `` `gradlew shadowJarinstead of gradle build```.

build.gradle



plugins {
    id 'com.github.johnrengelman.shadow' version '2.0.3'
    id 'java-library'
}

repositories {
    jcenter()

    maven {
        url '[Repository URL]'
        credentials {
            username "aws"
            password "[↑ Tokens paid out by acquiring tokens]"
        }
    }
}

dependencies {
    implementation platform('com.amazonaws:aws-java-sdk-bom:1.11.256')
    implementation 'com.amazonaws:aws-lambda-java-events:1.3.0'
    implementation 'com.amazonaws:aws-lambda-java-core:1.1.0'
    runtimeOnly 'com.amazonaws:aws-lambda-java-log4j2:1.2.0'
    implementation 'com.amazonaws.lambda:Greeting:1.0.0'

    testImplementation 'junit:junit:4.12'
}

jar {
    manifest {
        attributes "Main-Class" : "LambdaFunctionHandler.java"
    }
}

It can be confirmed that it works not only with Maven but also with Gradle.

You want to CI / CD even a shared library

I don't know if there is a request. Of course, Code Artifact can also be incorporated into Code Build.

The integration with CodeBuild is described in This section of the User Guide. As it says, let's attach the following permissions to the CodeBuild service role to publish.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "codeartifact:GetAuthorizationToken",
                "codeartifact:ReadFromRepository",
                "codeartifact:GetRepositoryEndpoint",
                "codeartifact:PublishPackageVersion",
                "codeartifact:PutPackageMetadata",
                "sts:GetServiceBearerToken"
            ],
            "Resource": "*"
        }
    ]
}

The point is buildspec.yml, but before that, let's review the settings.xml we created this time. Since it is not so much that the token acquisition is done manually every time CI / CI is turned, the `` `password``` tag is set as follows.

      <password>${env.CODEARTIFACT_TOKEN}</password>

Then, let's define buildspec.yml as follows. By the way, the aws codeartifact part does not work if you execute it as described in the user guide. Note that it will not work without `` `--query authorizationToken```. This option isn't even listed in the CLI documentation (https://docs.aws.amazon.com/cli/latest/reference/codeartifact/get-authorization-token.html), so it's pretty mysterious ... .. For the time being, if this option is not added, the time stamp will be output and it will not be the expected environment variable.

buildspec.yml


version: 0.2
 
phases:
  install:
    runtime-versions:
      java: corretto8
    commands:
      - pip install --upgrade pip
      - pip install --upgrade awscli
  pre_build:
    commands:
      - export CODEARTIFACT_TOKEN=$(aws codeartifact get-authorization-token --domain test-domain --domain-owner [Account ID] --query authorizationToken --output text)
  build:
    commands:
      - echo Build started on `date`
      - mvn -s ./settings.xml deploy
      - echo Build ended on `date`
cache:
  paths:
    - '/root/.m2/**/*'

The library's CI / CD pipeline is now complete!

Recommended Posts

Try managing Java libraries with AWS CodeArtifact
Try DB connection with Java
Try gRPC with Java, Maven
Try using Redis with Java (jar)
Using Java with AWS Lambda-Eclipse Preparation
Try bidirectional communication with gRPC Java
Try running AWS X-Ray in Java
Java libraries
Using Java with AWS Lambda-Implementation-Check CloudWatch Arguments
Using Java with AWS Lambda-Implementation-Stop / Launch EC2
Let's try WebSocket with Java and javascript!
Try using the Wii remote with Java
Try Java 8 Stream
Try to link Ruby and Java with Dapr
How to use Java framework with AWS Lambda! ??
Try to implement TCP / IP + NIO with JAVA
Roughly try Java 9
Try debugging a Java program with VS Code
Submit a job to AWS Batch with Java (Eclipse)
How to deploy Java to AWS Lambda with Serverless Framework
Try connecting to AzureCosmosDB Emulator for Docker with Java
Try building Java into a native module with GraalVM
Try DI with Micronaut
Install java with Homebrew
Try create with Trailblazer
Change seats with java
Install Java with Ansible
Try WebSocket with jooby
Switch java with direnv
Try WildFly with Docker
Download Java with Ansible
Let's scrape with Java! !!
Build Java with Wercker
Try Java return value
Endian conversion with JAVA
Regularly post imaged tweets on Twitter with AWS Lambda + Java
Log aggregation and analysis (working with AWS Athena in Java)
[Beginner] Try to make a simple RPG game with Java ①
Create a SlackBot with AWS lambda & API Gateway in Java
Try developing a containerized Java web application with Eclipse + Codewind
Try Spark Submit to EMR using AWS SDK for Java