S3でprivateなmavenリポジトリを管理する

[mixiグループ Advent Calendar 2016](http://qiita.com/advent-calendar/2016/mixi)の14日目の記事です。

最近S3を使ったmavenリポジトリを導入してみたのでその時のメモを公開します。
[Gradle2.4からS3をリポジトリとして使うためのサポートが入っている](https://docs.gradle.org/2.4/release-notes#support-for-amazon-web-services-s3-backed-repositories)ので比較的かんたんに設定できました。

# ライブラリ側の設定
リポジトリに登録するために、ライブラリのリポジトリ設定、publish用のpluginの設定、アップロードされるS3の設定が必要になります。

## ライブラリのリポジトリ設定
ここではpluginにmaven-publishを使っていますが、他のpluginでも設定はできるはずなので別のpluginを使う場合は読み替えてください

```groovy
apply plugin: 'maven-publish'

publishing {
    repositories {
        maven {
            url "s3://someS3Bucket/maven2"
            credentials(AwsCredentials) {
                accessKey "someKey"
                secretKey "someSecret"
            }
        }
    }
    publications {
        maven(MavenPublication) {
            groupId = GROUP_ID
            artifactId = ARTIFACT_ID
            version = VERSION
            // ライブラリの依存をpomに反映
            pom.withXml {
                def dependenciesNode = asNode().appendNode('dependencies')
                configurations.compile.allDependencies.each { dependency ->
                    def dependencyNode = dependenciesNode.appendNode('dependency')
                    dependencyNode.appendNode('groupId', dependency.group)
                    dependencyNode.appendNode('artifactId', dependency.name)
                    dependencyNode.appendNode('version', dependency.version)
                }
            }

            // aarは直接指定
            artifact source: file("${project.buildDir}/outputs/aar/example-release.aar")
            // javadoc付きjarや、source付きjarを指定する必要があればartifactとして追記
        }
    }
}
```

## AWSの設定
AWSにもいくつかの設定が必要になります
### バケットの作成
当然ながらS3のバケットがなければファイルを置くことができないので作成します。
この時に静的ウェブサイトホスティングを有効にしておきます。
すでに存在しているバケットを利用する場合は必要ありません

### バケットにアクセスするためのIAMポリシーを作成する
ライブラリユーザー用の読み込み専用のポリシーとアップロード可能なポリシーの2つを作成します。

ライブラリユーザー用ポリシー

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "s3:GetObject",
            "Resource": [
                "arn:aws:s3:::someS3Bucket/*",
                "arn:aws:s3:::someS3Bucket"
            ]
        }
    ]
}
```
アップロード可能ユーザー用ポリシー

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "s3:*",
            "Resource": [
                "arn:aws:s3:::someS3Bucket/*",
                "arn:aws:s3:::someS3Bucket"
            ]
        }
    ]
}
```

### IAMユーザーを作成
ライブラリを利用するユーザーとライブラリを作成するユーザーの2つのユーザーを作成します
access_keyとsecretはこの後使うので必ず控えておきます。
先程作成したポリシーをそれぞれにアタッチします

IAMのユーザーからアクセス権限-アクセス権限の追加で設定したIAMポリシーをアタッチする
このタイミングでAWS CLIなどを利用してバケットへのアクセスが正常にできること(権限がない場合はアクセスができないことも)を確認しておくことをおすすめします

## ライブラリを使う側の設定
ライブラリを使う側は下記の設定のみで、aarを参照することが可能になります

```groovy
repositories {
    maven {
        url "s3://someS3Bucket/maven2"
        credentials(AwsCredentials) {
            accessKey "someKey" // read-only-key
            secretKey "someSecret"
        }
    }
}
dependencies {
    compile "com.example:lib:1.0.0"
}
```

ライブラリを使う側のsourceリポジトリにaccess keyを埋め込むことでユーザー管理をしなくてもライブラリを使う権限を持っている人はライブラリを参照できることになります。

# 事故防止のためのtips
ライブラリ側のリポジトリの設定を

```groovy
repositories {
    maven {
        url "s3://someS3Bucket/maven2"
        credentials(AwsCredentials) {
            accessKey "someKey"
            secretKey "someSecret"
        }
    }
}
```
とすると上で書いていますが、開発者が誤って手元のバイナリをpublishしないためにurlやaccessKey,secretKeyは直接埋め込まないで、gradle.propertiesにread-onlyなキーを記述し、その上でリリース作業はCIサーバー上からのみ行うようにすることをおすすめします。
なので、記述としては以下のようになると思います
gradle.properties

```properties
YOUR_REPOSITORY_URL=s3://someS3Bucket/maven2
REPOSITORY_ACCESS_KEY=someKey
REPOSITORY_SECRET_KEY=someSecret
```

```groovy
repositories {
    maven {
        url YOUR_REPOSITORY_URL
        credentials(AwsCredentials) {
            accessKey REPOSITORY_ACCESS_KEY
            secretKey REPOSITORY_SECRET_KEY
        }
    }
}
```
read-onlyなキーにすることで、手元から誤ってリリースしてしまうことがなくなります。
CIサービスからのリリースを想定しているので、上記設定をした上でCIサーバに環境変数として

```
ORG_GRADLE_PROJECT_REPOSITORY_ACCESS_KEY=someKey
ORG_GRADLE_PROJECT_REPOSITORY_SECRET_KEY=someSecret
```
を設定すると、環境変数で設定した値が優先的に選択されるので、リポジトリへのリリースをすることができます。
gradleの変数の優先順位はgradleの公式ドキュメントの[14章](http://gradle.monochromeroad.com/docs/userguide/tutorial_this_and_that.html)と[20章](http://gradle.monochromeroad.com/docs/userguide/build_environment.html)を読むといいと思います

# おわりに
まだ運用を始めたばかりですが、今のところは特に問題もなく運用できています。
アクセスコントロールをAWSに任せられるのは楽でいいですね。