Jarvis AI
Talent Solutions
Public Sector
About
image

Passing Resources in Nested Cloudformation

Read Time 3 mins | Written by: Ryo Hang | Publish Date:

Passing Resources in Nested Cloudformation

passing resources in nested cloudformationIn past few posts, we always automated our stack with cloudformation. Cloudformation template(CFT) is pretty handy tool for engineer to recreate entire stack repetitively. As we wrote more and more cloudformation template for clients, we found a few drawbacks in our monolithic cloudformation template. AWS offer nested cloudformation template. As we converting our template into nested format, we encountered a few challenge like passing resources in nested cloudformation, intrinsic import function. We will walk you through them with comprehensive example as always.

  1. Body Size limit – cloudformation has a 51200 bytes body size limit.
  2. Duplication – there are some basic resource elements defined multiple times across templates such as roles, security groups etc.
  3. Monolithic – we’d like to break templates into small modules, and assemble them as necessary.

Passing Resources:

First we need to break monolithic cloudformation template into pieces, so that we can share resources between the templates without duplication. Hence, we need a way of passing resources in nested cloudformation templates. It took us a while to figure out how one stack traverse resource name from other stack and passing resources in nested cloudformation templates.

Export stack name:

It’s important to export stack name first, because every resource exists in cloudformation template. In order to export resource, we need the ability to tell other template the origin of the resource. We can’t wrap our head around that at the beginning, but it makes sense at the end.

In cloudformation , you can declare “Outputs” section that it allows to import into other stacks. The follow example demonstrates how to export a stack name.

   Outputs:
     StackName:
     Value: !Ref AWS::StackName

Export resource:

Now we can export resource to share with other template with name convention like  ${AWS::StackName}-ResourceName.

  Outputs:
    ResourceByRef:
      Value: !Ref 'Resource1'
      Description: xxxxx
      Export: 
        Name: !Sub "${AWS::StackName}-ResourceByRef"
    ResourceArn:
      Value: !Ref 'Resource2'
      Description: xxxxx
      Export: 
        Name: !Sub "${AWS::StackName}-ResourceArn"

Import resource:

Import resource is a little bit tricky. You don’t have to pass each resource as parameters to other template, all you need to do is to pass that stack as parameter. And look it up by name convention from previous output. e.g. ${AWS::StackName}-ResourceName

You also need to utilize intrinsic function. e.g Fn::ImportValue: !Sub "${AWS::StackName}-ResourceByRef"

Example(put everything together):

example Passing Resources in Nested Cloudformation

Master template:

Description: >
   A master template for demo https://blog.ascendingdc.com/passing-paramete…d-cloudformation/
Parameters:
  S3Location:
    Description: Your S3 location to store cloudformation template
    Type: String
    Default: "https://s3.amazonaws.com/tutorial-leyi/blog/"
    MinLength: 1
Resources:
  Bucket:
    Type: "AWS::CloudFormation::Stack"
    Properties:
      TemplateURL: !Join \["",\[!Ref S3Location,"template1.yml"\]\]
  EC2Role:
    Type: "AWS::CloudFormation::Stack"
    Properties:
      Parameters:
        TemplateStack: !GetAtt Bucket.Outputs.StackName
      TemplateURL: !Join \["",\[!Ref S3Location,"template2.yml"\]\]

Template 1:

--- AWSTemplateFormatVersion: ‘2010-09-09’ Description: Resource 1 template to create s3 bucket for export

Resources:      
  DataStorage:
    Type: "AWS::S3::Bucket"
    Properties:
      Tags: 
      - Key: "product"
        Value: "sample"
Outputs:
  DataStorageArn:
    Value: !GetAtt DataStorage.Arn
    Description: S3 bucket arn
    Export: 
      Name: !Sub "${AWS::StackName}-DataStorage"
  StackName:
    Value: !Ref AWS::StackName

Template 2:

--- AWSTemplateFormatVersion: ‘2010-09-09’ Description: Resource 2 grant role for s3 bucket

Parameters:
  TemplateStack:
    Description: template1 stack name
    Type: String
    AllowedPattern : "^\[a-zA-Z\]\[-a-zA-Z0-9\]\*$"
    MinLength: 1
    MaxLength : 1024
Resources:  
  ec2Role:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
        - Effect: Allow
          Principal:
            Service:
            - ec2.amazonaws.com
          Action:
          - sts:AssumeRole
      Path: "/"
      Policies:
      - PolicyName: log-service
        PolicyDocument:
          Statement:
          - Effect: Allow
            Action:
            - logs:CreateLogStream
            - logs:PutLogEvents
            Resource: "\*"
      - PolicyName: s3-policy
        PolicyDocument:
          Statement:
          - Effect: Allow
            Action:  \['s3:\*'\]
            Resource:
              - Fn::ImportValue: !Sub "${TemplateStack}-DataStorage"

Choosing Between Exports and Parameters

The pattern above uses Export plus Fn::ImportValue, but that is not the only way to move a value between stacks, and the choice has consequences that only appear later.

An export is account- and Region-global: the name must be unique across every stack in that scope, and once another stack imports it, the exporting stack is locked. CloudFormation will refuse to delete or modify the exported output while a consumer exists. That refusal is a feature — it stops you from deleting a VPC that six stacks depend on — but it also means a widely imported value becomes very hard to change. Renaming one requires unwinding every consumer first.

A parameter passed from a parent to a nested stack has the opposite profile. It creates no global name, imposes no deletion lock, and can differ per environment because the parent decides the value. The cost is that the wiring is explicit: every value has to be threaded down through the parent, which gets verbose in deep hierarchies.

A workable rule is to export values that are genuinely shared infrastructure with a long life — VPC IDs, subnet IDs, a shared KMS key ARN — and pass everything else as parameters. If you find yourself exporting something that changes per deployment, that is usually the wrong tool.

Naming Exports So They Survive Growth

The ${AWS::StackName}-ResourceName convention in the examples above is doing more work than it appears to. Because the stack name is part of the export name, the same template can be deployed several times — dev, staging, prod, or one per team — without the exports colliding. Hard-code a bare name like DataStorage and the second deployment fails outright, because the export name is already taken in that account and Region.

Two habits keep this maintainable. Keep the resource half of the name stable and descriptive, so Fn::ImportValue calls read clearly at the consuming end. And treat an export name as a public interface: once another stack imports it, changing it is a breaking change that needs the same care as changing an API.

Debugging a Failed Import

Three failures account for most of the time lost with cross-stack references.

“No export named X found” almost always means a scope mismatch rather than a typo. Exports do not cross Region or account boundaries — a stack in us-east-1 cannot import from us-west-2, and there is no cross-account import. Verify the exporting stack completed successfully in the same Region; a rolled-back stack leaves no exports behind.

“Export cannot be updated as it is in use” is the deletion lock doing its job. aws cloudformation list-imports --export-name <name> names the stacks currently consuming it, which is the list you have to unwind before the export can change.

Circular dependencies appear when two stacks import from each other. CloudFormation cannot order the deployment and fails. The fix is structural: extract the shared resource into a third stack that both import from, so the dependency graph stays acyclic.

Nested Stacks or Separate Stacks?

Nested stacks — where a parent declares AWS::CloudFormation::Stack children — give you one deployment unit. The whole tree updates together and rolls back together, which is exactly what you want when the pieces are meaningless apart, such as a service and the load balancer in front of it.

Separate stacks joined by exports give you independent lifecycles. A networking stack that changes twice a year should not be redeployed every time an application ships, and separate stacks let each move at its own pace.

The decision follows the change rate. Components that change together belong in one nested tree; components that change on different schedules belong in separate stacks with an export boundary between them.