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.