CodeBuildのSlack通知をTerraformでサクッと設定する

最近CodeBuildを利用し始めたのですが、CircleCI等のようにはサクッとSlack通知ができないのを少しもどかしく感じました。ビルドの状態がSlack上で楽に把握できないと、コンソールを見に行ってしまったり思考リソースを奪われてしまいますよね。

そこでよし通知をしようと思っても、まさにこれというTerraform Moduleがなかったのでmoduleを作ってみました。

**[en30/codebuild-to-slack/aws | Terraform Module Registry](https://registry.terraform.io/modules/en30/codebuild-to-slack/aws/0.0.1)**

これを利用するとCodeBuild上での以下のイベントを簡単にSlack通知できます。

- `IN_PROGRESS`: 開始
- `SUCCEEDED`: 成功
- `FAILED`: 失敗
- `FAULT`: AWS側が原因での失敗？
- `TIMED_OUT`: タイムアウト
- `STOPPED`: 中止

実際の通知例
![通知例](https://i.gyazo.com/a6c00090c6c8771f679c46c56a2e6172.png)

## 使い方
```conf:main.tf
variable "encrypted_slack_webhook_url" {}

resource "aws_kms_key" "slack_webhook_url" {
  description = "Key for Slack Webhook URL"
}

module "codebuild_notification" {
  source = "en30/codebuild-to-slack/aws"
  
  version                     = "0.0.1"
  encrypted_slack_webhook_url = "${var.encrypted_slack_webhook_url}"
  slack_channel               = "#app"
  kms_key_arn                 = "${aws_kms_key.slack_webhook_url.arn}"
}
```

`encrypted_slack_webhook_url`は以下のようにAWS CLIを使うことで得ることができます。

```console
$ aws kms encrypt --key-id $AWS_KMS_KEY_ID --plaintext $SLACK_WEBHOOK_URL --query CiphertextBlob --output text
```

`$AWS_KMS_KEY_ID`はmoduleへ`kms_key_arn`として渡しているkeyのid、`$SLACK_WEBHOOK_URL`はSlackのIncoming WebhookのURLです。

上の例のようにキーもTerraformで作ろうとすると

1. `aws_kms_key`の作成（`terraofmr apply`①）
2. キーを利用してwebhook urlを暗号化
3. moduleを利用した通知設置（`terraofrm apply`②）

と`terraform apply`で一発でいけないのが気持ち悪いところですが、よろしければ使ってみてください！

## 中身の簡単な説明
やっていることとしては

- CodeBuildのビルド状態が変化に関するCloudWatch EventでLambdaを起動
- Lambdaでイベント情報を整形してSlack通知

です。LambdaにRuby Runtimeも入ったことですし、僕はRubyが好きなのでLambdaはRubyで書きました。

それほど面白いところがあるわけではないですが、Lambdaのデプロイをシンプルに済ますために、標準ライブラリ、Lambdaの環境に元々入っている`aws-sdk`だけで済ますようにしています。

```rb:notify_slack.rb
require "uri"
require "net/http"
require "json"
require "base64"
require "aws-sdk"

COLORS = {
  "SUCCEEDED" => "good",
  "FAILED" => "danger",
  "FAULT" => "danger",
  "TIMED_OUT" => "danger",
  "STOPPED" => "warning",
}.freeze

def decrypt(encrypted_url)
  client = Aws::KMS::Client.new
  client.decrypt(ciphertext_blob: Base64.decode64(encrypted_url)).plaintext
end

def build_url(region, project, slug)
  region, project, slug = [region, project, slug].map(&URI.method(:encode_www_form_component))
  "https://#{region}.console.aws.amazon.com/codesuite/codebuild/projects/#{project}/build/#{slug}/log"
end

def format(event) # rubocop:disable Metrics/MethodLength
  project = event["detail"]["project-name"]
  status = event["detail"]["build-status"]
  slug = event["detail"]["build-id"].split("/").last

  {
    attachments: [
      {
        color: COLORS[status],
        title: slug,
        title_link: build_url(event["region"], project, slug),
        fallback: status,
        fields: [
          {
            title: "Status",
            value: status,
            short: true,
          },
          {
            title: "Initiator",
            value: event["detail"]["additional-information"]["initiator"],
            short: true,
          },
        ],
      },
    ],
  }
end

def notify_slack(slack_url, payload)
  Net::HTTP.post_form(URI.parse(slack_url), payload: JSON.dump(payload))
end

def lambda_handler(event:, context:)
  slack_url = decrypt(ENV["ENCRYPTED_SLACK_WEBHOOK_URL"])

  payload = {
    channel: ENV["SLACK_CHANNEL"],
    username: ENV["SLACK_USERNAME"],
    icon_emoji: ENV["SLACK_EMOJI"],
  }.merge(format(event))

  notify_slack(slack_url, payload)
end
```

以上です。

AWSもTerraformも使い始めて日が浅いので、何か少しでも気になる部分があれば気軽にコメントやIssue作成をお願いします。

## 参考
- [CodeBuildの開始と終了をSlackに通知する - blog.ton-up.net](https://blog.ton-up.net/2018/01/03/codebuild-notify-slack/)
- [terraform-aws-modules/terraform-aws-notify-slack: Terraform module which creates SNS topic and Lambda function which sends notifications to Slack](https://github.com/terraform-aws-modules/terraform-aws-notify-slack)