If you are a CloudFormation user you have probably come across a situation where an ID of a resource created in one stack (e.g. Subnet ID from VPC-Stack) is needed in another, independent stack, for example to create an EC2 instance in EC2-Stack.
The traditional approach is to pass Outputs
from one template to Parameters
in another template. However there are a few problems…
- The first problem with this method is that is that you somehow have to collect the Outputs from one stack and pass them to the Parameters in another. This can be done either manually or through some scripting like AWS-CLI or Ansible, but it’s an extra, mundane work.
- The second problem is that many stacks need the same set of parameters – VPC ID, Subnet IDs, perhaps some Security Group IDs, Route53 hosted zone name and ID, etc. The Parameters list can get very long and not very interesting.
There is a better option – CloudFormation Stack Exports
CloudFormation Stack Exports
The idea is that CFN Stacks that create resources Export
their output values to a global namespace under some well known Name
(global = account+region wide). Then the CFN Stacks that need those resources Import
them from that global namespace using the known export Name
.
For example in a VPC stack we can have an export like this:
Outputs: VpcId: Value: !Ref VPC Export: Name: MyVpcId
Then in the EC2 stack we can import it:
Resources: InstanceSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: [...] VpcId: Fn::ImportValue: MyVpcId
No stack parameters needed, all we need is to know that the VPC ID was exported under a name MyVpcId
.
Namespaces—{Project}:{EnvName}:{EnvIndex}:{ResourceName}
The obvious problem with the above is that you may want to deploy multiple versions of your stacks – for example dev-1, dev-2, test-1, test-2, prod-1, prod-2, etc. They can’t all export MyVpcId
because – as mentioned – the Exports namespace is global in the account/region and once one of your stacks exports MyVpcId
no other stack can export the same name.
Solution? Namespaces!
They are not required or prescribed by AWS and it’s up to the user to come up with something sensible. Over time I settled on a simple form: {Project}:{EnvName}:{EnvIndex}:{ResourceName}
For example the ECS Cluster name in dev-1 environment of MyApp deployment will be exported as: myapp:dev:1:ecs-cluster
Sometimes you may want to share some resources between environments, e.g. you may want to have a single VPC for all your Dev environments. In that case I use all
for the EnvIndex
field, for example: myapp:dev:all:vpc-id
With this “namespace system” in place we only need to pass EnvName
and EnvIndex
Parameters to each template and the template will figure out all the rest.
# vpc-template.yml --- Parameters: EnvName: Type: String Resources: VPC: Type: AWS::EC2::VPC Properties: [...] Outputs: VpcId: Value: !Ref VPC Export: Name: !Sub "myapp:${EnvName}:all:vpc-id"
Then in the ECS stack’s Security Group we import it with Fn::ImportValue
intrinsic function:
# ecs-template.yml --- Parameters: EnvName: Type: String EnvIndex: Type: String Resources: EcsSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: [...] VpcId: Fn::ImportValue: !Sub "myapp:${EnvName}:all:vpc-id"
And in the same stack we export the ECS Cluster Name and ARN, this time for a specific EnvIndex
:
# still in ecs-template.yml Outputs: EcsCluster: Value: !Ref EcsCluster Export: Name: !Sub "myapp:${EnvName}:${EnvIndex}:ecs-cluster-name" EcsClusterArn: Value: !GetAtt EcsCluster.Arn Export: Name: !Sub "myapp:${EnvName}:${EnvIndex}:ecs-cluster-arn"
And finally in the actual ECS Service stack that gets deployed through CI/CD we import the cluster name:
# task-template.yml --- [...] Resources: TaskService: Type: AWS::ECS::Service Properties: Cluster: Fn::ImportValue: !Sub "myapp:${EnvName}:${EnvIndex}:ecs-cluster-name" TaskDefinition: !Ref TaskDefinition [...]
This way we can build dev / test / prod stacks in the same account by setting EnvName
stack parameter and also multiple versions of each (blue / green or 1, 2, 3, ..) by setting the EnvIndex
parameter. And we don’t have to pass heaps of repetitive Parameters to each template.
View the exports
You can view all the exports in CloudFormation ➔ Exports tab:
And of course you can use AWS-CLI to look up the exports in your scripts with aws cloudformation list-exports
That’s all for today 🙂
Nice idea! Exactly what I was searching for.
Thanks
Cfn exports create a problem if you ever want to change the underlying resource that was exported when other stacks already import the value.
It’s just something to watch out for. I typically only use exports for thing like VPCs and route53 hosted zones which are unlikely to change over time.