[AWS]Slack + AWS Chatbot + Lambdaで朝会のファシリテーター指名してみた

現職場では、デイリースタンドアップとして、朝会を実施している。
そして、いつも開始日時になると、

「え、今日・・・司会担当・・・だれ・・・？」

とTheWordになる。
※TheWord　・・・　リモート会議において、 無音が続き、最初に話した人がファシリテーターをしなければいけない空間のこと

一応、[SlackBot](https://slack.com/intl/ja-jp/help/articles/202026038-Slackbot-%E5%85%A5%E9%96%80)を使用して、それとなく機械的にファシリテーターを指名しているのだが、

「この人お休みだよ〜」 や 「昨日もやったんですけど〜」 とか

**いちいち面倒臭い。**

よし。朝会のファシリテーターをいい感じ（前回当たった人は当たらない、スムーズに再抽選ができる、~~自分が選ばれない~~）に決めるToolを作ろう。

# はじめに

コミュニケーションToolはSlack
プラットフォームはAWSとする。

# 構成

全体構成はこんな感じ。
![構成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/300430/74455e19-833f-0f18-19e5-7440c26fa26b.png)

朝会の担当者をランダムに選択し、Slackに通知するTool。
実行トリガーは2種類存在する。
1. CloudWathEventsから定期的（朝会の始まる5分前）に実行
2. Slack→AWS ChatBotから実行

DynamoDBではファシリテーター担当履歴を管理。
（現時点では直近の担当者しか保存していない。）

# 構築

## AWS

ChatBot以外はCloudFormationで構築。
IAM等のユーザーは事前に発行しておくこと。
[ソースコード](https://github.com/yamachita0109/morning_facilitator)

### Lambda

#### ソース

Node.jsで実装。
コード全体とは以下の通り。

```index.js
'use strict'
const requestPromise = require('request-promise')
const AWS = require('aws-sdk')
const dynamodb = new AWS.DynamoDB.DocumentClient({
  region: 'ap-northeast-1'
})
let slackPostOption = {
  url: 'https://slack.com/api/chat.postMessage',
  method: 'POST',
  qs: {
    token: process.env.SLACK_TOKEN,
    channel: process.env.SLACK_CHANNEL,
    text: '',
    username: 'ぼくのいうことはぜったい'
  },
  json: true
}

exports.handler = async () => {
  return new Promise((resolve, reject) => {
    Promise.resolve()
    .then(() => {
      // 直近のファシリテーターを取得
      return getLatestFacilitator()
    })
    .then((latestFacilitator) => {
      // ランダムに取得（直近を除く）
      return getFacilitator(latestFacilitator)
    })
    .then((facilitator) => {
      // 直近のファシリテーターを登録
      return putLatestFacilitator(facilitator)
    })
    .then((facilitator) => {
      // Slackに通知
      return postSlack(facilitator)
    })
    .then(() => {
      resolve('Finish')
    })
    .catch(reject)
  })
}

const getLatestFacilitator = () => {
  const param = {
    TableName: 'FacilitatorHistory',
    Key: {
      status: 'latest'
    }
  }
  return new Promise((resolve, reject) => {
    dynamodb.get(param, (err, data) => {
      if (err) reject(err)
      resolve(data.Item ? data.Item.member : '')
    })
  })
}

const getFacilitator = (latestFacilitator) => {
  return new Promise((resolve, reject) => {
    const memberList = process.env.MEMBER.split(',')
    const get = () => {
      const facilitator = memberList[Math.floor(Math.random() * memberList.length)]
      if (latestFacilitator == facilitator) {
        get()
        return
      }
      resolve(facilitator)
    }
    get()
  })
}

const putLatestFacilitator = (facilitator) => {
  return new Promise((resolve, reject) => {
    var param = {
    TableName: 'FacilitatorHistory',
      Item:{
        status: 'latest',
        member: facilitator
      }
    }
    dynamodb.put(param, (err, data) => {
      if (err) reject(err)
      resolve(facilitator)
    })
  })
}

const postSlack = (facilitator) => {
  return new Promise((resolve, reject) => {
    slackPostOption.qs.text = `きょうのあさかいは <${facilitator}> だ。`
    requestPromise(slackPostOption)
    .then(resolve)
    .catch(reject)
  })
}
```
#### 内容

本来なら並列にできる部分もあるが、わかりやすく直列化している。

流れとしては
1. 直近のファシリテーターを取得
2. ファシリテーターをランダムに取得（直近のファシリテーターは除く）
3. Slackに担当者を通知
4. 2.で選ばれたファシリテーターを登録
といったシンプルなもの。
2.のファシリテーター取得ロジック部分で担当者ごとに重みをつけて、選ばれやすさを制御したり、自身だけ選ばれづらくするなどのイカサマはできそう。

### CloudFormation

#### yml

一部内容がベタが記されている部分があるので、使用する際には修正する必要がある。

```template.yml
AWSTemplateFormatVersion: 2010-09-09
Resources:
  MorningFacilitatorFunction:
    Type: AWS::Lambda::Function
    Properties:
      Code: ./release/app.zip
      FunctionName: MorningFacilitatorFunction
      Handler: index.handler
      Runtime: nodejs12.x
      # Lambdaの実行ロール
      Role: {lambda arn}
      MemorySize: 128
      Timeout: 30
      Environment:
        Variables:
          TZ: Asia/Tokyo
          # カンマ区切りでファシリテーター候補のSlackユーザーIDを定義
          MEMBER: '@yamada,@yamamoto,@yamashita,@yamagami,@yamsuda'
          # 各種Slackの情報
          SLACK_TOKEN: {slack api token}
          SLACK_CHANNEL: {slack channel}

  FacilitatorHistory:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: FacilitatorHistory
      AttributeDefinitions:
        - AttributeName: status
          AttributeType: S
      KeySchema:
        - AttributeName: status
          KeyType: HASH
      ProvisionedThroughput:
        ReadCapacityUnits: 3
        WriteCapacityUnits: 3

  FacilitatorEventsRule:
    Type: AWS::Events::Rule
    Properties:
      Name: FacilitatorEventsRule
      # 朝会開始時間5分前（GMTなのでJST変換すると+9時間）
      ScheduleExpression: cron(55 0 * * ? *)
      State: ENABLED
      Targets:
        - Arn: !GetAtt MorningFacilitatorFunction.Arn
          Id: lambda
          
  MorningFacilitatorPermission:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !Ref MorningFacilitatorFunction
      Principal: events.amazonaws.com
      SourceArn: !GetAtt FacilitatorEventsRule.Arn
```

#### CLI

デプロイする際は、AWS CLIを使用。
毎回入力するのは面倒なため、shを作成

```deploy.sh
#!/bin/sh

# ソースコードをアーカイブ
rm -fr release && mkdir release
zip -r app.zip index.js node_modules > /dev/null 2>&1
mv app.zip release/

# 必要な情報はベタがきするか、shの引数で対応
AWS_IAM_USER_NAME=$1
AWS_ACCESS_KEY_ID=$2
AWS_SECRET_ACCESS_KEY=$3
AWS_DEFAULT_REGION=$4
AWS_S3_BUCKET=$5
AWS_STACK=$6

aws configure set aws_access_key_id $AWS_ACCESS_KEY_ID --profile $AWS_IAM_USER_NAME
aws configure set aws_secret_access_key $AWS_SECRET_ACCESS_KEY --profile $AWS_IAM_USER_NAME
aws configure set region $AWS_DEFAULT_REGION --profile $AWS_IAM_USER_NAME

aws cloudformation package --template-file template.yml --s3-bucket $AWS_S3_BUCKET --s3-prefix `date '+%Y%m%d%H%M%S'` --output-template-file output.yml --profile $AWS_IAM_USER_NAME
aws cloudformation deploy --region $AWS_DEFAULT_REGION --template-file output.yml --stack-name $AWS_STACK --profile $AWS_IAM_USER_NAME
```

### AWS Chatbot

#### クライアントの設定

AWS Chatbotのコンソールを開き、チャットクライアント「Slack」を選択し、クライアントを設定を押下。
![スクリーンショット 2020-07-22 14.55.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/300430/c81f4d6c-8c26-7371-08b2-5d080ae8206f.png)

権限を求められるので、許可する。
![スクリーンショット 2020-07-22 14.56.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/300430/4d9efebb-e259-daed-60b9-69ebdf8847f9.png)

#### チャンネルの設定

新しいチャンネルを設定。
![スクリーンショット 2020-07-22 14.58.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/300430/d7e0f572-035e-b727-e07e-7539b8ffe481.png)

各種任意の値を入力し登録。
![screencapture-us-east-2-console-aws-amazon-chatbot-home-2020-07-22-15_02_56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/300430/88c8e194-bfd1-da8b-780f-2788baa3267e.png)

以上でAWS側の設定は完了。

## Slack

### SlackからLambda実行

メンバーが揃っているチャンネルで、@awsさんをインバイトする。
```
/invite @aws
```
このように表示されればOK。
<img width="714" alt="スクリーンショット 2020-07-22 15.22.48.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/300430/5b72fc9a-5919-66e7-37d0-9facfdaa56dd.png">

SlackからLambdaの実行。
```
@aws lambda invoke --function-name MorningFacilitatorFunction --region ap-northeast-1
```
すると、ファシリテーターが指名される。
<img width="715" alt="スクリーンショット 2020-07-22 15.27.03.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/300430/78e24128-f591-b471-fd41-3c024ee74460.png">

ただ、毎回このようなコマンドを入力するのは効率が悪いので、Slackワークフロー登録する。

### Slackワークフロー

ワークフロービルダーを開く。
<img width="626" alt="スクリーンショット 2020-07-22 15.31.16.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/300430/8a68b881-7273-ae60-10f6-f0de3c2ead77.png">

作成を押下して、適当な名前を入力。
※ 伸ばし棒が入らない・・・
<img width="524" alt="スクリーンショット 2020-07-22 15.32.36.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/300430/55dcf2c1-3124-4bfa-5b6a-9bbfacc06668.png">

ショートカットを選択、作成。
チャンネル名はメンバーが揃っているチャンネルを選択。
<img width="521" alt="スクリーンショット 2020-07-22 15.32.49.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/300430/afa526f0-be80-d4b6-5774-1163bdd4496a.png">
<img width="520" alt="スクリーンショット 2020-07-22 15.33.25.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/300430/ecb4f43c-fb20-e61e-f0ca-b4b5aa8be544.png">

ステップを追加し、メッセージを送信を選択。
<img width="701" alt="68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3330303433302f62646265323338632d656633302d373332392d306630352d3337386435383331643434662e706e67.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/300430/f2cb176e-ceb4-0576-b55e-a23ad1784a25.png">
<img width="529" alt="スクリーンショット 2020-07-22 15.33.46.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/300430/b1a167e0-3ff7-4062-8a30-cdcc813d8ad4.png">

メッセージの送信先に「ワークフローを開始したチャンネル」
メッセージテキストに`@aws lambda invoke --function-name MorningFacilitatorFunction --region ap-northeast-1`を入力。
<img width="523" alt="スクリーンショット 2020-07-22 15.34.16.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/300430/a38255f1-4846-07c7-219e-50d28d5d0116.png">

保存、そして公開。
<img width="246" alt="スクリーンショット 2020-07-22 15.34.23.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/300430/a4d268b5-8682-3dd6-eb55-593976d66527.png">

これでワークフロー登録完了。

ショートカットから起動することが可能。
<img width="403" alt="スクリーンショット 2020-07-22 15.45.44.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/300430/989b4a26-db51-49c5-3ce3-d5c71fac3a7b.png">

# 運用してみよう

ｶﾀｶﾀｶﾀ...

「...ふぅ。」

「おっとそろそろ朝会だなぁ。」

「今日の担当は・・・？」

![スクリーンショット 2020-07-22 15.51.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/300430/7fcb6e64-1242-71b4-16c1-3e5ae468106d.png)

「病弱太郎か。あれ？あいつ今日病気で休みだったよな・・」

「しょうがない。選ぶか。」

「ショートカットから...えい」

![スクリーンショット 2020-07-22 15.55.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/300430/84aad811-618f-b8b3-623f-22484feefaa1.png)

「ほうほう。朝会大好き子か。よしよし。今日の仕事を頑張ろう」

# まとめ

正直、AWSでやるにはオーバースペック感が否めない。笑
とわ言え、

- 朝会前のTheWordが少なくなる（かも？）
- 興味のあったAWS Chatbotをさわれた

のでよかったかと。

AWS Chatbotは触ってみて、無限の可能性を感じた。
SlackトリガーでAWSのServiceを使用できるので、CI/CDを全てAWS上で構築することができれば、SlackOnlyで業務が回りそう。