AWS CDK: 複数環境に対する context の自前管理

概要

CDK の context は cdk.json または cdk.context.json といったファイルに記述しますが、環境ごとにファイルを分けることができません。そのため複数環境の context を切り換えるにはひとつのファイル内にすべて記述して自前で切り換える仕組みを作るといった必要があると思います。また本番環境の context はリポジトリにコミットしておくとして、テスト用に作成する環境の context はどうしたら良いか、悩みどころでした。context ファイルを切り換えるオプション(SAM の --config-file オプションのような)があっても良さそうなものですが、現状はなさそうです。(2022/04 現在)

できれば CDK の標準に乗っかっておきたいところですが、別途自前で context (相当の設定) を読み込むようにしてみました。

context の指定方法

CDK の管理する context とは別に、環境毎の context を記述するファイルを以下のようにします。

  • context.json : 動作確認用の context
  • 環境名未指定時に参照される。
  • 動作確認用なのでバージョン管理対象外とする。
  • context.(環境名).json : 各環境用の context

環境名は実行時に CDK context として与えます。

例)

cdk synth --context env=staging

実装

コンテキスト情報の型を定義しておきます。context.json を JSON.parse() しているだけなので読み込み時に型チェックが効くわけではありませんが、context にどのような設定があるか分かり、コード補完が効くメリットがあります。がんばれば型チェックもできるかな?

// lib/Context.ts
interface Context {
    readonly project: string;
    readonly environment: string;
}

function createContext(env: string): Context {
    return JSON.parse(fs.readFileSync(`context${env ? `.${env}` : ""}.json`).toString());
}

export {Context, createContext}

Stack は Props で Context を受け取るようにします。

// lib/AppStack.ts
import * as cdk from "aws-cdk-lib";
import {Construct} from "constructs";
import {Context} from "./Context";

interface AppStackProps extends cdk.StackProps {
    readonly context: Context;
}

export class AppStack extends cdk.Stack {
    constructor(scope: Construct, id: string, props: AppStackProps) {
        const context = props.context;
        // context を参照
    }
}

メイン処理で Context を作り、Stack に Props で渡します。

// bin/app.ts
import * as cdk from "aws-cdk-lib";
import {createContext} from "../lib/Context";
import {AppStack} from "../lib/AppStack";

const app = new cdk.App();
const context = createContext(app.node.tryGetContext("env"));

new AppStack(app, "AppStack", {
    stackName: `${context.project}-app`,
    context,
});

まとめ

これだけなんですが、CDK は自由度が高いのが非常に良いですね。もう生の CloudFormation には戻れない...

Context を interface でなく class にして JSON からインスタンス化できれば、バリデーションなどのロジックを Context クラスに持たせることもできてすっきりできるのではないでしょうか。