[JAWS-UG CLI] Lambda:#13 Slackへのポスト 「おはようLambda」(CloudWatch Event対応用)

Lambda関数を作成して、slackのIncoming Webhooksに投稿してみます。

今回は、CloudWatch Eventsで定期実行できるように、データは関数の内部で持たせます。


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

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

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


AWS CLI
-------

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

* AWS CLI 1.10.17

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

結果(例):

      aws-cli/1.10.6 Python/2.7.5 Darwin/13.4.0 botocore/1.3.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": "AROAI2DMAFJEPZE7RBSUG",
          "CreateDate": "2015-10-26T01:55:54Z",
          "RoleName": "lambdaBasicExecution",
          "Path": "/",
          "Arn": "arn:aws:iam::XXXXXXXXXXXX:role/lambdaBasicExecution"
        }
      }
```
存在しない場合は、以下の手順を実施してください。

- IAMロールの作成 (Lambda: Basic execution): http://qiita.com/tcsh/items/6353876a5c4fef63b4d8


0. 準備
=======


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

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

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

```text:結果(例):
            Name                    Value             Type    Location
            ----                    -----             ----    --------
         profile       administrator-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'
```


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


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

```bash:変数の設定:
LAMBDA_FUNC_NAME='good_morning_lambda'
```

同名の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:good_morning_lambda
```

1.2. Slack関連の設定
---------------

```bash:変数の設定:
URL_SLACK_IN=<slackのIncoming WebHookのURL>
CHANNEL_SLACK='20160411'
USERNAME_SLACK='おはようLambda'
ICON_EMOJI_SLACK='sunrise'
TEXT_SLACK="Good Morning\!\`\`\`\'おはよう\!\'\`\`\`"
```


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

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

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

  FILE_LAMBDA_FUNC: ${FILE_LAMBDA_FUNC}

  URL_SLACK_IN:     ${URL_SLACK_IN}
  CHANNEL_SLACK:    ${CHANNEL_SLACK}
  USERNAME_SLACK:   ${USERNAME_SLACK}
  ICON_EMOJI_SLACK: ${ICON_EMOJI_SLACK}
  TEXT_SLACK:       ${TEXT_SLACK}

ETX
```

```bash:コマンド:
cat << EOF > ${FILE_LAMBDA_FUNC}
var Slack = require('slack-node');
console.log('Loading function');

exports.handler = function(event, context) {
  slack = new Slack();
  slack.setWebhook('${URL_SLACK_IN}');

  slack.webhook({
    channel:    "#" + "${CHANNEL_SLACK}",
    username:   "${USERNAME_SLACK}",
    icon_emoji: ":" + "${ICON_EMOJI_SLACK}" + ":",
    text:       "${TEXT_SLACK}"
  }, function(err, response) {
    console.log(response);
    context.succeed(response);
  });
};
EOF

cat ${FILE_LAMBDA_FUNC}
```

```bash:コマンド(任意):
eslint ${FILE_LAMBDA_FUNC}
```

```bash:コマンド:
npm install slack-node
```

```text:結果(例)
slack-node@0.2.0 node_modules/slack-node
└── requestretry@1.6.0 (when@3.7.7, fg-lodash@0.0.2, request@2.69.0)
```

```bash:コマンド:
zip -r ${LAMBDA_FUNC_NAME}.zip ${FILE_LAMBDA_FUNC} node_modules \
  && ls -l ${LAMBDA_FUNC_NAME}.zip
```

```text:結果(例)
-rw-r--r--  1 lab-office  staff  3122259  4 10 19:42 good_morning_lambda.zip
```

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

```bash:変数の設定:
LAMBDA_FUNC_DESC='AWS Lambda function for post slack incoming webhook.'
LAMBDA_RUNTIME='nodejs'
LAMBDA_HANDLER="${LAMBDA_FUNC_NAME}.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": "8fI549wlUY+qm7LHTh5XmV/CdCkqabpBTbCOfiFJf3o=",
    "FunctionName": "good_morning_lambda",
    "CodeSize": 1978932,
    "MemorySize": 128,
    "FunctionArn": "arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:function:good_morning_lambda",
    "Version": "$LATEST",
    "Role": "arn:aws:iam::XXXXXXXXXXXX:role/lambdaBasicExecution",
    "Timeout": 3,
    "LastModified": "2016-02-22T08:44:48.274+0000",
    "Handler": "good_morning_lambda.handler",
    "Runtime": "nodejs",
    "Description": "AWS Lambda function for post slack incoming webhook."
}
```

```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/good_morning_lambda-ac0802dc-9a1e-4d0d-a7c8-e88bfd13fc88?x-amz-security-token=AQoDYXdzECka4AO2NeNXdEJ0XHeft0nvNiPkug4SqexqnBLBZj%2FkpT%2FleFIDbSUva%2BunE48A4%2BxVrfprD2Qg6UIusixkHBTdA%2BvFzwVDD%2FRVcSqXXK5IDPejbVP39tR%2BlVz6FvLPbQgXJ90budQsNfGGxKuN0KG5PrjNAP%2BwLXIHfgI9ZW%2BBzPRGFNMp6KZN8GyoDl2SPrmazbLXwFbmF9RNNAyQ76yJCwKWgRL1J9tEBI34U6tGJLOfiTlG4ES5e1IHGALJRCxakodS7dhE3UpVO7%2BbTbAK%2F5DdkCb2H4iENiIs0VoVwBvm2J%2FMhLHjXwa9fibMHji4zaX81qr9PN4saIT%2FwnVf3nljCJv5oKM5cC3Wzh%2Bcb6CAh4AJppjI9nNDqozQzXnvDrEvp6vzP%2Boti52FHXG%2Bp5Y4zXTrpiu35vOca19DOPD8Gc%2FxaycrXujULkXQpe8%2F%2BGjmMIw8yZGJQNfzKkg7DothBPRVvgNZe%2FvwyiXEllSft%2BrfDhWkMAk1mq24e%2FWxRQ4TGr8oBJ1yM2nsD0yx7406px1uXXPyTCOy%2FI2lH3K%2BVhJ391gJ6vdgugk4oWuTE5XK%2BHiyS17cYgWDciISf%2B0FpWufVWcQYFgV%2Bu4csHnnh6tYHiqe8McHzuvKe4UAli0goPyqtgU%3D&AWSAccessKeyId=ASIAXXXXXXXXXXXXXXXXX&Expires=1456131365&Signature=Na9a83GZbmR6cL2XnvZOOafGCN8%3D"
    },
    "Configuration": {
        "Version": "$LATEST",
        "CodeSha256": "8fI549wlUY+qm7LHTh5Xmx/CdCkqabpBTbCOfiFJx3o=",
        "FunctionName": "good_morning_lambda",
        "MemorySize": 128,
        "CodeSize": 1978932,
        "FunctionArn": "arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:function:good_morning_lambda",
        "Handler": "good_morning_lambda.handler",
        "Role": "arn:aws:iam::XXXXXXXXXXXX:role/lambdaBasicExecution",
        "Timeout": 3,
        "LastModified": "2016-02-22T08:44:48.274+0000",
        "Runtime": "nodejs",
        "Description": "AWS Lambda function for post slack incoming webhook."
    }
}
```


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



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

```bash:変数の設定
FILE_OUTPUT_LAMBDA="${LAMBDA_FUNC_NAME}-out.txt" \
        && echo ${FILE_OUTPUT_LAMBDA}
```


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

        LAMBDA_FUNC_NAME:   ${LAMBDA_FUNC_NAME}
        FILE_OUTPUT_LAMBDA: ${FILE_OUTPUT_LAMBDA}

ETX
```

```bash:コマンド:
FILE_LOG_LAMBDA="${LAMBDA_FUNC_NAME}-$(date +%Y%m%d%H%M%S).log" \
        && echo ${FILE_LOG_LAMBDA}

aws lambda invoke \
        --function-name ${LAMBDA_FUNC_NAME} \
        --log-type Tail \
        ${FILE_OUTPUT_LAMBDA} \
        > ${FILE_LOG_LAMBDA} \

cat ${FILE_LOG_LAMBDA} \
   | jp.py 'StatusCode'
```

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


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

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

```text:結果:
{"status":"ok","statusCode":200,"headers":{"content-type":"text/html","content-length":"2","connection":"keep-alive","access-control-allow-origin":"*","content-security-policy":"referrer no-referrer;","date":"Mon, 22 Feb 2016 08:50:33 GMT","server":"Apache","strict-transport-security":"max-age=31536000; includeSubDomains; preload","x-frame-options":"SAMEORIGIN","x-cache":"Miss from cloudfront","via":"1.1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.cloudfront.net (CloudFront)","x-amz-cf-id":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=="},"response":"ok"}
```


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

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

```text:結果:
START RequestId: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx Version: $LATEST
2016-04-10T04:38:22.476Z	xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx	{ status: 'ok',
  statusCode: 200,
  headers: 
   { 'content-type': 'text/html',
     'transfer-encoding': 'chunked',
     connection: 'keep-alive',
     'access-control-allow-origin': '*',
     'content-security-policy': 'referrer no-referrer;',
     date: 'Sun, 10 Apr 2016 04:38:22 GMT',
     server: 'Apache',
     'strict-transport-security': 'max-age=31536000; includeSubDomains; preload',
     vary: 'Accept-Encoding',
     'x-frame-options': 'SAMEORIGIN',
     'x-cache': 'Miss from cloudfront',
     via: '1.1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.cloudfront.net (CloudFront)',
     'x-amz-cf-id': 'xxxxxxxxxxxxxxxxxxx_xx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx_x==' },
  response: 'ok' }
END RequestId: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
REPORT RequestId: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx	Duration: 998.63 ms	Billed Duration: 1000 ms 	Memory Size: 128 MB	Max Memory Used: 41 MB	
```



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

4.1. ロールARNの取得
-----------------------

```bash:変数の設定
LAMBDA_RULE_ROLE_ARN=$( \
aws iam list-roles \
  --query "Roles[?contains(RoleName, \`${IAM_ROLE_NAME}\`)].Arn" \
  --output text \
) \
  && echo ${LAMBDA_RULE_ROLE_ARN}
```


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

```bash:変数の設定
LAMBDA_STAT_ID='AllowScheduledEvents'
LAMBDA_PERMIT_ACTION='lambda:InvokeFunction'
LAMBDA_PERMIT_PRINCIPAL='events.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}
  LAMBDA_RULE_ROLE_ARN:    ${LAMBDA_RULE_ROLE_ARN}

ETX
```

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

```json:結果(例)
{
    "Statement": "{\"Action\":[\"lambda:InvokeFunction\"],\"Resource\":\"arn:aws:lambda:us-west-2:XXXXXXXXXXXX:function:good_morning_lambda\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"events.amazonaws.com\"},\"Sid\":\"AllowScheduledEvents\"}"
}
```

完了
====