Integration & Automation
Conditionally launch AWS CloudFormation resources based on user input
Editor’s note 10/27/2021: The information in this post is outdated. This pattern is no longer relevant. The post remains live for historical purposes only.
When you are developing in AWS CloudFormation, it’s a good practice to split code into reusable components. Using nested stacks is a great way to organize code, often leading to higher degrees of code reusability and readability. If you are in the business of managing large amounts of CloudFormation code, this statement may seem obvious.
This post takes this concept one step further by allowing nested stacks and parameter values to be optional. I’ll discuss two patterns that allow you to enable or disable additional features or launch from different template locations based on user input.
Pattern 1: Enable or disable a feature in your stack based on user input
This technique is particularly useful for packaging less commonly used features into your CloudFormation templates and deploying them only when you need them (for an example of this in action, see the Modular architecture for Aurora PostgreSQL Quick Start). To illustrate how this works, I’m using a sample stack that generates a random string as the test workload. When the Enable CI feature is set to true, a CI stack is deployed along with your main workload.
Providing this feature can be useful for community developers contributing to your project. Enabling CI at launch allows developers to launch the stack in a “development mode.” After the stack is up, they can inspect it to validate the reported issue. After they validate a bug, they can directly move to developing the fix in their fork. When the fixes are pushed, their staging branch (develop) CI pipeline will test those commits; if the tests pass, they will be merged to master (or the specified branch). The developer can then confidently send a PR (pull request) from their fork to the main repo. At this point, they can delete the development stack, which will delete the main stack and CI pipeline along with it.
This is just an example of how to enable a feature. This technique can be useful in other use cases, including launching optional features or switching launch modes of your stack from development to production.
A complete example of this pattern is in the GitHub repo.
To implement this technique, you need to add three components to your CloudFormation template:
- A parameter that stores the user’s input in the example below (see
EnableCIPipeline
in the blue box) - A condition definition that checks if the provided value is set to true in the example below (see
EnableTaskcatCI
in the orange box) - And lastly, a condition check that needs to be added to the stack resources in the example below (see
Conditions: EnableTaskcatCI
in the green box)
When EnableCIPipeline
is set to false, your template will just build the main stack
When EnableCIPipeline
is set to true, your template will build the main stack plus the CI stack.
Pattern 2: Conditionally choose template URLs for nested stacks
The code for this pattern is in the conditional_templateurl_example template in the GitHub repo.
Why use condition based template URLs? Let’s start with a use case. You work for WidgetOne as a DevOps Engineer. Your company develops a plugin for a product called BestAppEver. BestAppEver provides two versions of their software, latest and beta. For production deployments, BestAppEver provides a TemplateUrl
that deploys on RHEL, which is their only officially supported operating system. You, as a DevOps Engineer at WidgetOne, want to provide a way for your development community to test your new plugin against both the latest version and the beta version. Since BestAppEver only supports RHEL, you have a modified version of their template that leverages CentOS. You goal is to provide a CloudFormation template to your development team and community that can use CentOS for development and switch to RHEL for production deployments.
Let’s look at how you can use nested stacks and conditions to solve for WidgetOne’s use case, using the same template for both production and development without having to maintain a copy of the BestAppEver production template in your source tree. By setting the DeploymentType
to development, plugin developers can use the template to test upcoming changes that are still in beta.
To illustrate this technique, I’ll use a dummy Amazon Simple Storage Service (Amazon S3) location that points to the production location. For the development location, I’ll use the current development source tree, which will be staged to a development S3 bucket. The development bucket information can be passed in using the S3BucketName
and S3KeyPrefix
parameters.
Note: The development S3 bucket (S3BucketName) can be continually updated with your development repo contents as part of an optional CI/CD run (see the first pattern).
The value of DeploymentType
will determine the template URL.
To implement this technique, you need to add three components to your CloudFormation template:
- A parameter that stores the user’s input in the example below (see
DeploymentType
in the blue box) - A condition definition that checks if the provided value is set to production in the example below (see
UseVendored
in the orange box) - And lastly, the condition that sets the S3 path for
TemplateURL
(see the green box)
If the value of DeploymentType
is set to production, the TemplateURL
path will be [obsolete content, removed].
If the value of DeploymentType
is set to development, the TemplateURL
path will be [obsolete content, removed].
Conclusion
You can use a combination of CloudFormation nested stacks and CloudFormation conditions to allow the end user to optionally select features that can be deployed at stack creation using the first pattern described in this post. You can use the second pattern to enable end-users or developers to choose between different options based on stack input while ensuring that production deployment are using vendor provided official template URLs. If you want to test this, you can go to the GitHub repo and use the sample code. Any feedback, other use cases to suggest? Let us know in this blog post’s comments.