Invalidate CloudFront with Lambda and S3 events
Bla bla bla... You're here for the solution, not to hear me talk about it. See code example. Improvise, adapt and overcome.
One thing though, unless you have a shit metric ton of objects that you want to keep all hot and sizzling in cache I suggest you just invalidate the entire path, and not per object. Amazon has this weird pricing model where wildcard invalidations are priced as a single path.
lambda.py
import os
import time
import boto3
def lambda_handler(event, context):
client = boto3.client('cloudfront')
invalidation = client.create_invalidation(
DistributionId=os.environ['DISTRIBUTION_ID'],
InvalidationBatch={
'Paths': {
'Quantity': 1,
'Items': [
'/*',
]
},
'CallerReference': str(time.time())
}
)
terraform.tf
data "archive_file" "lambda" {
type = "zip"
source_file = "lambda_function.py"
output_path = "lambda.zip"
}
data "aws_iam_policy_document" "role" {
statement {
actions = [
"sts:AssumeRole",
]
principals {
type = "Service"
identifiers = ["lambda.amazonaws.com"]
}
}
}
resource "aws_iam_role" "this" {
name = "${local.prefix}-lambda"
assume_role_policy = "${data.aws_iam_policy_document.role.json}"
}
data "aws_iam_policy_document" "policy" {
statement {
actions = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
]
resources = ["arn:aws:logs:*:*:*"]
}
statement {
actions = [
"cloudfront:CreateInvalidation",
]
# You'd assume that Amazon lets you specify the ARN of the CloudFront
# distribution. But na-ah!
resources = ["*"]
}
}
resource "aws_iam_role_policy" "this" {
name = "${local.prefix}-lambda"
policy = "${data.aws_iam_policy_document.policy.json}"
role = "${aws_iam_role.this.name}"
}
resource "aws_lambda_function" "this" {
runtime = "python3.6"
filename = "lambda.zip"
function_name = "${local.prefix}-cloudfront-invalidation"
role = "${aws_iam_role.this.arn}"
handler = "lambda_function.lambda_handler"
source_code_hash = "${data.archive_file.lambda.output_base64sha256}"
environment {
variables = {
DISTRIBUTION_ID = "${aws_cloudfront_distribution.this.id}"
}
}
}
resource "aws_lambda_permission" "this" {
action = "lambda:InvokeFunction"
function_name = "${aws_lambda_function.this.arn}"
principal = "s3.amazonaws.com"
source_arn = "${aws_s3_bucket.this.arn}"
}
resource "aws_s3_bucket_notification" "this" {
# This would be the bucket that CloudFront fetches the objects from.
bucket = "${aws_s3_bucket.this.id}"
lambda_function {
lambda_function_arn = "${aws_lambda_function.this.arn}"
events = ["s3:ObjectCreated:*"]
filter_prefix = "index.html"
}
}