Deploying CloudFront Functions with the AWS Cloud Development Kit (CDK) and Go

Posted June 25, 2021 by Trevor Roberts Jr ‐ 6 min read

CloudFront Functions are pretty handy. Let's see how to automate their deployment with the AWS CDK.

In my previous article, I used a CloudFront Function to automate URL rewrites for my blog. In this article, I'll show how to use the AWS CDK to automate the deployment of a CloudFront Function.

What is CDK?

The AWS CDK is a tool for software developers to use languages they are familiar with to automate their cloud infrastructure deployment. CDK translates that source code into CloudFormation templates and deploys these templates on the developers' behalf. Due to my interest in Go, my example will be written in that language.

If you have not used the CDK before, you will need to make sure you have Node.js version 10.13.0 or later (EXCEPT 13.0.0 through 13.6.0). Your safest bet is to use an active long-term support version like 14.x, per the CDK Getting Started doc.

I am using a specific version of the CDK that contains the L2 constructs that I want to use (more on "constructs" in the next section). At the time of this article's publishing, the latest CDK version I could use is 2.0.0 rc9. Once you have an acceptable Node.js version installed, let's use NPM to install the AWS CDK.

npm install -g aws-cdk@2.0.0-rc.9

For this example, you will also need to have Go installed. I am using go version 1.16.3.

CDK Constructs

The CDK has a notion of different levels of constructs depending on how much control you want over your AWS resource configurations:

  • L1 constructs allow you to have the most control over the CloudFormation parameters set during the AWS resource deployment. You may find that the L1 construct function names have a prefix of "Cfn" (ex: CfnFunction).
  • L2 constructs automatically configure some default parameters for your resources according to AWS best practices. They typically are found without a prefix (ex: Function).
  • L3 constructs are known as "patterns" that consist of a combination of multiple L1 or L2 constructs to build a reference architecture.

Getting Started with CDK Go Language Support

First, create a directory for your CDK automation and initialize it with Go support.

mkdir cdk-go-cloudfront-function
cd cdk-go-cloudfront-function
cdk init --language=go

Your directory will now have a git repo initialized and a file structure similar to the following:

cdk-go-cloudfront-function
├── .git
├── .gitgnore
├── README.md
├── cdk-go-cloudfront-function.go
├── cdk-go-cloudfront-function_test.go
├── cdk.json
└── go.mod

CDK Sample Go Code

The cdk init process will automatically generate code for you, and you can modify it to meet your project needs.

Here are the packages you need to use for the CloudFront Function deployment.

import (
	"github.com/aws/aws-cdk-go/awscdk/v2"
	"github.com/aws/aws-cdk-go/awscdk/v2/awscloudfront"
	"github.com/aws/constructs-go/constructs/v10"
	"github.com/aws/jsii-runtime-go"
)

The first three packages contain the AWS code we'll need, at a minimum, to deploy our CloudFront Function automation. JSII may be new to you. It simplifies the process of creating parameter pointers. "Why is that necessary?", you may ask: the CDK team needed a way to make parameters capable of being null/nil. The only Go type that allows nil values is the pointer type. You can read more in the AWS Documentation

Further down in my code, I use one of the automatically-generated functions (NewCdkConstructGoStack) to store my JavaScript source code in a string variable.

	// The CloudFront Function source code
	myFunction := `function handler(event) {
		var request = event.request;
		var uri = request.uri;
	
		// Check whether the URI is missing a file name.
		if (uri.endsWith('/')) {
			request.uri += 'index.html';
		}
		// Check whether the URI is missing a file extension.
		else if (!uri.includes('.')) {
			request.uri += '/index.html';
		}
		return request;
	}`

Finally, I use an L2 construct to create a new CloudFront Function with the required parameters.

    // The L2 construct that tells CloudFormation how to deploy the CloudFront Function
	awscloudfront.NewFunction(stack, jsii.String("CFFuncUpdateSubdirPathCDK"), &awscloudfront.FunctionProps{
		FunctionName: jsii.String("update-subdir-path-cdk"),
		Code:         awscloudfront.FunctionCode(awscloudfront.FunctionCode_FromInline(jsii.String(myFunction))),
		Comment:      jsii.String("Path rewrite CloudFront Function deployed with CDK"),
	},
	)

Here is the source code in its entirety (you can also check it out on GitHub)

package main

import (
	"github.com/aws/aws-cdk-go/awscdk/v2"
	"github.com/aws/aws-cdk-go/awscdk/v2/awscloudfront"
	"github.com/aws/constructs-go/constructs/v10"
	"github.com/aws/jsii-runtime-go"
)

type CdkConstructGoStackProps struct {
	awscdk.StackProps
}

func NewCdkConstructGoStack(scope constructs.Construct, id string, props *CdkConstructGoStackProps) awscdk.Stack {
	var sprops awscdk.StackProps
	if props != nil {
		sprops = props.StackProps
	}
	stack := awscdk.NewStack(scope, &id, &sprops)

	// The CloudFront Function source code
	myFunction := `function handler(event) {
		var request = event.request;
		var uri = request.uri;
	
		// Check whether the URI is missing a file name.
		if (uri.endsWith('/')) {
			request.uri += 'index.html';
		}
		// Check whether the URI is missing a file extension.
		else if (!uri.includes('.')) {
			request.uri += '/index.html';
		}
		return request;
	}`

    // The L2 construct that tells CloudFormation how to deploy the CloudFront Function
	awscloudfront.NewFunction(stack, jsii.String("CFFuncUpdateSubdirPathCDK"), &awscloudfront.FunctionProps{
		FunctionName: jsii.String("update-subdir-path-cdk"),
		Code:         awscloudfront.FunctionCode(awscloudfront.FunctionCode_FromInline(jsii.String(myFunction))),
		Comment:      jsii.String("Path rewrite CloudFront Function deployed with CDK"),
	},
	)

	return stack
}

func main() {
	app := awscdk.NewApp(nil)

	NewCdkConstructGoStack(app, "CdkConstructGoStack", &CdkConstructGoStackProps{
		awscdk.StackProps{
			Env: env(),
		},
	})

	app.Synth(nil)
}

// env determines the AWS environment (account+region) in which our stack is to
// be deployed. For more information see: https://docs.aws.amazon.com/cdk/latest/guide/environments.html
func env() *awscdk.Environment {
	return nil
}

If you are curious to see what the equivalent CloudFormation template for your CDK code would look like, use the following command:

cdk synth

...And there you have it: with four lines of your own CDK code (and ~60 lines of boilerplate code), CDK will generate ~220 lines of a CloudFormation JSON template on your behalf.

If you have valid AWS credentials, you can go ahead and deploy the CDK code with

cdk deploy

When you are done with the CloudFront Function, you can remove it with

cdk destroy

As I was building out this example, I used the CDK API documentation and the actual source code to figure out the construct syntax. It took some getting used to, but it was fairly easy to get up and running after some experimentation.

If you found this article useful, let me know on Twitter!