今回のお話
8月にとある大型本番リリース作業があったためとても多忙で、ずいぶん間が空いてしまいました。すいません。
前々回および前回の記事で、AWS CDK Toolkit Libraryの基本、および「CDK CLIで行っていたあの機能、AWS CDK Toolkit Libraryではどうやるの?」という事を書きました。
最終回となる今回は、エラーハンドリング、そしてちょっと高度な「IIoHost」について紹介しようと思います。
※AWS CDK Toolkit LibraryのAWS公式サイトはこちら。 aws.amazon.com
エラーハンドリング
CDK Toolkit Libraryには、ToolkitError クラスにあらかじめエラーハンドリング用のヘルパー関数が用意されており、 容易にエラーハンドリング処理を実装できます。
具体的には以下の通りです。(すべてstatic関数です)*1
| ヘルパー関数 | 説明 | 備考 |
|---|---|---|
| isToolkitError(error) | CDK Toolkitライブラリのエラーかどうかを判定する | 以下エラーの親クラス |
| isAuthenticationError(error) | クレデンシャル違いなど、認証に関するエラーかどうかを判定する | |
| isAssemblyError(error) | CDKの定義間違いなので発生したエラーかどうかを判定する。 | |
| isContextProviderError(error) | ContextProvider(≒cintext)に起因するエラーかどうか判定する | ContextProviderは、synth時のcdk.context.json出力などに関係するクラス。 |
| withCause(message, error) | errorをcause(エラーの根本原因)としてオリジナルエラーをToolkitErrorとして返す | これはエラー種類の判定ではない |
下記がサンプルコードになります(一部AWS公式サイトより)
下記サンプルコードの通り、ToolkitErrorは、コンストラクタなどから自分で生成することもできます。
ちなみにCDK Toolkit Libraryのエラーは、エラータイプのinstanceofチェックに頼らず、ヘルパー関数を使用する事がAWS公式より案内されています。
import { ToolkitError } from '@aws-cdk/toolkit-lib'; try { // なんか処理 await toolkit.deploy(cloudAssemblySource, { stacks: { strategy: StackSelectionStrategy.ALL_STACKS } }); } catch (error) { if (ToolkitError.isAuthenticationError(error)) { // 認証エラー(クレデンシャル不一致など) console.error('Authentication failed. Check your AWS credentials.'); } else if (ToolkitError.isAssemblyError(error)) { // CDKの定義が違う console.error('CDK app error:', error.message); } else if (ToolkitError.isToolkitError(error)) { // context周りでエラー console.error('CDK Toolkit error:', error.message); } else if (ToolkitError.isToolkitError(error)) { // 上記に該当しないToolkitErrorのエラー console.error('CDK Toolkit error:', error.message); // withCauseで、cause付きのToolkitErrorを取得する throw ToolkitError.withCause(error.message, error); } else { // ToolkitErrorではない、予期しないエラー console.error('Unexpected error:', error); // こんな感じで、ToolkitErrorエラーの作成もできる throw new ToolkitError('予期しないエラー', 'unknown', error) } }
IIoHostによるメッセージとインタラクション
CDK Toolkit Libraryではオペレーションを実行時、下記「メッセージ」および「リクエスト」という2つの主要なメカニズムを介して通信します。
- メッセージ:オペレーションの実行状況などを通知する。(例:「デプロイの進行中」など)
- リクエスト:ユーザーからの入力または確認が必要な決定ポイントで、これらを確認する機会を提供する。(例:「このスタックをデプロイしていいですか?」など)
これらを構成するのに必要なのが IIoHost インターフェースです
IIoHostインターフェースについて
IIoHost インターフェースは、以下2つの構成で成り立っています。
notify:上記「メッセージ」を処理します。requestResponse:上記「リクエスト」を処理します。
ソースコードでは、下記の内容になっています。
import type { IoMessage, IoRequest } from './io-message'; export interface IIoHost { notify(msg: IoMessage<unknown>): Promise<void>; requestResponse<T>(msg: IoRequest<unknown, T>): Promise<T>; }
またCDK Toolkit Libraryのコンストラクタでは、以下のようにIIoHostを定義します。
const toolkit = new Toolkit({ ioHost: { notify: async function (msg) {...(なんか処理)}, requestResponse: async function (msg) {...(なんか処理)}, } });
notifyについて
notify には、IoMessage 型の引数msg の内容に応じたメッセージ処理を記載します。
AWS公式のサンプルでは下記のように msg.level に応じたログを出力する処理が記載されており、アプリのログ出力とも似通った分かりやすい内容となっています。
notify: async function (msg) { // notify関数の例 switch (msg.level) { case 'error': console.error(`[${msg.time}] ERROR: ${msg.message}`); break; case 'warning': console.warn(`[${msg.time}] WARNING: ${msg.message}`); break; case 'info': console.info(`[${msg.time}] INFO: ${msg.message}`); break; case 'debug': console.debug(`[${msg.time}] DEBUG: ${msg.message}`); break; case 'trace': console.debug(`[${msg.time}] TRACE: ${msg.message}`); break; default: console.log(`[${msg.time}] ${msg.level}: ${msg.message}`); } },
ちなみにmsgにはlevelの他にも、以下のようなプロパティがあります。
| プロパティ | 説明 | 備考 |
|---|---|---|
| time | 日時情報(Date型) | |
| action | 実行した処理(deploy, synth, listなど) | |
| code | メッセージコード | |
| message | メッセージ本文 | |
| span | メッセージスパン | 同じ操作に意味的に関連した複数メッセージのグループ。それ以外では無意味 |
| data | メッセージに添付されたデータ |
requestResponseについて
requestResponse は、ユーザーからの入力または確認が必要な場合に送信されるリクエストに対する挙動(=レスポンス処理)を記載します。
なおこのrequestResponse は、CDK Toolkit Library において非常に重要です。
なぜならCDK Toolkit Libraryでは、このような処理に対する初期値が「何も確認しない」になっているからです。(CDK CLIで言えば、--require-approval の 初期値が never になっているのと同じ)
そのため、例えば toolkit.destroy(=スタックの削除)のような危険な処理もユーザーに確認することなく、ダイレクトで実行されてしまいます。
そこで、これを防ぐために requestResponse でちゃんと確認するように設定する必要があります。
サンプルとしては以下です。(AWS公式より)
requestResponse: async (msg) => { // (例)本番だけは何も確認しない場合 if (environment === 'production') { console.log(`Auto-approving for production: ${msg.message}`); // msg.defaultResponseは「デフォルトの挙動を行う(=何も確認しない)」ための専用のプロパティ return msg.defaultResponse; } // 何かしら確認のためのプロンプトなどを出す処理を行う // ちなみに引数のmsgは、defaultResponseプロパティを持つ事以外notifyのmsgと同じです。 return showPromptForApproval(msg.message); }
なおこのIIoHost ですが、下記のいずれかの方法で定義できます。
ToolkitコンストラクタにnotifyやrequestResponseを直で定義するNonInteractiveIoHostクラスを継承したカスタムクラスを自分で定義し、それをToolkitコンストラクタのIIoHostに設定する
ちなみに後者を使用すると「変更する必要がある動作のみをカスタマイズしながら、既存の実装を活用できる」というメリットがあります。
これはAWS公式にサンプルコードがありますので、そちらを参照ください。
まとめ
以上、AWS CDK Toolkit LibraryのエラーハンドリングとIIoHostについての設定についてでした。
今日の内容、特にIIoHostのRequestResponse はちょっと難しい内容かもしれませんが、RequestResponse は非常に重要な設定なので、必ず最初に設定しておいた方が良いと思います。
ちなみに今回RequestResponseの部分について説明が駆け足になってしまいましたが、詳しく知りたい方は、山梨 蓮さん(X:@ren_yamanashi)がブログで非常に詳しく説明してくれていますので、ぜひそちらもご参照ください。
最後に
9/12(金)に大阪で開催される「JAWS-UG CDK支部#22 大阪でもCDKしたいねん」にて、LTをさせていただきます。
内容としては、前回&今回ブログで書いたような「CDK CLIでできてたあの機能、CDK Toolkit Libraryではどうやるの?」という内容になると思います。
また10/11(土)に金沢で開催される「JAWS Festa 2025」において、ありがたいことにCFPが採用され、登壇させていただくことになりました。
正式なタイトルは、公式ページをお楽しみください。
それでは、今回はこの辺で。