【AWS】CodePipelineをTerraformで作成してみよう【Terrafrom】

# AWS CodePipeline を Terraform で構築

設定項目が難しかったのでメモがてら

## バージョン

| Name      | Version |
| --------- | ------- |
| terraform | 1.1.6   |
| aws       | 4.0.0   |

## 作成リソース

CodePipelineのPipelineを作成する際に必要となるAWSリソースは以下の通り

- Codepipeline パイプライン
- S3 バケット or CodeCommit リポジトリ
- IAMロール

## 手順

### 1. IAMロール作成

moduleでも、resourceでも、好きな方法でIAMロールを作成します
僕は[以前のCodeBuildの記事](https://qiita.com/cokemaniaIIDX/items/7daff3eb7bf77639dcec)を応用してIAMロールをmodule化したのでそれを使います

```s:codepipeline.tf
#=========================#
# CodePipeline - IAM Role #
#=========================#
# Piplen実行用role

module "codepipeline_role" {
  source = "../modules/aws/iam/iam_role"

  role_name = "CodePipelineServiceRole"
  role_description = "service role for codepipeline."

  trusted_role_services = [
    "codepipeline.amazonaws.com"
  ]

  policy_names = [
    "CodePipelineBasePolicy",
  ]

  policy_json_files = [
    "./policy_json_files/codepipeline_base_policy.json"
  ]
}
```

ポリシーは例によって、いったんコンソールで作成して、自動作成されたIAMをコピペします

```json:policy_json_files/codepipeline_base_policy.json
{
  "Statement": [
      {
          "Action": [
              "iam:PassRole"
          ],
          "Resource": "*",
          "Effect": "Allow",
          "Condition": {
              "StringEqualsIfExists": {
                  "iam:PassedToService": [
                      "cloudformation.amazonaws.com",
                      "elasticbeanstalk.amazonaws.com",
                      "ec2.amazonaws.com",
                      "ecs-tasks.amazonaws.com"
                  ]
              }
          }
      },
      {
          "Action": [
              "codecommit:CancelUploadArchive",
              "codecommit:GetBranch",
              "codecommit:GetCommit",
              "codecommit:GetRepository",
              "codecommit:GetUploadArchiveStatus",
              "codecommit:UploadArchive"
          ],
          "Resource": "*",
          "Effect": "Allow"
      },
      {
          "Action": [
              "codedeploy:CreateDeployment",
              "codedeploy:GetApplication",
              "codedeploy:GetApplicationRevision",
              "codedeploy:GetDeployment",
              "codedeploy:GetDeploymentConfig",
              "codedeploy:RegisterApplicationRevision"
          ],
          "Resource": "*",
          "Effect": "Allow"
      },
      {
          "Action": [
              "codestar-connections:UseConnection"
          ],
          "Resource": "*",
          "Effect": "Allow"
      },
      {
          "Action": [
              "elasticbeanstalk:*",
              "ec2:*",
              "elasticloadbalancing:*",
              "autoscaling:*",
              "cloudwatch:*",
              "s3:*",
              "sns:*",
              "cloudformation:*",
              "rds:*",
              "sqs:*",
              "ecs:*"
          ],
          "Resource": "*",
          "Effect": "Allow"
      },
      {
          "Action": [
              "lambda:InvokeFunction",
              "lambda:ListFunctions"
          ],
          "Resource": "*",
          "Effect": "Allow"
      },
      {
          "Action": [
              "opsworks:CreateDeployment",
              "opsworks:DescribeApps",
              "opsworks:DescribeCommands",
              "opsworks:DescribeDeployments",
              "opsworks:DescribeInstances",
              "opsworks:DescribeStacks",
              "opsworks:UpdateApp",
              "opsworks:UpdateStack"
          ],
          "Resource": "*",
          "Effect": "Allow"
      },
      {
          "Action": [
              "cloudformation:CreateStack",
              "cloudformation:DeleteStack",
              "cloudformation:DescribeStacks",
              "cloudformation:UpdateStack",
              "cloudformation:CreateChangeSet",
              "cloudformation:DeleteChangeSet",
              "cloudformation:DescribeChangeSet",
              "cloudformation:ExecuteChangeSet",
              "cloudformation:SetStackPolicy",
              "cloudformation:ValidateTemplate"
          ],
          "Resource": "*",
          "Effect": "Allow"
      },
      {
          "Action": [
              "codebuild:BatchGetBuilds",
              "codebuild:StartBuild",
              "codebuild:BatchGetBuildBatches",
              "codebuild:StartBuildBatch"
          ],
          "Resource": "*",
          "Effect": "Allow"
      },
      {
          "Effect": "Allow",
          "Action": [
              "devicefarm:ListProjects",
              "devicefarm:ListDevicePools",
              "devicefarm:GetRun",
              "devicefarm:GetUpload",
              "devicefarm:CreateUpload",
              "devicefarm:ScheduleRun"
          ],
          "Resource": "*"
      },
      {
          "Effect": "Allow",
          "Action": [
              "servicecatalog:ListProvisioningArtifacts",
              "servicecatalog:CreateProvisioningArtifact",
              "servicecatalog:DescribeProvisioningArtifact",
              "servicecatalog:DeleteProvisioningArtifact",
              "servicecatalog:UpdateProduct"
          ],
          "Resource": "*"
      },
      {
          "Effect": "Allow",
          "Action": [
              "cloudformation:ValidateTemplate"
          ],
          "Resource": "*"
      },
      {
          "Effect": "Allow",
          "Action": [
              "ecr:DescribeImages"
          ],
          "Resource": "*"
      },
      {
          "Effect": "Allow",
          "Action": [
              "states:DescribeExecution",
              "states:DescribeStateMachine",
              "states:StartExecution"
          ],
          "Resource": "*"
      },
      {
          "Effect": "Allow",
          "Action": [
              "appconfig:StartDeployment",
              "appconfig:StopDeployment",
              "appconfig:GetDeployment"
          ],
          "Resource": "*"
      }
  ],
  "Version": "2012-10-17"
}
```

今回の例では、S3に対する権限、CodeCommitに関する権限が入っていることを確認します
構築時権限エラーが出たときは正しくポリシーが設定されているか見ると解決できる場合が多いです

### 2. パイプライン作成

このパイプラインの書き方がなかなか癖つよで難しかったです

#### 基本の理解

- stage
- source_artifact

が必須項目となっているので、この2つを埋めていきます

stageとは、パイプライン上の各ステージを指します
`ソースステージ`、`ビルドステージ`、`デプロイステージ`
の3つがあります

source_artifactは、ソースステージやビルドステージで作成した`「アーティファクト」`を格納する`S3バケット`です

パイプラインの基本的な流れは↓の通り

1. ソースステージ : ソースコード(S3,CodeCommit,GitHubなど)の内容を引っ張ってきてアーティファクトとしてS3バケットに保存
2. ビルドステージ : 保存されたソースアーティファクトを用いてビルドプロジェクトを実行、ビルド完了したものをビルドアーティファクトとしてS3バケットに保存
3. デプロイステージ : 保存されたビルドアーティファクトを用いてS3,ECS,EC2などにデプロイする

ビルドステージを飛ばしてデプロイ
デプロイはせずビルドで止める
などもできます

#### tfファイル作成

今回は
ソース:CodeCommit
ビルド:CodeBuild
を使った例を紹介します

```s:codepipeline.tf
#=========================#
# CodePipeline - Pipeline #
#=========================#
resource "aws_codepipeline" "pipeline01" {
  name = "demo-pipeline01"
  role_arn = module.codepipeline_role.arn

  artifact_store {
    type = "S3"
    location = "demo-codebuild-input"
  }

  stage {
    name = "Source"

    action {
      name = "Source"
      category = "Source"
      owner = "AWS"
      provider = "CodeCommit"
      version = "1"
      output_artifacts = ["source_output"]

      configuration = {
        RepositoryName = "demo-module-repo"
        BranchName = "dev"
        PollForSourceChanges = "false"
      }
    }
  }

  stage {
    name = "Build"

    action {
      name = "Build"
      category = "Build"
      owner = "AWS"
      provider = "CodeBuild"
      input_artifacts = ["source_output"]
      output_artifacts = ["build_output"]
      version = "1"

      configuration = {
        ProjectName = aws_codebuild_project.buildproject01.name
      }
    }
  }
}
```

- **role_arn** : 先ほど作成したIAMロールのarnを指定します
- **artifact_store** : アーティファクトを保存するためのS3バケットを指定します
  - **type** : アーティファクトの保存先は今のところS3しか対応していないため、S3を入れるしかないです
  - **location** : バケット名を指定します
- **stage** : ソースステージ、ビルドステージ、デプロイステージのことです パイプラインの構成に従って任意の数用意します
  - **name** : ソースの場合`Source`, ビルドの場合`Build`, デプロイの場合`Deploy`を指定します
  - **action** : 最大の癖つよポイント ここで指定する内容はterraformのドキュメントには載ってなくて、AWSのドキュメントを参照する必要がある
  - [AWSのドキュメントはこちら](https://docs.aws.amazon.com/ja_jp/codepipeline/latest/userguide/action-reference.html)
    - **name,category** : 基本的にステージの名前と同じものを指定
    - **owner** : 今回はすべてAWSのサービスを利用しているので、`AWS`を指定します GitHubなどを利用する場合は`ThirdParty`を指定する用です
    - **provider** : AWSの場合サービス名を指定します GitHubの場合は、、ちょっとわからない
    - **version** : ↑のAWSドキュメントに記載されているものを入力 基本`1`が入ります
    - **input_artifacts, output_artifacts** : アーティファクトの入出力先を指定します
      ここで指定した文字列がsource_artifactで指定したS3バケット内にフォルダとして作成され、その中にアーティファクトが格納されるようになります
    - **configuration** : ソースやビルド、デプロイに関する内容の詳細を指定します ここの内容はすべてAWSのドキュメントを参照します
      - **RepositoryName** : CodeCommitを利用する場合はCodeCommitリポジトリのリポジトリ名を入力
      - **BranchName** : ブランチ名を指定
      - **PollForSourceChanges** : リポジトリの変更を検知して自動でパイプラインを実行するかどうか指定
      - **ProjectName** : CodeBuildを利用する場合はビルドプロジェクトの名前を指定 ここでは前回の記事で作成したCodeBuild名を入力


完成したら`$ terraform plan` `$ terraform apply`
を実行します

作成自体は速攻で完了しますね

## 完了

その他、細かい設定はTerraformのドキュメントとAWSのドキュメントをさらに深く読む必要がありますが、基本はこれでできるはずです
参考になれば幸いです