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されたファイル自体のデータを取得することもできます。
以上