Skip to content

Instantly share code, notes, and snippets.

@jed
Last active April 24, 2024 15:45
Show Gist options
  • Save jed/56b1f58297d374572bc51c59394c7e7f to your computer and use it in GitHub Desktop.
Save jed/56b1f58297d374572bc51c59394c7e7f to your computer and use it in GitHub Desktop.
Using AWS CloudFormation to deploy an edge lambda
#!/bin/sh
aws cloudformation deploy \
--template-file stack.yaml \
--stack-name edge-lambda-test \
--capabilities CAPABILITY_IAM \
--parameter-overrides Nonce=$RANDOM
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
Nonce:
Type: String
Outputs:
Host:
Value: !GetAtt Distribution.DomainName
Resources:
Bucket:
Type: AWS::S3::Bucket
Distribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Enabled: true
Origins:
- Id: !Ref Bucket
DomainName: !GetAtt Bucket.DomainName
S3OriginConfig: {}
DefaultCacheBehavior:
TargetOriginId: !Ref Bucket
ForwardedValues:
QueryString: true
ViewerProtocolPolicy: redirect-to-https
LambdaFunctionAssociations:
- EventType: viewer-request
LambdaFunctionARN: !GetAtt IndexLambdaVersion.FunctionArn
IndexLambda:
Type: AWS::Lambda::Function
Properties:
Role: !GetAtt IndexLambdaRole.Arn
Runtime: nodejs6.10
Handler: index.handler
Code:
ZipFile: |
exports.handler = (event, ctx, cb) => {
const status = '200'
const headers = {
'content-type': [{
key: 'Content-Type',
value: 'application/json'
}]
}
const body = JSON.stringify(event, null, 2)
const response = {status, headers, body}
cb(null, response)
}
IndexLambdaRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
- edgelambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
IndexLambdaVersion:
Type: Custom::LatestLambdaVersion
Properties:
ServiceToken: !GetAtt PublishLambdaVersion.Arn
FunctionName: !Ref IndexLambda
Nonce: !Ref Nonce
# Custom resource for getting latest version of a lambda,
# as required by CloudFront.
PublishLambdaVersion:
Type: AWS::Lambda::Function
Properties:
Handler: index.handler
Runtime: nodejs6.10
Role: !GetAtt PublishLambdaVersionRole.Arn
Code:
ZipFile: |
const {Lambda} = require('aws-sdk')
const {send, SUCCESS, FAILED} = require('cfn-response')
const lambda = new Lambda()
exports.handler = (event, context) => {
const {RequestType, ResourceProperties: {FunctionName}} = event
if (RequestType == 'Delete') return send(event, context, SUCCESS)
lambda.publishVersion({FunctionName}, (err, {FunctionArn}) => {
err
? send(event, context, FAILED, err)
: send(event, context, SUCCESS, {FunctionArn})
})
}
PublishLambdaVersionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Policies:
- PolicyName: PublishVersion
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: lambda:PublishVersion
Resource: '*'
@LpmRaven
Copy link

LpmRaven commented Nov 25, 2020

So I have taken some of the code here and turned it into a CI/CD pipeline that will update to a new lambda@edge version every time code is released. https://github.com/LpmRaven/lambda-edge-language-region-redirect/tree/master/cloudformation

Hopefully, it will help someone as I had to generate a random nonce in 'buildspec' to force update the version number.

@haydenk
Copy link

haydenk commented Nov 28, 2020

@LpmRaven I agree it's awkward but I wouldn't say it's not practical. The current solution is relying on an outside resource to finish the deployment process after CloudFormation update has been completed. Keeping it in CloudFormation allows you easily to revert to VersionedIndexLambdaA if for some reason VersionedIndexLambdaB has some unintended side effects, for some reason, with another deployment.

Just to re-emphasis, I do admit it is a very awkward solution. Awkward enough to say either solution would be fine, it's just a matter of what you're willing to deal with long term.

@LpmRaven
Copy link

@haydenk Take a look at the link I provided in the previous reply. You can revert to a previous lambda version by hardcoding the arn attached to the CloudFront distribution ...and you don't need to change cloudformation every time you update your lambda@edge.

@koshic
Copy link

koshic commented Nov 29, 2020

Lambda update problem already solved in AWS SAM - see https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-specification-generated-resources-function.html#sam-specification-generated-resources-function-autopublishalias.

Just add AutoPublishAlias to AWS::Serverless::Function and SAM will create hash-based versions automatically than update CloudFront bindings.

Just rebuild lambda and run sam deploy:
image

@guerrerocarlos
Copy link

guerrerocarlos commented Jun 3, 2021

Has @haydenk points out, just adding a new AWS::Lambda::Version works, and it's actually practical because you can remove the previous AWS::Lambda::Version in the same update, making it compatible with continuous deployment. Also the AutoPublishAlias is only available for SAM not CloudFormation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment