Как создать новую версию функции Lambda с помощью CloudFormation?

Я пытаюсь создать новую версию лямбда-функции с помощью CloudFormation.

Я хочу иметь несколько версий одной и той же лямбда-функции, чтобы я мог (а) указывать псевдонимы на разные версии - например, DEV и PROD - и (б) иметь возможность откатиться к более ранней версии

Это определение моей лямбда-версии:

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

Версия создается при запуске "aws cloudformation create-stack", но последующие команды "aws cloudformation update-stack" ничего не делают. Не созданы новые версии Lambda.

Я пытаюсь получить новую версию функции Lambda, созданную после загрузки нового zip файла на S3 и запуска "update-stack". Могу ли я сделать это с CloudFormation? AWS :: Lambda :: Version действительно сломана (как упомянуто здесь https://github.com/hashicorp/terraform/issues/6067#issuecomment-211708071) или я просто не получаю что-то?

Обновление 1/11/17 Официальный ответ от поддержки Amazon: "... для публикации любой новой версии необходимо определить дополнение (sic) AWS :: Lambda :: Version resource..."

Команда AWS CloudFormation/Lambda, если вы читаете это - это недопустимо. Почини это.

Ответ 1

AWS :: Lambda :: Версия не полезна. Вы должны добавить новый ресурс для каждой версии Lambda. Если вы хотите публиковать новую версию для каждого обновления Cloudformation, вы должны взломать систему.

Я решил эту проблему, создав пользовательский ресурс с поддержкой Lambda, который запускается для каждого развертывания. Внутри этой лямбды я создаю новую версию для лямбда-функции, указанной в параметре.

Для источника лямбда вы можете проверить http://serverless-arch-eu-west-1.s3.amazonaws.com/serverless.zip

Вот пример облачной информации с использованием этой функции лямбда-развертывания (вам может потребоваться изменить ее):

{
  "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"
        }
      }
    }
  }
}

(Отказ от ответственности: этот код является частью моей книги, дополнительную информацию о Lambda & API Gateway вы можете проверить: https://www.amazon.com/Building-Serverless-Architectures-Cagatay-Gurturk/dp/1787129195)

Ответ 2

У меня есть похожий вариант использования (мне нужно использовать CloudFormation для управления лямбда-функцией, которая будет использоваться @edge в CloudFront, для которой всегда требуется определенная версия лямбда-функции, а не $LATEST), и мои поиски сначала привели меня к этому вопросу, но после еще нескольких копаний я был рад обнаружить, что теперь есть встроенная поддержка автоматического лямбда-управления версиями с новой функцией AutoPublishAlias в модели сервера приложений AWS (в основном дополнительный дополнительный набор высокоуровневых конструкций для шаблонов CloudFormation).

Объявлено здесь: https://github.com/awslabs/serverless-application-model/issues/41#issuecomment-347723981

Подробнее см.:

По сути, вы включаете AutoPublishAlias в свое определение AWS::Serverless::Function:

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

А затем в другом месте шаблона CloudFormation вы можете ссылаться на последнюю опубликованную версию как !Ref MyFunction.Version (синтаксис yaml).

Ответ 3

Ресурс AWS::Lambda::Version представляет только одну опубликованную лямбда-функцию version-, которая не будет автоматически публиковать новые версии при каждом обновлении вашего кода. Для этого у вас есть два варианта:

1. Пользовательский ресурс

Вы можете реализовать свой собственный пользовательский ресурс, который вызывает PublishVersion при каждом обновлении.

При таком подходе вам все равно нужно будет менять хотя бы один параметр каждый раз при обновлении стека, чтобы вызвать обновление пользовательского ресурса, которое вызовет действие PublishVersion. (Однако вам не придется обновлять шаблон.)

Вот полный рабочий пример:

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. Шаблонный препроцессор

Вы можете использовать шаблонный препроцессор, такой как внедренный Ruby (или просто вручную обновлять ваш шаблон при каждом развертывании), чтобы публиковать новую версию при каждом обновлении вашего кода, изменяя ресурс AWS::Lambda::Version Логический идентификатор всякий раз, когда ваш код обновляется.

Пример:

# 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

Чтобы создать/обновить стек при прохождении template.yml через препроцессор шаблона erb, выполните:

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

Ответ 4

Ответ обновлен на февраль 2018 года

Для обновления Lambda вы можете использовать AWS SAM (модель сервера без сервера) и его команды sam package и sam deploy. Они аналогичны командам aws cloudformation package и aws cloudformation deploy, но также позволяют автоматически обновлять версии Lambda.

SAM может упаковать ваш код (или взять созданный вами ZIP-пакет в противном случае), загрузить его на S3 и обновить с него версию $LATEST Lambda. (Если это все, что вам нужно, это также можно сделать с aws cloudformation, без SAM; примеры кода такие же, как ниже, но используйте только стандартные объявления CloudFormation). Затем с помощью SAM, если он настроен соответствующим образом, вы также можете автоматически опубликовать версию и обновить псевдоним, чтобы он указывал на нее. При желании он также может использовать AWS CodeDeploy для постепенного перемещения трафика из предыдущей версии в новую и отката в случае ошибок. Все это объясняется в безопасных лямбда-развертываниях.


Технически идея заключается в том, что каждый раз, когда вы обновляете стек, вам нужно, чтобы ваш AWS::Lambda::Function Code указывал на новый пакет в S3. Это гарантирует, что при обновлении стека версия Lambda $ LATEST будет обновлена из нового пакета. Затем вы также можете автоматизировать публикацию новой версии и переключить на нее псевдоним.

Для этого создайте шаблон SAM, который похож на (расширенный набор) шаблон CloudFormation. Он может включать в себя специфичные для SAM объявления, например, для AWS::Serverless::Function ниже. Направьте Code на каталог исходного кода (или предварительно упакованный ZIP) и установите свойство AutoPublishAlias.

...

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

Это упаковывает содержимое исходного каталога в виде ZIP (если Code уже не является ZIP), загружает его на S3 под новым автоматически сгенерированным ключом и генерирует окончательный шаблон CloudFormation в packaged.yaml, предоставляя вам правильный Code ссылка на него; как это:

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

Теперь вы можете использовать сгенерированный packaged.yaml с SAM, чтобы создать функцию Версия:

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

Это обновит лямбда-версию $LATEST и, если AutoPublishAlias был определен, опубликует ее как новую версию и обновит псевдоним, чтобы он указывал на вновь опубликованную версию.

Смотрите примеры в репозитории SAM GitHub для полного кода шаблона.

Ответ 5

Ищите похожую вещь, которая работает с функциями Lambda, развернутыми из S3.

Мой вариант использования был таким:

  • У вас есть шаблон облачной информации, который создает функцию Lambda из местоположения корзины S3
  • Вам нужно обновить эту функцию, чтобы вносить изменения в код локально и передавать изменения на S3
  • Теперь вы хотите перенести эти изменения в Lambda, чтобы попытаться обновить стек, и облачная информация говорит, что изменений нет, поэтому вам придется прибегнуть к ручному обновлению кода с помощью консоли AWS Lambda.

Не довольный этим, я искал альтернативу и наткнулся на этот вопрос. Ни один из ответов не сработал для меня, поэтому я взял некоторые идеи и адаптировал ответы здесь и сделал свою собственную версию, написанную на Python.

Этот код основан на ответе @wjordan, так что ему за идею и оригинальный ответ. Различия:

  • Это написано на Python
  • Он работает с лямбда-кодом, развернутым из корзины S3
  • Он обновляет код и публикует новую версию

Вам нужен параметр nonce. Вы изменяете значение этого параметра, когда код необходимо переиздать в Lambda. Это необходимо для того, чтобы облачная информация обновляла ваш пользовательский ресурс. Когда пользовательский ресурс обновляется, он запускает код Python, который в конечном итоге обновляет ваш лямбда-код.

Надеюсь, это кому-нибудь поможет.

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 

Ответ 6

К сожалению, это невозможно сделать с помощью CloudFormation. Вам нужно будет добавить новые разделы AWS::Lambda::Version в шаблон CloudFormation для каждой версии.

Ближайшим решением было бы создать шаблоны .erb и заставить его создавать шаблоны CloudFormation со всеми версиями.

Ответ 7

  1. Мы можем сделать пакет развертывания Lambda;
  2. Проход Лямбда пакет с версией в качестве одного из параметров Cloud Formation, например, "LambdaPakcageNameWithVersion";
  3. использование "LambdaPakcageNameWithVersion" в качестве ключа лямбда-кода s3;
  4. Новый Пакет Lamdba будет развернут при запуске команды aws-cli для обновить стек облачной информации или запустить конвейер CI/CD.

  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

Ответ 8

Это немного взломано и зависит от использования gitlab-ci (или чего-то подобного), но я считаю, что передача хеша коммита в шаблон облачной информации (через параметры шаблона) очень полезна.

(Это немного похоже на ответ @Jerry, но использует хеш коммита.)

В этом случае вы можете сделать что-то вроде:

В вашем шаблоне есть параметр для хэша коммита, например:

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

Затем вы можете сослаться на это в лямбда-ресурсе, например:

  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
      ...

Затем ваш конвейер ci должен выглядеть примерно так (при условии, что вы вызываете свой шаблон облачной информации 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

Вы также можете использовать эту технику для запуска cfn-init для метаданных EC2.

Ответ 9

Я решил эту проблему с помощью CI/CD, ant-скрипта и git-ревизии, чтобы создать уникальное zip-имя в корзине S3 для каждого коммита.

Сценарий ant запускается CI/CD для замены git-версии на имя zam файла лямбда-кода и шаблона облачной информации. Эти ссылки делаются перед копированием кода и сценариев облачной информации в S3. Это похоже на то, как работает SAM, но работает с простыми старыми стеками облачной информации и, что важно, с наборами стеков, которые, возможно, потребуется развернуть в нескольких учетных записях. На момент написания статьи SAM не был совместим с наборами стеков CF

Есть два файла: файл ant и файл свойств, который сообщает файлу ant, в какие каталоги исходных файлов лямбда-зипов нужно заархивировать.

Сначала файл ant build.xml:

<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="@[email protected]" 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>

Файл lambda.zip.build.properties выглядит следующим образом:

# 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 @[email protected] 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...

А затем шаблон CloudFormation:

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

Результатом является zip файл с уникальным именем lambda-code-0f993c3.zip и шаблон Cloudformation с S3Key, ссылающимся на уникальное имя.

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

Разверните шаблон из местоположения S3, и это заставит существующий лямбда-код обновляться каждый раз.