S3イベント通知からSNS経由でLambdaを起動し、イベントが発生したバケット名とオブジェクトキーを取得する
こんにちは、CX事業本部の若槻です。
S3バケットへのファイルのPUTをトリガーにしてLambdaを起動する仕組みを実装するとき、その後のシステムの拡張性などを考慮すると、S3イベント通知とLambdaの間でSNSトピックを経由させた方が都合がいい場合があります。
今回は、S3バケットにファイルがPUTされた際に、S3イベント通知からSNS経由でLambdaを起動し、イベントが発生したバケット名とオブジェクトキーを取得する方法を確認してみました。
環境準備
S3バケット、SNSトピック、Lambdaから成る以下のようなAWS環境を準備していきます。
CloudFormationで環境を構築します。テンプレートは以下のようになります。
Resources: myTopic: Type: AWS::SNS::Topic Properties: TopicName: myTopic Subscription: - Endpoint: !GetAtt myFunc.Arn Protocol: lambda myTopicPolicy: Type: AWS::SNS::TopicPolicy Properties: Topics: - !Ref myTopic PolicyDocument: Id: !Ref myTopic Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: s3.amazonaws.com Action: SNS:Publish Resource: !Ref myTopic Condition: ArnLike: aws:SourceArn: !Sub arn:aws:s3:::mybucket-${AWS::AccountId} myBucket: Type: AWS::S3::Bucket Properties: BucketName: !Sub mybucket-${AWS::AccountId} NotificationConfiguration: TopicConfigurations: - Event: s3:ObjectCreated:Put Topic: !Ref myTopic myRole: Type: AWS::IAM::Role Properties: RoleName: myRole AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole lambdaPermission: Type: AWS::Lambda::Permission Properties: Action: lambda:InvokeFunction FunctionName: !Ref myFunc Principal: sns.amazonaws.com SourceArn: !Ref myTopic myFunc: Type: AWS::Lambda::Function Properties: FunctionName: myFunc Code: ZipFile: | import json def lambda_handler(event, context): # 1. SNSトピックのeventデータ print(json.dumps(event, ensure_ascii=False)) # 2. S3イベント通知のeventデータ s3_event = json.loads(event['Records'][0]['Sns']['Message']) print(json.dumps(s3_event, ensure_ascii=False)) s3_event_record = s3_event['Records'][0]['s3'] # 3. S3イベントが発生したバケット名 bucket = s3_event_record['bucket']['name'] print(bucket) # 4. S3イベントが発生したオブジェクトキー key = s3_event_record['object']['key'] print(key) Handler: index.lambda_handler MemorySize: 128 Timeout: 30 Role: !GetAtt myRole.Arn Runtime: python3.7
上記テンプレートを用いて、デプロイコマンドでスタックのデプロイを行います。
$ aws cloudformation create-stack \ --stack-name myStack \ --template-body file://template.yml \ --capabilities CAPABILITY_NAMED_IAM
バケット名とオブジェクトキーの取得
結論から言いますと、SNSトピックをサブスクライブするLambdaでは以下のようなコードを用いれば、イベントが発生したバケット名とオブジェクトキーを取得することができます。
import json def lambda_handler(event, context): # 1. SNSトピックのeventデータ print(json.dumps(event, ensure_ascii=False)) # 2. S3イベント通知のeventデータ s3_event = json.loads(event['Records'][0]['Sns']['Message']) print(json.dumps(s3_event, ensure_ascii=False)) s3_event_record = s3_event['Records'][0]['s3'] # 3. S3イベントが発生したバケット名 bucket = s3_event_record['bucket']['name'] print(bucket) # 4. S3イベントが発生したオブジェクトキー key = s3_event_record['object']['key'] print(key)
ここでは、ファイルdemo.png
をバケットmybucket-123456789012
にプレフィクスraw_data/
としてPUTした際の、以下情報の内容と取得方法についてそれぞれ確認していきます.
- 1.SNSトピックのeventデータ
- 2.S3イベント通知のeventデータ
- 3.S3イベントが発生したバケット名
- 4.S3イベントが発生したオブジェクトキー
1. SNSトピックのeventデータ
SNSトピックからLambdaへはeventデータとして以下のようなデータが渡されてきます。トピックメッセージはMessage
キーの値に含まれます。
>> # 1. SNSトピックのeventデータ >> print(json.dumps(event, ensure_ascii=False)) { "Records": [ { "EventSource": "aws:sns", "EventVersion": "1.0", "EventSubscriptionArn": "arn:aws:sns:ap-northeast-1:123456789012:myTopic:af809669-1234-5678-9012-9c75d42eb040", "Sns": { "Type": "Notification", "MessageId": "c5284e6e-1234-5678-9012-18bef4f0b3ab", "TopicArn": "arn:aws:sns:ap-northeast-1:123456789012:myTopic", "Subject": "Amazon S3 Notification", "Message": "{\"Records\":[{\"eventVersion\":\"2.1\",\"eventSource\":\"aws:s3\",\"awsRegion\":\"ap-northeast-1\",\"eventTime\":\"2020-02-26T14:20:43.189Z\",\"eventName\":\"ObjectCreated:Put\",\"userIdentity\":{\"principalId\":\"A3TB...80N\"},\"requestParameters\":{\"sourceIPAddress\":\"11.22.33.44\"},\"responseElements\":{\"x-amz-request-id\":\"F8DB3...48CB\",\"x-amz-id-2\":\"reVXEnUIYHAIM5vk5WJpeTC/1W1ekbi8...=\"},\"s3\":{\"s3SchemaVersion\":\"1.0\",\"configurationId\":\"76d49dfc-1234-5678-9012-065324629fec\",\"bucket\":{\"name\":\"mybucket-123456789012\",\"ownerIdentity\":{\"principalId\":\"A3TB...80N\"},\"arn\":\"arn:aws:s3:::mybucket-123456789012\"},\"object\":{\"key\":\"raw_data/demo.png\",\"size\":15719,\"eTag\":\"d83d0...2cd823\",\"sequencer\":\"005E...16B2\"}}}]}", "Timestamp": "2020-02-26T14:20:45.309Z", "SignatureVersion": "1", "Signature": "BBTc5+4XirDlkcHlC+MWRyptM4EfyFf4yFmJGQic7shKrS...==", "SigningCertUrl": "https://sns.ap-northeast-1.amazonaws.com/SimpleNotificationService-a86cb...128f7b6.pem", "UnsubscribeUrl": "https://sns.ap-northeast-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:ap-northeast-1:123456789012:myTopic:af809669-1234-5678-9012-9c75d42eb040", "MessageAttributes": {} } } ] }
図中で示しますと、SNSトピックからLambdaに受け渡される赤枠部分のデータです。
2. S3イベント通知のeventデータ
S3イベント通知のeventデータは前述のSNSトピックのeventデータのMessage
キーの値にString形式で含まれています。よってMessage
キーの値に対してjson.loads
によるデシリアライズ(JSON→辞書オブジェクト)を行うことにより、以下のようなS3イベント通知のeventデータを得ることができます。
>> # 2. S3イベント通知のeventデータ >> s3_event = json.loads(event['Records'][0]['Sns']['Message']) >> print(json.dumps(s3_event, ensure_ascii=False)) { "Records": [ { "eventVersion": "2.1", "eventSource": "aws:s3", "awsRegion": "ap-northeast-1", "eventTime": "2020-02-26T14:20:43.189Z", "eventName": "ObjectCreated:Put", "userIdentity": { "principalId": "ABCDEFGH123456" }, "requestParameters": { "sourceIPAddress": "11.22.33.44" }, "responseElements": { "x-amz-request-id": "F8DB3...48CB", "x-amz-id-2": "reVXEnUIYHAIM5vk5WJpeTC/1...=" }, "s3": { "s3SchemaVersion": "1.0", "configurationId": "76d49dfc-1234-5678-9012-065324629fec", "bucket": { "name": "mybucket-123456789012", "ownerIdentity": { "principalId": "A3T...Q80N" }, "arn": "arn:aws:s3:::mybucket-123456789012" }, "object": { "key": "raw_data/demo.png", "size": 15719, "eTag": "d83d0...cd823", "sequencer": "005E...16B2" } } } ] }
図中で示しますと、S3バケットからSNSトピックに受け渡される赤枠部分のデータです。
3. S3イベントが発生したバケット名
前述のS3イベント通知のeventデータから、イベントが発生したバケット名を以下のようにして取得できます。
>> # 3. S3イベントが発生したバケット名 >> bucket = s3_event_record['bucket']['name'] >> print(bucket) mybucket-123456789012
4. S3イベントが発生したオブジェクトキー
前述のS3イベント通知のeventデータから、イベントが発生したオブジェクトキーを以下のようにして取得できます。
>> # 4. S3イベントが発生したオブジェクトキー >> key = s3_event_record['object']['key'] >> print(key) raw_data/demo.png
これでS3イベントが発生したバケット名とオブジェクトキーを取得することができました。
おわりに
「S3イベント通知のeventデータ」が「SNSトピックのeventデータ」にラッピングされているため、JSONデシリアライズしながら上手く取り出してあげることがミソとなります。もちろん、バケット名とオブジェクトキーが取得できれば、同じLambdaの中でPUTされたファイル自体のデータを取得することもできます。
以上