【AWS CodeBuild】TerraformでCodeBuild BuildProjectを作成してみた【Terraform】

# BuildProjectを Terraform で構築する

## 試したバージョン

|Name|Version|
|-|-|
|terraform|1.1.5|
|aws|4.0|

## 必要リソース

CodeBuild でビルドを行う際に作成が必要となるAWSリソースは以下の通り

- CodeBuild ビルドプロジェクト
- S3 バケット or CodeCommit リポジトリ
- IAM ロール
- (オプション)CloudWatchLogs ロググループ,ログストリーム

## 手順

`公式の手順`を参考にterraform tfファイルを作成していきます
[AWS CodeBuild - 開始方法](https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/getting-started.html)

### 1. ファイル準備

ビルド対象のソースファイルを作成します
ディレクトリ構成が以下の通りになるよう、公式手順を見ながら作成してください

```shell
$ tree tutorial/
tutorial/
├── buildspec.yaml
├── pom.xml
└── src
    ├── main
    │   └── java
    │       └── MessageUtil.java
    └── test
        └── java
            └── TestMessageUtil.java

5 directories, 4 files
```

### 2. S3 バケット作成

`ソースファイルの読み込み元として利用`するインプット用バケット、
`ビルド完了したアーティファクトの保管先`となるアウトプット用バケットの2つを作成します

input,outputを同じバケットにすることもできますが、分かりやすくするために2つ作成します

input,output用バケットはそれぞれ1つずつだけでいいと思うので、こちらはmoduleには含めず
`resource`で宣言します

```s:s3.tf
# input用バケット
resource "aws_s3_bucket" "input" {
  bucket = "tutorial-codebuild-input"
}

# output用バケット
resource "aws_s3_bucket" "output" {
  bucket = "tutorial-codebuild-output"
}
```

### 3. IAMロール作成

ビルドプロジェクトがビルドする際に利用するIAMロールを作成します

IAMポリシーに関しては、ビルドプロジェクトの仕様によって付与する権限が大幅に変わるため、
自力で作成すると権限の過不足が発生しやすいと思います
コンソールで作成すると、IAMロールを自動で作成/権限付与を行ってくれるので、
この点ではTerraformだと不便、、

一応、IAMロールに必要な権限は、以下の通りです

- S3 バケットに対するアクセス権限
  - inputバケットへの読み取り権限
  - outputバケットへの書き込み権限
- CloudWatchLogに対するアクセス権限
  - ロググループ作成権限
  - ログストリーム作成権限

#### tfファイル

```s:iam.tf
# IAM Role

resource "aws_iam_role" "codebuild_iam_role" {
  name = "CodeBuildServiceRole"
  assume_role_policy = data.aws_iam_policy_document.codebuild_assume_role_policy.json
}

# IAM Policy Document

data "aws_iam_policy_document" "codebuild_assume_role_policy" {
  statement {
    effect = "Allow"
    actions = ["sts:AssumeRole"]

    principals {
      type = "Service"
      identifiers = ["codebuild.amazonaws.com"]
    }
  }
}

# IAM Policy

resource "aws_iam_policy" "codebuild_base_policy" {
  name = "CodeBuildBasePolicy"
  policy = file("./codebuild_base_policy.json")
}

resource "aws_iam_policy" "codebuild_ro_policy" {
  name = "CodeBuildReadOnlyPolicy"
  policy = file("./codebuild_ro_policy.json")
}

# Policy Attachment

resource "aws_iam_role_policy_attachment" "codebuild_policy_attachment01" {
  role = aws_iam_role.codebuild_iam_role.name
  policy_arn = aws_iam_policy.codebuild_base_policy.arn
}

resource "aws_iam_role_policy_attachment" "codebuild_policy_attachment02" {
  role = aws_iam_role.codebuild_iam_role.name
  policy_arn = aws_iam_policy.codebuild_ro_policy.arn
}
```

IAMロールを一つと、ポリシーを2つ作成し、それぞれアタッチメントで関連付けを行っています

ポリシーのJSONは以下の通りです]
`<your aws project id>` はご自身のAWS ProjectIDに置き換えてください
input用、output用`bucket名`も、それぞれご自身が作成したものに合わせてください

```json:codebuild_base_policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Resource": [
        "*"
      ],
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ]
    },
    {
      "Effect": "Allow",
      "Resource": [
        "arn:aws:s3:::codepipeline-ap-northeast-1-*"
      ],
      "Action": [
        "s3:PutObject",
        "s3:GetObject",
        "s3:GetObjectVersion",
        "s3:GetBucketAcl",
        "s3:GetBucketLocation"
      ]
    },
    {
      "Effect": "Allow",
      "Resource": [
        "arn:aws:s3:::tutorial-codebuild-output",
        "arn:aws:s3:::tutorial-codebuild-output/*"
      ],
      "Action": [
        "s3:PutObject",
        "s3:GetBucketAcl",
        "s3:GetBucketLocation"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "codebuild:CreateReportGroup",
        "codebuild:CreateReport",
        "codebuild:UpdateReport",
        "codebuild:BatchPutTestCases",
        "codebuild:BatchPutCodeCoverages"
      ],
      "Resource": [
        "arn:aws:codebuild:ap-northeast-1:<your aws project id>:report-group/*"
      ]
    }
  ]
}
```

- CloudWatchへの書き込み権限
- CodePipelineへの権限(多分今回は不要)
- Output用バケットへの書き込み権限
- codebuildレポートへの買い込み権限

を付与していますね
もう一つ、ReadOnlyは以下の通りです

```json:codebuild_ro_policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:GetObjectVersion"
      ],
      "Resource": [
        "arn:aws:s3:::tutorial-codebuild-input/MessageUtil.zip",
        "arn:aws:s3:::tutorial-codebuild-input/MessageUtil.zip/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "s3:ListBucket",
        "s3:GetBucketAcl",
        "s3:GetBucketLocation"
      ],
      "Resource": [
        "arn:aws:s3:::tutorial-codebuild-input"
      ]
    }
  ]
}
```

- Input用のバケットに対してGetBucket権限等
- Inputバケット内のソースZipファイルに対してGetObject権限等

だけ付与しています

ちなみに、このポリシーどうやって作ったかというと、
コンソールで試しに作ってみたときに自動作成されたロールのポリシーをまねして作りました(本末転倒)

### 4. Build Project 作成

buildproject本体をterraformで作っていきます

```s:codebuild.tf
resource "aws_codebuild_project" "build_project01" {
  name = "build-project01"

  service_role  = aws_iam_role.codebuild_iam_role.arn

  source {
    type = "S3"
    location = "tutorial-codebuild-input/MessageUtil.zip"
  }

  environment {
    compute_type = "BUILD_GENERAL1_SMALL"
    image = "aws/codebuild/amazonlinux2-x86_64-standard:3.0"
    type = "LINUX_CONTAINER"
  }

  artifacts {
    type = "S3"
    location = "tutorial-codebuild-output"
  }

  logs_config {
    cloudwatch_logs {
      group_name = "/aws/codebuild/build-project01"
      stream_name = "build-log"
    }
  }
}
```

大きく分けて `source`, `artifact`, `environment`の3つの項目を詳しく設定します

- name : ビルドプロジェクトの名前を付けます
- service_role : さっき作ったIAMのarnを指定します
- source : input sourceに関する設定です
  - type : `S3`, `CODECOMMIT`, `GITHUB` などから選べます 今回はS3です
  - location : ↑で指定したソースの場所を指定します S3の場合はバケット名/ソースzipファイル とします
- artifact : output artifactに関する設定です
  - type : source 同様 output先のタイプを指定します outputは`S3`, `CODEPIPELINE`, `NO_ARTIFACTS`が指定できます
  - location : output先の場所を指定します 今回はさっき作ったS3バケットです
- environment : ビルド処理を行うマシンに関する設定です
  - compute_type : マシンのスペックです Amazon Linuxを利用する場合、`GENERAL1_SMALL`しか選べないようです
  - image : マシンのイメージです 今回は`aws/codebuild/amazonlinux2-x86_64-standard:3.0`にしました
  - type : Linux以外にも`Windows`マシンが選べたりします
- logs_config : CloudWatch Logsへのログ書き込みに関する設定です オプションの項目です
  - group_name : ロググループ名を指定します
  - stream_name : ログストリーム名を指定します
  - status : ログの書き込みをやめたいときは、statusを追加して値をDISABLEDにします

### 作成

`$ terraform plan` → `$ terraform apply`
で作成します
問題なければビルドプロジェクトが作成されます


### ソースファイル圧縮&アップロード

```shell
# カレントディレクトリ確認
$ ls
buildspec.yaml  pom.xml  src

# ソールファイル群をzip圧縮します
$ zip MessageUtil.zip *

# s3のインプット用バケットにアップロードします
$ aws s3 cp MessageUtil.zip s3://tutorial-codebuild-input
upload: ./MessageUtil.zip to s3://tutorial-codebuild-input/MessageUtil.zip
```

ソースのアップが完了したら、
作成されたビルドプロジェクトの詳細ページにアクセスして、ビルドも実行してみましょう
問題なくartifactがoutput用バケットに作成されていればこのチュートリアルは完了です

## 感想

CodeBuildのBuildProjectと、それに関連するリソースをterraformで作ってみました

BuildProjectのリソースを作成するためにIAMロールの指定が必須なのですが、
このIAMロールまでterraformでリソース作成するのは結構骨が折れました、、

IAMロールだけコンソールで作成、
もしくは、お試しでBuildProjectをコンソールで作成してみて、
自動生成されたIAMロールのARNをコピーしてcodebuild.tfにハードコーディングでもいいんじゃないかな
と思いました

この記事が誰かほかのterraformerのお役に立てれば幸いです

次は codepipelineをterraformで作る に挑んでみたいと思います