この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
AWS事業本部 梶原@福岡オフィスです。
AWSのブログで公開されている 'CloudFormation を使用して、既存の S3 バケットで Lambda 用の Amazon S3 通知設定を作成する方法を教えてください。 の内容の拡張になります。
参照元のブログの重要事項であげられているとおり
重要: 次の手順は、通知設定が作成されていない S3 バケットでの S3 通知設定にのみ適用されます。S3 バケットに通知設定が既に存在するか、手動で作成された通知設定がある場合、次の手順を実行すると、それらの設定は上書きされます。
既存バケットに通知設定が何もない場合にはよいのですが、すでに設定されている通知設定は、上書き消去。上書き消去!!!
されます
カスタムリソース処理自体が不安なようでしたら、CloudFormationのインポートを使用した方法もあります。
AWS CloudFormation リソースのインポートを使用して、既存の S3 バケットで AWS Lambda の Amazon S3 通知設定を作成するにはどうすればよいですか?
こちらの方法でもCloudFormationのインポートを用いて設定することは可能ですが、インポート時に設定を揃える必要があり、こちらも既存の設定は消えることになるのご注意ください
ということで、カスタムリソース部分を変更したので共有します。 Lambda関数部分(オブジェクト作成時に起動するLambdaはご自身の関数に置き換えてください
こちらのCloudFormationテンプレートを使用して(Lambda部分は変更しています)、Redshiftのログに自動でタグをつける方法はこちらのブログでご案内しています。
説明
実際のテンプレートは最後に記載しています。
パラメータ部
Parameters:
NotificationBucket:
Type: String
Description: S3 bucket that's used for the Lambda event notification
Prefix:
Type: String
Description: Prefix of the object key name for filtering rules
Suffix:
Type: String
Description: Suffix of the object key name for filtering rules
NotificationBucket: オブジェクト作成時にLamba関数を呼び出す既存のバケットになります
Prefix: Lamba関数を呼び出す際にフィルタリングを行うオブジェクトの接頭辞(Path)をパラメータで渡します
Suffix: Lamba関数を呼び出す際にフィルタリングを行うオブジェクトの接尾辞(拡張子など)をパラメータで渡します
Lambda関数(オブジェクト作成時に呼び出される)
S3NotificationLambdaFunction:
Type: 'AWS::Lambda::Function'
省略
特になにもしていないので、ご自身の関数に置き換えてください
Lambda用のロール
LambdaIAMRole:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- 'sts:AssumeRole'
Path: /
Policies:
- PolicyName: root
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- 's3:GetBucketNotification'
- 's3:PutBucketNotification'
Resource: !Sub 'arn:aws:s3:::${NotificationBucket}'
- Effect: Allow
Action:
- 'logs:CreateLogGroup'
- 'logs:CreateLogStream'
- 'logs:PutLogEvents'
Resource: 'arn:aws:logs:*:*:*'
オブジェクト作成時に呼び出されるLambda, カスタムリソースLambdaと共通で使っています。 実際に使用する際は適切なロールを別々に割り当ててください
カスタムリソース用にs3:GetBucketNotification, s3:PutBucketNotification の許可を付与しています
AWS Lambdaアクセス許可
LambdaInvokePermission:
Type: 'AWS::Lambda::Permission'
Properties:
FunctionName: !GetAtt S3NotificationLambdaFunction.Arn
Action: 'lambda:InvokeFunction'
Principal: s3.amazonaws.com
SourceAccount: !Ref 'AWS::AccountId'
SourceArn: !Sub 'arn:aws:s3:::${NotificationBucket}'
S3のイベントからLambdaを起動する際にはLambda関数に対してS3からのアクセス許可(起動許可)が必要となります。 イベントが発火しない(Labmda)場合は、この設定が抜けている事が多いです
カスタムリソース
LambdaTrigger:
Type: 'Custom::LambdaTrigger'
DependsOn: LambdaInvokePermission
Properties:
ServiceToken: !GetAtt CustomResourceLambdaFunction.Arn
Id: !Sub
- S3LambdaNotif-${UniqueId}
- UniqueId: !Select [0, !Split ['-', !Select [2, !Split [/, !Ref 'AWS::StackId']]]]
Bucket: !Ref NotificationBucket
Prefix: !Ref Prefix
Suffix: !Ref Suffix
LambdaArn: !GetAtt S3NotificationLambdaFunction.Arn
カスタムリソースLambdaをパラメータ付きで呼び出します。 Idは、CloudFormationスタックのIDを使用してユニークな文字列を作成しています。
S3のイベント通知設定に追加するLambda関数
CustomResourceLambdaFunction:
Type: 'AWS::Lambda::Function'
Properties:
Handler: index.lambda_handler
Role: !GetAtt LambdaIAMRole.Arn
Code:
コード部省略
Pythonで以下の処理を実装しています。
リソース作成、更新時
- 既存のS3のLambda通知設定を取得する
- Lambda通知設定を追加します(ID, Prefix, Suffix)を条件に追加、更新
- S3の通知設定追加処理を呼び出します。
リソース削除時
- 既存のS3のLambda通知設定を取得する
- Lambda通知設定を削除(IDで削除)
- S3の通知設定追加処理を呼び出します。
S3の通知設定追加処理
- 既存の通知(キュー、Topic)があれば、呼び出し条件に追加します
- S3の通知設定を更新します
スタックの作成
パラメータを指定して、通常通りスタックを作成します。
一応、設定が上書きされても問題ないS3を指定して検証してみてください。
既存のイベント設定がある場合には追加、スタックの削除で作成したイベントのみが削除されるかと思います。
Prefix, Suffixが既存のイベントと重複する場合は作成が失敗しますので、ご注意ください
Template
AWSTemplateFormatVersion: 2010-09-09
Description: >-
Sample template to illustrate use of existing S3 bucket as an event source for a Lambda function
Parameters:
NotificationBucket:
Type: String
Description: S3 bucket that's used for the Lambda event notification
Prefix:
Type: String
Description: Prefix of the object key name for filtering rules
Suffix:
Type: String
Description: Suffix of the object key name for filtering rules
Resources:
S3NotificationLambdaFunction:
Type: 'AWS::Lambda::Function'
Properties:
Code:
ZipFile: |
import json
def lambda_handler(event,context):
return 'Welcome... This is a test Lambda Function'
Handler: index.lambda_handler
Role: !GetAtt LambdaIAMRole.Arn
Runtime: python3.8
Timeout: 5
LambdaIAMRole:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- 'sts:AssumeRole'
Path: /
Policies:
- PolicyName: root
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- 's3:GetBucketNotification'
- 's3:PutBucketNotification'
Resource: !Sub 'arn:aws:s3:::${NotificationBucket}'
- Effect: Allow
Action:
- 'logs:CreateLogGroup'
- 'logs:CreateLogStream'
- 'logs:PutLogEvents'
Resource: 'arn:aws:logs:*:*:*'
LambdaInvokePermission:
Type: 'AWS::Lambda::Permission'
Properties:
FunctionName: !GetAtt S3NotificationLambdaFunction.Arn
Action: 'lambda:InvokeFunction'
Principal: s3.amazonaws.com
SourceAccount: !Ref 'AWS::AccountId'
SourceArn: !Sub 'arn:aws:s3:::${NotificationBucket}'
LambdaTrigger:
Type: 'Custom::LambdaTrigger'
DependsOn: LambdaInvokePermission
Properties:
ServiceToken: !GetAtt CustomResourceLambdaFunction.Arn
Id: !Sub
- S3LambdaNotif-${UniqueId}
- UniqueId: !Select [0, !Split ['-', !Select [2, !Split [/, !Ref 'AWS::StackId']]]]
Bucket: !Ref NotificationBucket
Prefix: !Ref Prefix
Suffix: !Ref Suffix
LambdaArn: !GetAtt S3NotificationLambdaFunction.Arn
CustomResourceLambdaFunction:
Type: 'AWS::Lambda::Function'
Properties:
Handler: index.lambda_handler
Role: !GetAtt LambdaIAMRole.Arn
Code:
ZipFile: |
import json
import boto3
import cfnresponse
SUCCESS = "SUCCESS"
FAILED = "FAILED"
print('Loading function')
s3 = boto3.resource('s3')
def lambda_handler(event, context):
print("Received event: " + json.dumps(event, indent=2))
responseData={}
try:
if event['RequestType'] == 'Delete':
print("Request Type:",event['RequestType'])
Id=event['ResourceProperties']['Id']
Bucket=event['ResourceProperties']['Bucket']
delete_notification(Id, Bucket)
print("Sending response to custom resource after Delete")
elif event['RequestType'] == 'Create' or event['RequestType'] == 'Update':
print("Request Type:",event['RequestType'])
Id=event['ResourceProperties']['Id']
Prefix=event['ResourceProperties']['Prefix']
Suffix=event['ResourceProperties']['Suffix']
LambdaArn=event['ResourceProperties']['LambdaArn']
Bucket=event['ResourceProperties']['Bucket']
add_notification(Id, Prefix, Suffix, LambdaArn, Bucket)
responseData={'Bucket':Bucket}
print("Sending response to custom resource")
responseStatus = 'SUCCESS'
except Exception as e:
print('Failed to process:', e)
responseStatus = 'FAILED'
responseData = {'Failure': 'Something bad happened.'}
cfnresponse.send(event, context, responseStatus, responseData)
def add_notification(Id, Prefix, Suffix, LambdaArn, Bucket):
bucket_notification = s3.BucketNotification(Bucket)
print(bucket_notification.lambda_function_configurations)
lambda_function_configurations = bucket_notification.lambda_function_configurations
if lambda_function_configurations is None:
lambda_function_configurations = []
else:
lambda_function_configurations = [e for e in lambda_function_configurations if e['Id'] != Id]
lambda_config = {}
lambda_config['Id'] = Id
lambda_config['LambdaFunctionArn'] = LambdaArn
lambda_config['Events'] = ['s3:ObjectCreated:*']
lambda_config['Filter'] = {'Key': {'FilterRules': [
{'Name': 'Prefix', 'Value': Prefix},
{'Name': 'Suffix', 'Value': Suffix}
]}
}
lambda_function_configurations.append(lambda_config)
print(lambda_function_configurations)
put_bucket_notification(bucket_notification, lambda_function_configurations)
print("Put request completed....")
def delete_notification(Id, Bucket):
bucket_notification = s3.BucketNotification(Bucket)
print(bucket_notification.lambda_function_configurations)
lambda_function_configurations = bucket_notification.lambda_function_configurations
if lambda_function_configurations is not None:
lambda_function_configurations = [e for e in lambda_function_configurations if e['Id'] != Id]
print(lambda_function_configurations)
put_bucket_notification(bucket_notification, lambda_function_configurations)
print("Delete request completed....")
def put_bucket_notification(BucketNotification, LambdaFunctionConfigurations):
notification_configuration = {}
if LambdaFunctionConfigurations is not None:
notification_configuration['LambdaFunctionConfigurations'] = LambdaFunctionConfigurations
if BucketNotification.queue_configurations is not None:
notification_configuration['QueueConfigurations'] = BucketNotification.queue_configurations
if BucketNotification.topic_configurations is not None:
notification_configuration['TopicConfigurations'] = BucketNotification.topic_configurations
print(notification_configuration)
response = BucketNotification.put(
NotificationConfiguration= notification_configuration
)
Runtime: python3.8
Timeout: 50
参考
AWS CloudFormation リソースのインポートを使用して、既存の S3 バケットで AWS Lambda の Amazon S3 通知設定を作成するにはどうすればよいですか?
https://aws.amazon.com/jp/premiumsupport/knowledge-center/cloudformation-s3-notification-config/
Amazon S3 コンソールを使用したイベント通知の有効化と設定
https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/enable-event-notifications.html
CloudFormation を使用して、既存の S3 バケットで Lambda 用の Amazon S3 通知設定を作成する方法を教えてください。
https://aws.amazon.com/jp/premiumsupport/knowledge-center/cloudformation-s3-notification-lambda/
S3にcreateObjectをトリガーにLambdaを起動するCloudformationテンプレート
https://dev.classmethod.jp/articles/cloudformation-s3-put-event/