AWS CloudFormationのコンソールでAWS CDKのConstruct Treeが見れるようになりました
こんにちは、CX事業本部 IoT事業部の若槻です。
このたびのアップデートにより、AWS CloudFormationのコンソールでAWS CDKのConstruct Treeが見れるようになったとのことなので、確認してみました。
確認してみた
次のようなCDK Stackをデプロイします。Stack内で明示的に使用しているConstructは3つです。
import { aws_events, aws_events_targets, aws_stepfunctions, aws_stepfunctions_tasks, Stack, StackProps, } from 'aws-cdk-lib'; import { Construct } from 'constructs'; export class AwsCdkAppStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props); // Lambda実行タスク const someTask = new aws_stepfunctions_tasks.EvaluateExpression( this, 'someTask', { expression: '{"key1": "val1"}', } ); // ステートマシン const myStateMachine = new aws_stepfunctions.StateMachine( this, 'myStateMachine', { stateMachineName: 'myStateMachine', definition: someTask, } ); // ステートマシン実行ルール new aws_events.Rule(this, 'rule', { ruleName: 'rule', schedule: aws_events.Schedule.cron({ minute: '0' }), targets: [new aws_events_targets.SfnStateMachine(myStateMachine)], }); } }
CloudFormationのコンソールでStackのResource一覧を見ると、[Tree view]と[Flat view]が切り替えられるようになっています。今表示しているのはTree viewが畳まれた状態です。
Tree Viewを展開した様子です。展開した2階層目にdefault childとなるResourceが配置され、さらに展開した3階層目以降にdefault child以外のIAMなどのResourceが配置されており、Construct Treeが分かりやすく表示されています!!!
Flat Viewだと今まであった見慣れた表示となります。
補足:Construct Treeについて
AWS CDK appでは、すべてのConstructに上位のConstructのスコープ引数が渡されることにより階層構造が作成され、これをConstruct Treeと呼びます。
Construct TreeのRootはApp
classのインスタンスです。その配下にStackがあり、さらにその配下にConstructがTreeを成していきます。
Treeの各Nodeの直下のchildrenはnode.children
で取得が可能です。実際に取得してみます。
#!/usr/bin/env node import 'source-map-support/register'; import * as cdk from 'aws-cdk-lib'; import { AwsCdkAppStack } from '../lib/aws-cdk-app-stack'; const app = new cdk.App(); const awsCdkAppStack = new AwsCdkAppStack(app, 'AwsCdkAppStack'); console.log(app.node.children); //App直下のchildrenを取得 console.log(awsCdkAppStack.node.children); //awsCdkAppStack直下のchildrenを取得
まずapp.node.children
の出力結果を見ると、TreeMetadata
とAwsCdkAppStack
が直下にあり、AwsCdkAppStack
にはさらにchildがあることが分かります。
[ <ref *1> TreeMetadata { node: Node { host: [Circular *1], _locked: false, _children: {}, _context: {}, _metadata: [], _dependencies: Set(0) {}, _validations: [], id: 'Tree', scope: [App] }, [Symbol(@aws-cdk/core.DependableTrait)]: { dependencyRoots: [Array] } }, <ref *2> AwsCdkAppStack { node: Node { host: [Circular *2], _locked: false, _children: [Object], _context: {}, _metadata: [], _dependencies: Set(0) {}, _validations: [], id: 'AwsCdkAppStack', scope: [App] }, _missingContext: [], _stackDependencies: {}, templateOptions: {}, _logicalIds: LogicalIDs { renames: {}, reverse: {} }, account: '${Token[AWS.AccountId.6]}', region: '${Token[AWS.Region.10]}', environment: 'aws://unknown-account/unknown-region', terminationProtection: undefined, _stackName: 'AwsCdkAppStack', tags: TagManager { tags: Map(0) {}, priorities: Map(0) {}, initialTagPriority: 50, resourceTypeName: 'aws:cdk:stack', tagFormatter: KeyValueFormatter {}, tagPropertyName: 'tags', renderedTags: [LazyAny] }, artifactId: 'AwsCdkAppStack', templateFile: 'AwsCdkAppStack.template.json', _versionReportingEnabled: true, synthesizer: DefaultStackSynthesizer { props: {}, assetManifest: [AssetManifestBuilder], useLookupRoleForStackOperations: true, _stack: [Circular *2], qualifier: 'hnb659fds', bucketName: 'cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}', repositoryName: 'cdk-hnb659fds-container-assets-${AWS::AccountId}-${AWS::Region}', _deployRoleArn: 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}', _cloudFormationExecutionRoleArn: 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}', fileAssetPublishingRoleArn: 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}', imageAssetPublishingRoleArn: 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-image-publishing-role-${AWS::AccountId}-${AWS::Region}', lookupRoleArn: 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}', bucketPrefix: '', dockerTagPrefix: '', bootstrapStackVersionSsmParameter: '/cdk-bootstrap/hnb659fds/version' }, [Symbol(@aws-cdk/core.DependableTrait)]: { dependencyRoots: [Array] } } ]
awsCdkAppStack.node.children
の出力結果を見ると見ると配下に4つのConstructがあることが分かります。
[ <ref *1> EvaluateExpression { node: Node { host: [Circular *1], _locked: false, _children: [Object], _context: {}, _metadata: [], _dependencies: Set(0) {}, _validations: [Array], id: 'someTask', scope: [AwsCdkAppStack] }, branches: [], retries: [], catches: [], choices: [], prefixes: [], incomingStates: [], startState: [Circular *1], comment: undefined, inputPath: undefined, parameters: undefined, outputPath: undefined, resultPath: undefined, resultSelector: undefined, endStates: [ [Circular *1] ], timeout: undefined, heartbeat: undefined, props: { expression: '{"key1": "val1"}' }, evalFn: SingletonFunction { node: [Node], stack: [AwsCdkAppStack], env: [Object], _physicalName: undefined, _allowCrossEnvironment: false, physicalName: '${Token[TOKEN.230]}', _warnIfCurrentVersionCalled: false, _invocationGrants: {}, _functionUrlInvocationGrants: {}, lambdaFunction: [Function], permissionsNode: [Node], architecture: [Architecture], functionArn: '${Token[TOKEN.250]}', functionName: '${Token[TOKEN.249]}', role: [Role], runtime: [Runtime], grantPrincipal: [Role], canCreatePermissions: true, [Symbol(@aws-cdk/core.DependableTrait)]: [Object] }, taskPolicies: [ [PolicyStatement] ], containingGraph: StateGraph { startState: [Circular *1], graphDescription: 'State Machine myStateMachine definition', policyStatements: [Array], allStates: [Set], allContainedStates: [Map], timeout: undefined }, [Symbol(@aws-cdk/core.DependableTrait)]: { dependencyRoots: [Array] } }, <ref *2> Function { node: Node { host: [Circular *2], _locked: false, _children: [Object], _context: {}, _metadata: [], _dependencies: Set(0) {}, _validations: [], id: 'Evalda2d1181604e4a4586941a6abd7fe42d', scope: [AwsCdkAppStack] }, stack: AwsCdkAppStack { node: [Node], _missingContext: [], _stackDependencies: {}, templateOptions: {}, _logicalIds: [LogicalIDs], account: '${Token[AWS.AccountId.9]}', region: '${Token[AWS.Region.13]}', environment: 'aws://unknown-account/unknown-region', terminationProtection: undefined, _stackName: 'AwsCdkAppStack', tags: [TagManager], artifactId: 'AwsCdkAppStack', templateFile: 'AwsCdkAppStack.template.json', _versionReportingEnabled: true, synthesizer: [DefaultStackSynthesizer], [Symbol(@aws-cdk/core.DependableTrait)]: [Object] }, env: { account: '${Token[AWS.AccountId.9]}', region: '${Token[AWS.Region.13]}' }, _physicalName: undefined, _allowCrossEnvironment: false, physicalName: '${Token[TOKEN.231]}', _warnIfCurrentVersionCalled: false, _invocationGrants: {}, _functionUrlInvocationGrants: {}, permissionsNode: Node { host: [Circular *2], _locked: false, _children: [Object], _context: {}, _metadata: [], _dependencies: Set(0) {}, _validations: [], id: 'Evalda2d1181604e4a4586941a6abd7fe42d', scope: [AwsCdkAppStack] }, canCreatePermissions: true, _layers: [], environment: {}, role: <ref *3> Role { node: [Node], stack: [AwsCdkAppStack], env: [Object], _physicalName: undefined, _allowCrossEnvironment: false, physicalName: '${Token[TOKEN.232]}', grantPrincipal: [Circular *3], principalAccount: '${Token[AWS.AccountId.9]}', assumeRoleAction: 'sts:AssumeRole', managedPolicies: [Array], attachedPolicies: [AttachedPolicies], dependables: Map(0) {}, _didSplit: false, assumeRolePolicy: [PolicyDocument], inlinePolicies: {}, permissionsBoundary: undefined, roleId: '${Token[TOKEN.237]}', roleArn: '${Token[TOKEN.238]}', roleName: '${Token[TOKEN.240]}', policyFragment: [PrincipalPolicyFragment], [Symbol(@aws-cdk/core.DependableTrait)]: [Object] }, grantPrincipal: <ref *3> Role { node: [Node], stack: [AwsCdkAppStack], env: [Object], _physicalName: undefined, _allowCrossEnvironment: false, physicalName: '${Token[TOKEN.232]}', grantPrincipal: [Circular *3], principalAccount: '${Token[AWS.AccountId.9]}', assumeRoleAction: 'sts:AssumeRole', managedPolicies: [Array], attachedPolicies: [AttachedPolicies], dependables: Map(0) {}, _didSplit: false, assumeRolePolicy: [PolicyDocument], inlinePolicies: {}, permissionsBoundary: undefined, roleId: '${Token[TOKEN.237]}', roleArn: '${Token[TOKEN.238]}', roleName: '${Token[TOKEN.240]}', policyFragment: [PrincipalPolicyFragment], [Symbol(@aws-cdk/core.DependableTrait)]: [Object] }, _architecture: undefined, functionName: '${Token[TOKEN.249]}', functionArn: '${Token[TOKEN.250]}', runtime: Runtime { name: 'nodejs14.x', supportsInlineCode: true, family: 0, bundlingDockerImage: [DockerImage], bundlingImage: [DockerImage], supportsCodeGuruProfiling: false }, timeout: undefined, architecture: Architecture { name: 'x86_64', dockerPlatform: 'linux/amd64' }, currentVersionOptions: undefined, [Symbol(@aws-cdk/core.DependableTrait)]: { dependencyRoots: [Array] } }, <ref *4> StateMachine { node: Node { host: [Circular *4], _locked: false, _children: [Object], _context: {}, _metadata: [], _dependencies: Set(0) {}, _validations: [], id: 'myStateMachine', scope: [AwsCdkAppStack] }, stack: AwsCdkAppStack { node: [Node], _missingContext: [], _stackDependencies: {}, templateOptions: {}, _logicalIds: [LogicalIDs], account: '${Token[AWS.AccountId.9]}', region: '${Token[AWS.Region.13]}', environment: 'aws://unknown-account/unknown-region', terminationProtection: undefined, _stackName: 'AwsCdkAppStack', tags: [TagManager], artifactId: 'AwsCdkAppStack', templateFile: 'AwsCdkAppStack.template.json', _versionReportingEnabled: true, synthesizer: [DefaultStackSynthesizer], [Symbol(@aws-cdk/core.DependableTrait)]: [Object] }, env: { account: '${Token[AWS.AccountId.9]}', region: '${Token[AWS.Region.13]}' }, _physicalName: 'myStateMachine', _allowCrossEnvironment: true, physicalName: 'myStateMachine', role: <ref *5> Role { node: [Node], stack: [AwsCdkAppStack], env: [Object], _physicalName: undefined, _allowCrossEnvironment: false, physicalName: '${Token[TOKEN.251]}', grantPrincipal: [Circular *5], principalAccount: '${Token[AWS.AccountId.9]}', assumeRoleAction: 'sts:AssumeRole', managedPolicies: [], attachedPolicies: [AttachedPolicies], dependables: [Map], _didSplit: false, assumeRolePolicy: [PolicyDocument], inlinePolicies: {}, permissionsBoundary: undefined, roleId: '${Token[TOKEN.256]}', roleArn: '${Token[TOKEN.257]}', roleName: '${Token[TOKEN.259]}', policyFragment: [PrincipalPolicyFragment], defaultPolicy: [Policy], [Symbol(@aws-cdk/core.DependableTrait)]: [Object] }, stateMachineType: 'STANDARD', stateMachineName: '${Token[TOKEN.269]}', stateMachineArn: '${Token[TOKEN.271]}', [Symbol(@aws-cdk/core.DependableTrait)]: { dependencyRoots: [Array] } }, <ref *6> Rule { node: Node { host: [Circular *6], _locked: false, _children: [Object], _context: {}, _metadata: [], _dependencies: Set(0) {}, _validations: [Array], id: 'rule', scope: [AwsCdkAppStack] }, stack: AwsCdkAppStack { node: [Node], _missingContext: [], _stackDependencies: {}, templateOptions: {}, _logicalIds: [LogicalIDs], account: '${Token[AWS.AccountId.9]}', region: '${Token[AWS.Region.13]}', environment: 'aws://unknown-account/unknown-region', terminationProtection: undefined, _stackName: 'AwsCdkAppStack', tags: [TagManager], artifactId: 'AwsCdkAppStack', templateFile: 'AwsCdkAppStack.template.json', _versionReportingEnabled: true, synthesizer: [DefaultStackSynthesizer], [Symbol(@aws-cdk/core.DependableTrait)]: [Object] }, env: { account: '${Token[AWS.AccountId.9]}', region: '${Token[AWS.Region.13]}' }, _physicalName: 'rule', _allowCrossEnvironment: true, physicalName: 'rule', targets: [ [Object] ], eventPattern: {}, _xEnvTargetsAdded: Set(0) {}, description: undefined, scheduleExpression: 'cron(0 * * * ? *)', ruleArn: '${Token[TOKEN.288]}', ruleName: '${Token[TOKEN.290]}', [Symbol(@aws-cdk/core.DependableTrait)]: { dependencyRoots: [Array] } } ]
このようにしてコマンドを使ってもCDKのConstruct Treeをたどることはできます。それが今回のアップデートによりマネジメントコンソール上で見れるようになり、例えば複雑な参照関係のあるCDK Appでのデプロイ失敗時などにデバッグを行う際などに役に立ちそうです。なかなか嬉しいアップデートでした。
参考
以上