AWS Cloud Operations & Migrations Blog

Managing resources using AWS CloudFormation Resource Types

Both custom resources and resource types are used to create an AWS CloudFormation resource that allow you to manage third-party resources. For example, during the creation of a simple website you may want to provision a third-party website monitor, which has a public API. In this case, you would develop and use a resource that creates, updates, or deletes the website monitor through its public API. You might be looking for a resource that allows you to look up an Amazon machine image (AMI) just before you create an Amazon Elastic Compute Cloud (Amazon EC2) instance in your template. This would be accomplished by creating a resource that interacts with the AWS public API.

One of the main challenges with custom resources is that the CloudFormation engine has little visibility into the inputs and outputs of the resource. This makes it difficult for deep integration with other services. Resource types address this, along with several other issues we’ve discovered from customer feedback.

In this blog post, I walk through how to manage a resource using both custom resources and resource types, dive deep into the mechanics of both approaches, and highlight how they differ.

Custom Resource WorkFlow

In order to properly compare, let’s start with a deep dive into custom resources. Custom resources are a way to write custom provisioning logic for external resources in CloudFormation templates that run anytime you create, update, or delete a stack. The custom logic is backed by Lambda or provided through Amazon Simple Notification Service (Amazon SNS). In the following example, I use a Lambda function to back the provisioning logic and CloudFormation is performing a Create operation on the custom resource.

Custom resource workflow

  1. The custom resource logic is authored and hosted in Lambda. Once deployed in Lambda, the custom resource logic can be used by multiple templates.
  2. The custom resource is declared in the CloudFormation template of Type AWS::CloudFormation::CustomResource. The custom resource has a required property named ServiceToken. In this case it is the ARN of the Lambda function that was created in step one. Except for the ServiceToken, all properties are optional and passed to the Lambda function as input parameters (as seen in step three). The following example is a custom resource that would be declared in a CloudFormation template.
MyWebsiteMonitor: 
  Type: AWS::CloudFormation::CustomResource 
  Properties: 
    ServiceToken: arn:aws:lambda:us-east-1:123456789012:function:test 
    WebsiteURL: "http://amazonaws.com" 
    MonitorName: Monitor1 
    PingIntervalInMinutes: 5
  1. When CloudFormation processes the custom resource, it calls the Lambda function defined in the ServiceToken and sends a JSON payload with several parameters as input. Note the ResponseURL parameter that is sent to the Lambda function. This is a pre-signed Amazon Simple Storage Service (Amazon S3) bucket URL that CloudFormation generates and monitors for a response from the Lambda function. The Lambda function uses this in the next step to let CloudFormation know if the creation of the custom resource was successful.
{
   "RequestType" : "Create",
   "ResponseURL" : "http://pre-signed-S3-url-for-response",
   "StackId" : "arn:aws:cloudformation:us-east-1:123456789012:stack/stack-name/guid",
   "RequestId" : "unique_id_for_this_create_request",
   "ResourceType" : "Custom:: MyWebsiteMonitor",
   "LogicalResourceId" : "MyWebsiteMonitor",
   "ResourceProperties" : {
      "WebsiteURL" : "http://amazonaws.com",
      "MonitorName" : "Monitor1",
      "PingIntervalInMinutes" : "5"
   }
}
  1. The Lambda function runs in response to being invoked by CloudFormation. Upon completion, it sends a JSON file to the Amazon S3 bucket defined by ResponseURL. In the following example, we return a Status of SUCCESS along with some other data that is used by the CloudFormation template to process the response.
{
"Status" : "SUCCESS",
"PhysicalResourceId" : "MyPhysicalResourceId",
"StackId" : "arn:aws:cloudformation:us-west-2:123456789012:stack/stack-name/guid",
"RequestId" : "unique_id_for_the_create_request",
"LogicalResourceId" : "MyWebsiteMonitor",
"Data" : {
  "output1" : "value1",
  "output2" : "value2",
  "output3" : "value3",
  }
}
  1. CloudFormation is notified that a file has been uploaded to the Amazon S3 bucket, and then retrieves and processes the response file.
  2. CloudFormation marks the resource status as either success or failed (success in this example).

Now, let’s review how resource types work.

Resource Type Workflow

Creating a resource type starts with authoring the resource type specification (schema) and handler code for five CloudFormation operations: Create, Update, Delete, Read, and List. The resource type specification and handler code files are then packaged and registered with the CloudFormation registry via the CloudFormation CLI (or through the AWS API). Once in the registry, you declare the resource in your template as you would any other resource. The workflow is like this:

Resource type workflow

  1. A resource type is authored using the CloudFormation CLI, a tool to accelerate resource type development (for more detail, check out the CloudFormation CLI section of this blog):
    1. Create the resource type specification
    2. Create handler code for the five CloudFormation operations
  2. The resource type specification and handler code files are registered with the CloudFormation registry.
  3. The resource is then declared in the CloudFormation template (as any other native resource would be)
MyWebsiteMonitor: 
  Type: MyCompany::Monitors::WebsiteMonitor 
  Properties: 
    WebsiteURL: "http://amazonaws.com" 
    Name: MyWebsiteMonitor 
    PingIntervalInMinutes: 5
  1. When CloudFormation processes the template, the resource is provisioned. The resource type handler code responds directly to CloudFormation signaling its completion. This can be done either synchronously or asynchronously. If it’s an asynchronous call, CloudFormation marks the resource as PENDING, and periodically checks for status.

Comparing Custom Resources and Resource Types

Now that we’ve discussed how to provision a resource via a custom resource and a resource type, let’s compare them in more detail. The main differences are:

  1. Schema first
  2. Language support
  3. Location of execution
  4. CloudFormation CLI
  5. CloudFormation registry

Schema First

When a custom resource is invoked, CloudFormation calls the ServiceToken endpoint and waits for the custom resource to signal completion by posting a file to an Amazon S3 bucket. It’s a great way to provision resources that are outside of scope of a native type, but it’s a black box for CloudFormation.

A resource type has a required schema (the resource provider specification), which provides CloudFormation with visibility into the parameters of the provisioning logic, allowing validation of templates that consume it. This explicit schema allows CloudFormation to treat the resource type like any other native resource, and it integrates with other CloudFormation features such as change sets and other AWS services. For example, AWS Config can record configurations for resources created with resource types.

Additionally, the schema defines a timeoutInMinutes property that specifies how long CloudFormation should wait for a response from the handler code. A common pain point for users of custom resources was the fixed one hour timeout. Through the use of the timeoutInMinutes property, developers can specify a customized amount of time to wait for a completion status before marking the resource as failed.

Resource types are first-class objects to the CloudFormation engine, and we are actively converting AWS native resources to resource types. If you would like to see open sourced examples of AWS resource types, visit our GitHub Organization. If you have an enhancement you’d like to see, we encourage you to submit an issue, or a pull request.

Language Support

Custom resources can be written in any language that Lambda supports. Currently, resource types can only be written in Java, Go, and Python.

Location of Execution

Another key difference is where the custom provisioning logic is executed. With custom resources, the logic and code needed to provision the resource is deployed in your account, as either a Lambda function or an Amazon SNS topic. When a custom resource is invoked by CloudFormation, the Lambda function or Amazon SNS topic in your account is executed and billed to your AWS account. This is similar to any other invocation of these services.

Resource types, on the other hand, are registered with the CloudFormation registry. The handler code is executed and managed by AWS in response to CloudFormation lifecycle events. The management of a Lambda function associated with your resource is no longer required. Additionally, the success or failure of the resource is handled directly in the handler code. You no longer have to construct JSON response payloads that are sent to an Amazon S3 bucket. There is a charge for invoking CloudFormation resource providers that are not native AWS types; consult the pricing information posted in the CloudFormation pricing documentation.

CloudFormation CLI

The custom resource development pattern allows a developer to get started quickly. You’ll likely encounter some common issues along the way, such as constructing the JSON response payload, and properly handling CloudFormation lifecycle events. The barrier to getting started is low, and it’s a good feeling to get something working straight away from scratch.

A resource type, on the other hand, has more upfront overhead and developmental rigor as compared to a custom resource. There are a number of new rules that must be followed, such as authoring a resource type specification. The CloudFormation CLI tool helps accelerate this process by standing up the scaffolding, providing a robust testing framework, and guiding you through the process of writing a resource type. The process can be broken down into three major steps:

  • Model: Define the resource type specification
  • Develop: Write and test the handler code that manages the resource
  • Register: Upload the resource type to your private CloudFormation registry

Let's walk through each of these steps in detail using the CloudFormation CLI:

Model: Define your schema

  • Use cfn init to generate an initial resource project that includes an example schema and handler code.
  • Modify the sample schema to define your resource.
  • Use cfn validate to validate your schema, as you make changes.
  • Once the schema definition is complete (or changed), update the project with cfn generate. This updates your project files to reflect the new schema, which includes the data model and tests.

Following is an example of a resource type specification for the AWS::SES::ConfigurationSet resource, alongside an example of how the resource would be declared in a CloudFormation template. The code for this resource type is open sourced and hosted in our GitHub repository.

Resource type specification
CloudFormation template
{
    "typeName": "AWS::SES::ConfigurationSet",
    "description": "Resource schema for AWS::SES::ConfigurationSet.",
    "sourceUrl": "https://github.com/aws-cloudformation/aws-cloudformation-resource-providers-ses.git",
    "properties": {
        "Name": {
            "description": "The name of the configuration set.",
            "type": "string",
            "pattern": "^[a-zA-Z0-9_-]{0,64}$",
            "maxLength": 64
        }
    },
    "createOnlyProperties": [
        "/properties/Name"
    ],
    "primaryIdentifier": [
        "/properties/Name"
    ],
    "additionalProperties": false,
    "handlers": {
        "create": {
            "permissions": [
                "ses:CreateConfigurationSet"
            ]
        },
        "read": {
            "permissions": [
                "ses:DescribeConfigurationSet"
            ]
        },
        "delete": {
            "permissions": [
                "ses:DeleteConfigurationSet"
            ]
        },
        "list": {
            "permissions": [
                "ses:ListConfigurationSets"
            ]
        }
    }
}
Type: AWS::SES::ConfigurationSet
Properties:
  Name: myChangeset

Develop: Write handler code that manages the resource

  • Modify the sample handler code generated by cfn init to manage the resource.
  • Use cfn invoke and cfn test to test your code.
    • A battery of tests is run to validate that your code is behaving as expected in response to lifecycle events. For example:
      • How does your resource handle update events? Does it delete and recreate the resource (resulting in an outage), or does it modify in place?
      • How does your resource react to when one of its parameters is sent an alphanumeric string when an integer is expected?

Register: Upload the resource type to CloudFormation

Use cfn submit to register your resource type in the CloudFormation registry and make your resource available to use in your CloudFormation templates.

CloudFormation Registry

Custom resources do not have a centralized mechanism for distribution into your account. If you author a custom resource and want to share it, you must provide the Lambda code along with instructions or a script to provision the Lambda function and the custom resource in the target account.

Resource types are made available through the CloudFormation registry, which is a repository of resource types available to your CloudFormation templates. Once a resource type is imported into your account, it is available to all of the CloudFormation templates in the account and Region in which it was imported. You can view the resource types available in the CloudFormation registry through the CloudFormation console or AWS CLI.

There are two types of resource types available today: public and private.

Public resources are native types supplied by Amazon and AWS and are available to all accounts. An example of a public resource type is AWS::SES::ConfigurationSet.

Private resource types are resources that have been imported into your account through the resource type registration process covered earlier. These could be resources authored by you or provided by a third party. Importing a private resource type is accomplished in two steps:

  1. Clone the resource type package from source or download locally.
  2. Use the CloudFormation CLI tool to register your resource: cfn submit.

Alternatively, you could use the AWS CLI by referencing the Amazon S3 location of the zipped package. When using the AWS CLI, you must specify the name of your resource type.

aws cloudformation register-type \
--region us-east-1 \
--type-name " MyCompany::Monitors::WebsiteMonitor " \
--schema-handler-package "s3://samplebucket/MyWebsiteMonitorResource.zip" \
--type RESOURCE

We have several partners such as Atlassian, Datadog, Densify, Dynatrace, Fortinet, New Relic, and Spotinst. They are providing access to their APIs via resource types for customers to manage their services within a CloudFormation template. Links to their resource types can be found in our GitHub repository.

The CloudFormation registry is an important first step to make the distribution and sharing of resource types easier. We are always iterating and improving and encourage your feedback on this important feature.

Available Now

Resource types are available in all regions and you can start using them today.

Conclusion

In this post, we walked through managing resources using a custom resource and a resource type. We covered what a custom resource and resource type are, the mechanics of each, and the key differentiators. A resource type is a new approach to managing resources which have tighter integration with AWS services, are easier to operationally manage, and have a centralized distribution model. For further reading, take a look at an example of a SAM application that uses a CloudFormation custom resource to create an object in an S3 bucket. We look forward to seeing what you create with resource types!

About the Author

Craig Lefkowitz

Craig is a Senior Developer Advocate for AWS CloudFormation based in Seattle. In his role, he is passionate about DevOps and automation. Outside of work, Craig enjoys tinkering with retro video games and amateur astrophotography.