Not long ago, one of our clients asked us for help in creating a proof of concept using certain features of AWS that allowed facial recognition. To do this, we used this post from Amazon as a reference.
Although the post detailed a full solution, we wanted our client to have a CloudFormation template in their hands that created the entire infrastructure without the need for manual intervention.
When we reviewed this possibility in detail, we ran into a roadblock because two required elements weren’t supported by CloudFormation at the time: Kinesis Video Stream and Rekognition Collection. After doing some research, I found that there were other AWS proprietary elements that weren’t yet supported, including Cloudfront Origin Group.
For the above, we had a few options: the first and least pleasant one was to do everything manually – which is a big no-no in DevOps culture.
So, how did we solve this issue?
CloudFormation’s Custom Resources to the rescue! These resources let us define "custom" elements that make it easy to create certain programmatic logic. This allows us to achieve client goals that wouldn’t otherwise be possible with available CloudFormation (CF) resources.
The definition of a custom resource within a CF template is quite simple. It’s defined like any other resource using the following nomenclature: AWS::CloudFormation::CustomResource or Custom::MyCustomResourceTypeName, where MyCustomResourceTypeName is the name given to the resource to easily recognize that it’s not a standard CF resource.
The next important element in a custom resource is the "service token," which contains the ARN of the "service" that CF will invoke and then send the corresponding requests when creating, modifying or deleting a stack. For example, the ARN of a lambda function or an SNS topic could be placed here.
Within the CF template, the definition of the custom resource would be:
In this way, the final solution would have an architecture similar to the one pictured below:
To function or not to function, that is the question…
To achieve our solution, the most complicated part was to create a “handler” for a lambda function that could deal with the requests that CF sent it. At the same time, it had to return information to CF in the correct format. Otherwise we ran the risk that the stack tasks would take an excessive amount of time, or worse leave the CF stack with incorrect conditions.
In the AWS CloudFormation, custom resources are implemented asynchronously and with a callback-style model. This implies that when CloudFormation invokes a custom resource, it doesn’t expect a response from it.
Instead CF will wait for a JSON object to be returned and uploaded to a "pre-signed" S3 that CF hands over as a parameter to the custom resource. In this JSON object, CF receives the output/response of the task performed by the custom resource, in a format understandable by CF.
Details about the handler
The handler has to comply with certain characteristics such as:
→ Handle 3 events:
→ Receive inputs including:
- Request type
- Resource properties
- For Update and Delete cases, it also receives a PhysicalResourceId that AWS CloudFormation will use to know if the Update or Delete process of the stack modified the same resource or if it was replaced by a new one.
→ Create outputs, which must be JSON objects stored in the S3 path indicated in ResponseURL, and with a structure similar to the following:
- Status, which indicates whether the operation performed by the custom resource was successful or not. SUCCESS must be indicated for success and FAILED for the opposite. In the case of failure, a Reason property may be included with a description of the failure.
- Data, which allows you to return values that can then be used by other CF resources.
For the stack to be created, the Status must be SUCCESS. If it is FAILED or there is no value, the operation will fail.
Defining the CF template
In the CloudFormation template, a Custom Resource type block must be defined. This type of block has the structure as follows:
In this case, the custom resource is named RekognitionVideoBlogLambdaInlineCaller, and the ServiceToken is the ARN of a lambda function that is defined to create elements that cannot be made by CF (Kinesis Video Stream and Rekognition Collection).
The lambda function can be defined as any lambda, either by a reference to a zip in an S3 bucket or by an inline definition. In the second case, there’s a 4kb limit in the size of the "embedded" function.
Since our goal was to make a template with everything necessary for our client to perform a complete test, we decided to make the lambda function inline.
Final key points
It’s important to note that everything that is sent as a message to the console in a custom resource can be viewed in the CloudWatch logs. While this is very useful, it can be dangerous as sensitive information could be exposed.
Another issue is testing the "pure" code of the lambda function. If an error occurs within our code, no response will be returned to CF, which could lead the stack to reach an inconsistent state until the standard CF timeout (usually 1 hour).
Finally, although CF may not support all AWS services, it provides us with the programming opportunity to access their APIs and complement our templates with whatever we need.
If you’re wondering how the CF template turned out, check it out here! And feel free to leave any questions or suggestions in the comments.