【问题标题】:Enable Lambda function to an S3 bucket using cloudformation使用 cloudformation 为 S3 存储桶启用 Lambda 函数
【发布时间】:2016-07-20 05:32:27
【问题描述】:

我们正在使用 CloudFormation 模板创建一个 S3 存储桶。每当将文件添加到 S3 存储桶时,我想关联(向 S3 存储桶添加事件)一个 Lambda 函数。

如何通过 CloudFormation 模板实现。 CloudFormation 中需要用到哪些属性。

【问题讨论】:

  • 您希望在创建存储桶或在存储桶中创建对象(PutObject)时触发 lambda?

标签: amazon-web-services amazon-s3 amazon-cloudformation


【解决方案1】:

这是一个完整的、自包含的 CloudFormation 模板,演示了如何在将文件添加到 S3 存储桶时触发 Lambda 函数:

Description: Upload an object to an S3 bucket, triggering a Lambda event, returning the object key as a Stack Output.
Parameters:
  Key:
    Description: S3 Object key
    Type: String
    Default: test
  Body:
    Description: S3 Object body content
    Type: String
    Default: TEST CONTENT
  BucketName:
    Description: S3 Bucket name
    Type: String
Resources:
  Bucket:
    Type: AWS::S3::Bucket
    DependsOn: BucketPermission
    Properties:
      BucketName: !Ref BucketName
      NotificationConfiguration:
        LambdaConfigurations:
        - Event: 's3:ObjectCreated:*'
          Function: !GetAtt BucketWatcher.Arn
  BucketPermission:
    Type: AWS::Lambda::Permission
    Properties:
      Action: 'lambda:InvokeFunction'
      FunctionName: !Ref BucketWatcher
      Principal: s3.amazonaws.com
      SourceAccount: !Ref "AWS::AccountId"
      SourceArn: !Sub "arn:aws:s3:::${BucketName}"
  BucketWatcher:
    Type: AWS::Lambda::Function
    Properties:
      Description: Sends a Wait Condition signal to Handle when invoked
      Handler: index.handler
      Role: !GetAtt LambdaExecutionRole.Arn
      Code:
        ZipFile: !Sub |
          exports.handler = function(event, context) {
            console.log("Request received:\n", JSON.stringify(event));
            var responseBody = JSON.stringify({
              "Status" : "SUCCESS",
              "UniqueId" : "Key",
              "Data" : event.Records[0].s3.object.key,
              "Reason" : ""
            });
            var https = require("https");
            var url = require("url");
            var parsedUrl = url.parse('${Handle}');
            var options = {
                hostname: parsedUrl.hostname,
                port: 443,
                path: parsedUrl.path,
                method: "PUT",
                headers: {
                    "content-type": "",
                    "content-length": responseBody.length
                }
            };
            var request = https.request(options, function(response) {
                console.log("Status code: " + response.statusCode);
                console.log("Status message: " + response.statusMessage);
                context.done();
            });
            request.on("error", function(error) {
                console.log("send(..) failed executing https.request(..): " + error);
                context.done();
            });
            request.write(responseBody);
            request.end();
          };
      Timeout: 30
      Runtime: nodejs4.3
  Handle:
    Type: AWS::CloudFormation::WaitConditionHandle
  Wait:
    Type: AWS::CloudFormation::WaitCondition
    Properties:
      Handle: !Ref Handle
      Timeout: 300
  S3Object:
    Type: Custom::S3Object
    Properties:
      ServiceToken: !GetAtt S3ObjectFunction.Arn
      Bucket: !Ref Bucket
      Key: !Ref Key
      Body: !Ref Body
  S3ObjectFunction:
    Type: AWS::Lambda::Function
    Properties:
      Description: S3 Object Custom Resource
      Handler: index.handler
      Role: !GetAtt LambdaExecutionRole.Arn
      Code:
        ZipFile: !Sub |
          var response = require('cfn-response');
          var AWS = require('aws-sdk');
          var s3 = new AWS.S3();
          exports.handler = function(event, context) {
            console.log("Request received:\n", JSON.stringify(event));
            var responseData = {};
            if (event.RequestType == 'Create') {
              var params = {
                Bucket: event.ResourceProperties.Bucket,
                Key: event.ResourceProperties.Key,
                Body: event.ResourceProperties.Body
              };
              s3.putObject(params).promise().then(function(data) {
                response.send(event, context, response.SUCCESS, responseData);
              }).catch(function(err) {
                console.log(JSON.stringify(err));
                response.send(event, context, response.FAILED, responseData);
              });
            } else if (event.RequestType == 'Delete') {
              var deleteParams = {
                Bucket: event.ResourceProperties.Bucket,
                Key: event.ResourceProperties.Key
              };
              s3.deleteObject(deleteParams).promise().then(function(data) {
                response.send(event, context, response.SUCCESS, responseData);
              }).catch(function(err) {
                console.log(JSON.stringify(err));
                response.send(event, context, response.FAILED, responseData);
              });
            } else {
              response.send(event, context, response.SUCCESS, responseData);
            }
          };
      Timeout: 30
      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: S3Policy
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Action:
                - 's3:PutObject'
                - 'S3:DeleteObject'
              Resource: !Sub "arn:aws:s3:::${BucketName}/${Key}"
Outputs:
  Result:
    Value: !GetAtt Wait.Data

【讨论】:

  • 非常感谢您的工作示例。它节省了很多时间!但是你能告诉我为什么那个模板中有S3Object吗?我没有看到它在任何地方被引用?
  • @Cherry S3ObjectS3ObjectFunction 自动在 S3 存储桶中创建一个对象来触发 Lambda 函数,仅用于测试。通常这将由您手动上传的对象触发,因此在您自己的实现中不需要它。
  • @wjordan S3Object 是否有理由需要“ServiceToken”?每当我尝试删除更多文件时,都会调用“BucketWatcher”,但不会调用“S3ObjectFunction”。这是权限/角色问题吗?我正在手动将文本文件放入创建的“存储桶”中。
  • 我意识到,该文件实际上需要命名为“test”才能正常工作。这就是为什么我的“S3ObjectFunction”没有被调用
  • 2021 年 4 月:这个 sn-p 在我运行时不会创建任何函数。有谁知道如何让它工作?
【解决方案2】:

您的 CloudFormation 模板中需要一个 NotificationConfiguration 属性。不幸的是,它似乎要求存储桶已经存在。为了解决这个问题,您可以创建一个初始堆栈,然后使用 NotificationConfiguration 对其进行更新。例如:

    // template1.json
    {
      "AWSTemplateFormatVersion": "2010-09-09",
      "Parameters": {
        "mylambda": {
          "Type": "String"
        }
      },
      "Resources": {
        "bucketperm": {
          "Type": "AWS::Lambda::Permission",
          "Properties" : {
            "Action": "lambda:InvokeFunction",
            "FunctionName": {"Ref": "mylambda"},
            "Principal": "s3.amazonaws.com",
            "SourceAccount": {"Ref": "AWS::AccountId"},
            "SourceArn": { "Fn::Join": [":", [
                "arn", "aws", "s3", "" , "", {"Ref" : "mybucket"}]]
            }
          }
        },
        "mybucket": {
          "Type": "AWS::S3::Bucket"
        }
      }
    }

    // template2.json -- adds the NotificationConfiguration
    {
      "AWSTemplateFormatVersion": "2010-09-09",
      "Parameters": {
        "mylambda": {
          "Type": "String"
        }
      },
      "Resources": {
        "bucketperm": {
          "Type": "AWS::Lambda::Permission",
          "Properties" : {
            "Action": "lambda:InvokeFunction",
            "FunctionName": {"Ref": "mylambda"},
            "Principal": "s3.amazonaws.com",
            "SourceAccount": {"Ref": "AWS::AccountId"},
            "SourceArn": { "Fn::Join": [":", [
                "arn", "aws", "s3", "" , "", {"Ref" : "mybucket"}]]
            }
          }
        },
        "mybucket": {
          "Type": "AWS::S3::Bucket",
          "Properties": {
            "NotificationConfiguration": {
              "LambdaConfigurations": [
                {
                  "Event" : "s3:ObjectCreated:*",
                  "Function" : {"Ref": "mylambda"}
                }
              ]
            }
          }
        }
      }
    }

您可以使用 AWS CLI 工具来创建堆栈,如下所示:

    $ aws cloudformation create-stack --stack-name mystack --template-body file://template1.json --parameters ParameterKey=mylambda,ParameterValue=<lambda arn>
    # wait until stack is created
    $ aws cloudformation update-stack --stack-name mystack --template-body file://template2.json --parameters ParameterKey=mylambda,ParameterValue=<lambda arn>

【讨论】:

  • 我们是否需要在 CFT 的资源部分为 lambda 编写代码?正如你所指的 mylambda??
  • 问题是如何在创建存储桶时触发 lambda,而不是在将新文​​件添加到存储桶时。
  • 不必在同一个模板中定义 lambda。存储桶配置只需要 lambda ARN。如果您的 lambda 设置在其他地方,只需将 ARN 作为模板参数传入即可。
  • @ataylor 你能在你的代码中提到如何将 ARN 作为模板参数传递..
  • 我已经更新了我的答案,将 lambda ARN 作为参数。
【解决方案3】:

我在用于创建 S3 存储桶的 cloudformation 中添加了下面的存储桶 perm 以及通知配置。它起作用了!!

"bucketperm": {
            "Type": "AWS::Lambda::Permission",
            "Properties": {
                "Action": "lambda:invokeFunction",
                "FunctionName": "<arnvalue>",
                "Principal": "s3.amazonaws.com"
            }
}

【讨论】:

【解决方案4】:

是的,可以通过 Cloudformation 实现,您需要配置的是:

1) AWS::S3::Bucket 资源和,

2) NotificationConfiguration 配置(在本例中使用 LambdaConfigurations)用于上述 s3 资源。

您需要的相关文档:

https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket.html#cfn-s3-bucket-notification

https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-notificationconfig.html

【讨论】:

    【解决方案5】:

    在 AWS 文档中明确指出 AWS::S3::Bucket 用于创建资源,如果我们有一个已经存在的存储桶,我们不能修改它来添加 NotificationConfiguration。 因此,上述模板必须不存在 S3 存储桶才能正常工作。 让 CloudFormation 创建包括 S3 存储桶在内的所有资源。

    【讨论】:

    • 我用 NotificationConfiguration 和 Lambda Permission 更新了一个现有的存储桶,它工作正常。
    猜你喜欢
    • 2017-10-15
    • 2016-12-09
    • 1970-01-01
    • 1970-01-01
    • 2020-07-05
    • 2015-04-30
    • 1970-01-01
    • 2020-04-29
    • 1970-01-01
    相关资源
    最近更新 更多