CDK CloudFormation Disassemblerで、CloudFormationのyamlをcdk化してみた

久々の投稿であることには触れずに本題をw

最近CDKを触る機会が多いので、記事を書きやすいトピックを探していたのだが、おあつらえ向けのものがあったので書きます。

こんなものがあります。

www.npmjs.com

「CloudFormationのtemplate jsonを喰わせると、よしなにCDKのコードにして吐き出してくれるよ!」というやつ。 どんな感じに出力されるのか興味があったが、最近ついにこれを触る機会が訪れた。

CDK化する対象はこれ。

docs.aws.amazon.com

1 yamljsonに変換

dasmはjson -> CDKに変換するツールなので、このままだと使えない。 yamljsonに変換するツールを使ってなんとかjsonにする。

このときこれみたいなオンラインの変換サービスを使ってみると、 !Ref関数などがことごとくnullになってしまう。この辺をなんとか頑張ってjson化する。

json化すると(きっと)こうなる。

gist.github.com

2 dasmに喰わせる

さっそく喰わせてみる

% npm i -g cdk-dasm
% cdk-dasm < cloudformation.template.json > cloudformation-stack.ts

すると、こんなファイルが出来上がる。

// generated by cdk-dasm at 2019-11-16T08:07:46.297Z

import { Stack, StackProps, Construct, Fn } from '@aws-cdk/core';
import ec2 = require('@aws-cdk/aws-ec2');
import ec2 = require('@aws-cdk/aws-ec2');
import ec2 = require('@aws-cdk/aws-ec2');
import ec2 = require('@aws-cdk/aws-ec2');
import ec2 = require('@aws-cdk/aws-ec2');
import ec2 = require('@aws-cdk/aws-ec2');
import ec2 = require('@aws-cdk/aws-ec2');
import ec2 = require('@aws-cdk/aws-ec2');
import ec2 = require('@aws-cdk/aws-ec2');
import ec2 = require('@aws-cdk/aws-ec2');
import ec2 = require('@aws-cdk/aws-ec2');
import ec2 = require('@aws-cdk/aws-ec2');
import ec2 = require('@aws-cdk/aws-ec2');
import ec2 = require('@aws-cdk/aws-ec2');
import ec2 = require('@aws-cdk/aws-ec2');
import ec2 = require('@aws-cdk/aws-ec2');
import ec2 = require('@aws-cdk/aws-ec2');
import ec2 = require('@aws-cdk/aws-ec2');
import ec2 = require('@aws-cdk/aws-ec2');
import ec2 = require('@aws-cdk/aws-ec2');
import ec2 = require('@aws-cdk/aws-ec2');
import ec2 = require('@aws-cdk/aws-ec2');

export class MyStack extends Stack {
    constructor(scope: Construct, id: string, props: StackProps = {}) {
        super(scope, id, props);
        new ec2.CfnVPC(this, 'VPC', {
            cidrBlock: {
              "Ref": "VpcCIDR"
            },
            enableDnsSupport: true,
            enableDnsHostnames: true,
            tags: [
              {
                "key": "Name",
                "value": {
                  "Ref": "EnvironmentName"
                }
              }
            ],
        });
        new ec2.CfnInternetGateway(this, 'InternetGateway', {
            tags: [
              {
                "key": "Name",
                "value": {
                  "Ref": "EnvironmentName"
                }
              }
            ],
        });
        new ec2.CfnVPCGatewayAttachment(this, 'InternetGatewayAttachment', {
            internetGatewayId: {
              "Ref": "InternetGateway"
            },
            vpcId: {
              "Ref": "VPC"
            },
        });
        new ec2.CfnSubnet(this, 'PublicSubnet1', {
            vpcId: {
              "Ref": "VPC"
            },
            availabilityZone: {
              "Fn::Select": [
                0,
                {
                  "fn:getAZs": ""
                }
              ]
            },
            cidrBlock: {
              "Ref": "PublicSubnet1CIDR"
            },
            mapPublicIpOnLaunch: true,
            tags: [
              {
                "key": "Name",
                "value": {
                  "Fn::Sub": "${EnvironmentName} Public Subnet (AZ1)"
                }
              }
            ],
        });
        new ec2.CfnSubnet(this, 'PublicSubnet2', {
            vpcId: {
              "Ref": "VPC"
            },
            availabilityZone: {
              "Fn::Select": [
                1,
                {
                  "Fn::GetAZs": ""
                }
              ]
            },
            cidrBlock: {
              "Ref": "PublicSubnet2CIDR"
            },
            mapPublicIpOnLaunch: true,
            tags: [
              {
                "key": "Name",
                "value": {
                  "Fn::Sub": "${EnvironmentName} Public Subnet (AZ1)"
                }
              }
            ],
        });
        new ec2.CfnSubnet(this, 'PrivateSubnet1', {
            vpcId: {
              "Ref": "VPC"
            },
            availabilityZone: {
              "Fn::Select": [
                0,
                {
                  "Fn::GetAZs": ""
                }
              ]
            },
            cidrBlock: {
              "Ref": "PrivateSubnet1CIDR"
            },
            mapPublicIpOnLaunch: false,
            tags: [
              {
                "key": "Name",
                "value": {
                  "Fn::Sub": "${EnvironmentName} Private Subnet (AZ1)"
                }
              }
            ],
        });
        new ec2.CfnSubnet(this, 'PrivateSubnet2', {
            vpcId: {
              "Ref": "VPC"
            },
            availabilityZone: {
              "Fn::Select": [
                1,
                {
                  "Fn::GetAZs": ""
                }
              ]
            },
            cidrBlock: {
              "Ref": "PrivateSubnet2CIDR"
            },
            mapPublicIpOnLaunch: false,
            tags: [
              {
                "key": "Name",
                "value": {
                  "Fn::Sub": "${EnvironmentName} Private Subnet (AZ2)"
                }
              }
            ],
        });
        new ec2.CfnEIP(this, 'NatGateway1EIP', {
            domain: "vpc",
        });
        new ec2.CfnEIP(this, 'NatGateway2EIP', {
            domain: "vpc",
        });
        new ec2.CfnNatGateway(this, 'NatGateway1', {
            allocationId: {
              "Fn::GetAtt": [
                "NatGateway1EIP",
                "AllocationId"
              ]
            },
            subnetId: {
              "Ref": "PublicSubnet1"
            },
        });
        new ec2.CfnNatGateway(this, 'NatGateway2', {
            allocationId: {
              "Fn::GetAtt": [
                "NatGateway2EIP",
                "AllocationId"
              ]
            },
            subnetId: {
              "Ref": "PublicSubnet2"
            },
        });
        new ec2.CfnRouteTable(this, 'PublicRouteTable', {
            vpcId: {
              "Ref": "VPC"
            },
            tags: [
              {
                "key": "Name",
                "value": {
                  "Fn::Sub": "${EnvironmentName} Public Routes"
                }
              }
            ],
        });
        new ec2.CfnRoute(this, 'DefaultPublicRoute', {
            routeTableId: {
              "Ref": "PublicRouteTable"
            },
            destinationCidrBlock: "0.0.0.0/0",
            gatewayId: {
              "Ref": "InternetGateway"
            },
        });
        new ec2.CfnSubnetRouteTableAssociation(this, 'PublicSubnet1RouteTableAssociation', {
            routeTableId: {
              "Ref": "PublicRouteTable"
            },
            subnetId: {
              "Ref": "PublicSubnet1"
            },
        });
        new ec2.CfnSubnetRouteTableAssociation(this, 'PublicSubnet2RouteTableAssociation', {
            routeTableId: {
              "Ref": "PublicRouteTable"
            },
            subnetId: {
              "Ref": "PublicSubnet2"
            },
        });
        new ec2.CfnRouteTable(this, 'PrivateRouteTable1', {
            vpcId: {
              "Ref": "VPC"
            },
            tags: [
              {
                "key": "Name",
                "value": {
                  "Fn::Sub": "${EnvironmentName} Private Routes (AZ1)"
                }
              }
            ],
        });
        new ec2.CfnRoute(this, 'DefaultPrivateRoute1', {
            routeTableId: {
              "Ref": "PrivateRouteTable1"
            },
            destinationCidrBlock: "0.0.0.0/0",
            natGatewayId: {
              "Ref": "NatGateway1"
            },
        });
        new ec2.CfnSubnetRouteTableAssociation(this, 'PrivateSubnet1RouteTableAssociation', {
            routeTableId: {
              "Ref": "PrivateRouteTable1"
            },
            subnetId: {
              "Ref": "PrivateSubnet1"
            },
        });
        new ec2.CfnRouteTable(this, 'PrivateRouteTable2', {
            vpcId: {
              "Ref": "VPC"
            },
            tags: [
              {
                "key": "Name",
                "value": {
                  "Fn::Sub": "${EnvironmentName} Private Routes (AZ2)"
                }
              }
            ],
        });
        new ec2.CfnRoute(this, 'DefaultPrivateRoute2', {
            routeTableId: {
              "Ref": "PrivateRouteTable2"
            },
            destinationCidrBlock: "0.0.0.0/0",
            natGatewayId: {
              "Ref": "NatGateway2"
            },
        });
        new ec2.CfnSubnetRouteTableAssociation(this, 'PrivateSubnet2RouteTableAssociation', {
            routeTableId: {
              "Ref": "PrivateRouteTable2"
            },
            subnetId: {
              "Ref": "PrivateSubnet2"
            },
        });
        new ec2.CfnSecurityGroup(this, 'NoIngressSecurityGroup', {
            groupName: "no-ingress-sg",
            groupDescription: "Security group with no ingress rule",
            vpcId: {
              "Ref": "VPC"
            },
        });
    }
}

うーん、やはりまだ安定番ではないだけのことはあってすごいコードだ・・・。

3 手で修正する

ここからは、cdk deployが通る状態になるまで、手で修正するしかない。 まぁ、「どのクラスを使うか」というのは網羅しているので、愚直に直していく。

なお、Parametersは固定値にした。

// generated by cdk-dasm at 2019-11-16T08:07:46.297Z

import { Stack, StackProps, Construct, Fn } from "@aws-cdk/core";
import ec2 = require("@aws-cdk/aws-ec2");

export class MyStack extends Stack {
  constructor(scope: Construct, id: string, props: StackProps = {}) {
    super(scope, id, props);
    const environmentName = "development";
    const vpcCidr = "10.192.0.0/16";
    const publicSubnet1CIDR = "10.192.10.0/24";
    const publicSubnet2CIDR = "10.192.11.0/24";
    const privateSubnet1CIDR = "10.192.20.0/24";
    const privateSubnet2CIDR = "10.192.21.0/24";

    const vpc = new ec2.CfnVPC(this, "VPC", {
      cidrBlock: vpcCidr,
      enableDnsSupport: true,
      enableDnsHostnames: true,
      tags: [
        {
          key: "Name",
          value: environmentName
        }
      ]
    });
    const internetGateway = new ec2.CfnInternetGateway(
      this,
      "InternetGateway",
      {
        tags: [
          {
            key: "Name",
            value: environmentName
          }
        ]
      }
    );
    new ec2.CfnVPCGatewayAttachment(this, "InternetGatewayAttachment", {
      internetGatewayId: internetGateway.ref,
      vpcId: vpc.ref
    }).addDependsOn(vpc);
    const publicSubnet1 = new ec2.CfnSubnet(this, "PublicSubnet1", {
      vpcId: vpc.ref,
      availabilityZone: Fn.select(0, Fn.getAzs(props.env!.region)),
      cidrBlock: publicSubnet1CIDR,
      mapPublicIpOnLaunch: true,
      tags: [
        {
          key: "Name",
          value: `${environmentName} Public Subnet (AZ1)`
        }
      ]
    });
    publicSubnet1.addDependsOn(vpc);
    const publicSubnet2 = new ec2.CfnSubnet(this, "PublicSubnet2", {
      vpcId: vpc.ref,
      availabilityZone: Fn.select(1, Fn.getAzs(props.env!.region)),
      cidrBlock: publicSubnet2CIDR,
      mapPublicIpOnLaunch: true,
      tags: [
        {
          key: "Name",
          value: `${environmentName} Public Subnet (AZ1)`
        }
      ]
    });
    publicSubnet2.addDependsOn(vpc);
    const privateSubnet1 = new ec2.CfnSubnet(this, "PrivateSubnet1", {
      vpcId: vpc.ref,
      availabilityZone: Fn.select(0, Fn.getAzs(props.env!.region)),
      cidrBlock: privateSubnet1CIDR,
      mapPublicIpOnLaunch: false,
      tags: [
        {
          key: "Name",
          value: `${environmentName} Private Subnet (AZ1)`
        }
      ]
    });
    privateSubnet1.addDependsOn(vpc);
    const privateSubnet2 = new ec2.CfnSubnet(this, "PrivateSubnet2", {
      vpcId: vpc.ref,
      availabilityZone: Fn.select(1, Fn.getAzs(props.env!.region)),
      cidrBlock: privateSubnet2CIDR,
      mapPublicIpOnLaunch: false,
      tags: [
        {
          key: "Name",
          value: `${environmentName} Private Subnet (AZ2)`
        }
      ]
    });
    privateSubnet2.addDependsOn(vpc);
    const natGateway1Eip = new ec2.CfnEIP(this, "NatGateway1EIP", {
      domain: "vpc"
    });
    const natGateway2Eip = new ec2.CfnEIP(this, "NatGateway2EIP", {
      domain: "vpc"
    });
    const natGateway1 = new ec2.CfnNatGateway(this, "NatGateway1", {
      allocationId: natGateway1Eip.attrAllocationId,
      subnetId: publicSubnet1.ref
    });
    natGateway1.addDependsOn(natGateway1Eip);
    natGateway1.addDependsOn(publicSubnet1);
    const natGateway2 = new ec2.CfnNatGateway(this, "NatGateway2", {
      allocationId: natGateway2Eip.attrAllocationId,
      subnetId: publicSubnet2.ref
    });
    natGateway2.addDependsOn(natGateway2Eip);
    natGateway2.addDependsOn(publicSubnet2);
    const publicRouteTable = new ec2.CfnRouteTable(this, "PublicRouteTable", {
      vpcId: vpc.ref,
      tags: [
        {
          key: "Name",
          value: `${environmentName} Public Routes`
        }
      ]
    });
    publicRouteTable.addDependsOn(vpc);
    const defaultPublicRoute = new ec2.CfnRoute(this, "DefaultPublicRoute", {
      routeTableId: publicRouteTable.ref,
      destinationCidrBlock: "0.0.0.0/0",
      gatewayId: internetGateway.ref
    });
    defaultPublicRoute.addDependsOn(publicRouteTable);
    defaultPublicRoute.addDependsOn(internetGateway);
    const publicSubnet1RouteTableAssociation = new ec2.CfnSubnetRouteTableAssociation(
      this,
      "PublicSubnet1RouteTableAssociation",
      {
        routeTableId: publicRouteTable.ref,
        subnetId: publicSubnet1.ref
      }
    );
    publicSubnet1RouteTableAssociation.addDependsOn(publicRouteTable);
    publicSubnet1RouteTableAssociation.addDependsOn(publicSubnet1);

    const publicSubnet2RouteTableAssociation = new ec2.CfnSubnetRouteTableAssociation(
      this,
      "PublicSubnet2RouteTableAssociation",
      {
        routeTableId: publicRouteTable.ref,
        subnetId: publicSubnet2.ref
      }
    );
    publicSubnet2RouteTableAssociation.addDependsOn(publicRouteTable);
    publicSubnet2RouteTableAssociation.addDependsOn(publicSubnet2);
    const privateRouteTable1 = new ec2.CfnRouteTable(
      this,
      "PrivateRouteTable1",
      {
        vpcId: vpc.ref,
        tags: [
          {
            key: "Name",
            value: `${environmentName} Private Routes (AZ1)`
          }
        ]
      }
    );
    privateRouteTable1.addDependsOn(vpc);
    const defaultPrivateRoute1 = new ec2.CfnRoute(
      this,
      "DefaultPrivateRoute1",
      {
        routeTableId: privateRouteTable1.ref,
        destinationCidrBlock: "0.0.0.0/0",
        natGatewayId: natGateway1.ref
      }
    );
    defaultPrivateRoute1.addDependsOn(privateRouteTable1);
    defaultPrivateRoute1.addDependsOn(natGateway1);
    const privateSubnet1RouteTableAssociation = new ec2.CfnSubnetRouteTableAssociation(
      this,
      "PrivateSubnet1RouteTableAssociation",
      {
        routeTableId: privateRouteTable1.ref,
        subnetId: privateSubnet1.ref
      }
    );
    privateSubnet1RouteTableAssociation.addDependsOn(privateRouteTable1);
    privateSubnet1RouteTableAssociation.addDependsOn(privateSubnet1);
    const privateRouteTable2 = new ec2.CfnRouteTable(
      this,
      "PrivateRouteTable2",
      {
        vpcId: vpc.ref,
        tags: [
          {
            key: "Name",
            value: `${environmentName} Private Routes (AZ2)`
          }
        ]
      }
    );
    privateRouteTable2.addDependsOn(vpc);
    const defaultPrivateRoute2 = new ec2.CfnRoute(
      this,
      "DefaultPrivateRoute2",
      {
        routeTableId: privateRouteTable2.ref,
        destinationCidrBlock: "0.0.0.0/0",
        natGatewayId: natGateway2.ref
      }
    );
    defaultPrivateRoute2.addDependsOn(privateRouteTable2);
    defaultPrivateRoute2.addDependsOn(natGateway2);
    const privateSubnet2RouteTableAssociation = new ec2.CfnSubnetRouteTableAssociation(
      this,
      "PrivateSubnet2RouteTableAssociation",
      {
        routeTableId: privateRouteTable2.ref,
        subnetId: privateSubnet2.ref
      }
    );
    privateSubnet2RouteTableAssociation.addDependsOn(privateRouteTable2);
    privateSubnet2RouteTableAssociation.addDependsOn(privateSubnet2);
    new ec2.CfnSecurityGroup(this, "NoIngressSecurityGroup", {
      groupName: "no-ingress-sg",
      groupDescription: "Security group with no ingress rule",
      vpcId: vpc.ref
    }).addDependsOn(vpc);
  }
}