理想を追い求めたCI/CDパイプラインをTerraformで実装するためのポイント(production環境編)

# はじめに
[前回の記事](https://qiita.com/neruneruo/items/0daf2b30c59582007c4e)の続編。
前回は、クロスアカウントで、ビルド環境でビルドしたコンテナイメージをstage環境にコピーする部分だったが、今回の範囲は、それをproduction環境にコピーする部分だ。

こうだったのが……
![crossaccountpipeline.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/81d433db-28ab-357e-2717-72dfeb4325ff.png)

こうじゃ！
![crossaccountpipeline2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/2330fbe7-cb64-129c-4e97-1c6bb7cc6f69.png)

前提知識としては、前回のパイプラインを理解できていること。
新しい概念は出てこないので、サクサク書いていく。

プロバイダの設定は前回と同じと考えてほしい。
本来だったらプロバイダを、dev, stage, production とするところを、簡略化するために今回も build_account, service_account としている。

また、ECRのコピー元を一意に特定するためにCommitIDを使っている。
このため、マージコミットはFast Forwardを使うようにする。Non Fast Forwardの場合は別の方法でCommitIDを連携する必要があるため注意が必要だ。

# 変更点
クロスアカウント設定等は前回と同様に行えばよい。
今回は、差分としてEventBridgeの設定部分を書いておく。

## EventBridgeの設定(build_account側)

```HCL
################################################################################
# EventBridge(Build Account)                                                   #
################################################################################
resource "aws_cloudwatch_event_rule" "codecommit_merge_build_account" {
  provider = aws.build_account

  name          = local.event_rule_name
  description   = "CodeCommit Merge Event Rule"
  event_pattern = data.template_file.event_pattern_build_account.rendered
}

resource "aws_cloudwatch_event_target" "event_bus_build_account" {
  provider = aws.build_account

  rule      = aws_cloudwatch_event_rule.codecommit_merge_build_account.name
  target_id = local.event_target_id
  arn       = "arn:aws:events:ap-northeast-1:${data.aws_caller_identity.service_account.account_id}:event-bus/default"
  role_arn  = aws_iam_role.eventbridge_build_account.arn
}

data "template_file" "event_pattern_build_account" {
  template = file("${path.module}/event_pattern_build_account.json")

  vars = {
    build_account_id          = data.aws_caller_identity.build_account.account_id
    codecommit_repository_arn = data.aws_codecommit_repository.application.arn
  }
}

################################################################################
# IAM Role for EventBridge                                                     #
################################################################################
resource "aws_iam_role" "eventbridge_build_account" {
  provider = aws.build_account

  name               = local.eventbridge_iam_role_name
  assume_role_policy = data.aws_iam_policy_document.eventbridge_build_account_assume.json
}

data "aws_iam_policy_document" "eventbridge_build_account_assume" {
  statement {
    effect = "Allow"

    actions = [
      "sts:AssumeRole",
    ]

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

resource "aws_iam_role_policy_attachment" "eventbridge_build_account" {
  provider = aws.build_account

  role       = aws_iam_role.eventbridge_build_account.name
  policy_arn = aws_iam_policy.eventbridge_build_account_custom.arn
}

resource "aws_iam_policy" "eventbridge_build_account_custom" {
  provider = aws.build_account

  name        = "SendEventToServiceAccountPolicy"
  description = "Send Event to Service-Account Policy"
  policy      = data.aws_iam_policy_document.eventbridge_build_account_custom.json
}

data "aws_iam_policy_document" "eventbridge_build_account_custom" {
  version = "2012-10-17"

  statement {
    effect = "Allow"

    actions = [
      "events:PutEvents",
    ]

    resources = [
      "arn:aws:events:ap-northeast-1:${data.aws_caller_identity.service_account.account_id}:event-bus/default",
    ]
  }
}

```

```JSON:event_pattern_build_account.json
{
  "detail-type": [
    "CodeCommit Repository State Change"
  ],
  "resources": [
    "${codecommit_repository_arn}"
  ],
  "detail": {
    "referenceType": [
      "branch"
    ],
    "event": [
      "referenceUpdated"
    ],
    "referenceName": [
      "production"
    ]
  },
  "source": [
    "aws.codecommit"
  ]
}
```

## EventBridgeの設定(service_account側)

```HCL
################################################################################
# EventBridge(Service Account)                                                 #
################################################################################
resource "aws_cloudwatch_event_rule" "codecommit_merge_service_account" {
  provider = aws.service_account

  name          = local.event_rule_name
  description   = "CodeCommit Merge Event Rule"
  event_pattern = data.template_file.event_pattern_service_account.rendered
}

resource "aws_cloudwatch_event_target" "event_bus_service_account" {
  provider = aws.service_account

  rule      = aws_cloudwatch_event_rule.codecommit_merge_service_account.name
  target_id = local.event_target_id
  arn       = aws_codepipeline.service_account.arn
  role_arn  = aws_iam_role.eventbridge_service_account.arn
}

data "template_file" "event_pattern_service_account" {
  template = file("${path.module}/event_pattern_service_account.json")

  vars = {
    build_account_id          = data.aws_caller_identity.build_account.account_id
    codecommit_repository_arn = data.aws_codecommit_repository.application.arn
  }
}

################################################################################
# IAM Role for EventBridge                                                     #
################################################################################
resource "aws_iam_role" "eventbridge_service_account" {
  provider = aws.service_account

  name               = local.eventbridge_iam_role_name
  assume_role_policy = data.aws_iam_policy_document.eventbridge_service_account_assume.json
}

data "aws_iam_policy_document" "eventbridge_service_account_assume" {
  statement {
    effect = "Allow"

    actions = [
      "sts:AssumeRole",
    ]

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

resource "aws_iam_role_policy_attachment" "eventbridge_service_account" {
  provider = aws.service_account

  role       = aws_iam_role.eventbridge_service_account.name
  policy_arn = aws_iam_policy.eventbridge_service_account_custom.arn
}

resource "aws_iam_policy" "eventbridge_service_account_custom" {
  provider = aws.service_account

  name        = "StartPipelinePolicy"
  description = "Start CodePipeline Policy"
  policy      = data.aws_iam_policy_document.eventbridge_service_account_custom.json
}

data "aws_iam_policy_document" "eventbridge_service_account_custom" {
  version = "2012-10-17"

  statement {
    effect = "Allow"

    actions = [
      "codepipeline:StartPipelineExecution",
    ]

    resources = [
      "arn:aws:codepipeline:ap-northeast-1:${data.aws_caller_identity.service_account.account_id}:*",
    ]
  }
}

```

```JSON:event_pattern_service_account.json
{
  "detail-type": [
    "CodeCommit Repository State Change"
  ],
  "account": [
    "${build_account_id}"
  ],
  "resources": [
    "${codecommit_repository_arn}"
  ],
  "detail": {
    "referenceType": [
      "branch"
    ],
    "event": [
      "referenceUpdated"
    ],
    "referenceName": [
      "production"
    ]
  },
  "source": [
    "aws.codecommit"
  ]
}
```

## ポイント
ポイントになるのはイベントパターンの

```JSON
  "detail": {
    "referenceType": [
      "branch"
    ],
    "event": [
      "referenceUpdated"
    ],
    "referenceName": [
      "production"
    ]
  },
```

の部分で、これによって、productionブランチの更新を拾える。
つまり、productionブランチに対するマージコミットのタイミングでイベント連携され、パイプラインを起動できるということだ。

CodeCommitのイベント連携であるため、CommitIDもちゃんとservice_account側に通知されるので、あとはbuildspec側で

```YAML
phases:
  pre_build:
    commands:
      (中略)
      - IMAGE_TAG=$(echo ${CODEBUILD_RESOLVED_SOURCE_VERSION} | cut -c 1-7)
```

で ```CODEBUILD_RESOLVED_SOURCE_VERSION``` の環境変数も参照可能になる。