Slack AppHomeでAWS環境構築モーダル作ってみた

[Slack AppHome](https://api.slack.com/lang/ja-jp/app-home-with-modal)は、アプリホーム画面を作れて便利ですね。
今回はAWS環境を構築する機能を作ってみました。
実装時のポイントなど書いていきます！これからAppHome使う方の参考になれば幸いです！

## なぜ作ったのか
私の現場ではEC2を使って複数のプロジェクト環境を作ることがあります。ただ、今まではAWSコンソールからEC2/CloudWatchなどを設定する運用になってました。
環境構築手順は、CloudFormationでテンプレート化できますが、それでもAWSコンソールログインなど手間ですね。そこで、みんな慣れているSlackから環境作れるようにしました。

## 完成イメージ
![aws-bot(CreateEC2).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/174816/4972a33e-c860-5875-60a2-2881d1b7f3ee.gif)

AppHomeのボタンを押すと、モーダルが表示する仕組みにしました。
モーダルへの入力は、操作を簡略化したかったのでAMI選択程度にしました。
そして、処理開始と終了の連絡を特定のチャンネルに飛ばすようにしました。  
(上のgifはデモ用なので、ami名やEIPなどは固定ですが、本物はami名/EIP取得して表示してます)

## 構成
![slack_app_home.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/174816/b50ad9f0-95ea-6b16-e7de-21c0e22b9b2c.jpeg)


AWS Lambda(nodejs)で処理する形にしました。
今回のケースでは、4回イベントを処理するタイミングがあります。
それぞれの処理内容を説明します。

### 1.AppHome画面表示
AppHomeへの内容表示は、`SlackAPI(views.publish)`で行います。
一度送れば何度でも表示できます。ただ、表示内容を更新したい場合もあると思うので、AppHome表示時の`app_home_opened`イベントをSubscribeして、毎回Publishして良いと思います。(subscribeには、自身のSlackアプリメニューのevent subscriptionsより購読設定ください。[参考：Using the Slack Events API](https://api.slack.com/events-api#subscriptions))  
以下のtypeでイベント内容を判別できます。

```JSON
{
  "body":{
    "event":{
      "type":"app_home_opened"
    }
  }
}
```

`views.publish`は以下のようなイメージです。

```javascript
const home = {
    "type":"home",
    "blocks": [{
      "type": "section",
      "text": {
        "type": "mrkdwn",
        "text": "*Welcome!!* \nDo you want to create EC2?????"
      },
      "accessory": {
        "type": "button",
        "action_id": "固有のaction_ID",
        "text": {
          "type": "plain_text",
          "text": "createEC2"
        }
      }      
    }]
  }
const args = {
  'user_id': "SlackのユーザID",
  'view': JSON.stringify(home)
  'token': "slackアプリ登録時のbot token"
}
await viewsPublish(args)
```
SlackのユーザIDは、SlackAPI`users.info`を使えば、ユーザ名から取得できます。
[参考：users.info](https://api.slack.com/methods/users.info)
`action_id`については後述します。

### 2.ボタン押下(AppHome)
ボタンはInteractiveComponentなので、まずは`InteractiveComponent`にRequestURLの登録が必要です。(https://api.slack.com/interactivity/handling)  
Interactivityから送られるPayloadには`trigger_id`があります。これを使うことで操作元に反応を返すことができます。なお`trigger_id`の有効時間は3秒です。3秒以内に返さないと反応してくれません。
以下のようにモーダル内容とtrigger_idを`views.open`に渡すとモーダル表示できます。[参考：views.open](https://api.slack.com/methods/views.open)  

```javascript
const modal = {
  "type": "modal",
  "title": {
    "type": "plain_text",
    "text": "CreateEC2"
  },
  "blocks": [
    {
      "type": "input",
      "block_id": "固有のBlockID",
      "element": {
        "type": "static_select",
        "action_id": "固有のActionID",
        "placeholder": {
          "type": "plain_text",
          "text": "Select a item",
          "emoji": true
        },
        "options":[
          ///
        ]
      },
    },
      ///
  ],
  "close": {
    "type": "plain_text",
    "text": "Cancel"
  },
  "submit": {
    "type": "plain_text",
    "text": "Save",
  },
}
const args = {
  'trigger_id': "受け取ったtrigger_id",
  'view': JSON.stringify(modal)
  'token': "slackアプリ登録時のbot token"
}
await viewsOpen(args)
```
ここでポイントは、`block_id`と`action_id`にそれぞれ一意な値を設定することです。
このあとモーダルから入力値を抽出するときにこの２つが必要となります。

### 3.モーダルOK押下(AppHome)
モーダルOKを押すと、typeが`view_submission`のPayloadが送られてきます。
また、`block_id`、`action_id`の名称からモーダルの入力項目を取り出せます。
Payload中では以下のように入力値が格納されています。

```JSON
{
  "view":{
    "state":{
      "values":{
        "設定したblock_id":{
          "設定したaction_id":{
            "value": "入力値"  // テキスト入力の場合
          }
        },
        "設定したblock_id":{
          "設定したaction_id":{
            "selected_option":{
              "value": "選択値"  // リスト選択の場合
            }
          }
        },
      }
    }
  }
}
```

#### 3-1.スタック作成
AWS-SDKからCloudFormation([createStack](https://docs.aws.amazon.com/AWSCloudFormation/latest/APIReference/API_CreateStack.html))をコールします。
このとき、`createStack`の`NotificationARNs`パラメータにSNSトピックARNを設定すると、スタック作成のイベント受け取れます。
スタック作成開始時と終了時に時間差でSlackチャンネル通知してあげると、裏でBotが頑張ってた感があって好きです^^
snsから来るイベントは以下のような感じです。

```JSON
{
    "Records": [
        {
            "EventSource": "aws:sns",
            "EventVersion": "1.0",
            "EventSubscriptionArn": "arn:XXXXX",
            "Sns": {
                "Type": "Notification",
                "MessageId": "XXXXX",
                "TopicArn": "arn:XXXXX",
                "Subject": "AWS CloudFormation Notification",
                "Message": "StackId=XXXXXX",
                "Timestamp": "2020-XX-XXT02:50:11.963Z",
                "SignatureVersion": "1",
                "Signature": "XXXXXX",
                "SigningCertUrl": "XXXX",
                "UnsubscribeUrl": "XXXX",
                "MessageAttributes": {}
            }
        }
    ]
}
```
その通知が処理中なのか、完了なのかは`Message`内容から判別しました。
上記は割愛してますが、メッセージ内には様々な情報が記載されています。このメッセージをパースすることで情報を取り出せます。(他にいい方法あれば教えて欲しいです・・私はsns初めてだったのでこれしか思いつきませんでした・・)

#### 3-2.モーダルクローズ
モーダルを使う際は画面クローズも考慮必要です。
OKボタンを押しても画面クローズを明示的に指定しないとモーダルは閉じません。
ステータス`200`でresponse`{"response_action": "clear"}`を返せば、モーダルが閉じてくれます。
[参考：Closing views](https://api.slack.com/surfaces/modals/using#closing_views)
Chancelボタンと×ボタンはResponseなしでも閉じてくれます。
これらの操作イベントを受けたい場合は、モーダル作成時に`notify_on_close:true`を指定すると`view_closed`イベントを受け取れます。

### 4.スタック作成完了
前述したスタック作成完了の通知ですね。
SNS通知内にはStackNameがあるので、AWS-SDKの`describeStacks`や`describeStackResources`でリソース詳細などを取得できます。
Slackへ通知メッセージは、[BlockKitBuilder](https://api.slack.com/block-kit)でいい感じのメッセージを作ってあげるといいですね。テンプレートも用意されているので、参考になります。

## おわりに
SlackAppHomeを使ってAWS環境構築を試してみました。
AppHomeなどを使ってみて、ChatOpsを実現すれば運用負荷が減り、チームが本業に集中できますね。

あとAppHomeは、Slackのスマホアプリからも使えるのがいいですね！
SlackAPIがボタンやモーダルなど使いやすいコンポーネントを用意してくれている+スマホ版表示はやってくれるので、スマホ対応方法を考えなくてよいです^^
これで、電車で移動中にポチポチで環境構築できます^^

実装方法がわかれば簡単なので、ぜひいろいろ作ってみてください^^