-
-
Save jed/56b1f58297d374572bc51c59394c7e7f to your computer and use it in GitHub Desktop.
#!/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: '*' |
Thanks much for this gist jed, and the highly-functional 'custom resource' - very helpful for my purposes, and a place I've found AWS documentation and support lacking. I deployed with an updated Runtime: of nodejs12.x with success.
One question - about the use of the nonce. Is this to provide idempotence, allowing multiple instances in a given account, or some other purpose I'm just not grokking? Thanks again!!
Thanks much for this gist jed, and the highly-functional 'custom resource' - very helpful for my purposes, and a place I've found AWS documentation and support lacking. I deployed with an updated Runtime: of nodejs12.x with success.
One question - about the use of the nonce. Is this to provide idempotence, allowing multiple instances in a given account, or some other purpose I'm just not grokking? Thanks again!!
i haven't used this in a while, but IIRC the nonce is to force a redeploy of the template, otherwise CloudFormation would just tell you that the template hasn't changed.
I was reading through AWS docs.
Seems now we get Lambda Versioning out of the box.
May be this could be modified as follows without using the Cloudformation custom resource and an additional IAM Role.
VersionedIndexLambda:
Type: 'AWS::Lambda::Version'
Properties:
FunctionName: !Ref IndexLambda
Distribution:
Type: AWS::CloudFront::Distribution
......
......
LambdaFunctionAssociations:
- EventType: viewer-request
LambdaFunctionARN: !Ref VersionedIndexLambda
Anybody tried this way?
I was reading through AWS docs.
Seems now we get Lambda Versioning out of the box.
May be this could be modified as follows without using the Cloudformation custom resource and an additional IAM Role.VersionedIndexLambda: Type: 'AWS::Lambda::Version' Properties: FunctionName: !Ref IndexLambda Distribution: Type: AWS::CloudFront::Distribution ...... ...... LambdaFunctionAssociations: - EventType: viewer-request LambdaFunctionARN: !Ref VersionedIndexLambdaAnybody tried this way?
@nom3ad I have tried this, I have found that the lambda version does not update when you redeploy your code. Jed has gifted you the solution above.
@LpmRaven Because AWS::Lambda::Version
resources can only be created or delete not updated.
So, what you would have to do is create a second version to replace the current version.
First Deploy
VersionedIndexLambdaA:
Type: 'AWS::Lambda::Version'
Properties:
FunctionName: !Ref IndexLambda
Distribution:
Type: AWS::CloudFront::Distribution
......
......
LambdaFunctionAssociations:
- EventType: viewer-request
LambdaFunctionARN: !Ref VersionedIndexLambdaA
Second Deploy - Will create a second lambda version now and deploy it to the distribution
VersionedIndexLambdaA:
Type: 'AWS::Lambda::Version'
Properties:
FunctionName: !Ref IndexLambda
VersionedIndexLambdaB:
Type: 'AWS::Lambda::Version'
Properties:
FunctionName: !Ref IndexLambda
Distribution:
Type: AWS::CloudFront::Distribution
......
......
LambdaFunctionAssociations:
- EventType: viewer-request
LambdaFunctionARN: !Ref VersionedIndexLambdaB
It has some oddities to it but it could be better than creating a whole other lambda just to deploy another lambda.
@LpmRaven Because
AWS::Lambda::Version
resources can only be created or delete not updated.So, what you would have to do is create a second version to replace the current version.
First Deploy
VersionedIndexLambdaA: Type: 'AWS::Lambda::Version' Properties: FunctionName: !Ref IndexLambda Distribution: Type: AWS::CloudFront::Distribution ...... ...... LambdaFunctionAssociations: - EventType: viewer-request LambdaFunctionARN: !Ref VersionedIndexLambdaASecond Deploy - Will create a second lambda version now and deploy it to the distribution
VersionedIndexLambdaA: Type: 'AWS::Lambda::Version' Properties: FunctionName: !Ref IndexLambda VersionedIndexLambdaB: Type: 'AWS::Lambda::Version' Properties: FunctionName: !Ref IndexLambda Distribution: Type: AWS::CloudFront::Distribution ...... ...... LambdaFunctionAssociations: - EventType: viewer-request LambdaFunctionARN: !Ref VersionedIndexLambdaBIt has some oddities to it but it could be better than creating a whole other lambda just to deploy another lambda.
Yes @haydenk, but unfortunately that is not at all practical for continuous deployment. Its much easier to have a lambda just publish a new version every time you make code changes.
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.
@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.
@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.
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.
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.
Thanks jed , this post has been alot helpful !!