ハンズオン 1 編ハンズオン 2 編ハンズオン 3 編の続き。

2 つめの S3 バケットまではハンズオン 3 編を使い回しで。ただし、前回の分は削除してしまったので、改めて作成。

Lambda Function 作成

2 つめのバケットに PUT されたことをきっかけに動く Lambda Function を作成 (後述するけど、これでは動かなかった)。Python で書いたことがないので気持ち悪いところとかあるのかもしれないけれど、とりあえずは動けばいいや的な。

import json
import urllib.parse
import boto3

s3 = boto3.client('s3')

def lambda_handler(event, context):
    bucket = event['Records'][0]['s3']['bucket']['name']
    key = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'], encoding='utf-8')

    s3_obj = s3.get_object(Bucket=bucket, Key=key)
    s3_json = json.loads(s3_obj['Body'].read())

    translate = boto3.client('translate')
    input_text = s3_json['results']['transcripts']

    response = translate.translate_text(
        Text = input_text,
        SourceLanguageCode = 'en',
        TargetLanguageCode = 'ja'
    )

    output_text = response['TranslatedText']

    return {
        'statusCode': 200,
        'body': json.dumps({
            'output_text': output_text
        })
    }
$ zip translate-function.zip translate-function.py
  adding: translate-function.py (deflated 51%)

$ aws lambda create-function \
    --function-name translate-function \
    --runtime python3.8 \
    --role "arn:aws:iam::760182048326:role/translate-function-role" \
    --handler translate-function.lambda_handler \
    --zip-file fileb://translate-function.zip
{
    "FunctionName": "translate-function",
    "FunctionArn": "arn:aws:lambda:ap-northeast-1:760182048326:function:translate-function",
    "Runtime": "python3.8",
    "Role": "arn:aws:iam::760182048326:role/translate-function-role",
    "Handler": "translate-function.lambda_handler",
    "CodeSize": 573,
    "Description": "",
    "Timeout": 3,
    "MemorySize": 128,
    "LastModified": "2020-05-10T10:06:48.639+0000",
    "CodeSha256": "cMOe2mMvBRbkGApMIgK/7vYPdRj60c40C0i1O4MITvI=",
    "Version": "$LATEST",
    "TracingConfig": {
        "Mode": "PassThrough"
    },
    "RevisionId": "b0813324-c726-481f-b2d3-5a0137e5443b",
    "State": "Active",
    "LastUpdateStatus": "Successful"
}

2 つ目の S3 バケットへのイベント作成

Lambda Function への権限設定

$ aws lambda add-permission \
    --function-name translate-function \
    --statement-id s3-put-event \
    --action lambda:InvokeFunction \
    --principal s3.amazonaws.com \
    --source-arn arn:aws:s3:::20200510-transcribe-output-mfjt
{
    "Statement": "{\"Sid\":\"s3-put-event\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"s3.amazonaws.com\"},\"Action\":\"lambda:InvokeFunction\",\"Resource\":\"arn:aws:lambda:ap-northeast-1:760182048326:function:translate-function\",\"Condition\":{\"ArnLike\":{\"AWS:SourceArn\":\"arn:aws:s3:::20200510-transcribe-output-mfjt\"}}}"
}

S3 へのイベント追加

$ aws s3api put-bucket-notification-configuration \
    --bucket 20200510-transcribe-output-mfjt \
    --notification-configuration '{"LambdaFunctionConfigurations": [{"LambdaFunctionArn": "arn:aws:lambda:ap-northeast-1:760182048326:function:translate-function", "Events": ["s3:ObjectCreated:Put"]}]}'

$ aws s3api get-bucket-notification-configuration --bucket 20200510-transcribe-input-mfjt
{
    "LambdaFunctionConfigurations": [
        {
            "Id": "NWRjZTc3NTgtZWE1Ny00NGFlLTliNTctN2I0ZTEwYWI3MjE4",
            "LambdaFunctionArn": "arn:aws:lambda:ap-northeast-1:760182048326:function:transcribe-function",
            "Events": [
                "s3:ObjectCreated:Put"
            ]
        }
    ]
}

動作確認

どこで確認すればいいのか分からなかったけれど、CloudWatch Logs で確認できるようだったので今回はそこで。このあたりから CLI 縛りがきつくなってきたので、普通にマネジメントコンソール経由で操作していた。

[ERROR] TypeError: list indices must be integers or slices, not str
Traceback (most recent call last):
  File "/var/task/translate-function.py", line 15, in lambda_handler
    input_text = s3_json['results']['transcripts']

JSON の配列の参照の仕方が間違っていたので Lambda Function を修正。ついでに翻訳結果のレスポンスも CloudWatch Logs で確認できるように修正{{fn ‘というか、ここまでの構成では確認する術がなかった’}}。

import json
import urllib.parse
import boto3

s3 = boto3.client('s3')

def lambda_handler(event, context):
    bucket = event['Records'][0]['s3']['bucket']['name']
    key = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'], encoding='utf-8')

    s3_obj = s3.get_object(Bucket=bucket, Key=key)
    s3_json = json.loads(s3_obj['Body'].read())

    translate = boto3.client('translate')
    input_text = s3_json['results']['transcripts'][0]['transcript']

    response = translate.translate_text(
        Text = input_text,
        SourceLanguageCode = 'en',
        TargetLanguageCode = 'ja'
    )

    output_text = response['TranslatedText']

    print(output_text)

    return {
        'statusCode': 200,
        'body': json.dumps({
            'output_text': output_text
        })
    }

再度 MP3 ファイルを PUT したら、CloudWatch Logs にこんにちは。外国語を話しますか?1つの言語では決して十分ではありません。と出力されたので上手く動いたことが確認できた。

Lambda Function 単体での動作確認は、イベントテンプレートの s3-put を流用して以下のようなテストイベントを作成すれば実行できた。

{
  "Records": [
    {
      "eventVersion": "2.0",
      "eventSource": "aws:s3",
      "awsRegion": "ap-northeast-1",
      "eventTime": "1970-01-01T00:00:00.000Z",
      "eventName": "ObjectCreated:Put",
      "userIdentity": {
        "principalId": "EXAMPLE"
      },
      "requestParameters": {
        "sourceIPAddress": "127.0.0.1"
      },
      "responseElements": {
        "x-amz-request-id": "EXAMPLE123456789",
        "x-amz-id-2": "EXAMPLE123/5678abcdefghijklambdaisawesome/mnopqrstuvwxyzABCDEFGH"
      },
      "s3": {
        "s3SchemaVersion": "1.0",
        "configurationId": "testConfigRule",
        "bucket": {
          "name": "20200510-transcribe-output-mfjt",
          "ownerIdentity": {
            "principalId": "EXAMPLE"
          },
          "arn": "arn:aws:s3:::20200510-transcribe-output-mfjt"
        },
        "object": {
          "key": "20200510100920_Transcription.json"
        }
      }
    }
  ]
}

サーバレスでやっていると、クラウド活用している感がより高まって楽しくなってくる。