Using empty CloudFormation Stacks to publish outputs for CloudFormation

Using CloudFormation Stacks with only outputs to manage dependency constraints with Terraform

Table Of Contents

Today I Explained

Terraform can be an excellent tool for provisioning infrastructure, as it supports cross-region & cross-account patterns which aren’t doable within CloudFormation without the use of Stacksets. This can present challenges when needing to expose outputs from this infrastructure to be consumable by CloudFormation.

Although it is possible to rely on the CloudFormation deployer to look-up these values before-hand to be passed as parameters, an alternative approach is to encode these values as predictable named parameter store entries. These parameter store entries can be resolved within CloudFormation, either as a default for a parameter or a dynamic reference.

An issue that arises with this use of parameter store entries is that no dependency relationship exists between CloudFormation & these Terraform provisioned parameter store entries. Should one of these parameters become removed due to deprecation, it means any CloudFormation stack relying on the parameter will suddenly fail.

An approach that allows for a strict dependency relationship is CloudFormation Outputs which have numerous restrictions on them to ensure consistency across the infrastructure as code within AWS. Two constraints are especially useful for our use-case:

  • You can’t delete a stack if another stack references one of its outputs.
  • You can’t modify or remove an output value that is referenced by another stack.

By preventing a CloudFormation output (or stack) from being deleted, it means that all consumers of this output between Terraform & CloudFormation must first be removed before it can be deleted. This provides an safe-guard when deprecating, as one can “test” the removal to confirm whether all usages have been successfully removed.

The second property includes the provision ‘You can’t modify’, which would stop the output from being updated. This can be worked around by taking advantage of Parameter Store. You cannot modify the output value of a CloudFormation Stack if it is being used, you can modify the value contained within a Parameter Store that is referenced by another stack. This allows for provisioning the parameter store entries in CloudFormation, with the output in the CloudFormation Stack being the name of the Parameter Store.

This can look something like the following within the output-only stack,

Resources:
  SampleValue:
    Type: AWS::SSM::Parameter
    Properties: 
      Name: 
        !Sub "${AWS::StackName}-SampleValueInSSM"
      Type: String
      Value: MyExampleValue1234

Outputs:
  SampleValue:
    Value:
      !Ref SampleValue
    Export:
      Name:
        'Fn::Sub': '${AWS::StackName}-SampleValue'

This can then be used within other CloudFormation stacks using ImportValue and resolve:ssm, and should any attempt be made to remove the output or delete the stack, it will fail with an error that Export ...-SampleValue cannot be deleted as it is in use by <stackname>.

The CloudFormation for importing the value is:

Outputs:
  GeneratedName:
    Value:
      Fn::Sub:
        - "{{resolve:ssm:${Stack}}}"
        - Stack:
            Fn::ImportValue: 'aws-cfn-using-outputs-from-terraform-SampleValue'