How to create a new version of a Lambda function using CloudFormation?
Asked Answered
A

14

69

I'm trying to create a new version of a Lambda function using CloudFormation.

I want to have multiple versions of the same Lambda function so that I can (a) point aliases at different versions - like DEV and PROD - and (b) be able to roll back to an earlier version

This is the definition of my Lambda version:

LambdaVersion:
  Type: AWS::Lambda::Version
  Properties:
    FunctionName:
      Ref: LambdaFunction

A version gets created when running "aws cloudformation create-stack" but the subsequent "aws cloudformation update-stack" commands don't do anything. There are no new Lambda versions created.

I'm trying to get a new version of the Lambda function created after I upload new zip file to S3 and then run "update-stack". Can I do it with CloudFormation? Is AWS::Lambda::Version really broken (as mentioned here https://github.com/hashicorp/terraform/issues/6067#issuecomment-211708071) or am I just not getting something?

Update 1/11/17 Official reply from Amazon support: "...for any new version to be published you need to define an addition (sic) AWS::Lambda::Version resource..."

AWS CloudFormation/Lambda team, if you're reading this - this is unacceptable. Fix it.

Aggappera answered 3/1, 2017 at 21:0 Comment(0)
M
25

AWS::Lambda::Version is not useful. You have to add a new resource for every Lambda version. If you want to publish a new version for every Cloudformation update, you have to hack the system.

I solved this issue creating a Lambda backed custom resource which is triggered for every deployment. Inside this Lambda, I am creating a new version for the Lambda function given in parameter.

For the Lambda's source you can check http://serverless-arch-eu-west-1.s3.amazonaws.com/serverless.zip

Here is the example Cloudformation using this Deployment Lambda function (You might need some modification):

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Parameters": {
    "DeploymentTime": {
      "Type": "String",
      "Description": "It is a timestamp value which shows the deployment time. Used to rotate sources."
    }
  },
  "Resources": {
    "LambdaFunctionToBeVersioned": {
      "Type": "AWS::Lambda::Function",
       ## HERE DEFINE YOUR LAMBDA AS USUAL ##
    },
    "DeploymentLambdaRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Principal": {
                "Service": [
                  "lambda.amazonaws.com"
                ]
              },
              "Action": [
                "sts:AssumeRole"
              ]
            }
          ]
        },
        "Path": "/",
        "ManagedPolicyArns": [
          "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
        ],
        "Policies": [
          {
            "PolicyName": "LambdaExecutionPolicy",
            "PolicyDocument": {
              "Version": "2012-10-17",
              "Statement": [
                {
                  "Effect": "Allow",
                  "Action": [
                    "lambda:PublishVersion"
                  ],
                  "Resource": [
                    "*"
                  ]
                }
              ]
            }
          }
        ]
      }
    },
    "DeploymentLambda": {
      "Type": "AWS::Lambda::Function",
      "Properties": {
        "Role": {
          "Fn::GetAtt": [
            "DeploymentLambdaRole",
            "Arn"
          ]
        },
        "Handler": "serverless.handler",
        "Runtime": "nodejs4.3",
        "Code": {
          "S3Bucket": {
            "Fn::Sub": "serverless-arch-${AWS::Region}"
          },
          "S3Key": "serverless.zip"
        }
      }
    },
    "LambdaVersion": {
      "Type": "Custom::LambdaVersion",
      "Properties": {
        "ServiceToken": {
          "Fn::GetAtt": [
            "DeploymentLambda",
            "Arn"
          ]
        },
        "FunctionName": {
          "Ref": "LambdaFunctionToBeVersioned"
        },
        "DeploymentTime": {
          "Ref": "DeploymentTime"
        }
      }
    }
  }
}

(Disclaimer: This code is a part of my book, for more information about Lambda & API Gateway you can check: https://www.amazon.com/Building-Serverless-Architectures-Cagatay-Gurturk/dp/1787129195)

Maddiemadding answered 5/1, 2017 at 11:12 Comment(7)
Thanks! That's pretty crazy. I have a support request out to AWS through our Enterprise support plan and if they confirm that AWS::Lambda::Version is useless I'll try this and accept this answer.Aggappera
@Aggappera Hi Boris any update if this feature was implemented since ?Fineman
That's awesome, I spent two days trying to limit roles creation at each publish, many thanks!Imagism
What is supposed to be put into HERE_DEFINE_YOUR_LAMBDA? That's not the actual Lambda function code, so what is it?Discontent
@GrzegorzOledzki I've edited to code to clarify. It's the actual Lambda code.Maddiemadding
@ÇağatayGürtürk I tried your piece of code and every time my Cloudformation stack becomes non responsive.Any suggestion? Other question whats the use of "DeploymentLambda" in your code?Hardwood
Please ignore this answer, it doesn't include what the code is and you can use the AWS Serverless Transform which works by default everywhere, as well as reference the output ARN via { "Fn::Sub": "${ServerlessLambdaFunction.Version}" }Freezedrying
P
46

I have a similar use case (needing to use CloudFormation to manage a lambda function to be used @edge in CloudFront, for which a specific lambda function version is always required, not $LATEST) and my searches landed me at this question first, but after a bit more digging I was happy to find there is now native support for automatic lambda versioning with the new AutoPublishAlias feature of the AWS Serverless Application Model (basically an optional extra set of higher-level constructs for your CloudFormation templates).

Announced here: https://github.com/awslabs/serverless-application-model/issues/41#issuecomment-347723981

For details see:

Essentially you include AutoPublishAlias in your AWS::Serverless::Function definition:

MyFunction:
  Type: "AWS::Serverless::Function"
  Properties:
    # ...
    AutoPublishAlias: MyAlias

And then elsewhere in the CloudFormation template you can reference the latest published version as !Ref MyFunction.Version (yaml syntax).

Premillennialism answered 25/1, 2018 at 9:18 Comment(3)
This is so simple and should be the new approved answer. Thank youNickey
While this seems like a great answer it should be noted the answer relies on AWS SAM. Note the function type is AWS::Serverless::Function. Didn't work for me.Devaluate
It may not be the approved answer because some of us may be here due to the version-leakage issue (github.com/serverless/serverless/issues/400) that occurs when you use AutoPublishAlias and the implications. For example, I use that for "staging", but also want a "production" alias setup against that same version. Custom-resource works either way where AutoPublishAlias can only solve part of my CI/CD challenge.Betroth
M
25

AWS::Lambda::Version is not useful. You have to add a new resource for every Lambda version. If you want to publish a new version for every Cloudformation update, you have to hack the system.

I solved this issue creating a Lambda backed custom resource which is triggered for every deployment. Inside this Lambda, I am creating a new version for the Lambda function given in parameter.

For the Lambda's source you can check http://serverless-arch-eu-west-1.s3.amazonaws.com/serverless.zip

Here is the example Cloudformation using this Deployment Lambda function (You might need some modification):

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Parameters": {
    "DeploymentTime": {
      "Type": "String",
      "Description": "It is a timestamp value which shows the deployment time. Used to rotate sources."
    }
  },
  "Resources": {
    "LambdaFunctionToBeVersioned": {
      "Type": "AWS::Lambda::Function",
       ## HERE DEFINE YOUR LAMBDA AS USUAL ##
    },
    "DeploymentLambdaRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Principal": {
                "Service": [
                  "lambda.amazonaws.com"
                ]
              },
              "Action": [
                "sts:AssumeRole"
              ]
            }
          ]
        },
        "Path": "/",
        "ManagedPolicyArns": [
          "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
        ],
        "Policies": [
          {
            "PolicyName": "LambdaExecutionPolicy",
            "PolicyDocument": {
              "Version": "2012-10-17",
              "Statement": [
                {
                  "Effect": "Allow",
                  "Action": [
                    "lambda:PublishVersion"
                  ],
                  "Resource": [
                    "*"
                  ]
                }
              ]
            }
          }
        ]
      }
    },
    "DeploymentLambda": {
      "Type": "AWS::Lambda::Function",
      "Properties": {
        "Role": {
          "Fn::GetAtt": [
            "DeploymentLambdaRole",
            "Arn"
          ]
        },
        "Handler": "serverless.handler",
        "Runtime": "nodejs4.3",
        "Code": {
          "S3Bucket": {
            "Fn::Sub": "serverless-arch-${AWS::Region}"
          },
          "S3Key": "serverless.zip"
        }
      }
    },
    "LambdaVersion": {
      "Type": "Custom::LambdaVersion",
      "Properties": {
        "ServiceToken": {
          "Fn::GetAtt": [
            "DeploymentLambda",
            "Arn"
          ]
        },
        "FunctionName": {
          "Ref": "LambdaFunctionToBeVersioned"
        },
        "DeploymentTime": {
          "Ref": "DeploymentTime"
        }
      }
    }
  }
}

(Disclaimer: This code is a part of my book, for more information about Lambda & API Gateway you can check: https://www.amazon.com/Building-Serverless-Architectures-Cagatay-Gurturk/dp/1787129195)

Maddiemadding answered 5/1, 2017 at 11:12 Comment(7)
Thanks! That's pretty crazy. I have a support request out to AWS through our Enterprise support plan and if they confirm that AWS::Lambda::Version is useless I'll try this and accept this answer.Aggappera
@Aggappera Hi Boris any update if this feature was implemented since ?Fineman
That's awesome, I spent two days trying to limit roles creation at each publish, many thanks!Imagism
What is supposed to be put into HERE_DEFINE_YOUR_LAMBDA? That's not the actual Lambda function code, so what is it?Discontent
@GrzegorzOledzki I've edited to code to clarify. It's the actual Lambda code.Maddiemadding
@ÇağatayGürtürk I tried your piece of code and every time my Cloudformation stack becomes non responsive.Any suggestion? Other question whats the use of "DeploymentLambda" in your code?Hardwood
Please ignore this answer, it doesn't include what the code is and you can use the AWS Serverless Transform which works by default everywhere, as well as reference the output ARN via { "Fn::Sub": "${ServerlessLambdaFunction.Version}" }Freezedrying
H
25

This post is out-of-date. I am updating it here so others can see the correct solution for versioning Lambdas as of 06-09-2020, without the need for extra custom versioning Lambdas.

This:

Description: Lambda Example
Resources:
  Function:
    Type: AWS::Lambda::Function
    Properties:
      Handler: index.handler
      Code:
        ZipFile: |
          'Example Code';
      Runtime: nodejs12.x
      Timeout: 5

Becomes this:

Description: Lambda Example
Transform: AWS::Serverless-2016-10-31
Resources:
  Function:
    Type: AWS::Serverless::Function
    Properties:
      AutoPublishAlias: live
      Handler: index.handler
      InlineCode: |
        'Example Code';
      Runtime: nodejs12.x
      Timeout: 5

The Transform: allows AWS::Serverless::Function inside of a CloudFormation template which in turn supports lambda versioning. And then you can reference the versioned ARN via { "Fn::Sub": "${ServerlessLambdaFunction.Version}" }.

Don't let the dated "Best Answer" above - built for that persons book - throw you down a rabbit hole like I did.

You're welcome.

Huston answered 9/6, 2020 at 19:26 Comment(6)
This is simple and works well - it should be the approved answer.Claycomb
This didn't work for codeuri. It did publish live version, but didn't update the function code. Do you have working example for codeuri?Sledge
It should be noted that AutoPublishAlias works through the transform by appending the given alias to the function name as a CFN resource name. This means you CAN NOT use an alias that includes non-alphanumeric characters.Mccutcheon
This can have its own issues: github.com/aws/aws-cdk/issues/5334Carpetbag
@Sledge Function code can be updated through generating new zip file names in S3, e.g. by appending a random string to the zip name. In my CI, I generate a random string, append it to the zip name, upload this zip file to S3 and pass the same string as parameter to aws cloudformation deploy so that CodeUri/Key receives the random string. AFAIK the only way to force update a Lambda function whenever the code changes (bc now it always updates, regardless if code has changed or not).Tomas
"Fn::Sub": "${ServerlessLambdaFunction.Version}"Freezedrying
B
12

The AWS::Lambda::Version resource only represents a single published Lambda function version- it will not automatically publish new versions on every update of your code. To accomplish this, you have two options:

1. Custom resource

You can implement your own Custom Resource that calls PublishVersion on each update.

For this approach, you'll still need to change at least one Parameter every time you update your stack, in order to trigger an update on the Custom Resource that will trigger the PublishVersion action. (You won't have to actually update the template, though.)

Here's a full, working example:

Launch Stack

Description: Publish a new version of a Lambda function whenever the code is updated.
Parameters:
  Nonce:
    Description: Change this string when code is updated.
    Type: String
    Default: "Test"
Resources:
  MyCustomResource:
    Type: Custom::Resource
    Properties:
      ServiceToken: !GetAtt MyFunction.Arn
      Nonce: !Ref Nonce
  MyFunction:
    Type: AWS::Lambda::Function
    Properties:
      Handler: index.handler
      Role: !GetAtt LambdaExecutionRole.Arn
      Code:
        ZipFile: !Sub |
          var response = require('cfn-response');
          exports.handler = function(event, context) {
            return response.send(event, context, response.SUCCESS, {Result: '${Nonce}'});
          };
      Runtime: nodejs4.3
  LambdaDeploy:
    Type: Custom::LambdaVersion
    Properties:
      ServiceToken: !GetAtt LambdaDeployFunction.Arn
      FunctionName: !Ref MyFunction
      Nonce: !Ref Nonce
  LambdaDeployFunction:
    Type: AWS::Lambda::Function
    Properties:
      Handler: "index.handler"
      Role: !GetAtt LambdaExecutionRole.Arn
      Code:
        ZipFile: !Sub |
          var AWS = require('aws-sdk');
          var response = require('cfn-response');
          exports.handler = (event, context) => {
            console.log("Request received:\n", JSON.stringify(event));
            if (event.RequestType == 'Delete') {
              return response.send(event, context, response.SUCCESS);
            }
            var lambda = new AWS.Lambda();
            lambda.publishVersion({FunctionName: event.ResourceProperties.FunctionName}).promise().then((data) => {
              return response.send(event, context, response.SUCCESS, {Version: data.Version}, data.FunctionArn);
            }).catch((e) => {
              return response.send(event, context, response.FAILED, e);
            });
          };
      Runtime: nodejs4.3
  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal: {Service: [lambda.amazonaws.com]}
          Action: ['sts:AssumeRole']
      Path: /
      ManagedPolicyArns:
      - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
      - PolicyName: PublishVersion
        PolicyDocument:
          Version: 2012-10-17
          Statement:
          - Effect: Allow
            Action: ['lambda:PublishVersion']
            Resource: '*'
Outputs:
  LambdaVersion:
    Value: !GetAtt LambdaDeploy.Version
  CustomResourceResult:
    Value: !GetAtt MyCustomResource.Result

2. Template preprocessor

You can use a template preprocessor like embedded Ruby (or just manually updating your template on each deploy) to publish a new Version on each update of your code by changing the AWS::Lambda::Version resource's Logical ID whenever your code is updated.

Example:

# template.yml
Description: Publish a new version of a Lambda function whenever the code is updated.
<%nonce = rand 10000%>
Resources:
  LambdaVersion<%=nonce%>:
    Type: AWS::Lambda::Version
    Properties:
      FunctionName: !Ref MyFunction
  MyCustomResource:
    Type: Custom::Resource
    Properties:
      ServiceToken: !GetAtt MyFunction.Arn
      Nonce: <%=nonce%>
  MyFunction:
    Type: AWS::Lambda::Function
    Properties:
      Handler: index.handler
      Role: !GetAtt LambdaExecutionRole.Arn
      Code:
        ZipFile: !Sub |
          var response = require('cfn-response');
          exports.handler = function(event, context) {
            return response.send(event, context, response.SUCCESS, {Result: '<%=nonce%>'});
          };
      Runtime: nodejs4.3
  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal: {Service: [lambda.amazonaws.com]}
          Action: ['sts:AssumeRole']
      Path: /
      ManagedPolicyArns:
      - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Outputs:
  LambdaVersion:
    Value: !GetAtt LambdaVersion<%=nonce%>.Version
  CustomResourceResult:
    Value: !GetAtt MyCustomResource.Result

To create/update the stack while passing template.yml through the erb template preprocessor, run:

aws cloudformation [create|update]-stack \
  --stack-name [stack_name] \
  --template-body file://<(ruby -rerb -e "puts ERB.new(ARGF.read).result" < template.yml) \
  --capabilities CAPABILITY_IAM
Bladdernut answered 4/1, 2017 at 3:1 Comment(6)
I want to have multiple versions of the same Lambda function so that I can (a) point aliases at different versions - like DEV and PROD - and (b) be able to roll back to an earlier versionAggappera
You may want to consider using CodePipline to deploy different CloudFormation config templates into the same CloudFormation template. This may be a less fragile way of maintaining a dev and prod environment.Chet
@Aggappera thanks for the additional context, I've updated my answer with full example templates for two different approaches.Bladdernut
Should this solution also work with S3 bucket (not using Code:). I get errors. A version for this Lambda function exists ( 1 ). Modify the function to create a new versionExquisite
@Bladdernut yes agree with boris, unless you (stupidly?) add an infinite list of LambdaVersion in the CloudFormation, the Lambda will not automatically retain a list of previous version you can easily roll back to unfortunately.Banebrudge
Yes, but there should be an easier way with plain CloudFormation!Devaluate
P
7

Answer updated for February 2018

You can use AWS SAM (Serverless Application Model), and its sam package and sam deploy commands to update Lambda. They are similar to aws cloudformation package and aws cloudformation deploy commands, but also let you update Lambda versions automatically.

SAM can package your code (or take ZIP package you created otherwise), upload it to S3, and update the $LATEST Version of the Lambda from it. (If this is all you need, this can also be done with aws cloudformation, without SAM; code examples are same as below, but only use CloudFormation's standard declarations). Then, with SAM, if configured accordingly, you can also automatically publish a Version and update an Alias to point to it. It can also, optionally, use AWS CodeDeploy to gradually move traffic from previous Version to new one, and rollback in case of errors. All this is explained in Safe Lambda deployments.


Technically, the idea is that every time you update the stack, you need your AWS::Lambda::Function's Code to point to the new package in S3. This will ensure that when you update the stack, Lambda's $LATEST version will be updated from the new package. Then, you can also automate the publishing of new Version and switch an Alias to it.

For it, create a SAM template, which is similar to (a superset of) CloudFormation template. It may include SAM-specific declarations, like the one for AWS::Serverless::Function below. Point the Code to source code directory (or a prepackaged ZIP), and set the AutoPublishAlias property.

...

MyFunction:
    Type: AWS::Serverless::Function
    Properties:
      ...  # all usual CloudFormation properties are accepted 
      AutoPublishAlias: dev  # will publish a Version and create/update Alias `dev` to point to it
      Code: ./my/lambda/src
...

Run:

$ sam package --template-file template.yaml --output-template-file packaged.yaml --s3-bucket my-bucket

This packages source directory contents as a ZIP (if Code is not a ZIP already), uploads it to S3 under new autogenerated key, and generates final CloudFormation template to packaged.yaml, putting for you the proper Code reference into it; like this:

...
MyFunction:
    Properties:
      Code:
        S3Bucket: my-bucket
        S3Key: ddeeaacc44ddee33ddaaee223344
...

Now you can use generated packaged.yaml with SAM, to create function Version:

sam deploy --template-file packaged.yaml --stack-name my-stack [--capabilities ...]

This will update Lambda's $LATEST version, and, if AutoPublishAlias was defined, publish it as a new Version and update the Alias to point to the newly published Version.

See the examples in SAM GitHub repo for a complete template code.

Principate answered 7/2, 2018 at 11:53 Comment(2)
This doesn't address the problem of adding multiple versions to a lambda function via cloudformation. All this does is use some nice tooling provided to generate some cloudformation and update the code at $LATEST. It will not add a version to the lambda function.Gibbosity
Yes, indeed, I didn't realise the question also included this. I've updated the answer to include this too (publishing the Version and updating the Alias to it).Principate
P
6

Looking for a similar thing that works with Lambda functions deployed from S3.

My use case was this:

  • You have a cloudformation template that creates a Lambda function from an S3 bucket location
  • You need to update this function so you make code changes locally and push the changes to S3
  • You now want to push these changes to Lambda so you try to update the stack and cloudformation says there are no changes to update so you have to resort to manually updating the code using the AWS Lambda console.

Not happy with this I looked for an alternative and came across this question. None of the answers exactly worked for me so I have taken some ideas and adapted the answers here and made my own version written in Python.

This code is adapted from the answer from @wjordan so credit to him for the idea and the original answer. The differences are:

  • This is written in Python
  • It works with Lambda code deployed from an S3 bucket
  • It updates the code and publishes a new version

You need a nonce parameter. You change the value of this parameter when the code needs to be republished to Lambda. This is to ensure that cloudformation will update your custom resource. When the custom resource is updated, it will run the Python code that ultimately updates your Lambda code.

Hope this helps someone.

Description: Publish a new version of a Lambda function whenever the code is updated.
Parameters:
  Nonce:
    Description: Change this string when code is updated.
    Type: String
    Default: "Test"
Resources:
  MyCustomResource:
    Type: Custom::Resource
    Properties:
      ServiceToken: !GetAtt MyFunction.Arn
      Nonce: !Ref Nonce
  MyFunction:
    Type: AWS::Lambda::Function
    Properties:
      Handler: index.handler
      Role: !GetAtt LambdaExecutionRole.Arn
      Code:
        S3Bucket: BucketContainingYourLambdaFunction
        S3Key: KeyToYourLambdaFunction.zip
      Runtime: "python3.6"
  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal: {Service: [lambda.amazonaws.com]}
          Action: ['sts:AssumeRole']
      Path: /
      ManagedPolicyArns:
      - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
  LambdaDeployCustomResource:
    Type: Custom::LambdaVersion
    Properties:
      ServiceToken: !GetAtt LambdaDeployFunction.Arn
      FunctionName: !Ref MyFunction
      S3Bucket: BucketContainingYourLambdaFunction
      S3Key: KeyToYourLambdaFunction.zip
      Nonce: !Ref Nonce
  LambdaDeployFunction:
    Type: AWS::Lambda::Function
    DependsOn: LambdaDeployFunctionExecutionRole
    Properties:
      Handler: "index.handler"
      Role: !GetAtt LambdaDeployFunctionExecutionRole.Arn
      Code:
        ZipFile: !Sub |
          import boto3
          import json
          import logging
          import cfnresponse
          import time
          from botocore.exceptions import ClientError

          def handler(event, context):
            logger = logging.getLogger()
            logger.setLevel(logging.INFO)
            logger.info (f"Input parameters from cloud formation: {event}")
            responseData = {}
            if (event["RequestType"] == 'Delete'):
              logger.info("Responding to delete event...")
              cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData)

            try:            
              lambdaClient = boto3.client('lambda')
              s3Bucket = event['ResourceProperties']['S3Bucket']
              s3Key = event['ResourceProperties']['S3Key']
              functionName = event['ResourceProperties']['FunctionName']
              logger.info("Updating the function code for Lambda function '{}' to use the code stored in S3 bucket '{}' at key location '{}'".format(functionName, s3Bucket, s3Key))
              logger.info("Sleeping for 5 seconds to allow IAM permisisons to take effect")
              time.sleep(5)             
              response = lambdaClient.update_function_code(
                FunctionName=functionName,
                S3Bucket='{}'.format(s3Bucket),
                S3Key='{}'.format(s3Key),
                Publish=True)
              responseValue = "Function: {}, Version: {}, Last Modified: {}".format(response["FunctionName"],response["Version"],response["LastModified"])
              responseData['Data'] = responseValue
              cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, response["FunctionArn"])
            except ClientError as e:
              errorMessage = e.response['Error']['Message']
              logger.error(errorMessage)
              cfnresponse.send(event, context, cfnresponse.FAILED, responseData)
      Runtime: "python3.6"
      Timeout: "30"
  LambdaDeployFunctionExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal: 
            Service: lambda.amazonaws.com
          Action: 
            - sts:AssumeRole
      Path: /
      ManagedPolicyArns:
      - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
      - PolicyName: ReadS3BucketContainingLambdaCode
        PolicyDocument:
          Version: 2012-10-17
          Statement:
          - Effect: Allow
            Action: 
              - s3:GetObject              
            Resource: ArnOfS3BucketContainingLambdaCode/*
      - PolicyName: UpdateCodeAndPublishVersion
        PolicyDocument:
          Version: 2012-10-17
          Statement:
          - Effect: Allow
            Action: 
              - lambda:UpdateFunctionCode
              - lambda:PublishVersion
            Resource: '*'
Outputs:
  LambdaVersion:
    Value: !GetAtt LambdaDeploy.Version
  CustomResourceResult:
    Value: !GetAtt MyCustomResource.Result 
Plowboy answered 17/4, 2018 at 15:41 Comment(0)
B
2

Unfortunately, this is not possible to do using CloudFormation. You will need to add new AWS::Lambda::Version sections in your CloudFormation template for each version.

The closest solution would be to create .erb templates and have it generate CloudFormation templates with all the versions.

Bebeeru answered 3/1, 2017 at 22:43 Comment(0)
W
2

This is a bit of a hack, and depends on using gitlab-ci (or something similar), but I find passing the commit hash into a cloudformation template (via the template's parameters) very useful.

(It's a bit like @Jerry 's answer, but using the commit hash.)

In this case you could do something like:

Have a parameter in your template for the commit hash, e.g.:

AWSTemplateFormatVersion: '2010-09-09'
Description: Template for Lambda Sample.
Parameters:
  ciCommitSha:
    Type: String
  s3Bucket:
    Type: String
  ...

You can then reference this in the lambda resource, like this:

  CFNLambda:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: cfn_trigger_fn
      Description: lambda which gets triggered by cloudformation
      Runtime: python3.7
      Code:
        S3Bucket: !Ref s3Bucket
        S3Key: !Join [ ".", [ !Ref ciCommitSha, "zip"]]
      Handler: function.handler
      ...

Your ci pipeline then needs to look something like (assuming you call your cloudformation template stack-template.yaml):

variables:
  REGION: us-east-1
  S3_BUCKET_NAME: my-bucket

stages:
 - build
 - push
 - deploy

build-package:
  stage: build
  script:
    - some code to produce a deployment package called function.zip
  artifacts:
    name: deployment_package
    paths:
      - function.zip


push-code:
  stage: push
  script:
    - aws s3 cp function.zip s3://$S3_BUCKET_NAME/$CI_COMMIT_SHA.zip

deploy-trigger-stack:
  stage: deploy
  script: 
      - aws cloudformation deploy
            --template-file stack-template.yaml
            --stack-name my-stack
            --region $REGION
            --no-fail-on-empty-changeset
            --capabilities CAPABILITY_NAMED_IAM
            --parameter-overrides
            ciCommitSha=$CI_COMMIT_SHA
            s3Bucket=$S3_BUCKET_NAME

You can use this technique for triggering cfn-init on EC2 metadata as well..

Woodcut answered 13/8, 2019 at 12:55 Comment(0)
T
1
  1. We can make a Lambda deployment package;
  2. Pass the Lambda package with the version as one of Cloud Formation parameters, e.g. "LambdaPakcageNameWithVersion";
  3. Use "LambdaPakcageNameWithVersion" as the Lambda code s3 key;
  4. The new Lamdba package will be deployed when running the aws-cli command to update the cloudformation stack or running CI/CD pipeline.

  MyLambda:
    Type: AWS::Lambda::Function
    Properties:
      Role: LambdaRole
      Code:
        S3Bucket: LambdaPackageS3Bucket
        S3Key: !Sub "${LambdaPakcageNameWithVersion}"
      FunctionName: LambdaFunctionName
      Handler: lambda_function.lambda_handler
      Runtime: python3.6
      Timeout: 60
Topmost answered 9/6, 2018 at 7:48 Comment(0)
F
1

Worked for me the following:

"LambdaAlias": {
            "Type": "AWS::Lambda::Alias",
            "DeletionPolicy" : "Retain",
            "Properties": {
                "FunctionName": {
                    "Ref": "LambdaFunction"
                },
                "FunctionVersion": {
                    "Fn::GetAtt": ["LambdaVersion","Version"]
                },
                "Name": "MyAlias"
            }
Fluorene answered 12/12, 2019 at 15:57 Comment(1)
So i just had to add DeletionPolicy: RetainFluorene
N
0

I solved this using CI/CD, an ant script and the git revision to create a unique zip name in the S3 bucket for each commit.

The ant script is invoked by the CI/CD to substitute the git revision into the name of the lambda code zip file and cloudformation template. These references are made before copying the code and cloudformation scripts to S3. This is similar to the way SAM works but this works with plain old Cloudformation Stacks and importantly Stack Sets that might need be deployed across a number of accounts. At time of writing SAM was not compatible with CF Stack Sets

There are two files: the ant file and a properties file that tells the ant file which lambda source directories to zip.

First the build.xml ant file:

<project basedir="." name="AWS Lambda Tooling Bucket Zip" default="ziplambdas">
    <!-- this ant file is responsible for zipping up lambda source code that needs to be placed on an S3 bucket for deployment.
        It reads a file `lambda-zip-build.properties` that contains a list of lambda folders and the corresponding zip names.
        This allows a lambda to include source code and any required library packages into a single zip for deployment.
        For further information refer to the comments at the top of the zip properties file.
    -->

    <property name="ant.home" value="${env.ANT_HOME}" />
    <taskdef resource="net/sf/antcontrib/antlib.xml">
        <classpath path="${ant.home}/lib/ant-contrib-1.0b3.jar" />
    </taskdef>

    <!-- <available file=".git" type="dir" property="git.present"/> -->
    <available file="../../.git" type="dir" property="git.present"/>

    <!-- get the git revision to make a unique filename on S3. This allows the zip key to be replaced, forcing an update if CloudFormation is deployed. Clunky,
         AWS Support raised but advice was to use SAM, which is not compatible with StackSets ... *sigh* -->
    <target name="gitrevision" description="Store git revision in ${repository.version}" if="git.present">
        <exec executable="git" outputproperty="git.revision" failifexecutionfails="false" errorproperty="">
            <arg value="describe"/>
            <arg value="--tags"/>
            <arg value="--always"/>
            <arg value="HEAD"/>
        </exec>
        <condition property="repository.version" value="${git.revision}" else="unknown">
            <and>
                <isset property="git.revision"/>
                <length string="${git.revision}" trim="yes" length="0" when="greater"/>
            </and>
        </condition>
        <echo>git revision is ${git.revision} </echo>
    </target>

    <target name="replace.git.revision.in.files" depends="gitrevision" description="replace the git marker text in cloudformation files and zip properties file">
        <replace dir="." token="@git.revision@" value="${git.revision}" summary="yes"/>
    </target>

    <property file="lambda.zip.build.properties"/>

    <!-- zip the lambda code into a unique zip name based on the git revision -->
    <target name="ziplambdas" description="Create Zip files based on the property list" depends="replace.git.revision.in.files">
        <property file="lambda.zip.build.properties" prefix="zipme." />
        <propertyselector property="zip.list" match="^zipme\.(.*)" select="\1"/>

        <foreach list="${zip.list}" delimiter="," target="zip" param="folder"/>
    </target>

    <target name="zip">
        <propertycopy property="zip.path" from="${folder}" />
        <basename property="zip.file" file="${zip.path}" />
        <echo message="${folder} is being zipped to ${zip.path}"/>
        <zip destfile="${zip.path}">
            <zipfileset dir="${folder}">
               <exclude name="**/${zip.file}"/>
            </zipfileset> 
        </zip>
    </target>

</project>

The lambda.zip.build.properties file looks like this:

# This property file contains instructions for CI/CD Build Process to zip directories containing lambda code to place on the S3  bucket.
# Lambda source code when deployed by CloudFormation must be available inside a Zip file in a S3 bucket.
# CI/CD runs an ant task that reads this file to create the appropriate zip files referenced by the CloudFormation scripts. 
# 
# Each property key value pair below contains a key of the top level directory containing the lambda code (in python, javascript or whatever), 
# and a value of the path to the zip file that should be deployed to S3. The @git.revision@ tag is substituted with the actual git revision before copying to S3.
# This allows the lambda S3key to change for each deployment and forces a lambda code update. 
#
# for example: myproject/lambda/src=myproject/lambda/[email protected]@.zip
#              ^^ Directory    ^^ Zip File
#
###################################################################################################################################################################################
myproject/lambda/src=myproject/lambda/[email protected]@.zip
# place your key value pairs above here...

And then the CloudFormation Template:

Resources:
  MyLambda:
    Type: AWS::Lambda::Function
    Properties:
    # git.revision is placed when code is zipped up by CI/CD and placed on S3 bucket. It allows a unique name for each commit and thereby forces
    # lambda code to be replaced on cloudformation stackset redeployment.
      Code:
        S3Bucket: mybucket
        S3Key: myproject/lambda/[email protected]@.zip
      Handler: autotag-costcentre.lambda_handler
      MemorySize: 128
      Runtime: python3.7
      Timeout: 10
      .... etc

The result is a zip file with a unique name lambda-code-0f993c3.zip and a Cloudformation template with S3Key referencing the unique name.

S3Key: myproject/lambda/lambda-code-0f993c3.zip

Deploy the template from the S3 location and it will force the existing lambda code to be refreshed every time.

Noelnoelani answered 22/8, 2019 at 5:56 Comment(0)
F
0

I do a checksum on the folder, and append this to S3Key of the lambda

- lambda_src_version=$(find ${Lambda1} -type f -print0  | xargs -0 sha1sum | sha1sum )
- lambda_src_version =$(echo ${lambda_src_version//[[:blank:]]/})
- export S3Key="lambda_functions/${stage}/cloudformation-${lambda_src_version}.zip"
- zip - -r . -x '*.git*' | aws s3 cp - s3://${S3Bucket}/${S3Key}
- sam deploy --template-file cloudformation.yml --stack-name XXX --parameter-overrides Lambda1Bucket=${S3Bucket} Lambda1CodeZip="${S3Key}"

// cloudformation.yml snippet

Parameters:      
  Lambda1CodeZip:
    Type: String
  Lambda1Bucket:
    Type: String 

Type: 'AWS::Lambda::Function'
    Properties: 
      Code:
        S3Bucket: !Sub ${Lambda1Bucket}
        S3Key: !Sub ${Lambda1CodeZip}
Footman answered 31/12, 2020 at 1:15 Comment(0)
R
0

I had a similar problem and solved it for my situation which is not applicable to some of the others mentioned. My Lambda code is in a zip file in a versioned bucket. To force a new version the function has to be replaced. To force a function replacement, I force a function name change by incorporating the code object's version ID. Object Version IDs can have a period in them which is invalid in a function name, so any periods begin truncation of the object version ID.

rLambdaVersion:
  Type: AWS::Lambda::Version
  Properties:
    FunctionName: !Ref rLambdaFunction
    ProvisionedConcurrencyConfig:
      ProvisionedConcurrentExecutions: !Ref pProvisionedConcurrency

rLambdaFunction:
  Type: 'AWS::Lambda::Function'
  Properties:
    #FunctionName incorporates the code object version id so that code updates cause function version updates
    #S3 object verion IDs can have a period in them which is invalid in a function name so any period is truncated
    FunctionName: !Sub
      - 'lambda-function-name-${CodeObjectVersionId}'
      - CodeObjectVersionId:
          Fn::Select: [0, !Split [".", !Ref pLambdaCodeS3ObjectVersion]]
    Code:
      S3Bucket: !Ref pS3Bucket
      S3Key: !Ref pLambdaCodeS3Key
      S3ObjectVersion: !Ref pLambdaCodeS3ObjectVersion
Reducer answered 6/4, 2022 at 13:57 Comment(0)
F
0

Another strategy to update AWS Lambda Version using CloudFormation only consists to do the following changes in your template:

  • update your Lambda Function
  • add a new Lambda Version (keep the previous version if needed)
  • refers this version in an Lambda alias
  • Update your CloudFormation template in AWS

Example : template-stack.yml

# Your updated Lambda function
MyLambdaFunction
  Type: AWS::Lambda::Function
  Properties:
    FunctionName: myFunctionName
    ...

# Current version
MyLambdaVersionV1:
  Type: AWS::Lambda::Version
  Properties:
    Description: V1
    FunctionName: !Ref MyLambdaFunction  #Previously deployed - stay inchanged

# New version
MyLambdaVersionV2:
  Type: AWS::Lambda::Version
  Properties:
    Description: V2
    FunctionName: !Ref MyLambdaFunction #Nextly deployed - Will be created

# Your Lambda Alias
MyLambdaAlias:
  Type: AWS::Lambda::Alias
  Properties:
    FunctionName: !Ref MyLambdaFunction
    FunctionVersion: !Ref MyLambdaVersionV2.Version #Update the reference to the next version here
    Name: myAliasName

It's quite a strange way to work, but it allows updating your lambda function while creating a new version. Note that this also changes the so-called "Latest" version.

When you no longer need a version, you can remove it from the template and redeploy your stack again.

Fatten answered 31/1, 2023 at 12:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.