AWS CloudFormationのコンソールでAWS CDKのConstruct Treeが見れるようになりました

2022.09.15

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは、CX事業本部 IoT事業部の若槻です。

このたびのアップデートにより、AWS CloudFormationのコンソールでAWS CDKConstruct Treeが見れるようになったとのことなので、確認してみました。

確認してみた

次のようなCDK Stackをデプロイします。Stack内で明示的に使用しているConstructは3つです。

lib/aws-cdk-app-stack.ts

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で取得が可能です。実際に取得してみます。

bin/aws-cdk-app.ts

#!/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の出力結果を見ると、TreeMetadataAwsCdkAppStackが直下にあり、AwsCdkAppStackにはさらにchildがあることが分かります。

app.node.children

 [
  <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があることが分かります。

awsCdkAppStack.node.children

[
  <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でのデプロイ失敗時などにデバッグを行う際などに役に立ちそうです。なかなか嬉しいアップデートでした。

参考

以上