現職場では、デイリースタンドアップとして、朝会を実施している。
そして、いつも開始日時になると、
「え、今日・・・司会担当・・・だれ・・・?」
とTheWordになる。
※TheWord ・・・ リモート会議において、 無音が続き、最初に話した人がファシリテーターをしなければいけない空間のこと
一応、SlackBotを使用して、それとなく機械的にファシリテーターを指名しているのだが、
「この人お休みだよ〜」 や 「昨日もやったんですけど〜」 とか
いちいち面倒臭い。
よし。朝会のファシリテーターをいい感じ(前回当たった人は当たらない、スムーズに再抽選ができる、自分が選ばれない)に決めるToolを作ろう。
はじめに
コミュニケーションToolはSlack
プラットフォームはAWSとする。
構成
朝会の担当者をランダムに選択し、Slackに通知するTool。
実行トリガーは2種類存在する。
- CloudWathEventsから定期的(朝会の始まる5分前)に実行
- Slack→AWS ChatBotから実行
DynamoDBではファシリテーター担当履歴を管理。
(現時点では直近の担当者しか保存していない。)
構築
AWS
ChatBot以外はCloudFormationで構築。
IAM等のユーザーは事前に発行しておくこと。
ソースコード
Lambda
ソース
Node.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)
})
}
内容
本来なら並列にできる部分もあるが、わかりやすく直列化している。
流れとしては
- 直近のファシリテーターを取得
- ファシリテーターをランダムに取得(直近のファシリテーターは除く)
- Slackに担当者を通知
- 2.で選ばれたファシリテーターを登録
といったシンプルなもの。
2.のファシリテーター取得ロジック部分で担当者ごとに重みをつけて、選ばれやすさを制御したり、自身だけ選ばれづらくするなどのイカサマはできそう。
CloudFormation
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を作成
#!/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」を選択し、クライアントを設定を押下。
チャンネルの設定
以上でAWS側の設定は完了。
Slack
SlackからLambda実行
メンバーが揃っているチャンネルで、@awsさんをインバイトする。
/invite @aws
SlackからLambdaの実行。
@aws lambda invoke --function-name MorningFacilitatorFunction --region ap-northeast-1
ただ、毎回このようなコマンドを入力するのは効率が悪いので、Slackワークフロー登録する。
Slackワークフロー
作成を押下して、適当な名前を入力。
※ 伸ばし棒が入らない・・・
ショートカットを選択、作成。
チャンネル名はメンバーが揃っているチャンネルを選択。
メッセージの送信先に「ワークフローを開始したチャンネル」
メッセージテキストに@aws lambda invoke --function-name MorningFacilitatorFunction --region ap-northeast-1
を入力。
これでワークフロー登録完了。
運用してみよう
カタカタカタ...
「...ふぅ。」
「おっとそろそろ朝会だなぁ。」
「今日の担当は・・・?」
「病弱太郎か。あれ?あいつ今日病気で休みだったよな・・」
「しょうがない。選ぶか。」
「ショートカットから...えい」
「ほうほう。朝会大好き子か。よしよし。今日の仕事を頑張ろう」
まとめ
正直、AWSでやるにはオーバースペック感が否めない。笑
とわ言え、
- 朝会前のTheWordが少なくなる(かも?)
- 興味のあったAWS Chatbotをさわれた
のでよかったかと。
AWS Chatbotは触ってみて、無限の可能性を感じた。
SlackトリガーでAWSのServiceを使用できるので、CI/CDを全てAWS上で構築することができれば、SlackOnlyで業務が回りそう。