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.
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.
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.
Do nothing on the confirmation screen and "create repository"!
This completes the creation. Next, let's connect. For connection settings, press "View connection procedure" on the details screen of the created repository.
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.
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 ...
Let's create the following library in Maven project.
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
<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
<?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 ...
The repository has been added successfully!
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
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
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
<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.
This time, I will try to port what I built with Maven with Gradle build. Do not change the contents of the Java source.
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
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.
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