CDK(typescript)をVSCodeでデバッグ実行を行う記事

記事タイトルとURLをコピーする

CS1の石井です。

タイトルの通りCDKのsynth時にVSCodeを用いてデバッグ実行を行う手法をまとめた記事です。

本記事を書いた動機

私は今までCDKで扱う環境ごとのパラメータはcdk.jsonにちょろちょろ書いていました。

ただ、最近パラメータの量も多くなりバリデーションチェックを行いたいと思うようになってきました。

そこで、下記の記事を参考にzodというパッケージを使ってみました。

参考にしたzodの記事:https://go-to-k.hatenablog.com/entry/ts-cdk-zod-validation

zodの使い方は大変参考になったのですが、変数の格納状況をデバッグ機能を使って行いたいと思い調査しました。

未来の自分のためにも備忘録がてら調査した内容を本記事にてまとめようと思います。

早速結論

cdkのプロジェクトファイルに下記のjsonを入れ、任意の場所にブレークポイントを設定し、デバッグ実行押すだけです。

// .vscode/launch.json
{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "pwa-node",
      "request": "launch",
      "name": "CDK Synth Debug",
      "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/ts-node",
      "args": [
        "${workspaceFolder}/node_modules/.bin/cdk",
        "synth",
        "-c",
        "stage=Stg"
      ],
      "skipFiles": [
        "<node_internals>/**"
      ],
      "console": "integratedTerminal",
      "internalConsoleOptions": "neverOpen"
    }
  ]
}

解説

configurations以下のパラメータについてまとめておきます。 重要なのはargsで指定するstage=Stgの部分です。

これをstage=ProdにすればProd用のパラメータを取得します。

フィールド名 説明
type デバッガのタイプを指定します。"pwa-node"はVS Codeの新しいJavaScriptデバッガを指します。
request デバッグセッションのタイプを指定します。"launch"は新しいプログラムの起動を指します。
name デバッグ設定の名前を指定します。これはVS Codeのデバッグドロップダウンメニューに表示されます。
runtimeExecutable デバッグセッションを開始するために使用する実行可能ファイルを指定します。ここではts-nodeを指定しています。
args 実行可能ファイルに渡す引数の配列を指定します。ここではCDKのsynthコマンドとその引数を指定しています。
skipFiles デバッガがスキップするべきファイルパターンの配列を指定します。ここではNode.jsの内部モジュールをスキップしています。
console デバッグ出力を表示するコンソールを指定します。"integratedTerminal"はVS Codeの統合ターミナルを指します。
internalConsoleOptions デバッグセッションの開始時に内部コンソールを開くかどうかを指定します。"neverOpen"は内部コンソールを開かないことを指します。

サンプルを使って試す

まずはプロジェクトを作ります、ついでに私がバリデーションチェックで使ったzodというパッケージもインストールします。

mkdir debug-test; cd debug-test
npx cdk init --language=typescript
npm install zod
mkdir -p lib/config
mkdir -p lib/types
mkdir .vscode
touch .vscode/launch.json
touch lib/config/config-stack-props.ts
touch lib/types/stack-input-schema.ts
touch lib/types/stack-validator.ts

今回のサンプルでは「npx cdk synth -c stage=Stg | Prod」といったコマンドでstageごとに異なるパラメータを取得してみます。

作成した空ファイルに以下のように記載します。

config定義ファイル

// lib/config/config-stack-props.ts
import { StackProps } from "aws-cdk-lib";
import { StackInput } from "../types/stack-input-schema";

export interface ConfigStackProps extends StackProps {
  config: StackInput;
}

export const configStackProps: ConfigStackProps = {
  config: {
    Prefix: "hoge",
    Stg: {
      "$comment": "HogeProject",
      "Env": "stg"
    },
    Prod: {
      "$comment": "HogeProject",
      "Env": "prod",
    }
  },
};

スキーマ定義ファイル

// lib/types/stack-input-schema.ts
import { z } from "zod";

const StageSchema = z.object({
  "$comment": z.string(),
  "Env": z.string(),
});

export const StackInputSchema = z.object({
  "Prefix": z.string(),
  "Stg": StageSchema,
  "Prod": StageSchema,
});

export type StackInput = z.infer<typeof StackInputSchema>;

バリデーションファイル

// lib/types/stack-validator.ts
import { IValidation } from "constructs";
import { StackInput, StackInputSchema } from "./stack-input-schema";

export class StackValidator implements IValidation {
  private stackInput: StackInput;

  constructor(stackInput: StackInput) {
    this.stackInput = stackInput;
  }

  public validate(): string[] {
    const errors: string[] = [];

    try {
      StackInputSchema.parse(this.stackInput);
    } catch (e) {
      errors.push(JSON.stringify(e));
    }

    return errors;
  }
}

Stackファイル

// lib/debug-test-stack.ts
import { Construct } from 'constructs';
import * as cdk  from 'aws-cdk-lib';
import { aws_s3 as s3 } from 'aws-cdk-lib';
import { StackInput } from "./types/stack-input-schema";
import { ConfigStackProps } from './config/config-stack-props';
import { StackValidator } from './types/stack-validator';

export class DebugTestStack extends cdk.Stack {
  private stackInput: StackInput;
  private init(props: ConfigStackProps): void {
    this.stackInput = props.config;
    const stackValidator = new StackValidator(this.stackInput);
    this.node.addValidation(stackValidator);
  }

  constructor(scope: Construct, id: string, props: ConfigStackProps) {
    super(scope, id, props);

    this.init(props);
    // Stg や Prod ごとの環境変数を取得 from config/config-stack-props
    const stagename: 'Stg' | 'Prod' = this.node.tryGetContext('stage') as 'Stg' | 'Prod';
    const context = props.config[stagename];
    
    // Prefix取得
    const Prefix = props.config['Prefix'];

    // S3へのデプロイ内容を定義
    const sourcebucket = new s3.Bucket(this, 'source',{
      bucketName: `s3-${Prefix}-${context.Env}-src`,
      autoDeleteObjects: true,
      removalPolicy: cdk.RemovalPolicy.DESTROY
    })
    
  }
}

cdkエンドポイントファイル

// bin/debug-test.ts
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { DebugTestStack } from '../lib/debug-test-stack';
import { configStackProps } from '../lib/config/config-stack-props';  // configStackPropsをインポート

const app = new cdk.App();
new DebugTestStack(app, 'DebugTestStack', {
  config: configStackProps.config,  // configStackPropsのconfigを使う
});

※lunch.jsonは記事冒頭のjsonを貼り付けてください。

デバッグ実行

StackファイルのS3を作成する28行目にブレークポイントを設定して、早速F5で実行してみました。

正常にブレークポイントで止まり、デバッグコンソールにも狙い通りの変数が格納されていることがわかります。

また定義された変数以外にもnodeの状態も確認可能です。これでCDKがCFnを生成に至るまでの過程がわかるので、CDKの使いっぷりがイメージしやすくなるかなと思います。

まとめ

簡単でしたが、以上となります。

ステップ実行できる環境があると、開発の敷居が下がると思っています。

typescriptやったことないからCDKはちょっと・・・と思っている人に刺さればいいなぁと思います。