Dependabot と GitHub Actions でAWS CDKのスナップショットテストを自動化してみた
はじめに
こんにちは、CX事業本部 Delivery部の塚本です。
今回は、DependabotとAWS CDKのスナップショットテストを使い、自動でCDKのバージョンアップによるデグレを検知できる構成を作ってみました。
今回やりたいことは以下です。
『Dependabotで aws-cdk-lib
のアップデートのプルリクが作成された際に、CDKのスナップショットテストを実行する』
※初めてDependabotを触ったので、基本的な内容も多めの記事になってます
Dependabot とは
Dependabot は GitHub のツールで、ライブラリのバージョンを更新するプルリクを自動で生成してくれる機能を持っています。
OSSのリポジトリで上ような状態を目にしたことがあるかと思いますが、こちらがDependabotによるプルリクです。
以下の3つの機能があります。
- Dependabot Alert
- Dependabot security updates
- Dependabot version updates
今回は Dependabot version updates 機能が記事の対象です。
CDKのスナップショットテスト とは
CDKで生成される CloudFormation のテンプレートに「変更がないこと」を確認するテストです。
スナップショットテストは主に以下の確認で利用します。
- コードリファクタ時にデグレが起きないこと
- CDKフレームワークのバージョンアップ時にデグレが起きないこと
今回は CDKフレームワークのバージョンアップ時 が記事の対象です。
やりたいこと
今回は Dependabot による aws-cdk-lib
のアップデートを検知し、GitHub Actions上でスナップショットテストを行うところまでやっていきます。
フロー図を示します。
結果
先に結果から紹介して、後でコードの全体を紹介します。
以下の package.json を利用します。
現在(2023/5/24)時点では aws-cdk-lib
の最新は2.80.0なので、Dependabotの更新対象になるようにわざとバージョンを 2.60.0 に指定します。
比較のために @types/node
のバージョンも下げておきます。
{ "name": "sample-dependabot", "version": "0.1.0", "bin": { "sample": "bin/sample.js" }, "scripts": { "build": "tsc", "watch": "tsc -w", "test": "jest", "cdk": "cdk" }, "devDependencies": { "@types/jest": "^29.5.1", "@types/node": "20.1.7", "jest": "^29.5.0", "ts-jest": "^29.1.0", "aws-cdk": "2.60.0", "ts-node": "^10.9.1", "typescript": "~5.0.4" }, "dependencies": { "aws-cdk-lib": "2.60.0", "constructs": "^10.0.0" } }
ソースをプッシュした時点で以下のようにDependabotによるプルリクエストが作成されました。
GitHub Actionsにて、 aws-cdk-lib
の更新時のみスナップショットテストが走るように出来ているか確認します。
下画像で確認できる通り、aws-cdk-lib
の更新時にスナップショットテストの実行ができています。
また、他のライブラリ更新時にはスナップショットテストは実行されません。
やりたいことが実現できていることを確認できました。
ソース紹介
ここからはソースの紹介です。 今回作ったソース全体はこちらのリポジトリに載せています。
実際にDependabotによって作成されたプルリクも見ることができます。
Dependabot version updates の設定
Dependabot version updatesはリポジトリ内の .github
配下に dependabot.yml
を作成するだけで有効化できます。
version: 2 updates: - package-ecosystem: "npm" # See documentation for possible values directory: "/" # Location of package manifests schedule: interval: "weekly" open-pull-requests-limit: 5
各設定値について少しだけ触れておきます。
schedule
schedule: interval: "weekly"
毎週1回バージョンチェックを実行します。デフォルトで月曜日に設定されており、変更には schedule.day
という追加のオプションを利用します。
参考:
open-pull-requests-limit
open-pull-requests-limit: 5
Dependabotがバージョン更新でオープンできるプルリクエストの上限値です。
5に設定しているので、5つすでにプルリクが作成されている場合は新たなプルリクを作成しなくなります。
参考:
GitHub Actions の設定
GitHub Actionsは .github/workflows
ディレクトリに YAML ファイルを配置することで有効化できます。
参考:
全体は以下のようになりました。
name: Dependabot CDK version update workflow on: pull_request: branches: [main] permissions: pull-requests: write contents: read jobs: output-target-library-name: runs-on: ubuntu-latest if: ${{ github.actor == 'dependabot[bot]' }} outputs: LIBRARY_NAME: ${{ steps.output-library-name.outputs.LIBRARY_NAME }} steps: - name: Dependabot metadata id: metadata uses: dependabot/fetch-metadata@v1.5.0 with: github-token: "${{ secrets.GITHUB_TOKEN }}" - name: Output library name id: output-library-name run: echo "LIBRARY_NAME=$DEPENDENCY_NAME" >> "$GITHUB_OUTPUT" # 変数のアウトプット(参照: https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-output-parameter) env: DEPENDENCY_NAME: ${{steps.metadata.outputs.dependency-names}} # 対象ライブラリ名の取得(参照: https://github.com/dependabot/fetch-metadata#:~:text=Subsequent%20actions%20will%20have%20access%20to%20the%20following%20outputs%3A) snapshot-test: runs-on: ubuntu-latest needs: output-target-library-name if: ${{ needs.output-target-library-name.outputs.LIBRARY_NAME == 'aws-cdk-lib' }} steps: - name: Checkout uses: actions/checkout@v3 - name: Use Node.js uses: actions/setup-node@v3 with: node-version: 18 - name: npm ci run: npm ci - name: CDK snapshot test run: npm test
構成について、少しだけ紹介します。
jobs
jobs: output-target-library-name: ~~~~~ ~~~~~ snapshot-test: if: ${{ needs.output-target-library-name.outputs.LIBRARY_NAME == 'aws-cdk-lib' }} ~~~~~ ~~~~~
ジョブは2つに分割しました。
1つ目 output-target-library-name
はDependabot で作成されたプルリクが何のライブラリを対象としているか出力するようにしています。
2つ目 snapshot-test
は 1つ目のジョブの出力を受け取り、CDKのバージョンアップであればジョブ自体を実行するようにしています。
job間の値の受け渡し
抜粋:
jobs: output-target-library-name: outputs: LIBRARY_NAME: ${{ steps.output-library-name.outputs.LIBRARY_NAME }} steps: - name: Output library name id: output-library-name run: echo "LIBRARY_NAME=$DEPENDENCY_NAME" == "$GITHUB_OUTPUT" env: DEPENDENCY_NAME: ${{steps.metadata.outputs.dependency-names}} snapshot-test: needs: output-target-library-name if: ${{ needs.output-target-library-name.outputs.LIBRARY_NAME == 'aws-cdk-lib' }}
run: echo "LIBRARY_NAME=$DEPENDENCY_NAME" == "$GITHUB_OUTPUT"
上記のコードでステップのアウトプットのパラメータを設定します。
参考: workflow-commands-for-github-actions
outputs: LIBRARY_NAME: ${{ steps.output-library-name.outputs.LIBRARY_NAME }}
上記のコードでジョブのアウトプットのパラメータを設定します。次のジョブで値を使うためです。
needs: output-target-library-name if: ${{ needs.output-target-library-name.outputs.LIBRARY_NAME == 'aws-cdk-lib' }}
上記のコードでは前に実行されたジョブのアウトプットを取得して判定をしています。
needs
で特定のジョブを指定することで、前のジョブが失敗した場合には次のジョブを実行しない、という動きができます。
needs.[job-id].outputs.
とすることで、依存しているジョブのアウトプットを取得することができます。
参考:
アップデート対象ライブラリ名の取得
steps: - name: Dependabot metadata id: metadata uses: dependabot/fetch-metadata@v1.5.0 with: github-token: "${{ secrets.GITHUB_TOKEN }}" - name: Output library name id: output-library-name run: echo "LIBRARY_NAME=$DEPENDENCY_NAME" >> "$GITHUB_OUTPUT" # 変数のアウトプット(参照: https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-output-parameter) env: DEPENDENCY_NAME: ${{steps.metadata.outputs.dependency-names}} # 対象ライブラリ名の取得(参照: https://github.com/dependabot/fetch-metadata#:~:text=Subsequent%20actions%20will%20have%20access%20to%20the%20following%20outputs%3A)
Dependabotのfetch-metadata action を利用して作成されたプルリクの情報を取得します。
fetch-metadata
によって自動的にそのステップの steps.[step-id].outputs
に値が埋め込まれます。
参考:
steps.[step-id].outputs.dependency-names
を以降のステップで参照することで、対象のライブラリ名を取得することができます。
Dependabot のプルリクを判別
if: ${{ github.actor == 'dependabot[bot]' }}
上のコードでDependabotからのプルリクであることを判定しています。
AWS CDKのソース
今回CDKのコードは簡単なものになっているので、詳細な説明は省いて簡単に紹介します。
Stackの定義です。簡単にDynamoDBテーブルを作るだけのものです。
import { Stack, StackProps, RemovalPolicy } from "aws-cdk-lib"; import * as dynamodb from "aws-cdk-lib/aws-dynamodb"; import { Construct } from "constructs"; export class SampleStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props); const sampleTable = new dynamodb.Table(this, "sampleTable", { partitionKey: { name: "id", type: dynamodb.AttributeType.STRING }, billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, removalPolicy: RemovalPolicy.DESTROY, }); } }
スナップショットテストの定義です。toMatchSnapshot
を使ってシンプルに記述できます。
import * as cdk from "aws-cdk-lib"; import { Template } from "aws-cdk-lib/assertions"; import { SampleStack } from "../lib/sample-stack"; test("snapshot test", () => { const app = new cdk.App(); const stack = new SampleStack(app, "MyTestStack"); // スタックからテンプレート(JSON)を生成 const template = Template.fromStack(stack).toJSON(); // 生成したテンプレートとスナップショットが同じか検証 expect(template).toMatchSnapshot(); });
今後の展望
今回はDependabotとAWS CDKのスナップショットテストを組み合わせてみました。
まず、初めて触るDependabotの使い方やGitHub Actionsとの連携方法が分かったので勉強になりました。
実プロジェクトで運用していないので考慮事項は色々ありますが、とりあえず実現可能だと分かって良かったです。
さらに実プロジェクトでの運用のために、以下のような発展系を試してみたいと思いました。
- スナップショットテストが成功した際の自動マージを実行する
- dev, stg, prdのように環境を想定し、それぞれの環境への自動デプロイも考慮してみる
- DependabotのプルリクをSlackで通知する
また読んでください。
参考文献
Dependabot関連:
- https://docs.github.com/ja/code-security/dependabot/working-with-dependabot
- https://dev.classmethod.jp/articles/github-dependabot-2021/
- https://github.blog/2020-06-01-keep-all-your-packages-up-to-date-with-dependabot/
AWS CDKスナップショットテスト関連:
- https://pages.awscloud.com/rs/112-TZM-766/images/CDK%E3%81%A7%E3%82%82%E3%83%86%E3%82%B9%E3%83%88%E3%81%8C%E3%81%97%E3%81%9F%E3%81%84.pdf
- https://dev.classmethod.jp/articles/cdk-v2-snapshot-test-lets-start/
GitHub Actions関連:
- https://docs.github.com/ja/code-security/dependabot/dependabot-version-updates/about-dependabot-version-updates#dependabot-version-updates-%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6
- https://dev.classmethod.jp/articles/github-dependabot-auto-merge/
- https://docs.github.com/ja/code-security/dependabot/working-with-dependabot/automating-dependabot-with-github-actions#approve-a-pull-request