S3にオブジェクトを作成したイベントをトリガーにしてSNS経由でLambda関数を呼び出す

S3にオブジェクトを作成した際にPUTやPOSTのイベントが発生する。

それをトリガーにしてSNS経由でLambda関数を呼び出し、作成されたS3オブジェクトを取得して何らかの処理をするための各種設定とLambda関数の作成を行った。

作業した環境

Macからaws-cliで作業する。バージョンは

$ aws --version
aws-cli/1.11.13 Python/2.7.10 Darwin/18.6.0 botocore/1.4.70

以下、設定の作業内容。

必要なリソースの作成

S3バケットを作る

$ aws s3 mb s3://nabewata07-event-test00 --region ap-northeast-1
make_bucket: nabewata07-event-test00

SNS Topicを作成する

$ aws sns create-topic --name event-test-topic01 --region ap-northeast-1
{
    "TopicArn": "arn:aws:sns:ap-northeast-1:xxxxxxxxxxxxx:event-test-topic01"
}

SNSトピックをトリガーにしたLambda関数を作成する

イベントを発行したオブジェクトのContentTypeを表示する関数

console.log('Loading function');

const aws = require('aws-sdk');

const s3 = new aws.S3({ apiVersion: '2006-03-01' });

exports.handler = async (event, context) => {
    // Get the object from the event and show its content type
    const jsonStr = event.Records[0].Sns.Message;
    const obj = JSON.parse(jsonStr)
    const target = obj.Records[0].s3;
    const bucket = target.bucket.name;
    const key = decodeURIComponent(target.object.key.replace(/\+/g, ' '));
    const params = {
        Bucket: bucket,
        Key: key,
    };
    try {
        const { ContentType, Body } = await s3.getObject(params).promise();
        console.log('CONTENT TYPE:', ContentType);
        // do something with content body
        //console.log('Body:', Body.toString());
        return ContentType;
    } catch (err) {
        console.log(err);
        throw new Error(message);
    }
};

作成したコードをzip圧縮する

デプロイのため。

$ zip index.js.zip index.js
  adding: index.js (deflated 47%)

Lambda関数を作成する

$ aws lambda create-function --function-name s3GetObjectSample --runtime nodejs10.x --role arn:aws:iam::xxxxxxxxxxxx:role/service-role/LambdaS3FuncRole --handler index.handler --zip-file fileb://index.js.zip --region ap-northeast-1
{
    "CodeSha256": "78l2lot/Qie5DQX2R7j1PMXLbiNivMToAaVbny7OQpQ=",
    "FunctionName": "s3GetObjectSample",
    "CodeSize": 635,
    "MemorySize": 128,
    "FunctionArn": "arn:aws:lambda:ap-northeast-1:xxxxxxxxxx:function:s3GetObjectSample",
    "Version": "$LATEST",
    "Role": "arn:aws:iam::xxxxxxxxx:role/service-role/LambdaS3FuncRole",
    "Timeout": 3,
    "LastModified": "2019-07-28T23:34:32.759+0000",
    "Handler": "index.handler",
    "Runtime": "nodejs10.x",
    "Description": ""
}

Lambda関数のポリシーに必要な権限

LambdaS3FuncRoleには以下のポリシー設定が必要。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject"
            ],
            "Resource": "arn:aws:s3:::nabewata07-event-test00/upload/sampledata/*"
        },
        {
            "Effect": "Allow",
            "Action": "logs:CreateLogGroup",
            "Resource": "arn:aws:logs:ap-northeast-1:xxxxxxxxxxxxxx:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": [
                "arn:aws:logs:ap-northeast-1:xxxxxxxxxxxxxx:log-group:/aws/lambda/s3GetObjectSample:*"
            ]
        }
    ]
}

SNS TopicからLambda関数を呼び出す許可を設定する

Lambda関数のFunction policyで設定する。

$ aws lambda add-permission --function-name s3GetObjectSample --statement-id invoke-from-sns-topic00 --action lambda:InvokeFunction --principal sns.amazonaws.com --source-arn arn:aws:sns:ap-northeast-1:xxxxxxxxxxxx:event-test-topic01
{
    "Statement": "{\"Sid\":\"invoke-from-sns-topic00\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"sns.amazonaws.com\"},\"Action\":\"lambda:InvokeFunction\",\"Resource\":\"arn:aws:lambda:ap-northeast-1:325528992442:function:s3GetObjectSample\",\"Condition\":{\"ArnLike\":{\"AWS:SourceArn\":\"arn:aws:sns:ap-northeast-1:325528992442:event-test-topic01\"}}}"
}

LambdaがSNSトピックをサブスクライブする設定を行う

--topic-arnは先に作成したSNS TopicのARNを指定。

--notification-endpointには先に作成したLambda関数のARNを指定。

$ aws sns subscribe --protocol lambda \
--topic-arn arn:aws:sns:ap-northeast-1:325528992442:event-test-topic01 \
--notification-endpoint arn:aws:lambda:ap-northeast-1:325528992442:function:s3GetObjectSample \
--region ap-northeast-1

{
    "SubscriptionArn": "arn:aws:sns:ap-northeast-1:325528992442:event-test-topic01:af6d12be-b9ed-40f3-9041-49c59fa3360f"
}

SNS TopicがS3から呼び出される許可をする

下記のStatementをSNSのアクセスポリシーに追加する。

{
    "Sid": "Stmt1561879283049",
    "Effect": "Allow",
    "Principal": "*",
    "Action": "sns:Publish",
    "Resource": "arn:aws:sns:ap-northeast-1:xxxxxxxxxxxx:event-test-topic01",
    "Condition": {
        "ArnEquals": {
            "aws:SourceArn": "arn:aws:s3:::nabewata07-event-test00"
        }
    }
}

これはCLIから実行する方法がわからなかったのでマネジメントコンソールのGUIから行った。

S3バケットにイベントの設定を行う

設定用のJSONファイルを作る

対象のバケットupload/sampledataというプレフィックスかつ.csvというサフィックスが付いたオブジェクトがPUTまたはPOSTによって作成されたときに先ほど作成したSNSトピックに通知する設定にした。

{
    "TopicConfigurations": [
        {
            "Filter": {
                "Key": {
                    "FilterRules": [
                        {
                            "Name": "Prefix",
                            "Value": "upload/sampledata"
                        },
                        {
                            "Name": "Suffix",
                            "Value": ".csv"
                        }
                    ]
                }
            },
            "Id": "event-test-topic01",
            "TopicArn": "arn:aws:sns:ap-northeast-1:xxxxxxxxxxxxx:event-test-topic01",
            "Events": [
                "s3:ObjectCreated:Put",
                "s3:ObjectCreated:Post"
            ]
        }
    ]
}

これをs3_put_notification.jsonというファイル名で保存した。

S3バケットにイベントの設定を反映する

$ aws s3api put-bucket-notification-configuration --bucket nabewata07-event-test00 --notification-configuration file://s3_put_notification.json

Lambda関数を呼び出してみる

S3にオブジェクトをアップロードする

$ touch test.csv
$ aws s3 cp ./test.csv s3://nabewata07-event-test00/upload/sampledata/
upload: ./test.csv to s3://nabewata07-event-test00/upload/sampledata/test.csv

CloudWatchLogsでログが確認できればOK

Lambdaが/aws/lambda/s3GetObjectSampleというロググループを作成するため、そこで確認した。

INFO CONTENT TYPE: text/csv

所感

それぞれのリソースを作ってからそれらの連携の設定と、連携を許可する権限の設定があり少しややこしい。

連携の設定はしたけど許可の設定を忘れていて実行できないなどが発生しないためにもここに書いてある各設定を忘れないようにしたい。