[JAWS-UG CLI] Lambda:#23 Lambda関数の作成・実行 (microservice_http_endpoint-python)




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


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

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


DynamoDBへの権限
----------------

DynamoDBに対して読み取り権限があること。


AWS CLI
-------

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

- AWS CLI 1.10.47

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

結果(例):

      aws-cli/1.11.34 Python/2.7.10 Darwin/15.6.0 botocore/1.4.91

バージョンが古い場合は最新版に更新しましょう。

```bash:コマンド:
sudo -H pip install -U awscli
```


IAM Role
--------

LambdaMicroserviceHttpEndpointRoleロールが存在すること。


0. 準備
=======


0.1. リージョンの決定
---------------------

```bash:変数の設定
export AWS_DEFAULT_REGION='ap-northeast-1'
```


0.2. プロファイルの確認
-----------------------

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

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

結果(例):

            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.3. DynamoDBテーブル名の指定
-----------------------------

```bash:コマンド:
DYNAMO_TABLE_NAME='microservice-http'
```


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


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

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

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

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

結果(例):

      A client error (ResourceNotFoundException) occurred when calling the GetFunction operation: Function not found: arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:function:microservice_http_endpoint-20170101


1.2. IAM Roleの指定
-------------------

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

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

結果(例):

      {
          "Role": {
            "AssumeRolePolicyDocument": {
                "Version": "2012-10-17",
                "Statement": [
                    {
                        "Action": "sts:AssumeRole",
                        "Principal": {
                            "Service": "lambda.amazonaws.com"
                        },
                        "Effect": "Allow",
                        "Sid": ""
                    }
                ]
            },
            "RoleId": "AROAXXXXXXXXXXXXXXXXX",
            "CreateDate": "2016-12-31T01:23:45Z",
            "RoleName": "LambdaMicroserviceHttpEndpointRole",
            "Path": "/",
            "Arn": "arn:aws:iam::XXXXXXXXXXXX:role/LambdaMicroserviceHttpEndpointRole"
          }
      }

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

結果(例):

      arn:aws:iam::XXXXXXXXXXXX:role/LambdaMicroserviceHttpEndpointRole


1.3. Lambda関数
---------------

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

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

          FILE_LAMBDA_FUNC: ${FILE_LAMBDA_FUNC}
          PY_FUNC_NAME:     ${PY_FUNC_NAME}

ETX
```

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

import boto3
import json

print('Loading function')


def respond(err, res=None):
    return {
        'statusCode': '400' if err else '200',
        'body': err.message if err else json.dumps(res),
        'headers': {
            'Content-Type': 'application/json',
        },
    }


def ${PY_FUNC_NAME}(event, context):
    #print("Received event: " + json.dumps(event, indent=2))

    operations = {
        'DELETE': lambda dynamo, x: dynamo.delete_item(**x),
        'GET': lambda dynamo, x: dynamo.scan(**x),
        'POST': lambda dynamo, x: dynamo.put_item(**x),
        'PUT': lambda dynamo, x: dynamo.update_item(**x),
    }

    operation = event['httpMethod']
    if operation in operations:
        payload = event['queryStringParameters'] if operation == 'GET' else json.loads(event['body'])
        dynamo = boto3.resource('dynamodb').Table(payload['TableName'])
        return respond(None, operations[operation](dynamo, payload))
    else:
        return respond(ValueError('Unsupported method "{}"'.format(operation)))
EOF

cat ${FILE_LAMBDA_FUNC}
```

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

結果(例):

      adding: microservice_http_endpoint-20170101.py (deflated 43%)


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

```bash:変数の設定:
LAMBDA_FUNC_DESC='Microservice HTTP Endpoint 20170101'
LAMBDA_RUNTIME='python2.7'
LAMBDA_HANDLER="${LAMBDA_FUNC_NAME}.${PY_FUNC_NAME}"
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}
```

結果(例):

      {
        "CodeSha256": "+edf74XvKEs4oYFOflSRJydF5n5F0SEL34ujm7+kAPk=",
        "FunctionName": "microservice_http_endpoint-20170101",
        "CodeSize": 726,
        "MemorySize": 128,
        "FunctionArn": "arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:function:microservice_http_endpoint-20170101",
        "Version": "$LATEST",
        "Role": "arn:aws:iam::XXXXXXXXXXXX:role/LambdaMicroserviceHttpEndpointRole",
        "Timeout": 3,
        "LastModified": "2016-12-31T01:23:45.678+0000",
        "Handler": "microservice_http_endpoint-20170101.lambda_handler",
        "Runtime": "python2.7",
        "Description": "Microservice HTTP Endpoint 20170101"
      }

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

結果(例):

      {
        "Code": {
          "RepositoryType": "S3",
          "Location": "https://awslambda-ap-ne-1-tasks.s3-ap-northeast-1.amazonaws.com/snapshots/XXXXXXXXXXXX/microservice_http_endpoint-20170101-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
        },
        "Configuration": {
          "Version": "$LATEST",
          "CodeSha256": "+edf74XvKEs4oYFOflSRJydF5n5F0SEL34ujm7+kAPk=",
          "FunctionName": "microservice_http_endpoint-20170101",
          "MemorySize": 128,
          "CodeSize": 350,
          "FunctionArn": "arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:function:microservice_http_endpoint-20170101",
          "Handler": "microservice_http_endpoint-20170101.lambda_handler",
          "Role": "arn:aws:iam::XXXXXXXXXXXX:role/LambdaMicroserviceHttpEndpointRole",
          "Timeout": 3,
          "LastModified": "2016-12-31T01:23:45.678+0000",
          "Runtime": "python2.7",
          "Description": "Microservice HTTP Endpoint 20170101"
        }
      }


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


3.1. GET
--------

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

```bash:コマンド:
cat << EOF > ${FILE_INPUT}
{
         "httpMethod": "GET",
         "queryStringParameters": {
             "TableName": "microservice-http"
         }
}
EOF

cat ${FILE_INPUT}
```

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

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

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

```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'
```

結果(例):

      200

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

結果(例):

      {"body": "{\"Count\": 1, \"Items\": [{\"Description\": \"sample\", \"Name\": \"taro\"}], \"ScannedCount\": 1, \"ResponseMetadata\": {\"RetryAttempts\": 0, \"HTTPStatusCode\": 200, \"RequestId\": \"GJTSB9TE10U7O0SS9JC9050FD7VV4KQNSO5AEMVJF66Q9ASUAAJG\", \"HTTPHeaders\": {\"x-amzn-requestid\": \"GJTSB9TE10U7O0SS9JC9050FD7VV4KQNSO5AEMVJF66Q9ASUAAJG\", \"content-length\": \"89\", \"server\": \"Server\", \"connection\": \"keep-alive\", \"x-amz-crc32\": \"2586798783\", \"date\": \"Sat, 31 Dec 2016 13:22:26 GMT\", \"content-type\": \"application/x-amz-json-1.0\"}}}", "headers": {"Content-Type": "application/json"}, "statusCode": "200"}

```bash:コマンド:
aws dynamodb scan \
        --table-name ${DYNAMO_TABLE_NAME}
```

結果(例):

      {
        "Count": 1,
        "Items": [
          {
              "Description": {
                  "S": "sample"
              },
              "Name": {
                  "S": "taro"
              }
          }
        ],
        "ScannedCount": 1,
        "ConsumedCapacity": null
      }


3.2. POST
---------

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

```bash:コマンド:
cat << EOF > ${FILE_INPUT}
{
        "httpMethod": "POST",
        "body": "{\"TableName\":\"microservice-http\",\"Item\":{\"Name\":\"jiro\",\"Description\":\"sample2\"}}"
}
EOF

cat ${FILE_INPUT}
```

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

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

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

```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'
```

結果(例):

      200

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

結果(例):

      {"body": "{\"ResponseMetadata\": {\"RetryAttempts\": 0, \"HTTPStatusCode\": 200, \"RequestId\": \"C96O73JS0QB4NRH3U5D30JE9GRVV4KQNSO5AEMVJF66Q9ASUAAJG\", \"HTTPHeaders\": {\"x-amzn-requestid\": \"C96O73JS0QB4NRH3U5D30JE9GRVV4KQNSO5AEMVJF66Q9ASUAAJG\", \"content-length\": \"2\", \"server\": \"Server\", \"connection\": \"keep-alive\", \"x-amz-crc32\": \"2745614147\", \"date\": \"Sat, 31 Dec 2016 13:57:18 GMT\", \"content-type\": \"application/x-amz-json-1.0\"}}}", "headers": {"Content-Type": "application/json"}, "statusCode": "200"}

```bash:コマンド:
aws dynamodb scan \
        --table-name ${DYNAMO_TABLE_NAME}
```

結果(例):

      {
        "Count": 2,
        "Items": [
          {
              "Description": {
                  "S": "sample2"
              },
              "Name": {
                  "S": "jiro"
              }
          },
          {
              "Description": {
                  "S": "sample"
              },
              "Name": {
                  "S": "taro"
              }
          }
        ],
        "ScannedCount": 2,
        "ConsumedCapacity": null
      }


3.3. DELETE
-----------

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

```bash:コマンド:
cat << EOF > ${FILE_INPUT}
{
        "httpMethod": "DELETE",
        "body": "{\"TableName\":\"microservice-http\",\"Key\":{\"Name\":\"jiro\"}}"
}
EOF

cat ${FILE_INPUT}
```

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

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

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

```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'
```

結果(例):

      200

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

結果(例):

      {"body": "{\"ResponseMetadata\": {\"RetryAttempts\": 0, \"HTTPStatusCode\": 200, \"RequestId\": \"R43F60O4UNN5B1FR3UKFR25R43VV4KQNSO5AEMVJF66Q9ASUAAJG\", \"HTTPHeaders\": {\"x-amzn-requestid\": \"R43F60O4UNN5B1FR3UKFR25R43VV4KQNSO5AEMVJF66Q9ASUAAJG\", \"content-length\": \"2\", \"server\": \"Server\", \"connection\": \"keep-alive\", \"x-amz-crc32\": \"2745614147\", \"date\": \"Sat, 31 Dec 2016 13:58:51 GMT\", \"content-type\": \"application/x-amz-json-1.0\"}}}", "headers": {"Content-Type": "application/json"}, "statusCode": "200"}

```bash:コマンド:
aws dynamodb scan \
        --table-name ${DYNAMO_TABLE_NAME}
```

結果(例):

      {
        "Count": 1,
        "Items": [
          {
              "Description": {
                  "S": "sample"
              },
              "Name": {
                  "S": "taro"
              }
          }
        ],
        "ScannedCount": 1,
        "ConsumedCapacity": null
      }


完了
====