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.