JS7® JobScheduler Slack通知設定

# 概要

JS7 JobScheduler にて、WorkFlowの正常終了／エラー時に、Slack通知を送る方法です。
（通知の基本的な説明は省略します）
 CommandFragmentを使った基本的な設定と、通知をカスタマイズする方法を説明します。

# 前提条件

- JS7 jobscheduler の環境準備、初期設定を済ませていること。
まだの方はこちらから
[JS7® JobScheduler 初期設定、サンプルWorkFlowの登録](https://qiita.com/natsumadenikimero/items/2d3162b688329f5ecae2)
- 通知設定の基本的な理解ができていること
こちらの記事でまとめてあります。
[JS7® JobScheduler メール通知設定](https://qiita.com/natsumadenikimero/items/704d3a0e4c07dc84c247)
- Slack通知用のIncoming Webhook のURLを用意してください。
- 本例はJOCがLinux環境を前提としています。<br>WindowsでSlack通知の設定を行うにはCommandFragmentをPowerShell用などに置き換えてください。
- Slack通知に curlコマンドを使用します。<br>Docker sosberlin/js7:joc-2-x-x にはcurlが含まれていません。別途追加してください。
同様に、Docker版ではdateコマンドでの処理用にcoreutilsを追加してください。

# Slack通知 基本設定

まずは、CommandFragment を使った基本的な設定方法でSlack通知設定を行います。

## MessageFragment（本文）

まず通知用のメッセージを定義します。
MessageFragments から、 Add Child Node でMessageを新規作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529537/73f21bdb-51b3-4d44-6e21-aa4239a2d635.png)

名前は `slack_on_success`とします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529537/ef96988b-40f9-092d-95be-e7fc061a4984.png)

本文は例えばこんな感じです。
レイアウトブロックで適当なメッセージを作っています。
モニター変数の箇所は、実際の通知文では値が入ります。
※お好みで加工してください

```json:slack_on_success
{
  "attachments": [
    {
      "color": "#28a745",
      "blocks": [
        {
          "type": "header",
          "text": {
            "type": "plain_text",
            "text": "Workflow: ${MON_O_WORKFLOW_NAME} - ${MON_O_SEVERITY}"
          }
        },
        {
          "type": "section",
          "fields": [
            {
              "type": "mrkdwn",
              "text": "*Controller ID:*\n${MON_O_CONTROLLER_ID}"
            },
            {
              "type": "mrkdwn",
              "text": "*Agent ID:*\n${MON_OS_AGENT_ID}"
            }
          ]
        },
        {
          "type": "divider"
        },
        {
          "type": "section",
          "fields": [
            {
              "type": "mrkdwn",
              "text": "*Order ID:*\n<${JOC_HREF_ORDER}|${MON_O_ORDER_ID}>"
            },
            {
              "type": "mrkdwn",
              "text": "*Order Log:*\n<${JOC_HREF_ORDER_LOG}|Link>"
            },
            {
              "type": "mrkdwn",
              "text": "*Status:*\n${MON_O_SEVERITY}"
            },
            {
              "type": "mrkdwn",
              "text": "*Time elapsed:*\n${MON_O_TIME_ELAPSED}"
            },
            {
              "type": "mrkdwn",
              "text": "*Start Time:*\n${MON_O_START_TIME}"
            },
            {
              "type": "mrkdwn",
              "text": "*End Time:*\n${MON_O_END_TIME}"
            }
          ]
        },
        {
          "type": "divider"
        },
        {
          "type": "section",
          "fields": [
            {
              "type": "mrkdwn",
              "text": "*Workflow Path:*\n<${JOC_HREF_WORKFLOW}|${MON_O_WORKFLOW_PATH}>"
            },
            {
              "type": "mrkdwn",
              "text": "*Job Name:*\n<${JOC_HREF_JOB}|${MON_OS_JOB_NAME}>"
            },
            {
              "type": "mrkdwn",
              "text": "*Job Criticality:*\n${MON_OS_JOB_CRITICALITY}"
            },
            {
              "type": "mrkdwn",
              "text": "*Job Log:*\n<${JOC_HREF_JOB_LOG}|Link>"
            }
          ]
        }
      ]
    }
  ]
}
```

同様に、エラー通知用のメッセージも作成します。
名前は `slack_on_failure` とします。
本文はこんな感じです　(色を変えただけです)

```json:slack_on_failure
{
  "attachments": [
    {
      "color": "#dc3545",
      "blocks": [
        {
          "type": "header",
          "text": {
            "type": "plain_text",
            "text": "Workflow: ${MON_O_WORKFLOW_NAME} - ${MON_O_SEVERITY}"
          }
        },
        {
          "type": "section",
          "fields": [
            {
              "type": "mrkdwn",
              "text": "*Controller ID:*\n${MON_O_CONTROLLER_ID}"
            },
            {
              "type": "mrkdwn",
              "text": "*Agent ID:*\n${MON_OS_AGENT_ID}"
            }
          ]
        },
        {
          "type": "divider"
        },
        {
          "type": "section",
          "fields": [
            {
              "type": "mrkdwn",
              "text": "*Order ID:*\n<${JOC_HREF_ORDER}|${MON_O_ORDER_ID}>"
            },
            {
              "type": "mrkdwn",
              "text": "*Order Log:*\n<${JOC_HREF_ORDER_LOG}|Link>"
            },
            {
              "type": "mrkdwn",
              "text": "*Status:*\n${MON_O_SEVERITY}"
            },
            {
              "type": "mrkdwn",
              "text": "*Time elapsed:*\n${MON_O_TIME_ELAPSED}"
            },
            {
              "type": "mrkdwn",
              "text": "*Start Time:*\n${MON_O_START_TIME}"
            },
            {
              "type": "mrkdwn",
              "text": "*End Time:*\n${MON_O_END_TIME}"
            }
          ]
        },
        {
          "type": "divider"
        },
        {
          "type": "section",
          "fields": [
            {
              "type": "mrkdwn",
              "text": "*Workflow Path:*\n<${JOC_HREF_WORKFLOW}|${MON_O_WORKFLOW_PATH}>"
            },
            {
              "type": "mrkdwn",
              "text": "*Job Name:*\n<${JOC_HREF_JOB}|${MON_OS_JOB_NAME}>"
            },
            {
              "type": "mrkdwn",
              "text": "*Job Criticality:*\n${MON_OS_JOB_CRITICALITY}"
            },
            {
              "type": "mrkdwn",
              "text": "*Job Log:*\n<${JOC_HREF_JOB_LOG}|Link>"
            }
          ]
        }
      ]
    }
  ]
}
```

## MonitorFragment (通知手段）

次にSlack通知用のコマンドの設定を行います。
MonitorFragment より、CommandFragment を作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529537/e4346aaf-5ecb-9ddb-db4f-cb610c9c3d6d.png)

まず正常時の通知設定を作ります。
名前は`command_slack_on_success`とします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529537/6502137c-3df3-9402-0564-5a80738a6e9d.png)

MessageRef (どのMessageを参照するか)で先ほど作ったメッセージ`slack_on_success`を選びます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529537/513ad9a3-6676-03d5-3683-120a13545895.png)

Command にて、Slack通知用のコマンドを登録します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529537/7fdc269a-e032-1b05-a12a-5207d6fe2c04.png)

```shell:command_slack_on_success
curl -s -X POST -H "Content-type: application/json" --data '${MESSAGE}' https://hooks.slack.com/services/xxxxxxxx
```

ここで`${MESSAGE}`は、直前の MessageRef にて選択したMessageの内容が含まれます。

:::note warn
`${MESSAGE}`はシングルクォートで囲ってください。（ダブルクォートはNG）
コマンド実行時は、`${MESSAGE}`および本文中のモニター変数が最初から展開された状態となります。
シェル変数、環境変数と、通知設定で使うモニター変数とでは、展開されるタイミングが異なります。
:::

同様に、エラー時の通知設定を作成してください。

|項目|値|
|-|-|
|name|command_slack_on_failure|
|MessageRef|slack_on_failure|
|Command|`curl -s -X POST -H "Content-type: application/json" --data '${MESSAGE}' https://hooks.slack.com/services/xxxxxxxx`|

## Notifications（ステータスと設定の関連付け）

既存の設定に、Slack通知を追加します。

Notification `notify_on_success` の NotificationMonitors にて、CommandFragmentRef を新たに追加してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529537/7467c4fa-5816-5e6a-c880-8cbcf9991224.png)

CommandFragmentRef にて、先ほど作った command_slack_on_success を選びます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529537/319efc77-5614-1fef-b6f1-c7d2f0d3f55d.png)

NotificationObjects － WorkflowsRef に通知対象の設定が選択されていることをご確認ください。
※デフォルトはnone(対象なし)となっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529537/0a3fb0bd-e64a-8eda-906c-f91f9dd85a99.png)

同様に、エラー時の設定にもSlack通知を追加してください。

|項目|値|
|-|-|
|name|notify_on_failure|
|CommandFragmentRef|command_slack_on_failure|

# Slack通知 カスタマイズ設定

基本設定の方法では気になる点がいくつかありました。

- 時刻がUTCとなってしまう（Ver 2.3以前のみ）、日時表記を調整したい
- わずかな違い（色違い）のメッセージを２つ作る必要がある
- エラーメッセージをうまく表記しづらい（空文字だとSlackでエラーとなる）
- jocのリンク内容がいまひとつ
  - httpsで表示されるべきところがhttpとなる。
  - ドメイン名であるべきところがIPアドレスとなる。
  - フロントでport 80, 443 でアクセスするようにしても port 4446となってしまう。

ここでは、CommandFragment の定義にカスタマイズ用の処理を記述することで、上記の問題をクリアした通知設定を行います。

## MessageFragment（本文）

新規にMessageを作成してください。
名前は`slack_common`とします。
本文はこんな感じです。

```json:slack_common
{
  "attachments": [
    {
      "color": "$color",
      "blocks": [
        {
          "type": "header",
          "text": {
            "type": "plain_text",
            "text": "Workflow: ${MON_O_WORKFLOW_NAME} - ${MON_N_TYPE}"
          }
        },
        {
          "type": "section",
         "text": {
            "type": "mrkdwn",
            "text": "${TMP_MSG}"
          },
          "fields": [
            {
              "type": "mrkdwn",
              "text": "*Controller ID:*\n${MON_O_CONTROLLER_ID}"
            },
            {
              "type": "mrkdwn",
              "text": "*Agent ID:*\n${MON_OS_AGENT_ID}"
            }
          ]
        },
        {
          "type": "divider"
        },
        {
          "type": "section",
          "fields": [
            {
              "type": "mrkdwn",
              "text": "*Order ID:*\n<${TMP_JOC_HREF_ORDER}|${MON_O_ORDER_ID}>"
            },
            {
              "type": "mrkdwn",
              "text": "*Order Log:*\n<${TMP_JOC_HREF_ORDER_LOG}|Link>"
            },
            {
              "type": "mrkdwn",
              "text": "*Status:*\n${MON_O_SEVERITY}"
            },
            {
              "type": "mrkdwn",
              "text": "*Time elapsed:*\n${MON_O_TIME_ELAPSED}"
            },
            {
              "type": "mrkdwn",
              "text": "*Start Time:*\n${TMP_MON_O_START_TIME}"
            },
            {
              "type": "mrkdwn",
              "text": "*End Time:*\n${TMP_MON_O_END_TIME}"
            }
          ]
        },
        {
          "type": "divider"
        },
        {
          "type": "section",
          "fields": [
            {
              "type": "mrkdwn",
              "text": "*Workflow Path:*\n<${TMP_JOC_HREF_WORKFLOW}|${MON_O_WORKFLOW_PATH}>"
            },
            {
              "type": "mrkdwn",
              "text": "*Job Name:*\n<${TMP_JOC_HREF_JOB}|${MON_OS_JOB_NAME}>"
            },
            {
              "type": "mrkdwn",
              "text": "*Job Criticality:*\n${MON_OS_JOB_CRITICALITY}"
            },
            {
              "type": "mrkdwn",
              "text": "*Job Log:*\n<${TMP_JOC_HREF_JOB_LOG}|Link>"
            }
          ]
        }
      ]
    }
  ]
}
```

ポイントは、モニター変数以外に、カスタマイズ用に、シェル変数を使っていることです。
例：`$color`, `${TMP_MSG}` 時刻、リンク関連の変数`${TMP_MON_O_XXX}`, `${TMP_JOC_XXX}`

## MonitorFragment (通知手段）

新規にCommandFragment を作成してください。
名前は `command_slack_common`とします。
MessageRef は `slack_common`を選んでください。

コマンドは下記のようになります。

```shell:command_slack_common
slack_url="https://hooks.slack.com/services/xxxxxxxx"
rep_url="https://js7.saitamanokusa.com"

# status で色分け  0-OK, 1-WARNING, 2-CRITICAL, 3- UNKNOWN
case "${MON_N_STATUS}" in
  0 ) color="#28a745" ;;
  1 ) color="#ffc107" ;;
  2 ) color="#dc3545";;
  * ) color="#17a2b8";;
esac

# メッセージがある場合
[ -n "${MON_O_ERROR_TEXT}" ] && TMP_MSG="${TMP_MSG}*ErrorMessage:* ${MON_O_ERROR_TEXT}"
[ -n "$TMP_MSG" ] && TMP_MSG="${TMP_MSG}\n"
[ -n "${MON_OS_ERROR_TEXT}" ] && TMP_MSG="${TMP_MSG}*Error:* ${MON_OS_ERROR_TEXT}"
[ -n "$TMP_MSG" ] && TMP_MSG="${TMP_MSG}\n"
[ -n "${MON_N_WARN_TEXT}" ] && TMP_MSG="${TMP_MSG}*Warning:* ${MON_N_WARN_TEXT}"
[ -z "$TMP_MSG" ] && TMP_MSG=" "

# URL 修正
TMP_JOC_HREF_ORDER=$(echo "${JOC_HREF_ORDER}" | sed -r "s|^http(s)?://[^/]+?|$rep_url|g")
TMP_JOC_HREF_JOB_LOG=$(echo "${JOC_HREF_JOB_LOG}" | sed -r "s|^http(s)?://[^/]+?|$rep_url|g")
TMP_JOC_HREF_ORDER_LOG=$(echo "${JOC_HREF_ORDER_LOG}" | sed -r "s|^http(s)?://[^/]+?|$rep_url|g")
TMP_JOC_HREF_WORKFLOW=$(echo "${JOC_HREF_WORKFLOW}" | sed -r "s|^http(s)?://[^/]+?|$rep_url|g")
TMP_JOC_HREF_JOB=$(echo "${JOC_HREF_JOB}" | sed -r "s|^http(s)?://[^/]+?|$rep_url|g")

# TimeZone反映、日付表記調整
[ -n "${MON_O_START_TIME}" ] && TMP_MON_O_START_TIME=$(env TZ=JST-9 date -d"${MON_O_START_TIME}" +"%F %T %Z")
[ -n "${MON_O_END_TIME}" ] && TMP_MON_O_END_TIME=$(env TZ=JST-9 date -d"${MON_O_END_TIME}" +"%F %T %Z")
# Ver 2.3.x 以前はこちら
# [ -n "${MON_O_START_TIME}" ] && TMP_MON_O_START_TIME=$(env TZ=JST-9 date -d"${MON_O_START_TIME}Z" +"%F %T %Z")
# [ -n "${MON_O_END_TIME}" ] && TMP_MON_O_END_TIME=$(env TZ=JST-9 date -d"${MON_O_END_TIME}Z" +"%F %T %Z")


# Slack通知
json=$(cat << EOS
${MESSAGE}
EOS
)
curl -s -X POST -H "Content-type: application/json" --data "$json" ${slack_url}
```

- `${MON_N_STATUS}`が Notification設定のtype `SUCCESS`,`WARNING`,`ERROR`該当します。
この値で色分けをします。
- Error,Warning時のメッセージを追加します。空の場合はSlackエラー防止でスペースを入れます。
- JOCの各リンク用URLを`$rep_urlで`置換します。※この処理が不要な場合は消してください。
- 日時の表記を`yyyy-mm-dd hh:mm:ss JST`となるように調整します。（お好みで修正してください）
※busybox date コマンドでは `date -d` の値によってはエラーとなるため、`coreutils` の追加インストールが必要。
※ver 2.4以降は値にTimeZoneが付くため、利用バージョンに応じて処理は変更してください。
- `${MESSAGE}`はシェル変数を含む複数行が展開されるため、ヒアドキュメント内に納まるようにしています。

コマンドの処理の内容は、`service-monitor.log`で確認できます。

### 参考 モニター変数と環境変数の使い分け

こちらでも説明してますが、モニター変数は、環境変数`$JS7_XXX`でも同じ値を参照できます。
[JS7® JobScheduler メール通知設定](https://qiita.com/natsumadenikimero/items/704d3a0e4c07dc84c247)

モニター変数は展開タイミングが特殊であるため、慣れが必要です。
最初は、環境変数`$JS7_XXX`のほうが扱いやすいかと思います。

モニター変数を使うことで、`service-monitor.log`にて、モニター変数が展開された（値が入った）、実際に実行されるコマンド内容が出力されます。そのため具体的にどのコマンドの部分でエラーになっているか、原因が掴みやすくなります。
※環境変数では展開されない。
そのため、動作に慣れてきたら、モニター変数を使うのも便利です。

## Notifications（ステータスと設定の関連付け）

Notification の正常時、エラー時の設定にSlack通知を追加してください（もしくは置き換え）

|項目|値|
|-|-|
|name|notify_on_success|
|CommandFragmentRef|command_slack_common

|項目|値|
|-|-|
|name|notify_on_failure|
|CommandFragmentRef|command_slack_common

以上で設定終了です。

WorkFlowを実行して、正しく通知が飛ぶことを確認してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529537/edfdcca8-a057-68bb-50b9-f5301571ebd6.png)

動作チェックには下記のサンプルWorkflowを実行しています。

- Success時 01_HelloWorld/jduHelloWorld
- Warning時 04_ErrorHandling/jduWarnIfShorterThan
- Error時 04_ErrorHandling/jduFail

:::note warn
jocをdocker版で起動している場合は、curlコマンドが入っているか確認してください。
もしなければ`apk add curl`で追加してください。
また、dateコマンドでの処理用にcoreutilsも追加してください。 `apk add coreutils`
:::

# 最後に

今回で行ったように、CommandFragmentを使った通知のカスタマイズ設定に慣れれば、通知系はもちろん、監視、ログ転送などにも応用ができそうです。