Optional Parameters For Pre-existing Resources in AWS CloudFormation Templates

stack creates new AWS resources unless user specifies pre-existing

Background

I like to design CloudFormation templates that create all of the resources necessary to implement the desired functionality without requiring a lot of separate, advanced setup. For example, the AWS Git-backed Static Website creates all of the interesting pieces including a CodeCommit Git repository, S3 buckets for web site content and logging, and even the Route 53 hosted zone.

Creating all of these resources is great if you were starting from scratch on a new project. However, you may sometimes want to use a CloudFormation template to enhance an existing account where one or more of the AWS resources already exist.

For example, consider the case where the user already has a CodeCommit Git repository and a Route 53 hosted zone for their domain. They still want all of the enhanced functionality provided in the Git-backed static website CloudFormation stack, but would rather not have to fork and edit the template code just to fit it in to the existing environment.

What if we could use the same CloudFormation template for different types of situations, sometimes pluging in pre-existing AWS resources, and other times letting the stack create the resources for us?

Solution

With assistance from Ryan Scott Brown, the Git-backed static website CloudFormation template now allows the user to optionally specify a number of pre-existing resources to be integrated into the new stack. If any of those parameters are left empty, then the CloudFormation template automatically creates the required resources.

Let’s walk through relevant pieces of the CloudFormation template code using the CodeCommit Git repository as an example of an optional resource. [Note: Code exerpts below may have been abbreviated and slightly altered for article clarity.]

In the CloudFormation template Parameters section, we allow the user to pass in the name of a CodeCommit Git repository that was previously created in the AWS account. If this parameter is specified, then the CloudFormation template uses the pre-existing repository in the new stack. If the parameter is left empty when the template is run, then the CloudFormation stack will create a new CodeCommit Git repository.

Parameters:
  PreExistingGitRepository:
    Description: "Optional Git repository name for pre-existing CodeCommit repository. Leave empty to have CodeCommit Repository created and managed by this stack."
    Type: String
    Default: ""

We add an entry to the Conditions section in the CloudFormation template that will indicate whether or not a pre-existing CodeCommit Git repository name was provided. If the parameter is empty, then we will need to create a new repository.

Conditions:
  NeedsNewGitRepository: !Equals [!Ref PreExistingGitRepository, ""]

In the Resources section, we create a new CodeCommit Git repository, but only on the condition that we need a new one (i.e., the user did not specify one in the parameters). If a pre-existing CodeCommit Git repository name was specified in the stack parameters, then this resource creation will be skipped entirely.

Resources:
  GitRepository:
    Condition: NeedsNewGitRepository
    Type: "AWS::CodeCommit::Repository"
    Properties:
      RepositoryName: !Ref GitRepositoryName
    DeletionPolicy: Retain

We then come to parts of the CloudFormation template where other resources need to refer to the CodeCommit Git repository. We need to use an If conditional to refer to the correct resource, since it might be a pre-existing one passed in a parameter or it might be one created in this stack.

Here’s an example where the CodePipeline resource needs to specify the Git repository name as the source of a pipeline stage.

Resources:
  CodePipeline:
    Type: "AWS::CodePipeline::Pipeline"
    [...]
      RepositoryName: !If [NeedsNewGitRepository, !Ref GitRepositoryName, !Ref PreExistingGitRepository]

We use the same conditional to place the name of the Git repository in the CloudFormation stack outputs so that the user can easily find out what repository is being used by the stack.

Outputs:
  GitRepositoryName:
    Description: Git repository name
    Value: !If [NeedsNewGitRepository, !Ref GitRepositoryName, !Ref PreExistingGitRepository]

We also want to show the URL for cloning the repository. If we created the repository in the stack, this is an easy attribute to query. If a pre-existing repository name was passed in, we can’t determine the correct URL; so we just output that it is not available and hope the user remembers how to access the repository they created in the past.

Outputs:
  GitCloneUrlHttp:
    Description: Git https clone endpoint
    Value: !If [NeedsNewGitRepository, !GetAtt GitRepository.CloneUrlHttp, "N/A"]

Read more from Amazon about the AWS CloudFormation Conditions that are used in this template.

Replacing a Stack Without Losing Important Resources

You may have noticed in the above code that we specify a DeletionPolicy of Retain for the CodeCommit Git repository. This keeps the repository from being deleted if and when the the CloudFormation stack is deleted.

This prevents the accidental loss of what may be the master copy of the website source. It may still be deleted manually if you no longer need it after deleting the stack.

A number of resources in the Git-backed static website stack are retained, including the Route53 hosted zone, various S3 buckets, and the CodeCommit Git repository. Not coincidentally, all of these retained resources can be subsequently passed back into a new stack as pre-existing resources!

Though CloudFormation stacks can often be updated in place, sometimes I like to replace them with completely different templates. It is convenient to leave foundational components in place while deleting and replacing the other stack resources that connect them.