[JAWS-UG CLI] Lambda:#16 sns-message (Python版)



前提条件
========


Lambdaへの権限
--------------

Lambdaに対してフル権限があること。


AWS CLI
-------

以下のバージョンで動作確認済

* AWS CLI 1.10.38

```bash:コマンド:
aws --version
```

```text:結果(例):
aws-cli/1.10.38 Python/2.7.11 Darwin/15.5.0 botocore/1.4.28
```


IAM Role
--------

'lambdaBasicExecution'ロールが存在すること。

```bash:変数の設定:
IAM_ROLE_NAME='lambdaBasicExecution'
```

```bash:コマンド:
aws iam get-role \
   --role-name ${IAM_ROLE_NAME}
```

```json:結果(例):
      {
          "Role": {
            "AssumeRolePolicyDocument": {
                "Version": "2012-10-17",
                "Statement": [
                    {
                        "Action": "sts:AssumeRole",
                        "Principal": {
                            "Service": "lambda.amazonaws.com"
                        },
                        "Effect": "Allow",
                        "Sid": ""
                    }
                ]
            },
            "RoleId": "AROAXXXXXXXXXXXXXXXXX",
            "CreateDate": "2015-10-26T01:55:54Z",
            "RoleName": "lambdaBasicExecution",
            "Path": "/",
            "Arn": "arn:aws:iam::XXXXXXXXXXXX:role/lambdaBasicExecution"
          }
      }
```

存在しない場合は、
http://qiita.com/tcsh/items/455fcea33fc1171a5502
の手順に従って作成してください。


0. 準備
=======


0.1. 変数の確認
---------------

プロファイルが想定のものになっていることを確認します。

```bash:変数の確認:
aws configure list
```

```text:結果(例):

            Name                    Value             Type    Location
            ----                    -----             ----    --------
         profile       lambdaFull-prjz-mbp13        env    AWS_DEFAULT_PROFILE
      access_key     ****************XXXX shared-credentials-file
      secret_key     ****************XXXX shared-credentials-file
          region        ap-northeast-1        env    AWS_DEFAULT_REGION
```


0.2. IAM RoleのARN取得
----------------------

```bash:変数の設定:
IAM_ROLE_NAME='lambdaBasicExecution'
```

```bash:コマンド:
IAM_ROLE_ARN=$( \
  aws iam get-role \
    --role-name ${IAM_ROLE_NAME} \
    --query 'Role.Arn' \
    --output text \
) \
  && echo ${IAM_ROLE_ARN}
```

```text:結果(例):
      arn:aws:iam::XXXXXXXXXXXX:role/lambdaBasicExecution
```


0.3. トピックの指定
-------------------

通知先を登録するトピックを指定します。

```bash:変数の設定:
SNS_TOPIC_NAME="$(date +%Y%m%d)-handson-topic" \
  && echo ${SNS_TOPIC_NAME}
```




0.4. トピックARNの取得
----------------------

```bash:コマンド:
SNS_TOPIC_ARN=$( \
        aws sns list-topics \
          --query "Topics[?contains(TopicArn, \`${SNS_TOPIC_NAME}\`)].TopicArn" \
          --output text \
) \
        && echo ${SNS_TOPIC_ARN}
```

```text:結果(例):
      arn:aws:sns:ap-northeast-1:XXXXXXXXXXXX:20160620-handson-topic
```



1. 事前作業
===========


1.1. Lambda関数名の決定
-----------------------

```bash:変数の設定:
LAMBDA_FUNC_NAME="sns-message-$( date '+%Y%m%d' )"
```

同名のLambda関数の不存在確認

```bash:コマンド:
aws lambda get-function \
  --function-name ${LAMBDA_FUNC_NAME}
```

```text:結果(例):
      A client error (ResourceNotFoundException) occurred when calling the GetFunction operation: Function not found: arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:function:sns-message-20160620
```


1.2. Lambda関数
---------------

```bash:変数の設定:
FILE_LAMBDA_FUNC="${LAMBDA_FUNC_NAME}.py"
```

```bash:変数の確認:
cat << ETX

    FILE_LAMBDA_FUNC: ${FILE_LAMBDA_FUNC}

ETX
```


```bash:コマンド:
cat << EOF > ${FILE_LAMBDA_FUNC}
from __future__ import print_function

import json

print('Loading function')


def lambda_handler(event, context):
    #print("Received event: " + json.dumps(event, indent=2))
    message = event['Records'][0]['Sns']['Message']
    print("From SNS: " + message)
    return message
EOF

cat ${FILE_LAMBDA_FUNC}
```

```bash:コマンド:
zip ${LAMBDA_FUNC_NAME}.zip ${FILE_LAMBDA_FUNC}
```

```text:結果(例):

      adding: sns-message-20160620.py (deflated 43%)
```


2. Lambda関数の作成
===================

```bash:変数の設定:
LAMBDA_FUNC_DESC='An Amazon SNS trigger that logs the message pushed to the SNS topic.'
LAMBDA_RUNTIME='python2.7'
LAMBDA_HANDLER="${LAMBDA_FUNC_NAME}.lambda_handler"
FILE_LAMBDA_ZIP="${LAMBDA_FUNC_NAME}.zip"
```

```bash:変数の確認:
cat << ETX

  LAMBDA_FUNC_NAME:  ${LAMBDA_FUNC_NAME}
  LAMBDA_FUNC_DESC: "${LAMBDA_FUNC_DESC}"
  LAMBDA_RUNTIME:    ${LAMBDA_RUNTIME}
  FILE_LAMBDA_ZIP    ${FILE_LAMBDA_ZIP}
  IAM_ROLE_ARN:      ${IAM_ROLE_ARN}
  LAMBDA_HANDLER:    ${LAMBDA_HANDLER}

ETX
```

```bash:コマンド:
aws lambda create-function \
  --function-name ${LAMBDA_FUNC_NAME} \
  --description "${LAMBDA_FUNC_DESC}" \
  --zip-file fileb://${FILE_LAMBDA_ZIP} \
  --runtime ${LAMBDA_RUNTIME} \
  --role ${IAM_ROLE_ARN} \
  --handler ${LAMBDA_HANDLER}
```

```json:結果(例):
{
    "CodeSha256": "y8yjqcUI8TGWAxqVt3x3BVzOv3Pb+rGr+0KPghz36i8=", 
    "FunctionName": "sns-message-20160620", 
    "CodeSize": 380, 
    "MemorySize": 128, 
    "FunctionArn": "arn:aws:lambda:us-west-2:xxxxxxxxxxxx:function:sns-message-20160620", 
    "Version": "$LATEST", 
    "Role": "arn:aws:iam::xxxxxxxxxxxx:role/lambdaBasicExecution", 
    "Timeout": 3, 
    "LastModified": "2016-06-19T15:10:47.747+0000", 
    "Handler": "sns-message-20160620.lambda_handler", 
    "Runtime": "python2.7", 
    "Description": "An Amazon SNS trigger that logs the message pushed to the SNS topic."
}
```

```bash:コマンド:
aws lambda list-functions 
```

```json:結果(例):
{
    "Functions": [
        {
            "Version": "$LATEST", 
            "CodeSha256": "y8yjqcUI8TGWAxqVt3x3BVzOv3Pb+rGr+0KPghz36i8=", 
            "FunctionName": "sns-message-20160620", 
            "MemorySize": 128, 
            "CodeSize": 380, 
            "FunctionArn": "arn:aws:lambda:us-west-2:xxxxxxxxxxxx:function:sns-message-20160620", 
            "Handler": "sns-message-20160620.lambda_handler", 
            "Role": "arn:aws:iam::xxxxxxxxxxxx:role/lambdaBasicExecution", 
            "Timeout": 3, 
            "LastModified": "2016-06-19T15:10:47.747+0000", 
            "Runtime": "python2.7", 
            "Description": "An Amazon SNS trigger that logs the message pushed to the SNS topic."
        }
    ]
}
```


```bash:コマンド:
aws lambda get-function \
  --function-name ${LAMBDA_FUNC_NAME}
```

```json:結果(例):
{
    "Code": {
        "RepositoryType": "S3", 
        "Location": "https://awslambda-us-west-2-tasks.s3-us-west-2.amazonaws.com/snapshots/xxxxxxxxxxxx/sns-message-20160620-94f6e8eb-ee3b-4c00-8c8a-954642396e71?x-amz-security-token=FQoDYXdzEO%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaDEruHidCLdnnzUDGQyK0A4dUQtu%2B4zoY%2FrVVjjQEHA3PZ4wZmeokZ4wNGVvL6Gl05QSctL4dRe79VgjBK%2BtYIUm1p6HgzxlWcrpV%2BhNgZsxsCqinRcBg79210XdopPa0wlPbswNIhqsHMyJS0Gs4x6eiKKJ9Gq2vMsiPoPKN8yKECvkDhCcSRs2wxCZrDybDutmqWKZowrNABgM4hzJGNz9xAh4dxN4Pr8fh%2F185glbq2iusjf3JP9gRIQKlT%2Bn9Xry1Jvj4qE0bl0iptY%2BRb%2FrzxTiz393p%2BYC0MJ63FR85T8GYtlhXzqAntufp4bxbkAKJFSsvEiO0LqssF%2Fse8aMTeIaPOfKOhOelT49HrbM0WLuRu7DBU416etC1AjkNCKMdPBPc9x%2FrCEcJKVfy5uENqrM8bKEFAdGMBMwFyUSHHnklsMTY8GSJtisBQIUVHNVTE5jbELx5kH9SaTolRfyTTVWLBn4Amlcobjhf1adlW58BmGa5Jge%2Bm1SzuvocaMRZQms%2BN6lNv9dLEgqT%2BlvVObkEqBk8m0O0ZLZat61SNoR81ymItLyuhmyg1QNbkOqpATQCg0wXg3WReKGqWUR1%2FckoucyauwU%3D&AWSAccessKeyId=ASIAJHC2NNIWC2M5YV3Q&Expires=1466349693&Signature=a%2FeD%2F%2FLuqd9HX23v4uDDAMMcuzQ%3D"
    }, 
    "Configuration": {
        "Version": "$LATEST", 
        "CodeSha256": "y8yjqcUI8TGWAxqVt3x3BVzOv3Pb+rGr+0KPghz36i8=", 
        "FunctionName": "sns-message-20160620", 
        "MemorySize": 128, 
        "CodeSize": 380, 
        "FunctionArn": "arn:aws:lambda:us-west-2:xxxxxxxxxxxx:function:sns-message-20160620", 
        "Handler": "sns-message-20160620.lambda_handler", 
        "Role": "arn:aws:iam::xxxxxxxxxxxx:role/lambdaBasicExecution", 
        "Timeout": 3, 
        "LastModified": "2016-06-19T15:10:47.747+0000", 
        "Runtime": "python2.7", 
        "Description": "An Amazon SNS trigger that logs the message pushed to the SNS topic."
    }
}
```

```bash:コマンド:
aws lambda get-function-configuration \
  --function-name ${LAMBDA_FUNC_NAME}
```

```json:結果(例):
{
    "CodeSha256": "y8yjqcUI8TGWAxqVt3x3BVzOv3Pb+rGr+0KPghz36i8=", 
    "FunctionName": "sns-message-20160620", 
    "CodeSize": 380, 
    "MemorySize": 128, 
    "FunctionArn": "arn:aws:lambda:us-west-2:xxxxxxxxxxxx:function:sns-message-20160620", 
    "Version": "$LATEST", 
    "Role": "arn:aws:iam::xxxxxxxxxxxx:role/lambdaBasicExecution", 
    "Timeout": 3, 
    "LastModified": "2016-06-19T15:10:47.747+0000", 
    "Handler": "sns-message-20160620.lambda_handler", 
    "Runtime": "python2.7", 
    "Description": "An Amazon SNS trigger that logs the message pushed to the SNS topic."
}
```


3. Lambda関数の動作確認
=======================


3.1. サンプルデータの作成
-------------------------

```bash:変数の設定:
FILE_INPUT="${LAMBDA_FUNC_NAME}-data.json" \
    && echo ${FILE_INPUT}
```

```bash:コマンド:
cat << EOF > ${FILE_INPUT}
{
  "Records": [
    {
      "EventVersion": "1.0",
      "EventSubscriptionArn": "arn:aws:sns:EXAMPLE",
      "EventSource": "aws:sns",
      "Sns": {
        "SignatureVersion": "1",
        "Timestamp": "1970-01-01T00:00:00.000Z",
        "Signature": "EXAMPLE",
        "SigningCertUrl": "EXAMPLE",
        "MessageId": "95df01b4-ee98-5cb9-9903-4c221d41eb5e",
        "Message": "Hello from SNS!",
        "MessageAttributes": {
          "Test": {
            "Type": "String",
            "Value": "TestString"
          },
          "TestBinary": {
            "Type": "Binary",
            "Value": "TestBinary"
          }
        },
        "Type": "Notification",
        "UnsubscribeUrl": "EXAMPLE",
        "TopicArn": "${SNS_TOPIC_NAME}",
        "Subject": "TestInvoke"
      }
    }
  ]
}
EOF

cat ${FILE_INPUT}
```

JSONファイルを作成したら、フォーマットが壊れてないか必ず確認します。

```bash:コマンド:
jsonlint -q ${FILE_INPUT}
```

エラーが出力されなければOKです。


3.2. lambda関数の手動実行
-------------------------

```bash:変数の設定:
FILE_OUTPUT_LAMBDA="${LAMBDA_FUNC_NAME}-out.txt"
FILE_LOG_LAMBDA="${LAMBDA_FUNC_NAME}-$(date +%Y%m%d%H%M%S).log"
```

```bash:変数の確認:
cat << ETX

  LAMBDA_FUNC_NAME:   ${LAMBDA_FUNC_NAME}
  FILE_INPUT:         ${FILE_INPUT}
  FILE_OUTPUT_LAMBDA: ${FILE_OUTPUT_LAMBDA}
  FILE_LOG_LAMBDA:    ${FILE_LOG_LAMBDA}

ETX
```

```bash:コマンド:
aws lambda invoke \
  --function-name ${LAMBDA_FUNC_NAME} \
  --log-type Tail \
  --payload file://${FILE_INPUT} \
  ${FILE_OUTPUT_LAMBDA} \
  > ${FILE_LOG_LAMBDA}
```

```bash:コマンド:
cat ${FILE_LOG_LAMBDA} \
  | jp.py 'StatusCode'
```

```text:結果(例):
      200
```


3.3. lambda関数の実行結果の確認
-------------------------------

```bash:コマンド:
cat ${FILE_OUTPUT_LAMBDA}
```

```text:結果(例):
1970-01-01T00:00:00Z
```


3.4. lambda関数のログの確認
---------------------------

```bash:コマンド:
cat ${FILE_LOG_LAMBDA} \
  | jp.py 'LogResult' \
  | sed 's/"//g' \
  | base64 --decode
```

```text:結果(例):

```


4. permissionの追加
======================


4.1. Lambda実行権限の付与
---------------------------

```bash:変数の設定
LAMBDA_STAT_ID='AllowSNS'
LAMBDA_PERMIT_ACTION='lambda:InvokeFunction'
LAMBDA_PERMIT_PRINCIPAL='sns.amazonaws.com'
```

```bash:変数の確認
cat << ETX

  LAMBDA_FUNC_NAME:        ${LAMBDA_FUNC_NAME}
  LAMBDA_STAT_ID:         "${LAMBDA_STAT_ID}"
  LAMBDA_PERMIT_ACTION:    ${LAMBDA_PERMIT_ACTION}
  LAMBDA_PERMIT_PRINCIPAL: ${LAMBDA_PERMIT_PRINCIPAL}

ETX
```

```bash:コマンド:
aws lambda add-permission \
  --function-name ${LAMBDA_FUNC_NAME} \
  --statement-id "${LAMBDA_STAT_ID}" \
  --action ${LAMBDA_PERMIT_ACTION} \
  --principal ${LAMBDA_PERMIT_PRINCIPAL}
```

```json:結果(例)
{
    "Statement": "{\"Action\":[\"lambda:InvokeFunction\"],\"Resource\":\"arn:aws:lambda:us-west-2:xxxxxxxxxxxx:function:sns-message-20160620\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"sns.amazonaws.com\"},\"Sid\":\"AllowSNS\"}"
}
```


完了
====