Integration & Automation
AWS CloudFormation custom resource creation with Python, AWS Lambda, and crhelper
In this post, we’ll cover how to author robust AWS CloudFormation custom resources using AWS Lambda and the custom resource helper (crhelper) framework for Python. Examples of this can be found in the Amazon EKS Architecture, AWS Cloud9 Cloud-Based IDE, and Axomo Quick Starts. For the uninitiated, we’ll quickly go over the key technologies involved in this.
AWS CloudFormation is a service that enables you to describe and provision all the infrastructure resources in your cloud environment. You can model your environment in JSON or YAML templates, or in code, by using tools like the AWS Cloud Development Kit (CDK).
Because AWS CloudFormation provides a powerful extension mechanism through AWS Lambda-backed custom resources, you can write your own resources to extend AWS CloudFormation beyond AWS resources and provision any other resource you can think of. For example, you can integrate a third-party software as a service (SaaS) product, or you can even provision on-premises resources in hybrid environments. Another great use case is to extend AWS CloudFormation by providing utility resources that perform tasks to transform or process properties of your infrastructure.
Custom resources, although powerful, can be daunting. Also, implementing a robust, best-practice resource can take some trial and error, combined with a big chunk of utility code to handle things like signaling status, exception handling, timeouts, etc.
Enter crhelper, an open-source project that assists in writing custom resources by implementing best practices and abstractions to simplify the resource code and ease the burden of implementing some common patterns in custom resources. Let’s dive in and walk through the creation of a custom resource using crhelper.
Create the custom resource
Let’s start by putting together a resource that returns the sum of two numbers. First, make sure you are running a *nix prompt (Linux, Mac, or Windows subsystem for Linux). The shell environment should be configured with Python 3.5 or higher and to the AWS Command Line Interface (AWS CLI).
Create an empty folder that you’ll use to place your Lambda source. Then use pip to install crhelper into the folder, and create the lambda_function.py
file to put your resource code into.
Now open the lambda_function.py
file in your favorite editor or integrated development environment (IDE), and place the following code into the lambda_function.py file.
from crhelper import CfnResource
helper = CfnResource()
@helper.create
@helper.update
def sum_2_numbers(event, _):
s = int(event['ResourceProperties']['No1']) + int(event['ResourceProperties']['No2'])
helper.Data['Sum'] = s
@helper.delete
def no_op(_, __):
pass
def handler(event, context):
helper(event, context)
Looking at the code, we’re first importing and instantiating the crhelper CfnResource
class, and then we define our Sum function with the create
and update
decorators. These decorators mark which function in your code should be invoked for the different CloudFormation stack actions (Create, Update, and Delete). We’ve defined both create
and update
to the same function, and we have omitted the delete
decorator. This results in the Sum function being invoked on both create
and update
events, while delete
events pass without invoking any custom code because, in this case, there are no underlying resources to delete.
For the next step, you’ll need a basic Lambda execution role. Most people who have used Lambda will already have this role created; if not, follow the steps in the Lambda documentation to create one.
Next, package the code up and push your function up to the Lambda service, substituting the role ARN for the ARN of a Lambda basic execution role in your account.
You’ll need to take down the FunctionArn
from the output to use in the next step.
Test the custom resource in a template
Now you can create a basic AWS CloudFormation template to test this out.
If you execute this template in AWS CloudFormation (in the same region as the Lambda function), you should see that the outputs contain Sum with a value of 3 as calculated by the Lambda function. Try updating the template by entering an invalid entry (like a string) for one of the numbers, and see how crhelper is able to help surface errors to AWS CloudFormation.
Conclusion
Although this post aims to provide a very basic walkthrough of how to build custom resources using crhelper, the crhelper documentation provides a more complete example of its usage. Or, for a look at a real-world implementation, see the Amazon Elastic Kubernetes Service (Amazon EKS) Quick Start, which uses crhelper for each one of its nine custom resources.