使用 CloudFormation 在 S3 存储桶中创建 Lambda 通知
Posted
技术标签:
【中文标题】使用 CloudFormation 在 S3 存储桶中创建 Lambda 通知【英文标题】:Create a Lambda notification in an S3 bucket with CloudFormation 【发布时间】:2016-12-09 17:30:31 【问题描述】:我正在尝试为 CloudFormation 模板中的 Lambda 函数创建 S3 触发器。 S3 存储桶已存在,正在创建 Lambda 函数。
This 表示无法使用 CFT 修改预先存在的基础架构(在本例中为 S3),但 this 似乎表示存储桶必须是预先存在的。
似乎无法使用 CFT 类型“AWS::Lambda...”创建触发器,并且源服务需要创建触发器。就我而言,这是 s3 存储桶的 NotificationConfiguration-LambdaConfiguration。所有这些都正确吗?
当我尝试使用 CFT 将 NotificationConfiguration 添加到现有 S3 存储桶时,它说我不能。有没有办法做到这一点?
【问题讨论】:
我相当肯定,虽然存储桶必须存在,但在创建模板之前它不必存在。在同一模板中与通知配置和 lambda 函数一起创建存储桶是否适合您的用例?如果是这样,这种方法将比修改现有基础架构更容易为您提供帮助。无论哪种方式都有一个解决方案,但一个更漂亮 当您说“S3 存储桶已存在”时,您是否还暗示该存储桶是在 CloudFormation 之外创建的? 【参考方案1】:很遗憾,官方的AWS::CloudFormation
模板只允许你控制Amazon S3 NotificationConfiguration
作为父AWS::S3::Bucket
资源的NotificationConfiguration
property,这意味着你不能把这个配置附加到任何现有的bucket上,你必须将其应用到 CloudFormation 管理的存储桶以使其工作。
一种解决方法是使用putBucketNotificationConfiguration
javascript API 调用将PUT Bucket Notification
API 调用直接实现为Lambda-backed Custom Resource。但是,由于修改 S3 存储桶上的 NotificationConfiguration 仅限于存储桶的创建者,因此您还需要添加一个 AWS::S3::BucketPolicy
资源,以授予您的 Lambda 函数对 s3:PutBucketNotification
操作的访问权限。
这是一个完整的、自包含的 CloudFormation 模板,该模板演示了如何使用 2 个 Lambda 支持的自定义资源 (BucketConfiguration
to设置存储桶通知配置,S3Object
将对象上传到存储桶)和第三个 Lambda 函数(BucketWatcher
用于在对象上传到存储桶时触发等待条件)。
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 (must already exist)
Type: String
Resources:
BucketConfiguration:
Type: Custom::S3BucketConfiguration
DependsOn:
- BucketPermission
- NotificationBucketPolicy
Properties:
ServiceToken: !GetAtt S3BucketConfiguration.Arn
Bucket: !Ref BucketName
NotificationConfiguration:
LambdaFunctionConfigurations:
- Events: ['s3:ObjectCreated:*']
LambdaFunctionArn: !GetAtt BucketWatcher.Arn
S3BucketConfiguration:
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)
var respond = (e) => response.send(event, context, e ? response.FAILED : response.SUCCESS, e ? e : );
process.on('uncaughtException', e=>failed(e));
var params = event.ResourceProperties;
delete params.ServiceToken;
if (event.RequestType === 'Delete')
params.NotificationConfiguration = ;
s3.putBucketNotificationConfiguration(params).promise()
.then((data)=>respond())
.catch((e)=>respond());
else
s3.putBucketNotificationConfiguration(params).promise()
.then((data)=>respond())
.catch((e)=>respond(e));
;
Timeout: 30
Runtime: nodejs4.3
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
DependsOn: BucketConfiguration
Properties:
ServiceToken: !GetAtt S3ObjectFunction.Arn
Bucket: !Ref BucketName
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)
var respond = (e) => response.send(event, context, e ? response.FAILED : response.SUCCESS, e ? e : );
var params = event.ResourceProperties;
delete params.ServiceToken;
if (event.RequestType == 'Create' || event.RequestType == 'Update')
s3.putObject(params).promise()
.then((data)=>respond())
.catch((e)=>respond(e));
else if (event.RequestType == 'Delete')
delete params.Body;
s3.deleteObject(params).promise()
.then((data)=>respond())
.catch((e)=>respond(e));
else
respond(Error: 'Invalid request type');
;
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"
NotificationBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref BucketName
PolicyDocument:
Statement:
- Effect: "Allow"
Action:
- 's3:PutBucketNotification'
Resource: !Sub "arn:aws:s3:::$BucketName"
Principal:
AWS: !GetAtt LambdaExecutionRole.Arn
Outputs:
Result:
Value: !GetAtt Wait.Data
【讨论】:
使用 CloudFormation 在 AWS 中设置简单的东西需要多少代码真是太疯狂了 使用 CFN 通用资源提供程序(本质上是 boto3 代理)的类似解决方案:github.com/ab77/cfn-generic-custom-resource#s3【参考方案2】:我使用另一种解决方案,因为我个人不喜欢自定义资源。我创建了一个云跟踪来捕获相关存储桶的 S3 写入事件,然后创建一个带有 S3 事件模式的 cloudwatch/eventbridge 规则来触发我的 Lambda 函数。通过这种方式,我可以将我的 Lambda 函数挂接到现有的 S3 存储桶而无需接触它,并且可以通过 cloudformation 轻松完成。
【讨论】:
请注意,这种方式不适用于跨账户 senario,因为我发现我的云跟踪无法捕获另一个账户上的 s3 事件。这可能是可行的,但我找不到简单的设置方法。以上是关于使用 CloudFormation 在 S3 存储桶中创建 Lambda 通知的主要内容,如果未能解决你的问题,请参考以下文章
AWS Cloudformation 模板 - 在 S3 存储桶中设置区域
CloudFormation 模板设置 S3 存储桶默认加密 [重复]
如何使用 cloudformation 为 S3 存储桶设置半随机名称
AWS CloudFormation 更新 Lambda 代码以在 S3 存储桶中使用最新版本