AzureとMattermostでChatOpsを実現する

## 概要

MattermostとAzureのサービスを連携してChatOpsをしたいと思い、サクッとできるんじゃないかと思っていたら意外と苦労したためその方法をメモします

## 目標

* Mattermostからチャット(カスタムスラッシュコマンド)で何か操作を行えるようにする

## 前提

* WSL 2(ubuntu 18.04)
* Mattermost v5.18.0
* Azure Function v3(Powershell)

## MattermostとAzureの組み合わせ

AzureのサービスではLogicAppsがローコードでいけて良さそうと思ったんですが・・

### LogicAppsと組み合わせてみる

愚直にMattermostと組み合わせるとダメでした。カスタムスラッシュコマンド実行時に付与される認証とLogicAppsの認証が混在しLogicAppsの実行に401 UnAuthorizedで失敗します。[こちら](https://www.tech-findings.com/2020/02/The-request-has-both-SAS-authentication-scheme-and-Bearer-authorization-scheme.html)が詳しく解説してくれています。わたしはあきらめた。。

### Functionと組み合わせてみる

![slash.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133866/fe8e48a1-8b2c-6799-030f-45a2522507d6.png)

Functionも認証機能を持ちますが設定によってanonymousアクセスを有効(関数内で認証機能を独自に実装する必要あり)にできるのでこれでやります。

## Mattermostカスタムスラッシュコマンドとは

Mattermostのチャット上で `/<slash_command名>`と発言すれば、任意の場所にデプロイしたプログラムをHttp経由で起動できるものです。これを利用すればチャット上から楽に運用操作ができます。
詳しくは[公式ドキュメント](https://docs.mattermost.com/developer/slash-commands.html)を参照してください。

## ローカル環境を構成する

Functionを開発する環境を構成します。Mattermostは適宜準備ください。
開発はCLIベースで行います。

* [Azure Functions Core Tools](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-run-local?tabs=linux%2Ccsharp%2Cbash#v2)をインストールする
* [Azure CLI](https://docs.microsoft.com/ja-jp/cli/azure/install-azure-cli?view=azure-cli-latest)をインストールする
* [.NET Core SDK](https://docs.microsoft.com/ja-jp/dotnet/core/install/linux-ubuntu#1804-)をインストールする

## Azure Functionを開発する

[公式ドキュメント](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-create-first-azure-function-azure-cli?tabs=bash%2Cbrowser&pivots=programming-language-powershell)を参考にAzure Functions Core Toolsを使ってHttpTrigger templateをベースに開発をします。

### プロジェクトを作成する

HttpTrigger templateを使ってコードを生成します。
[公式ドキュメント](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-create-first-azure-function-azure-cli?tabs=bash%2Cbrowser&pivots=programming-language-powershell)に従って開発してください。

### Functionの認証機能をanonymousにする

カスタムスラッシュコマンドに認証機能があるため、Functionの認証機能は無効にします。
前述したMattermostの[公式ドキュメント](https://docs.mattermost.com/developer/slash-commands.html)よりカスタムスラッシュコマンドを発行したとき、Functionを呼び出すリクエストに認証Tokenを含めることができます。
生成したコードに修正を加えます。`function.json`を以下のようにします。

```diff:LocalFunctionProj/HttpExmple/function.json
 {
   "bindings": [
     {
-      "authLevel": "function",
+      "authLevel": "anonymous",
       "type": "httpTrigger",
       "direction": "in",
       "name": "Request",
       "methods": [
         "get",
         "post"
       ]
     },
     {
       "type": "http",
       "direction": "out",
       "name": "Response"
     }
   ]
 }
```

### カスタムスラッシュコマンドの実行処理を記載する

自動生成した`run.ps1`を以下のようにします。レスポンスボディの仕様は[Slash Commands parameter](https://developers.mattermost.com/integrate/slash-commands/#parameters)を参照してください。
以下の例では実処理は何もしておらず、Mattermostに多少リッチにレスポンスするコードを加えています。これは[Attachment機能](https://docs.mattermost.com/developer/message-attachments.html#example-message-attachment)を利用します。
また認証機能としてFunctionAppの環境変数(アプリケーション構成)に設定されたTokenとHttpリクエストのTokenヘッダが一致するかをチェックします。
TokenはMattermostにカスタムスラッシュコマンドを登録したときに自動的に発行されます（後述）。

```powershell:LocalFunctionProj/HttpExmple/run.ps1
using namespace System.Net

param($Request, $TriggerMetadata)

$green = "#44ff1f"
$red = "#FF0000"
$retStatus = [HttpStatusCode]::OK
$retBody = @{
    "response_type"="in_channel"
    "attachments"=@(
        @{
            "title"= "Result"
            "color"=$green
            "text"="message"
        }
    )
}

$authorizationHeader = $Request.Headers.Authorization
$token = $authorizationHeader.split(' ')[1]
if ($token -eq $env:MattermostCustomSlashToken) {
    $retBody["attachments"][0]["text"] = "Success!"
} else {
    $retBody["attachments"][0]["text"] = "Invalid token is set"
    $retBody["attachments"][0]["color"] = $red
    $retStatus = [HttpStatusCode]::UnAuthorized
}

Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
    StatusCode = $retStatus
    Body = $retBody | ConvertTo-Json | Out-String
})
```

### Functionをデプロイする準備を行う

AZ CLIを使って構築します。事前に`az login`コマンドで認証する必要があります。
リソースグループ名など名前はお好みで変えてください。

#### ストレージアカウントを構築する

```bash
az storage account create \
  --name sttestslash \
  --location japaneast \
  --resource-group rg-test-slash-jpeast \
  --sku Standard_LRS
```

#### FunctionApp(v3)を構築する

```bash
az functionapp create \
  --resource-group rg-test-slash-jpeast \
  --consumption-plan-location japaneast \
  --runtime powershell \
  --functions-version 3 \
  --name func-pwshfunc-jpeast \
  --storage-account sttestslash
```

#### 開発した関数をFunctionAppにデプロイする

```bash
func azure functionapp publish func-pwshfunc-jpeast
```

うまくいくと以下のようになります。Invoke urlは後述の手順で利用します。

```console
# func azure functionapp publish func-pwshfunc-jpeast      
Getting site publishing info...
Creating archive for current directory...
Uploading 5.18 KB [###########################################] 
Upload completed successfully.
Deployment completed successfully.
Syncing triggers...
Functions in func-pwshfunc-jpeast:
    HttpExample - [httpTrigger]
        Invoke url: https://func-pwshfunc-jpeast.azurewebsites.net/api/httpexample
```

## MattermostのカスタムスラッシュコマンドをAzure Functionと統合する

デプロイしたFunctionとMattermostを統合します。

### カスタムスラッシュコマンドを作成する

[Mattermost公式ドキュメント](https://docs.mattermost.com/developer/slash-commands.html#custom-slash-command)を参照して作成する

* ホーム > 統合機能> スラッシュコマンド を選択

![スクリーンショット 2020-09-15 181235.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133866/ce1a4fdf-0169-7b9d-e9c3-fa0743478406.png)

![スクリーンショット 2020-09-15 181342.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133866/992e33dd-a916-d56a-1e61-e7fef9304626.png)

* 最低限の入力は以下に示したパラメータとなります
    * タイトル: 任意のタイトル
    * コマンドトリガーワード: コマンド名（頭にスラッシュは不要）
    * リクエストURL: カスタムスラッシュコマンド用に開発したFunctionの`Invoke url`

![スクリーンショット 2020-09-15 181421.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133866/882bd7a5-fdf3-53e4-235b-e6bdb2c14557.png)

* 保存し設定完了したらトークンを控えます。この文字列で認証を行います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133866/9c9f8a22-4bdc-23e3-5af4-f112bfd62e21.png)

### TokenをFunctionAppに設定する

* Azureポータルより作成したFunctionAppにアクセスし、新しいアプリケーション設定を追加します

![スクリーンショット 2020-09-15 183617.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133866/58ddc90a-1bb8-864c-5571-686baf6e52b9.png)

* `MattermostCustomSlashToken`とう名前で追加し値はTokenを設定します

![スクリーンショット 2020-09-15 183742.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133866/d869a4ef-d7c8-a556-ad5a-0f89b05bbe8a.png)

### Mattermostからカスタムスラッシュコマンドを呼び出す

チャットに `/`付きでコマンドを打ち込み、Enterで実行します

![スクリーンショット 2020-09-15 184312.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133866/9715a4f6-a93c-3b39-88b6-270de6575d1f.png)

以下のように処理結果が返却されれば成功です！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133866/8d9d13be-66c7-9410-de24-0721d1be1d0e.png)

あとはFunctionを実装すれば、VM起動・停止とかMattermostから操作ができますね！

以上