I want to wrap up the VPC template from the previous blog entry “Moving forward with CloudFormation templates”

What we ended up with there was a VPC with a Private and a Public Sub-net in 3 Availability Zones.

Now I want to start to use the Outputs section of the template.

And when that has been introduced, I want to use Nested Stacks

Why Outputs?

When we create a VPC with the template from the last blog, we get a VPC, but information about that stack is lost on us. You need to query for all VPC to find the VPC, need to search for sub-nets to be able to use them.

With Outputs, you can query the Stack it self to get information about the resources it creates.

Real example

I have the template from last blog stored in a file called blogvpc.yaml, then I create a stack with it.

$ aws --profile blogg cloudformation create-stack \
  --stack-name forblog \
  --template-body file://blogvpc.yaml
{
  "StackId":"arn:aws:cloudformation:eu-west-2:123NaN123201:stack/forblog/ef5b8d00-91fd-11eb-b513-06c721e1f154"
}

When it was done, I could see the result:

$ aws --profile blogg cloudformation describe-stacks \
  --stack-name forblog  \
  --template-body file://blogvpc.yaml
{
    "Stacks": [
        {
            "StackId": "arn:aws:cloudformation:eu-west-2:123NaN123201:stack/forblog/ef5b8d00-91fd-11eb-b513-06c721e1f154",
            "StackName": "forblog",
            "Description": "This is an attempt to create a VPC in a Cloudformation stack",
            "CreationTime": "2021-03-31T08:48:59.519Z",
            "RollbackConfiguration": {},
            "StackStatus": "CREATE_COMPLETE",
            "DisableRollback": false,
            "NotificationARNs": [],
            "Tags": [],
            "EnableTerminationProtection": false,
            "DriftInformation": {
                "StackDriftStatus": "NOT_CHECKED"
            }
        }
    ]
}

As you can see there is not a lot of useful information in this output.

Yes, I can run some other commands, for digging into the Stack.

$ aws --profile blogg cloudformation describe-stack-resources \
  --stack-name forblog
{
    "StackResources": [
        {
            "StackName": "forblog",
            "StackId": "arn:aws:cloudformation:eu-west-2:123NaN123201:stack/forblog/ef5b8d00-91fd-11eb-b513-06c721e1f154",
            "LogicalResourceId": "GatewayToInternet",
            "PhysicalResourceId": "forbl-Gatew-1ILE8XW7D3OMG",
            "ResourceType": "AWS::EC2::VPCGatewayAttachment",
            "Timestamp": "2021-03-31T08:49:36.433Z",
            "ResourceStatus": "CREATE_COMPLETE",
            "DriftInformation": {
                "StackResourceDriftStatus": "NOT_CHECKED"
            }
        },

But that gives me all the resources, and is tedious to format queries to grep out the parts that I need, most of the resources are elements I do not want to know about.

Adding Outputs

I write plural of Outputs as that is the name of the section in CloudFormation, but we start with adding only 1.

To our existing template, we add the following at the end.

Outputs:
  VPCid:
    Description: The VPCid
    Value: !Ref VPC

And then run an update of the stack

$ aws --profile blogg cloudformation update-stack \
    --stack-name forblog \
    --template-body file://blogvpc.yaml
{
  "StackId":"arn:aws:cloudformation:eu-west-2:123NaN123201:stack/forblog/ef5b8d00-91fd-11eb-b513-06c721e1f154"
}

Then I can check for the new description

$ aws --profile blogg cloudformation describe-stacks \
    --stack-name forblog
{
    "Stacks": [
        {
            "StackId": "arn:aws:cloudformation:eu-west-2:123NaN123201:stack/forblog/ef5b8d00-91fd-11eb-b513-06c721e1f154",
            "StackName": "forblog",
            "Description": "This is an attempt to create a VPC in a Cloudformation stack",
            "CreationTime": "2021-03-31T08:48:59.519Z",
            "LastUpdatedTime": "2021-03-31T09:20:15.381Z",
            "RollbackConfiguration": {},
            "StackStatus": "UPDATE_COMPLETE",
            "DisableRollback": false,
            "NotificationARNs": [],
            "Outputs": [
                {
                    "OutputKey": "VPCid",
                    "OutputValue": "vpc-085967d5f0b867c95",
                    "Description": "The VPCid"
                }
            ],
            "Tags": [],
            "EnableTerminationProtection": false,
            "DriftInformation": {
                "StackDriftStatus": "NOT_CHECKED"
            }
        }
    ]
}

Only the parts we choose to output will be visible there. But now we will turn the output to something useful.

Adding export

We can export values from a stack to use it in another stack, we just need to add an Export: part to the Outputs statements we want to export.

But as we do not get that far on VPCid alone we add export for a Private Sub-net as well.

So I change the Outputs part to this:

Outputs:
  VPCid:
    Description: The VPCid
    Value: !Ref VPC
    Export:
      Name: BlogVpc
  PrivateSubnet0:
    Description: A subnet
    Value: !Ref PrivateSubnet0
    Export:
      Name: PrivateSubnet0

and update the stack, and then check for the description I get:

$ aws --profile blogg cloudformation describe-stacks \
    --stack-name forblog
{
    "Stacks": [
        {
            "StackId": "arn:aws:cloudformation:eu-west-2:123NaN123201:stack/forblog/ef5b8d00-91fd-11eb-b513-06c721e1f154",
            "StackName": "forblog",
            "Description": "This is an attempt to create a VPC in a Cloudformation stack",
            "CreationTime": "2021-03-31T08:48:59.519Z",
            "LastUpdatedTime": "2021-03-31T10:47:07.379Z",
            "RollbackConfiguration": {},
            "StackStatus": "UPDATE_COMPLETE",
            "DisableRollback": false,
            "NotificationARNs": [],
            "Outputs": [
                {
                    "OutputKey": "PrivateSubnet0",
                    "OutputValue": "subnet-0bd91e942916edd41",
                    "Description": "A subnet",
                    "ExportName": "PrivateSubnet0"
                },
                {
                    "OutputKey": "VPCid",
                    "OutputValue": "vpc-085967d5f0b867c95",
                    "Description": "The VPCid",
                    "ExportName": "BlogVpc"
                }
            ],
            "Tags": [],
            "EnableTerminationProtection": false,
            "DriftInformation": {
                "StackDriftStatus": "NOT_CHECKED"
            }
        }
    ]
}

This change does not look like something to write home about, but actually, now we can use the resources from this stack in another.

Using the export in another template

Let us create a template called avm.yaml that starts an EC2 instance in our VPC.

---
Description: A silly template for using Export

Parameters:

  AMI:
    Type: AWS::EC2::Image::Id

Resources:

  TheVM:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: !Ref AMI
      InstanceType: t3.micro
      SubnetId:
        Fn::ImportValue: PrivateSubnet0

Outputs:
  TheVMid:
    Description: ID of the VM
    Value: !Ref TheVM

The newest AMI for Ubuntu 18.04 in eu-west-2 is, when I write this, ami-066213f162acbccdc so I use that when I create the stack:

$ aws --profile blogg cloudformation create-stack \
    --stack-name thevm \
    --template-body file://avm.yaml \
    --parameters ParameterKey=AMI,ParameterValue=ami-066213f162acbccdc
{
  "StackId": "arn:aws:cloudformation:eu-west-2:123NaN123201:stack/thevm/233c51a0-921b-11eb-b3b7-024fe784c338"
}

And then when we get description of the stack, we get the ID of an instance.

$ aws --profile blogg cloudformation describe-stacks \
    --stack-name thevm
{
    "Stacks": [
        {
            "StackId": "arn:aws:cloudformation:eu-west-2:123NaN123201:stack/thevm/233c51a0-921b-11eb-b3b7-024fe784c338",
            "StackName": "thevm",
            "Description": "A silly template for using Export",
            "Parameters": [
                {
                    "ParameterKey": "AMI",
                    "ParameterValue": "ami-066213f162acbccdc"
                }
            ],
            "CreationTime": "2021-03-31T12:18:01.855Z",
            "RollbackConfiguration": {},
            "StackStatus": "CREATE_COMPLETE",
            "DisableRollback": false,
            "NotificationARNs": [],
            "Outputs": [
                {
                    "OutputKey": "TheVMid",
                    "OutputValue": "i-0c6c58bb36a3e4afc",
                    "Description": "ID of the VM"
                }
            ],
            "Tags": [],
            "EnableTerminationProtection": false,
            "DriftInformation": {
                "StackDriftStatus": "NOT_CHECKED"
            }
        }
    ]
}

Cleanup

And then a cleanup:

$ aws --profile blogg cloudformation delete-stack \
  --stack-name thevm

Checking for if the stack has been removed:

$ aws --profile blogg cloudformation describe-stacks \
    --stack-name thevm

An error occurred (ValidationError) when calling the DescribeStacks operation: Stack with id thevm does not exist

It is gone, so now I remove the VPC stack.

$ aws --profile blogg cloudformation delete-stack \
    --stack-name forblog

Things to note about Exports

Uniqueness of the Name

The ExportName is unique in a region for your account. I find it useful to prefix the exports with a value so two stacks from the same template can coexist in the same region. So you have the same templates for stage environments as the production environments.

More about this below.

Locking of resources

Resources that you export can not be replaced or deleted. CloudFormation will protest if you try to change your stack with that result. See here

Nesting

That was the Export part, let us look at Nesting next.

Why nesting

With nesting, you can start stacks from a parent stack. Doing that allows you to reuse templates. I will now rewrite the VPC template from previous blog to use that. And by that reduce the repetition significantly.

The nested stack templates must exist in S3 and be readable for the account that creates the stack. Actually, if you create a stack from a local file, the AWS CLI just copies your template to an S3 bucket it creates for that purpose.

The Parent stack.

The complete stack is available here

Let us just dive in and take a look at it

Description and Parameters

I like having a link to the template in the description together with a description of what the templates implements

The parameters have sane default values, so you do not need to pass any when you start the stack. But there is the ExportPrefix which can be used if you are starting many different stacks from the same template.

---
Description: Template for simple VPC, 9 subnets in 3 AZ, IPv4 only. Url for template, https://s3.eu-central-1.amazonaws.com/templates.bitbit/techblog/vpc.nested.yaml

Parameters:
  SecondOctet:
    Description: The X in your 10.X.0.0/16 VPC
    Type: Number
    MinValue: 0
    MaxValue: 255
    Default: 42
  ExportPrefix:
    Type: String
    Description: Custom prefix for exported values. Can be empty
    AllowedPattern: ^[a-zA-Z0-9-_]*$
    ConstraintDescription: '^[a-zA-Z0-9-_]'
    MaxLength: '28'
    MinLength: '0'
    Default: vpc-
  Owner:
    Description: Name of owner of the resource
    Type: String
    Default: Techblog

Basic Resources

Here we define the VPC, Internet Gateway and routing tables, the usual stuff. Everything that is not Availability Zone specific

Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      EnableDnsSupport: true
      EnableDnsHostnames: true
      CidrBlock: !Sub 10.${SecondOctet}.0.0/16
      Tags:
        - Key: owner
          Value: !Ref Owner
        - Key: StackDescription
          Value: !Ref 'AWS::StackName'
        - Key: Network
          Value: Public
        - Key: Name
          Value: !Sub ${AWS::StackName} - VPC

  InternetGW:
    Type: AWS::EC2::InternetGateway

  GatewayToInternet:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGW

  RouteTablePublic:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: owner
          Value: !Ref Owner
        - Key: Name
          Value: !Sub ${AWS::StackName} - Route Table Public
  RoutePublicIPv4:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      RouteTableId: !Ref RouteTablePublic
      GatewayId: !Ref InternetGW

  RouteTablePrivate:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: owner
          Value: !Ref Owner
        - Key: Name
          Value: !Sub ${AWS::StackName} - Route Table Private

Sub-nets

And here comes the special part, when defining the sub-nets, we define stacks for each of the Availability Zones, and those define resources specific for the Availability Zone.

We need to send some parameters to the stacks, but the only thing that differs between the stacks is the AZ parameter, which is used to denote the Availability Zone.

SubnetsAZa:
  Type: AWS::CloudFormation::Stack
  Properties:
    TemplateURL: https://s3.eu-central-1.amazonaws.com/templates.bitbit/techblog/vpc.az.yaml
    Parameters:
      SecondOctet: !Ref SecondOctet
      ExportPrefix: !Ref ExportPrefix
      Owner: !Ref Owner
      AZ: 0
      VPC: !Ref VPC
      RouteTablePublic: !Ref RouteTablePublic
      RouteTablePrivate: !Ref RouteTablePrivate
SubnetsAZb:
  Type: AWS::CloudFormation::Stack
  Properties:
    TemplateURL: https://s3.eu-central-1.amazonaws.com/templates.bitbit/techblog/vpc.az.yaml
    Parameters:
      SecondOctet: !Ref SecondOctet
      ExportPrefix: !Ref ExportPrefix
      Owner: !Ref Owner
      AZ: 1
      VPC: !Ref VPC
      RouteTablePublic: !Ref RouteTablePublic
      RouteTablePrivate: !Ref RouteTablePrivate
SubnetsAZc:
  Type: AWS::CloudFormation::Stack
  Properties:
    TemplateURL: https://s3.eu-central-1.amazonaws.com/templates.bitbit/techblog/vpc.az.yaml
    Parameters:
      SecondOctet: !Ref SecondOctet
      ExportPrefix: !Ref ExportPrefix
      Owner: !Ref Owner
      AZ: 2
      VPC: !Ref VPC
      RouteTablePublic: !Ref RouteTablePublic
      RouteTablePrivate: !Ref RouteTablePrivate

Outputs

This stack only outputs and exports resources that are not Availability Zone specific.

Outputs:
  VPCid:
    Description: The VPCid
    Value: !Ref VPC
    Export:
      Name: !Sub ${ExportPrefix}VPCid
  VPCidr:
    Description: The VPC IPv4 Subnet
    Value: !GetAtt VPC.CidrBlock
    Export:
      Name: !Sub ${ExportPrefix}VPCidr
  RouteTablePublic:
    Description: Route Table for the Public Subnets
    Value: !Ref RouteTablePrivate
    Export:
      Name: !Sub ${ExportPrefix}RouteTablePublic
  RouteTablePrivate:
    Description: Route Table for the Private Subnets
    Value: !Ref RouteTablePrivate
    Export:
      Name: !Sub ${ExportPrefix}RouteTablePrivate

The child stacks

Description and Parameters

As in the parent template here us a link to the template in the description together with a description of what the templates implements

The parameters here have their values to passed by the parent stack.

---
Description: Template for subnets in an Availability Zone VPC, IPv4 only. Url for template, https://s3.eu-central-1.amazonaws.com/templates.bitbit/techblog/vpc.az.yaml

Parameters:
  SecondOctet:
    Description: The X in your 10.X.0.0/16 VPC
    Type: Number
    MinValue: 0
    MaxValue: 255
  ExportPrefix:
    Type: String
    Description: Custom prefix for exported values. Can be empty
    AllowedPattern: ^[a-zA-Z0-9-_]*$
    ConstraintDescription: '^[a-zA-Z0-9-_]'
    MaxLength: '28'
    MinLength: '0'
  Owner:
    Description: Name of owner of the resource
    Type: String
  AZ:
    Description: The number of the AZ the resource should be in, 0 - 2 for 3 AZ
    Type: Number
    MinValue: 0
    MaxValue: 2
  VPC:
    Description: The VPC this AZ belongs to
    Type: AWS::EC2::VPC::Id
  RouteTablePublic:
    Description: The route table for the Public Subnet
    Type: String
  RouteTablePrivate:
    Description: The route table for the Private Subnet
    Type: String

The Sub-nets

Here we define the 3 types of sub-nets we have in each Availability Zone. One Public Sub-net, one Private Sub-net behind NAT gateway, and a Private Sub-net without access to Internet.

I use the Availability Zone number in the definition of of the CIDR for the sub-net.

Resources:
  PublicSubnet:
    Type: 'AWS::EC2::Subnet'
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone:
        Fn::Select:
          - !Ref AZ
          - Fn::GetAZs: ""
      CidrBlock: !Sub 10.${SecondOctet}.${AZ}.0/24
      MapPublicIpOnLaunch: true
      Tags:
        - Key: owner
          Value: !Ref Owner
        - Key: StackDescription
          Value: !Ref 'AWS::StackName'
        - Key: Network
          Value: Public
        - Key: Name
          Value: !Sub ${ExportPrefix}PublicSubnet-${AZ}
  PrivateSubnet:
    Type: 'AWS::EC2::Subnet'
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone:
        Fn::Select:
          - !Ref AZ
          - Fn::GetAZs: ""
      CidrBlock: !Sub 10.${SecondOctet}.1${AZ}.0/24
      Tags:
        - Key: owner
          Value: !Ref Owner
        - Key: StackDescription
          Value: !Ref 'AWS::StackName'
        - Key: Network
          Value: Private
        - Key: Name
          Value: !Sub ${ExportPrefix}PrivateSubnet-${AZ}

  PrivateNATSubnet:
    Type: 'AWS::EC2::Subnet'
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone:
        Fn::Select:
          - !Ref AZ
          - Fn::GetAZs: ""
      CidrBlock: !Sub 10.${SecondOctet}.2${AZ}.0/24
      Tags:
        - Key: owner
          Value: !Ref Owner
        - Key: StackDescription
          Value: !Ref 'AWS::StackName'
        - Key: Network
          Value: Private
        - Key: Name
          Value: !Sub ${ExportPrefix}PrivateNATSubnet-${AZ}

NAT Gateway and route table

There is a NAT Gateway in each Availability Zone. And it has its own route table.

NatGWIP:
  Type: AWS::EC2::EIP
  Properties:
    Domain: vpc

NatGW:
  Type: AWS::EC2::NatGateway
  Properties:
    AllocationId: !GetAtt NatGWIP.AllocationId
    SubnetId: !Ref PublicSubnet
    Tags:
      - Key: owner
        Value: !Ref Owner

RouteTableNATPrivate:
  Type: AWS::EC2::RouteTable
  Properties:
    VpcId: !Ref VPC
    Tags:
      - Key: owner
        Value: !Ref Owner

RoutePrivateNATIPv4:
  Type: AWS::EC2::Route
  Properties:
    DestinationCidrBlock: 0.0.0.0/0
    RouteTableId: !Ref RouteTableNATPrivate
    NatGatewayId: !Ref NatGW

Associating the sub-nets and route tables

That needs to be done for each sub-net. The isolated Private Sub-net uses a Route Table that was created in the parent stack and sent as a Parameter. Same for the Public Sub-net. There is a unique Route Table for each NAT’ed sub-net as they have their own NAT gateway.

Route2SubnetPublicIPv4:
  Type: AWS::EC2::SubnetRouteTableAssociation
  Properties:
    RouteTableId: !Ref RouteTablePublic
    SubnetId: !Ref PublicSubnet

Route2SubnetPrivateNATIPv4:
  Type: AWS::EC2::SubnetRouteTableAssociation
  Properties:
    RouteTableId: !Ref RouteTableNATPrivate
    SubnetId: !Ref PrivateNATSubnet



Route2SubnetPrivateIPv4:
  Type: AWS::EC2::SubnetRouteTableAssociation
  Properties:
    RouteTableId: !Ref RouteTablePrivate
    SubnetId: !Ref PrivateSubnet

Outputs

Here we export the Sub-nets for the Availability Zone and the Route Table for the NAT Gateway. Remember that the ExportName needs to be unique, so I prefix it with the ExportPrefix Parameter, and Postfix with the number of the Availability Zone in the AZ Parameter.

Outputs:
  PublicSubnet:
    Description: Public Subnet
    Value: !Ref PublicSubnet
    Export:
      Name: !Sub ${ExportPrefix}PublicSubnetAZ${AZ}

  PrivateSubnet:
    Description: Private Subnet
    Value: !Ref PrivateSubnet
    Export:
      Name: !Sub ${ExportPrefix}PrivateSubnetAZ${AZ}
  PrivateNATSubnet:
    Description: Private NAT Subnet
    Value: !Ref PrivateNATSubnet
    Export:
      Name: !Sub ${ExportPrefix}PrivateNATSubnetAZ${AZ}

  RouteTableNATPrivate:
    Description: Route Table for Private NAT Subnet
    Value: !Ref RouteTableNATPrivate
    Export:
      Name: !Sub ${ExportPrefix}RouteTableNATPrivateAZ${AZ}

Take it for a spin…

We create a single stack from the parent template by running

$ aws --profile blogg cloudformation create-stack \
    --stack-name nested-blog \
    --template-url https://s3.eu-central-1.amazonaws.com/templates.bitbit/techblog/vpc.nested.yaml
{
    "StackId": "arn:aws:cloudformation:eu-west-2:123NaN123201:stack/nested-blog/edb4d580-9237-11eb-9a54-0a355b7dff20"
}

Then after quite some while we can look at the stack

$ aws --profile blogg cloudformation describe-stacks \
    --stack-name nested-blog
{
    "Stacks": [
        {
            "StackId": "arn:aws:cloudformation:eu-west-2:123NaN123201:stack/nested-blog/edb4d580-9237-11eb-9a54-0a355b7dff20",
            "StackName": "nested-blog",
            "Description": "Template for simple VPC, 9 subnets in 3 AZ, IPv4 only. Url for template, https://s3.eu-central-1.amazonaws.com/templates.bitbit/techblog/vpc.nested.yaml",
            "Parameters": [
                {
                    "ParameterKey": "Owner",
                    "ParameterValue": "Techblog"
                },
                {
                    "ParameterKey": "ExportPrefix",
                    "ParameterValue": "vpc-"
                },
                {
                    "ParameterKey": "SecondOctet",
                    "ParameterValue": "42"
                }
            ],
            "CreationTime": "2021-03-31T15:44:07.614Z",
            "RollbackConfiguration": {},
            "StackStatus": "CREATE_COMPLETE",
            "DisableRollback": false,
            "NotificationARNs": [],
            "Outputs": [
                {
                    "OutputKey": "RouteTablePrivate",
                    "OutputValue": "rtb-0ad3e61a075aeea7c",
                    "Description": "Route Table for the Private Subnets",
                    "ExportName": "vpc-RouteTablePrivate"
                },
                {
                    "OutputKey": "VPCid",
                    "OutputValue": "vpc-05e40be58b0da76cc",
                    "Description": "The VPCid",
                    "ExportName": "vpc-VPCid"
                },
                {
                    "OutputKey": "VPCidr",
                    "OutputValue": "10.42.0.0/16",
                    "Description": "The VPC IPv4 Subnet",
                    "ExportName": "vpc-VPCidr"
                },
                {
                    "OutputKey": "RouteTablePublic",
                    "OutputValue": "rtb-0ad3e61a075aeea7c",
                    "Description": "Route Table for the Public Subnets",
                    "ExportName": "vpc-RouteTablePublic"
                }
            ],
            "Tags": [],
            "EnableTerminationProtection": false,
            "DriftInformation": {
                "StackDriftStatus": "NOT_CHECKED"
            }
        }
    ]
}

That does not show us the Sub-net stacks, but we can ask CloudFormation for all exports, and get a long list, I include it here for fun and profit.

$ aws --profile blogg cloudformation list-exports
{
    "Exports": [
        {
            "ExportingStackId": "arn:aws:cloudformation:eu-west-2:123NaN123201:stack/nested-blog-SubnetsAZa-1S3IELV5150CM/fcf7d8d0-9237-11eb-a30a-0a953df5d268",
            "Name": "vpc-PrivateNATSubnetAZ0",
            "Value": "subnet-0fa4f57de3e503ff2"
        },
        {
            "ExportingStackId": "arn:aws:cloudformation:eu-west-2:123NaN123201:stack/nested-blog-SubnetsAZb-8ULH4BH3LOQ6/fcf4a480-9237-11eb-b513-06c721e1f154",
            "Name": "vpc-PrivateNATSubnetAZ1",
            "Value": "subnet-027efcf30c5209d95"
        },
        {
            "ExportingStackId": "arn:aws:cloudformation:eu-west-2:123NaN123201:stack/nested-blog-SubnetsAZc-1N9U05AXMUP8V/fce11c80-9237-11eb-93ff-022e9396137a",
            "Name": "vpc-PrivateNATSubnetAZ2",
            "Value": "subnet-00ef014554daa6ccc"
        },
        {
            "ExportingStackId": "arn:aws:cloudformation:eu-west-2:123NaN123201:stack/nested-blog-SubnetsAZa-1S3IELV5150CM/fcf7d8d0-9237-11eb-a30a-0a953df5d268",
            "Name": "vpc-PrivateSubnetAZ0",
            "Value": "subnet-0a1ad9437c884b157"
        },
        {
            "ExportingStackId": "arn:aws:cloudformation:eu-west-2:123NaN123201:stack/nested-blog-SubnetsAZb-8ULH4BH3LOQ6/fcf4a480-9237-11eb-b513-06c721e1f154",
            "Name": "vpc-PrivateSubnetAZ1",
            "Value": "subnet-025a179fe5b3820f8"
        },
        {
            "ExportingStackId": "arn:aws:cloudformation:eu-west-2:123NaN123201:stack/nested-blog-SubnetsAZc-1N9U05AXMUP8V/fce11c80-9237-11eb-93ff-022e9396137a",
            "Name": "vpc-PrivateSubnetAZ2",
            "Value": "subnet-07d004eb9c1192773"
        },
        {
            "ExportingStackId": "arn:aws:cloudformation:eu-west-2:123NaN123201:stack/nested-blog-SubnetsAZa-1S3IELV5150CM/fcf7d8d0-9237-11eb-a30a-0a953df5d268",
            "Name": "vpc-PublicSubnetAZ0",
            "Value": "subnet-017edbd14176b6c02"
        },
        {
            "ExportingStackId": "arn:aws:cloudformation:eu-west-2:123NaN123201:stack/nested-blog-SubnetsAZb-8ULH4BH3LOQ6/fcf4a480-9237-11eb-b513-06c721e1f154",
            "Name": "vpc-PublicSubnetAZ1",
            "Value": "subnet-03460753b66112c9f"
        },
        {
            "ExportingStackId": "arn:aws:cloudformation:eu-west-2:123NaN123201:stack/nested-blog-SubnetsAZc-1N9U05AXMUP8V/fce11c80-9237-11eb-93ff-022e9396137a",
            "Name": "vpc-PublicSubnetAZ2",
            "Value": "subnet-062419d50a7aa907c"
        },
        {
            "ExportingStackId": "arn:aws:cloudformation:eu-west-2:123NaN123201:stack/nested-blog-SubnetsAZa-1S3IELV5150CM/fcf7d8d0-9237-11eb-a30a-0a953df5d268",
            "Name": "vpc-RouteTableNATPrivateAZ0",
            "Value": "rtb-0ea59402ad6fb359b"
        },
        {
            "ExportingStackId": "arn:aws:cloudformation:eu-west-2:123NaN123201:stack/nested-blog-SubnetsAZb-8ULH4BH3LOQ6/fcf4a480-9237-11eb-b513-06c721e1f154",
            "Name": "vpc-RouteTableNATPrivateAZ1",
            "Value": "rtb-096736e362d8009b4"
        },
        {
            "ExportingStackId": "arn:aws:cloudformation:eu-west-2:123NaN123201:stack/nested-blog-SubnetsAZc-1N9U05AXMUP8V/fce11c80-9237-11eb-93ff-022e9396137a",
            "Name": "vpc-RouteTableNATPrivateAZ2",
            "Value": "rtb-0f044d02cd62dbf41"
        },
        {
            "ExportingStackId": "arn:aws:cloudformation:eu-west-2:123NaN123201:stack/nested-blog/edb4d580-9237-11eb-9a54-0a355b7dff20",
            "Name": "vpc-RouteTablePrivate",
            "Value": "rtb-0ad3e61a075aeea7c"
        },
        {
            "ExportingStackId": "arn:aws:cloudformation:eu-west-2:123NaN123201:stack/nested-blog/edb4d580-9237-11eb-9a54-0a355b7dff20",
            "Name": "vpc-RouteTablePublic",
            "Value": "rtb-0ad3e61a075aeea7c"
        },
        {
            "ExportingStackId": "arn:aws:cloudformation:eu-west-2:123NaN123201:stack/nested-blog/edb4d580-9237-11eb-9a54-0a355b7dff20",
            "Name": "vpc-VPCid",
            "Value": "vpc-05e40be58b0da76cc"
        },
        {
            "ExportingStackId": "arn:aws:cloudformation:eu-west-2:123NaN123201:stack/nested-blog/edb4d580-9237-11eb-9a54-0a355b7dff20",
            "Name": "vpc-VPCidr",
            "Value": "10.42.0.0/16"
        }
    ]
}

We can get the short version of that by using the --query argument

$ aws --profile blogg cloudformation list-exports \
    --query Exports[].Name
[
    "vpc-PrivateNATSubnetAZ0",
    "vpc-PrivateNATSubnetAZ1",
    "vpc-PrivateNATSubnetAZ2",
    "vpc-PrivateSubnetAZ0",
    "vpc-PrivateSubnetAZ1",
    "vpc-PrivateSubnetAZ2",
    "vpc-PublicSubnetAZ0",
    "vpc-PublicSubnetAZ1",
    "vpc-PublicSubnetAZ2",
    "vpc-RouteTableNATPrivateAZ0",
    "vpc-RouteTableNATPrivateAZ1",
    "vpc-RouteTableNATPrivateAZ2",
    "vpc-RouteTablePrivate",
    "vpc-RouteTablePublic",
    "vpc-VPCid",
    "vpc-VPCidr"
]

Clean up afterwards.

$ aws --profile blogg cloudformation delete-stack \
    --stack-name nested-blog

This will remove the child stacks and then the parent stack.

Now the VPC stack is out of the way, and next time I can describe templates defining something more interesting than a VPC.

Jónas Helgi Pálsson

Senior Systems Consultant at Redpill Linpro

Jónas joined Redpill Linpro over a decade ago and has in that period worked as both a consultant and a system administrator. Main focus currently for Jónas is AWS and infrastructure on that platform. Previously been working with KVM and OpenStack, dabbles with programming and has a soft spot for openSUSE.

Containerized Development Environment

Do you spend days or weeks setting up your development environment just the way you like it when you get a new computer? Is your home directory a mess of dotfiles and metadata that you’re reluctant to clean up just in case they do something useful? Do you avoid trying new versions of software because of the effort to roll back software and settings if the new version doesn’t work?

Take control over your local development environment with containerization and Dev-Env-as-Code!

... [continue reading]

Ansible-runner

Published on February 27, 2024

Portable Java shell scripts with Java 21

Published on February 21, 2024