[JAWS-UG CLI] Lambda #18 slack-echo-command (Python版)


# 前提条件


## Lambdaへの権限

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


## AWS CLI

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

* AWS CLI 1.10.58

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

```text:結果(例):
      aws-cli/1.10.58 Python/2.7.11 Darwin/15.6.0 botocore/1.4.48
```


## 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"
          }
      }
```

IAMロールが存在しない場合、http://qiita.com/tcsh/items/6353876a5c4fef63b4d8 の手順に従って作成してください。


## Slack

Slackのチーム管理権限があること


# 0. 準備


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

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


## 0.2. 変数の確認

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

```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.3. IAM RoleのARN取得

```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.4. Slackの設定

まず、Slash Commandの追加を行います。

1. https://${SLACK_TEAM_NAME}.slack.com/apps にアクセスします。(App Directory 画面)
   - 検索窓に 'Slash Commands' と入力します。
   - 'Slash Commands'が表示されるので、クリックします。

2. Browse Apps > Slash Commands (画面)
   - [Add Configuration](ボタン)をクリックします。

3. New configuration (画面)
   - Choose a Command(入力欄)に下記の文字列を入力します。
     - /sayhello
   - [Add Slash Command Integration](ボタン)をクリックします。

4. Edit configuration (画面)
   - 'Integration Settings'欄の'Token'の値をメモに保存します。
     - 例: DxAoIy9PdXXEXe08mvH9VsQL

```bash:変数の設定:
SLACK_TOKEN='<SlackのTokenの値>'
```


# 1. 事前作業


## 1.1. Lambda関数名の決定

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

同名の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:slack-echo-command-20160829
```


## 1.2. 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}
          SLACK_TOKEN:      ${SLACK_TOKEN}

ETX
```

```bash:コマンド:
cat << EOF > ${FILE_LAMBDA_FUNC}
import boto3
from base64 import b64decode
from urlparse import parse_qs
import logging

#ENCRYPTED_EXPECTED_TOKEN = "<kmsEncryptedToken>" # Enter the base-64 encoded, encrypted Slack command token (CiphertextBlob)

#kms = boto3.client('kms')
#expected_token = kms.decrypt(CiphertextBlob = b64decode(ENCRYPTED_EXPECTED_TOKEN))['Plaintext']
expected_token = '${SLACK_TOKEN}'

logger = logging.getLogger()
logger.setLevel(logging.INFO)

def lambda_handler(event, context):
    req_body = event['body']
    params = parse_qs(req_body)
    token = params['token'][0]
    if token != expected_token:
        logger.error("Request token (%s) does not match exptected", token)
        raise Exception("Invalid request token")

    user = params['user_name'][0]
    command = params['command'][0]
    channel = params['channel_name'][0]
    command_text = params['text'][0]

    return "%s invoked %s in %s with the following text: %s" % (user, command, channel, command_text)
EOF

cat ${FILE_LAMBDA_FUNC}
```

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

```text:結果(例):
      adding: slack-echo-command-20160829.py (deflated 43%)
```


# 2. Lambda関数の作成

```bash:変数の設定:
LAMBDA_FUNC_DESC='A function that handles a Slack slash command and echoes the details back to the user.'
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}
```

```json:結果(例):

      {
        "CodeSha256": "WnGd6iPBS0rGCJor4zyuOaGBtzQ3I/OgHerXWqZzbL8=",
        "FunctionName": "slack-echo-command-20160829",
        "CodeSize": 350,
        "MemorySize": 128,
        "FunctionArn": "arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:function:slack-echo-command-20160829",
        "Version": "$LATEST",
        "Role": "arn:aws:iam::XXXXXXXXXXXX:role/lambdaBasicExecution",
        "Timeout": 3,
        "LastModified": "2016-08-28T01:23:45.678+0000",
        "Handler": "slack-echo-command-20160829.lambda_handler",
        "Runtime": "python2.7",
        "Description": "A function that handles a Slack slash command and echoes the details back to the user."
      }
```

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

```json:結果(例):

      {
        "Code": {
          "RepositoryType": "S3",
          "Location": "https://awslambda-ap-ne-1-tasks.s3-ap-northeast-1.amazonaws.com/snapshots/XXXXXXXXXXXX/HelloWorld-2979ba79-b08f-495d-9ee6-46397c95ba13?x-amz-security-token=AQoDYXdzEDoa8AMR6t8h66eOXhN3%2Fx7XpuRxvf7pVn7IuWV4cEmwx0CtZT6yxCJ1%2BWmigYXqGoyQHuBYOWnxbhmwEcTg839qMuhSu1fk0fXpXf0oJOLkhKMudNqhdElyFQpzyT6Q8GDfhAsfbX9wvwCDTty4imxz7MczF%2FQl6tgvTYdip08ap5fAyrknZGV1%2B1Ggnp5w6JOjydYxuUsWwhoxoEWzi7SoVTmpRQQA91c4VW9lNotOAHACFxo6klzDPM8mxR9RJl66WxFugL0wQJyLUpmtjS9XoArD86sEWWiIccMpV2BQipTPQlzL%2F1Hoy%2BDF6QUxyPUihlDjPBoJTISTP8W1wxmzW%2BLbilAfFQRPY7CFjzR0k%2FA%2FIX5x9iyz52Pu1Q0ASTw1l%2Fq%2Fo3pRbvzWR79QS%2BpxXrwbYzoQHKiK62DSTsQo5tqKPsiDCYzrPxbq8lm7pNBPG%2FsxjePRWBVJeRl08WxEjSjoRRwBOPX5mz1BCUoUBPGG5tEENp87A%2FCdDgibFWM5DdYhwtaYPY7FTmi8DvqjQHL9jOmP8YuVteBTBcv8nFW6UbErPjwwn79FKG1u5M9HoTWUqUMBByz6D4tTRSEw6iJU7XdCujFnhnHe5V8imZ1KGI7fDWpciJhrhml0wnKPCK%2Fe9lK1P2kO7ldSWc7zn5hcIOD2tbEF&AWSAccessKeyId=ASIAJFVALOKV5SJVYPPA&Expires=1445825978&Signature=bvwu1Ny34LgTmZeOO3q4sn7x3Fg%3D"
        },
        "Configuration": {
          "Version": "$LATEST",
          "CodeSha256": "WnGd6iPBS0rGCJor4zyuOaGBtzQ3I/OgHerXWqZzbL8=",
          "FunctionName": "slack-echo-command-20160829",
          "MemorySize": 128,
          "CodeSize": 350,
          "FunctionArn": "arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:function:slack-echo-command-20160829",
          "Handler": "slack-echo-command-20160829.lambda_handler",
          "Role": "arn:aws:iam::XXXXXXXXXXXX:role/lambdaBasicExecution",
          "Timeout": 3,
          "LastModified": "2016-08-28T01:23:45.678+0000",
          "Runtime": "python2.7",
          "Description": "A function that handles a Slack slash command and echoes the details back to the user."
        }
      }
```


# 3. Lambda関数の動作確認


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

コマンド関連の変数の指定

```bash:変数の設定:
STR_COMMAND='sayhello'
STR_CHANNEL_NAME='general'
```

```bash:変数の設定:
STR_TEXT='ec2'
STR_USER_NAME='Steve'
```

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

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

        SLACK_TOKEN:      ${SLACK_TOKEN}
        STR_COMMAND:      ${STR_COMMAND}
        STR_TEXT:         ${STR_TEXT}
        STR_USER_NAME:    ${STR_USER_NAME}
        STR_CHANNEL_NAME: ${STR_CHANNEL_NAME}
        FILE_INPUT:       ${FILE_INPUT}

ETX
```

```bash:サンプルデータ:
cat << EOF > ${FILE_INPUT}
{
        "body": "token=${SLACK_TOKEN}&command=/sayhello&text=ec2&user_name=Steve&channel_name=general"
}
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:結果(例):

      "Steve invoked /sayhello in general with the following text: ec2"
```


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

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

```text:結果(例):

      START RequestId: 6a466adf-6cec-11e6-9e83-f3993fe0dc41 Version: $LATEST
      END RequestId: 6a466adf-6cec-11e6-9e83-f3993fe0dc41
      REPORT RequestId: 6a466adf-6cec-11e6-9e83-f3993fe0dc41      Duration: 0.34 ms       Billed Duration: 100 ms         Memory Size: 128 MB     Max Memory Used: 45 MB
```


# 完了

Lambdaの実行許可は、RestAPI作成後に行います。