Bridging the terraform - CloudFormation gap

Thumbnail

CloudFormation does not cover all AWS Resource types. Terraform does a better job in covering resource types just in time. So if you want to use a resource type which CloudFormation does not support yet, but you want to use CloudFormation, you have to build a Custom Resource with an own Lambda Function. CDK to the rescue: use AwsCustomResource.

AwsCustomResource gives you an easy tool to implement these resources - Without programming a Custom Resource Lambda.

Analyse

At first, you have to check whether CloudFormation does support the Resource Type. The CloudFormation doc is the right place to start.

In this example, I want to create an SES EmailIdentity. We see at SES Resource Type Reference - AWS CloudFormation, that this resource type is not supported.

Cloudformation does not support that. Terraform has this resource type: AWS: aws_ses_email_identity - Terraform by HashiCorp. So, let`s bridge the gap.

How do we use the type with CDK?

At first, you have to look at the API for this resource and determine the CRUD lifecycle, or just CUD (Create/Update/Delete).

If we look at the SES API or in the Node CDK we see: VerifyEmailIdentity creates a EmailIdentity and sends out a verification mail, deleteIdentity deletes it.

Caution: real emails are sent out, so I will notice in my own domain when you try this…

CLI test create

Sometimes its good to test this with the CLI first, so we try:

aws ses verify-email-identity --email-address "info@mydomain.com"

Replace “info@mydomain.com” with you domain/email!

Now check in the AWS Console whether the Resource is created.

CLI test delete

Now we try the delete:

aws ses delete-identity --identity info@mydomain.com

Replace “info@mydomain.com” with you domain/email!

And check that it`s deleted. An Update is a delete and re-create in this case.

Implement Resource in CDK

With the AwsCustomResource you get the “CUD” properties:

onCreate, onDelete, onUpdate.

With our recherche from the SES API we get:

Call ______ Property
onCreate verifyEmailIdentity
onDelete deleteIdentity

The first AwsCustomResource

The code for the custom create now look like this:

    new AwsCustomResource(this, 'Identity', {
      onCreate: {
        service: 'SES',
        action: 'verifyEmailIdentity',
        parameters: {
          EmailAddress:  emailIdentity,
        },
        physicalResourceId: PhysicalResourceId.of("Identity"),
        // PhysicalResourceId.fromResponse('VerificationToken') // Use the token returned by the call as physical id
      },

One thing to mention is that with the example from the CDK documentation, which uses PhysicalResourceId.fromResponse('VerificationToken') you get an error from CloudFormation about Identity/Resource/Default (Identity2D60E2CC) Invalid PhysicalResourceId. So I changed to a static value.

The custom resource is backed by Lambda. The CDK creates a particular function, which interprets the service and action parameters.

This is the simple architecture:

Architecture

CDK adds the managed policy AWSLambdaBasicExecutionRole to the Lambda function. To give the function the additional rights it needs, we have to add some rights:

 policy: AwsCustomResourcePolicy.fromStatements(
        [ new PolicyStatement({
          actions: ['ses:verifyEmailIdentity',
                  'ses:deleteIdentity'],
          effect: Effect.ALLOW,
          resources: ['*']
        })]
        )
      });

The actions in the policy are just the action from the onCreate and the other hooks.

When we look at the terraform aws provider code, we see the similarities:

resource_aws_ses_email_identity

func resourceAwsSesEmailIdentityCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).sesconn

	email := d.Get("email").(string)
	email = strings.TrimSuffix(email, ".")

	createOpts := &ses.VerifyEmailIdentityInput{
		EmailAddress: aws.String(email),
	}

	_, err := conn.VerifyEmailIdentity(createOpts)
	if err != nil {
		return fmt.Errorf("Error requesting SES email identity verification: %s", err)
	}

	d.SetId(email)

	return resourceAwsSesEmailIdentityRead(d, meta)
}

We see that terraform is doing the same. The CDK AwsCustomResourceis just a simple generic way of doing this.

Summary

If you use CDK and a resource is missing, consider the AwsCustomResource to create it in an easy way.

The code for this is available in our cdk-template repository on github.

Conclusion

Thanks to

Photo by Alex Radelich on Unsplash