• 作成:

CodeBuildがSlackに通知するデータが少なすぎて混乱するのでChatbotを諦めてLambdaで処理する

問題

CodeBuildがAWS Chatbot経由でSlackに通知してくるビルド結果の情報が少なすぎる。

Bitbucket(CircleCI)との比較

Bitbucket
CodeBuild

具体的なトラブル

コミットAに続けてBをした時、 Aの時点で修正が完了しているのに、 Bで問題が起きてテストが失敗したので、 Aで修正が完了していることに気が付かなかった事案がありました。

また私もコードレビューなどを行ったりしてもらう時に、 PRのCIが通ってるのかSlackのログを見るだけでは容易に分からないというフラストレーションを抱えています。

生産性を下げています。

目指すこと

Bitbucket/CircleCIの通知のようにリッチなものを目指す。まずはせめてCodeBuildの環境変数に含まれているブランチ名を出力することを行い、それ以後の発展の礎にする。

本当はコミットメッセージなども目立った形で表示したいが、とりあえず小さい発展を目指す。

方法

結構調べたが、デフォルトのChatbotを使った方法だとカスタムが困難だと分かってきたので、諦めてAWS Lambdaにデータを飛ばしてwebhookで通知させることでごり押しすることにしました。マネージドじゃない自前構築はなるべくやりたくなかったのですが。

通知Lambdaコード

とりあえず簡単にするため雑な通知コードを書きました。環境変数を通知しているので環境変数に機密データを書かないように注意してください。 Secretを使いましょう。まあ多分今後ブランチ名だけとかに絞りますが。

import type { MrkdwnElement } from "@slack/types";
import { IncomingWebhook } from "@slack/webhook";
import type { CodeBuildCloudWatchStateHandler } from "aws-lambda";

/** webhookは環境変数から設定しておく。 */
const webhookUrl = process.env.WEBHOOK_URL;
if (typeof webhookUrl !== "string") {
  throw new Error("require webhook from ENV");
}
const webhook = new IncomingWebhook(webhookUrl, {
  username: "extra-aws-codebuild-notification",
});

/** CodeBuildの結果を受け取って環境変数込みでwebhookに通知する。 */
export const handler: CodeBuildCloudWatchStateHandler = async (event) => {
  const mrkdwnType: MrkdwnElement["type"] = "mrkdwn";
  await webhook.send({
    text: "extra-aws-codebuild-notification",
    blocks: [
      {
        type: "section",
        fields: [
          {
            type: mrkdwnType,
            text: `build-status: ${event.detail["build-status"]}`,
          },
          ...event.detail["additional-information"].environment[
            "environment-variables"
          ].map(({ name, value }) => ({
            type: mrkdwnType,
            text: `${name}: ${value}`,
          })),
        ],
      },
    ],
  });
};

通知トリガーCDKコード

import path from "node:path";
import { Stack, StackProps } from "aws-cdk-lib";
import * as codebuild from "aws-cdk-lib/aws-codebuild";
import * as targets from "aws-cdk-lib/aws-events-targets";
import * as lambda from "aws-cdk-lib/aws-lambda";
import { NodejsFunction } from "aws-cdk-lib/aws-lambda-nodejs";
import * as logs from "aws-cdk-lib/aws-logs";
import { Construct } from "constructs";

interface ExtraAwsCodebuildNotificationStackProps extends StackProps {
  /** ビルド結果をトラッキングして通知して欲しいCodeBuildプロジェクト。 */
  project: codebuild.Project;
  /** 通知先になるwebhook、Slackを想定している。 */
  webhookUrl: string;
}

/** 追加情報を多く含んだ通知を実現する。 */
export class ExtraAwsCodebuildNotificationStack extends Stack {
  constructor(
    scope: Construct,
    id: string,
    props: ExtraAwsCodebuildNotificationStackProps
  ) {
    super(scope, id, props);

    const { project, webhookUrl } = props;

    const func = new NodejsFunction(this, "Function", {
      entry: path.join(
        __dirname,
        "function",
        "extra-aws-codebuild-notification.ts"
      ),
      architecture: lambda.Architecture.ARM_64,
      environment: {
        WEBHOOK_URL: webhookUrl,
      },
      logRetention: logs.RetentionDays.THREE_MONTHS,
    });

    project.onBuildFailed("ExtraAwsCodebuildNotificationFailed", {
      target: new targets.LambdaFunction(func),
    });
    project.onBuildSucceeded("ExtraAwsCodebuildNotificationSucceeded", {
      target: new targets.LambdaFunction(func),
    });
  }
}

SNS Topicを経由するしかないのかな、それはeventの処理が面倒だなと思ったのですが、 onBuildFailedなどを使うことで直接ターゲット指定できるようです。

考えてみると環境変数などプロジェクトの内容が直に関わってくる内容しか無かったので結果の画像は無いんですが、とりあえずブランチ名などは表示できました。

今後の発展

標準にあるビルド結果へのリンクの実装は当然として、アーティファクトにログなどを入れたりすることでHEADのコミットメッセージを表示したり、 CloudWatchにアクセスすることでビルド失敗時にログ全体を表示したりするなどの改良を行っていきたい。

CodeCommit/CodeBuildは確かに使いにくいが、 GitHubやBitbucketなどに比べて拡張は比較的容易…いや容易か…? 別にGitHub Actionsでも同じぐらいのことは簡単に出来るのでは…? しかも頑張ってもGitHub以上の使い勝手になるわけでもないというのがつらい。全体の生産性のため、少しずつ改善していきたいとは思っています。