AWS CodeArtifactをMavenから使ってみる

# What's？

* 2020年6月に登場した、AWS CodeArtifactを使ってみたい
* そもそも、AWS CodeArtifactとはどういうものか？
* 試しに使ってみる

ということを調べたり、やってみたりする記事です。

最後の「使ってみる」の部分には、Apache Mavenを利用します。つまり、Javaでのサンプルということになりますね。


# AWS CodeArtifactとは

AWSの提供する、マネージドなアーティファクトリポジトリサービスです。

[AWS CodeArtifact](https://aws.amazon.com/jp/codeartifact/)

[AWS CodeArtifact Documentation](https://docs.aws.amazon.com/codeartifact/index.html)

[AWS CodeArtifact によるソフトウェアパッケージ管理](https://aws.amazon.com/jp/blogs/news/software-package-management-with-aws-codeartifact/)

[AWSがソフトウェアパッケージのリポジトリサービス「AWS CodeArtifact」正式リリース。npmやMavenなどのパッケージを共有可能](https://www.publickey1.jp/blog/20/awsaws_codeartifactnpmmavenpypi.html)

平たく言うと、ソースコードをビルドしたりして生成された、パッケージを管理してくれるものになります。

対応しているのは、このあたりですね。

* npm
* PyPI
* Maven

[AWS CodeArtifact Concepts / Package](https://docs.aws.amazon.com/codeartifact/latest/ug/codeartifact-concepts.html#welcome-concepts-package)

要するに、JavaScript、Python、Java、の3つですと。

AWS CodeArtifactを使わない場合は、以下のような選択肢から環境を構築していることかと思います（GitHub Packagesは違いますが）。

* [Sonatype Nexus](https://www.sonatype.com/product-nexus-repository)
* [JFrog Artifactory](https://jfrog.com/artifactory/)
* [Verdaccio](https://verdaccio.org/)
* [pypiserver](https://github.com/pypiserver/pypiserver)
* [GitHub Packages](https://docs.github.com/ja/packages/publishing-and-managing-packages/about-github-packages)

この管理を、AWSに任せることができるというサービスですね。


# AWS CodeArtifactにおける要素

「AWSが提供するマネージドなパッケージリポジトリだ」と書くとあっさりしていますが、いろいろと概念、要素があるようなので理解しておく必要がありそうですね。

順にちょっと見ていってみましょう。

概要自体は、こちらに書いてあります。

[AWS CodeArtifact Concepts](https://docs.aws.amazon.com/codeartifact/latest/ug/codeartifact-concepts.html)


## リポジトリ

パッケージをまとめたもので、おそらく他のパッケージマネージャーを知っていると、1番理解のしやすい概念かと。

[AWS CodeArtifact Concepts / Repository](https://docs.aws.amazon.com/codeartifact/latest/ug/codeartifact-concepts.html#welcome-concepts-repository)

リポジトリは、各種パッケージマネージャーのクライアントツール（npm、PyPI、Mavenなど）のエンドポイントとなります。

アクセス制御を行うためのポリシーを設定することもできます。

また、リポジトリはPolyglotであり、複数の言語で利用することができます。言語ごとにリポジトリを作らなくても良い、と言っていますね。

リポジトリは、後述のドメイン内に作ることになります。ドメインなしのリポジトリは存在できません。

> You cannot create a repository without a domain. When you use the CreateRepository API to create a repository, you must specify a domain name. You cannot move a repository from one domain to another.

[Domain overview](https://docs.aws.amazon.com/codeartifact/latest/ug/domain-overview.html)

また、アップストリームのリポジトリも持つことができたり、外部リポジトリへの接続を行うことができます。

[Add an external connection](https://docs.aws.amazon.com/codeartifact/latest/ug/external-connection.html)

外部接続を追加すると、AWS CodeArtifactリポジトリにパッケージがない場合に、パブリックなnpm RegistryやMaven Repositoryなどの外部リポジトリからパッケージを取得することができます。


## パッケージ / パッケージバージョン / アセット

パッケージは、パッケージマネージャーを使ってソフトウェアをインストールするために必要となるメタデータです。

[AWS CodeArtifact Concepts / Package](https://docs.aws.amazon.com/codeartifact/latest/ug/codeartifact-concepts.html#welcome-concepts-package)

また、パッケージを構成するアセット（`.tgz`ファイルや`JAR`ファイルなど）を含めたものが、パッケージバージョンです。

[AWS CodeArtifact Concepts / Package version](https://docs.aws.amazon.com/codeartifact/latest/ug/codeartifact-concepts.html#welcome-concepts-package-version)

[AWS CodeArtifact Concepts / Asset](https://docs.aws.amazon.com/codeartifact/latest/ug/codeartifact-concepts.html#welcome-concepts-asset)


## ドメイン

リポジトリを集約する概念のようです。

[AWS CodeArtifact Concepts / Domain](https://docs.aws.amazon.com/codeartifact/latest/ug/codeartifact-concepts.html#welcome-concepts-domain)

パッケージのアセットとメタデータの保存先は、実はリポジトリではなくドメインです。ドメイン内にいくつリポジトリがあっても、ドメイン内に同じバージョン、アセットのパッケージは1回だけ保存されます。

また、AWS KMSを使った暗号化も行われます。

パッケージの取得は、リポジトリを介して行うことになります。

ドメインはアカウント内で複数持つことができますが、通常は単一のドメインを勧めているようです。

> Although an organization can have multiple domains, we recommend a single production domain that contains all published artifacts so that teams can find and share packages across their organization.

ドメインの内容を見ると

* リポジトリのストレージ＋暗号化
* リポジトリ間のメタデータのコピー（※アップストリームリポジトリに関連）
* アクセス管理（リポジトリにアクセスすることができるアカウント制限や、ポリシーの管理）
* リポジトリの名前空間の提供

といったところがドメインの主な機能なようですね。

[Domain overview](https://docs.aws.amazon.com/codeartifact/latest/ug/domain-overview.html)


## アップストリームリポジトリ

[AWS CodeArtifact Concepts / Upstream repository](https://docs.aws.amazon.com/codeartifact/latest/ug/codeartifact-concepts.html#welcome-concepts-upstream)

アップストリームリポジトリに関しては、個別のドキュメントを見た方がわかりやすい気がします。

[Working with upstream repositories in CodeArtifact](https://docs.aws.amazon.com/codeartifact/latest/ug/repos-upstream.html)

AWS CodeArtifactリポジトリが、他のAWS CodeArtifactリポジトリをアップストリーム（上流）のリポジトリにすることができる、というものです。

こういう使い方も可能みたいですね。

> If an upstream repository has an external connection to a public repository, the repositories that are downstream from it can pull packages from that public repository. For example, suppose that the repository my-repo has an upstream repository named upstream, and upstream has an external connection to a public npm repository. In this case, a package manager that is connected to my-repo can pull packages from the npm public repository.

アップストリームリポジトリに対してパブリックな外部リポジトリへの接続を定義し、ダウンストリームリポジトリからアップストリームリポジトリ経由でパブリックリポジトリからパッケージを取得する、といったケースですね。


# 始め方（ざっくり）

AWS CodeArtifactの使う時のざっくりした流れを書きます。

まあ、ここのまとめなんですけど。

[Getting started using the AWS CLI](https://docs.aws.amazon.com/codeartifact/latest/ug/getting-started-cli.html)


## AWS CodeArtifact側

1. ドメインを作成する
1. リポジトリを作成する

外部リポジトリへの接続定義は、任意でしょう。


## パッケージマネージャーのクライアント側

1. AWS CodeArtifactへログインする
2. パッケージマネージャーのクライアントの設定に、リポジトリの情報を追加する
3. 使う

最初にログインがあるのがポイントで、トークンを発行して環境変数に設定する、などの手順を踏む必要があります。

[Authentication with npm](https://docs.aws.amazon.com/codeartifact/latest/ug/npm-auth.html)

[Configure pip without the login command](https://docs.aws.amazon.com/codeartifact/latest/ug/python-configure-without-pip.html)

[Using Maven / Pass an auth token using an environment variable](https://docs.aws.amazon.com/codeartifact/latest/ug/env-var.html)


# 使ってみる

と、前置きはこれくらいにして、そろそろ使っていってみましょう。

AWS CodeArtifactでリポジトリを作って、Java（Maven）で使ってみる、というのを今回のシナリオにしてみます。

今回の環境は、こちらです。

```shell
$ aws --version
aws-cli/2.0.34 Python/3.7.3 Linux/4.15.0-112-generic botocore/2.0.0dev38
```

AWSのクレデンシャルは、環境変数で設定しているものとします。

```shell
$ export AWS_ACCESS_KEY_ID=.....
$ export AWS_SECRET_ACCESS_KEY=.....
$ export AWS_DEFAULT_REGION=.....
```


# AWS CodeArtifactのリポジトリを作る

こちらを参考に。

[Getting started using the AWS CLI](https://docs.aws.amazon.com/codeartifact/latest/ug/getting-started-cli.html)

なお、普段ならこの手のものはTerraformで構築するのですが、TerraformがまだAWS CodeArtifactには対応していないので、AWS CLIでやっていきます。


> （余談）Terraformでのサポートに関するissue
> [Add AWS CodeArtifact service support](https://github.com/terraform-providers/terraform-provider-aws/issues/13714)

AWS CLIでの、AWS CodeArtifactに関するリファレンスはこちら。

[codeartifact](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/codeartifact/index.html)

最初に、AWS KMS CMKを作成します。

```shell
$ aws kms create-key
$ aws kms create-alias --alias-name alias/code-artifact-key --target-key-id [作成したAWS KMS（CMK）のARN]
```

今回は、専用のキーにしました。キーを指定しない場合は、AWSが管理するキーが使われるようです。

そして、このCMKを使ってドメインを作ります。ドメイン名は、ドキュメントのそのままですが、`my-domain`にしました。

```shell
$ aws codeartifact create-domain --domain my-domain --encryption-key alias/code-artifact-key
```

次に、リポジトリを作成します。リポジトリを作成するには、ドメイン名を指定する必要があります。

```shell
$ aws codeartifact create-repository --domain my-domain --repository my-repo
```

リポジトリ名も、ドキュメントそのまま`my-repo`にしました。

ドキュメントにある`-domain-owner`というオプションは完全に無視しましたが、今回はそのまま作成したAWSアカウントIDが使われます。

ドキュメントでは、他にも外部リポジトリへの接続やアップストリームなどの例がありますが、今回はここまでで。

作成したドメイン、リポジトリの情報を確認してみましょう。

```shell
$ aws codeartifact describe-domain --domain my-domain
{
    "domain": {
        "name": "my-domain",
        "owner": "[AWSアカウントID]",
        "arn": "arn:aws:codeartifact:ap-northeast-1:[AWSアカウントID]:domain/my-domain",
        "status": "Active",
        "createdTime": "2020-07-26T14:32:35.003000+09:00",
        "encryptionKey": "arn:aws:kms:ap-northeast-1:[AWSアカウントID]:key/a6529646-1a8a-4772-a28a-b62c1e82992c",
        "repositoryCount": 1,
        "assetSizeBytes": 0
    }
}


$ aws codeartifact describe-repository --domain my-domain --repository my-repo
{
    "repository": {
        "name": "my-repo",
        "administratorAccount": "[AWSアカウントID]",
        "domainName": "my-domain",
        "domainOwner": "[AWSアカウントID]",
        "arn": "arn:aws:codeartifact:ap-northeast-1:[AWSアカウントID]:repository/my-domain/my-repo",
        "upstreams": [],
        "externalConnections": []
    }
}
```

これで、準備は完了です。


# Mavenから使う

では、Mavenから使ってみましょう。

ドキュメントは、こちらを参考に。

[Using CodeArtifact with Maven](https://docs.aws.amazon.com/codeartifact/latest/ug/using-maven.html)

今回の環境は、こちらです。

```shell
$ java --version
openjdk 11.0.8 2020-07-14
OpenJDK Runtime Environment (build 11.0.8+10-post-Ubuntu-0ubuntu118.04.1)
OpenJDK 64-Bit Server VM (build 11.0.8+10-post-Ubuntu-0ubuntu118.04.1, mixed mode, sharing)


$ mvn --version
Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 11.0.8, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "4.15.0-112-generic", arch: "amd64", family: "unix"
```

こちらを見ながら、やっていってきましょう。

[Use CodeArtifact with mvn](https://docs.aws.amazon.com/codeartifact/latest/ug/maven-mvn.html)


## `settings.xml`の作成と、認証トークンの取得

まずは`settings.xml`を用意します。今回は、このサンプル専用に作成することにしました。

`settings.xml`

```shell
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
                      https://maven.apache.org/xsd/settings-1.0.0.xsd">
    <servers>
        <server>
            <id>codeartifact</id>
            <username>aws</username>
            <password>${env.CODEARTIFACT_TOKEN}</password>
        </server>
    </servers>
</settings>
```

ユーザー名は`aws`みたいですね。パスワードは、環境変数として設定します。

こんな感じですね。

```shell
$ export CODEARTIFACT_TOKEN=`aws codeartifact get-authorization-token --domain my-domain --query authorizationToken --output text`
```

[Pass an auth token using an environment variable](https://docs.aws.amazon.com/codeartifact/latest/ug/env-var.html)

注意事項にもありますが、この認証トークンの有効期限は最大で12時間だそうです。これが切れると、都度取得する必要があります。

> CodeArtifact authorization tokens are valid for a default period of 12 hours. You can change how long a token is valid using the --durationSeconds argument (max authorization period is 12 hours). When the authorization period expires, you must call get-authorization-token to get another token. The authorization period begins after get-authorization-token is called.

毎日取得してね、という感じですね。


## アーティファクトをリポジトリにアップロードする

続いて、アーティファクトをリポジトリにアップロードしてみます。というわけで、簡単なライブラリを作ってみましょう。

`pom.xml`

```xml
<?xml version="1.0" encoding="UTF-8"?>
<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.github.charon.r13b</groupId>
    <artifactId>library</artifactId>
    <version>0.0.1</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <repositories>
        <repository>
            <id>codeartifact</id>
            <name>codeartifact</name>
            <url>https://my-domain-[AWSアカウントID].d.codeartifact.ap-northeast-1.amazonaws.com/maven/my-repo/</url>
        </repository>
    </repositories>

    <distributionManagement>
        <repository>
            <id>codeartifact</id>
            <name>codeartifact</name>
            <url>https://my-domain-[AWSアカウントID].d.codeartifact.ap-northeast-1.amazonaws.com/maven/my-repo</url>
        </repository>
    </distributionManagement>

    <dependencies>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.11</version>
        </dependency>
    </dependencies>
</project>
```

今回の例では`repositories/repository`は不要なのですが、なんとなく。少なくとも、`distributionManagement`の方の`repository`があればOKです。

Apache Commons Langは、なんとなく依存する外部ライブラリとして使っておきました。

ドキュメントを見た時の以下のURLの解釈に迷ったのですが、

```xml
<url>https://my-domain-domain-owner-id.d.codeartifact.us-west-2.amazonaws.com/maven/my-repo/</url>
```

こう読めばいいんですね。

```xml
<url>https://[AWS CodeArtifactのドメイン名]-[ドメインのOwner id（AWSアカウントID）].d.codeartifact.[リージョン].amazonaws.com/maven/[リポジトリ名]/</url>
```

あとは、`mvn deploy`で。今回は、`settings.xml`はオプションで指定しています。

```shell
$ mvn deploy -s settings.xml
```

`BUILD SUCCESS`になれば、アーティファクトがアップロードされているので、確認してみましょう。

```shell
$ aws codeartifact list-packages --domain my-domain --repository my-repo
{
    "packages": [
        {
            "format": "maven",
            "namespace": "com.github.charon.r13b",
            "package": "library"
        }
    ]
}
```

OKです。


## アップロードされたアーティファクトを使ってみる

最後に、アップロードされたアーティファクトを使ってみます。

`pom.xml`

```xml
<?xml version="1.0" encoding="UTF-8"?>
<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.github.charon.r13b</groupId>
    <artifactId>app</artifactId>
    <version>0.0.1</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <repositories>
        <repository>
            <id>codeartifact</id>
            <name>codeartifact</name>
            <url>https://my-domain-[AWSアカウントID].d.codeartifact.ap-northeast-1.amazonaws.com/maven/my-repo/</url>
        </repository>
    </repositories>

    <dependencies>
        <dependency>
            <groupId>com.github.charon.r13b</groupId>
            <artifactId>library</artifactId>
            <version>0.0.1</version>
        </dependency>
    </dependencies>
</project>
```

こちらは利用側なので、`repositories/repository`の定義があればOKです。

```xml
    <repositories>
        <repository>
            <id>codeartifact</id>
            <name>codeartifact</name>
            <url>https://my-domain-[AWSアカウントID].d.codeartifact.ap-northeast-1.amazonaws.com/maven/my-repo/</url>
        </repository>
    </repositories>
```

`depencencies/dependency`には、先ほどアップロードしたアーティファクトを含めています。

```xml
    <dependencies>
        <dependency>
            <groupId>com.github.charon.r13b</groupId>
            <artifactId>library</artifactId>
            <version>0.0.1</version>
        </dependency>
    </dependencies>
```

サンプルコードは、こんな感じで。

`src/main/java/com/github/charon/r13b/app/App.java`

```java
package com.github.charon.r13b.app;

import com.github.charon.r13b.library.MessageService;

public class App {
    public static void main(String... args) {
        MessageService messageService = new MessageService("★★★", "★★★");
        System.out.println(messageService.decorate("Hello World!!"));
    }
}
```

ビルドして、実行。`settings.xml`は、アーティファクトをアップロードした時に使ったものと、同じものを使用しています。

```shell
$ mvn compile exec:java -s settings.xml -Dexec.mainClass=com.github.charon.r13b.app.App
```

実行できました、と。

```shell
[INFO] --- exec-maven-plugin:1.6.0:java (default-cli) @ app ---
★★★Hello World!!★★★
```

なお、認証トークンがない状態だと、`401`になるのでアーティファクトを使うのにも認証が必要ですよ、と。

```shell
[ERROR] Failed to execute goal on project app: Could not resolve dependencies for project com.github.charon.r13b:app:jar:0.0.1: Failed to collect dependencies at com.github.charon.r13b:library:jar:0.0.1: Failed to read artifact descriptor for com.github.charon.r13b:library:jar:0.0.1: Could not transfer artifact com.github.charon.r13b:library:pom:0.0.1 from/to codeartifact (https://my-domain-[AWSアカウントID].d.codeartifact.ap-northeast-1.amazonaws.com/maven/my-repo/): Authentication failed for https://my-domain-[AWSアカウントID].d.codeartifact.ap-northeast-1.amazonaws.com/maven/my-repo/com/github/charon/r13b/library/0.0.1/library-0.0.1.pom 401 Unauthorized

## ココ
401 Unauthorized 
```

これで、確認ができました。


# ポリシーについて

今回、利用したIAMユーザーが`AdministratorAccess`ポリシーを持っていたのでなんでもやれているのですが、実際にはIAMユーザーでアーティファクトのアップロードの可否、参照の可否などを設定していくことになると思います。

ドメイン、リポジトリにそれぞれ設定できるので、見ておきましょう。

[Domain policies](https://docs.aws.amazon.com/codeartifact/latest/ug/domain-policies.html)

[Repository policies](https://docs.aws.amazon.com/codeartifact/latest/ug/repo-policies.html)


# 後片付け

最後に作成したリソースの後片付けをしておきます。

リポジトリとドメインの削除。

```shell
$ aws codeartifact delete-repository --domain my-domain --repository my-repo
$ aws codeartifact delete-domain --domain my-domain
```

AWS KMSのCMKの削除。

```shell
$ aws kms delete-alias --alias-name alias/code-artifact-key
$ aws kms schedule-key-deletion --key-id [AWS KMS（CMK）のキーID]
```