z/OSからSlackへのメッセージ送信

# はじめに
z/OSのJOBLOGをSlackに送信するってのをやっている人がいました。
こちら=> [zOS_Joblog_to_Slack](https://github.com/Tomo-92/zOS_Joblog_to_Slack/blob/master/README.ja.md)

Slackにメッセージ送信するのは結構簡単にできそうだったので、自分でもやってみようかなと思い、試してみました。
上のサンプルはJavaからSlackのAPIを発行するようになっていますが、個人的にコンパイラ言語を使うのが最近は鬱陶しく感じられるので、もうちょっとお手軽に試せるように、USS上のシェル・スクリプトからSlackにメッセージを送信するための部品を作ってみました。

# 参考情報
[zOS_Joblog_to_Slack](https://github.com/Tomo-92/zOS_Joblog_to_Slack/blob/master/README.ja.md)
[Slack API](https://api.slack.com/)
[Slack API- Messaging](https://api.slack.com/messaging)
[Slack API - Your Apps](https://api.slack.com/apps)
[Slackにファイルをアップロードする](https://qiita.com/yaju/items/2e1ab8a25b6e207bfbe6)
[Slack でのIncoming Webhook の利用](https://slack.com/intl/ja-jp/help/articles/115005265063)

# 全体像
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132331/d6695ea6-01a1-c326-5fba-da555bdc8413.png)


# 前提

## Slack API

Web APIが使えるようにする必要があります。試した手順は以下に記載しますが、メッセージを送付したいワークスペースにアプリを作成して、Web API発行時に指定するTokenを取得するというようなことをやる必要があります。
冒頭のリンクのサンプルでは[Incomming Webhook](https://slack.com/intl/ja-jp/help/articles/115005265063)という簡易的にメッセージ送信を行える仕組みを使っているようですが、ここではIncomming Webhookではなく、chat.postMessage, files.uploadを利用しています。
参考: [Sending messages - Many ways to send messages](https://api.slack.com/messaging/sending#sending_methods)

## z/OS USS

ここではUSS(Unix System Service)上のシェルからSlackにメッセージを送信することを想定しています。(以前作成した[Lightweight Command Utility on z/OS USS](https://qiita.com/tomotagwork/items/bb7f9592658389d2c5c6)と連携して活用することを念頭においているので。)
シェル・スクリプトからSlackのAPIを手っ取り早く発行する方法として、curlコマンドを使います(シェルからHTTPのリクエストを発行するためのコマンド)。ただし、curlコマンドはUSSではデフォルトでは提供されていないようで、Rocket社提供のものをインストールしておく必要があります。以下からフリーで入手可能です(要ユーザー登録)。
参考: [cURL for z/OS](https://www.rocketsoftware.com/ja/product-categories/mainframe/curl-for-zos)

今回試した環境
z/OS V2.3
cURL for z/OS V7.52.1

```
[CICS004@EPLEX1:/] curl --version
curl 7.52.1 (i370-ibm-openedition) libcurl/7.52.1 OpenSSL/1.0.2l zlib/1.2.11 libssh2/1.8.0 nghttp2/1.18.1
Protocols: dict file ftp ftps gopher http https imap imaps pop3 pop3s rtsp scp sftp smb smbs smtp smtps telnet tftp
Features: IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz TLS-SRP HTTP2 UnixSockets HTTPS-proxy Metalink
```



<Font color="gray">※余談: なるべく追加のコンポーネントは無しでやりたかったのですが、curlくらいはあってもいいと思うし、お手軽さを考えて、今回はこれを前提とします。というか、API化だのモダナイゼーションだのデジタルトランスフォーメーションだのと言ってる割りに、こういうツールがデフォルトで使えるようになっていないというのがイケてないんだよなぁ。curlとかgzipとかbashとか、オープン系だとごくふつーーーーに使えるものが使えないってのが、本当にフラストレーション溜まります。こういうのがデフォルトで使えるだけで、いろんなものとの連携が格段にやりやすくなって世界が広がると思うんですけど。色々大仰なことをやる前にこういうところ整備するのが先だと思うんだけどなぁ。</font>

また、メインフレームはネットワーク上セキュアな環境にあることが多いと思いますので、ダイレクトに外のサーバー(Slackのサーバー)にアクセスできないことが多いと思います。今回使っているテスト環境もダイレクトに外の環境とは接続できません。そのため、上に示した図の通り、今回は外部にアクセス可能な所に一時的にProxyサーバーを立てて、そこを経由してSlack APIを発行しています。(Proxy Server部分の詳細はここでは割愛しますが、一般的なHTTPSの通信ができるProxyがあればよいはずです。)

# 設定/実装
さて、ここからは実際にz/OS USS上のシェルスクリプトからSlackにメッセージを送信するまでの、一連の手順例を記載していきます。

## Slack側設定

メッセージ送付対象のワークスペースやアカウントは設定済みの想定です。

### (1)アプリケーション作成

以下にアクセスします。
https://api.slack.com/apps

Slackのアカウントにサインインしていないと、以下の画面になると思うので、対象のワークスペースにサインインした後、再度上のURLにアクセスします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132331/023a6f73-60c9-c1a1-3ace-327b1918a0b1.png)

サインインしている場合、以下のような画面になるので、Create New Appをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132331/414d9058-2022-da75-5f1a-574cab2b8ee7.png)

アプリの名前と対象のワークスペース名を指定してCreate Appをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132331/eb124b03-7ffc-a970-eaf9-479cddd139bf.png)

### (2)権限設定

Permissionsを選択して権限設定を行います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132331/31566cfe-db28-bf32-b266-b8fc6f207f19.png)

Scopeの欄の Add an OAuth Scopeをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132331/f8aece9b-3747-1a8f-cad4-042afa4609ee.png)

chat:write:userと、files:write:userを追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132331/aa833232-0756-b503-71ad-dff5231a234e.png)

Install App to Workspaceをクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132331/43816c7b-2665-a0cc-8f64-c536fd4d00a2.png)

許可するをクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132331/e03d44e2-2de7-728c-fc56-a9f72f867c67.png)


### (3)ユーザー作成

左側のメニューからBot Usersを選択して、Add a Bot Userをクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132331/96148284-6c99-3cdb-74d1-5fa3983f7f9e.png)

名前を指定して(ここではデフォルトで設定されている名前そのまま)、Add Bot Userをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132331/5b28fc30-957a-34a4-940f-16be222c595d.png)

黄色い帯のメッセージのreinstall your appのリンクをクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132331/c48f3609-8db4-c1b9-2964-f589bba1d2ba.png)

許可するをクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132331/91187f4f-f7f3-70a3-5d36-c174be7e5d01.png)

アクセスTokenがアサインされるので、Bot User OAuth Access Tokenの方のTokenをメモっておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132331/78cc2de4-0683-b2d5-cea0-0ecf4784c8f5.png)



### (4)Slack API動作確認

上のTokenを使用してRESTのAPIを発行できるようになっているはずなので、z/OSからやるまえに、一旦ブラウザのツールなどから動作確認してみましょう。
ChromeのプラグインのTalend API Tester(昔Restlet Clientという名前だったのがいつの間にか変わってた...)を使ってやってみます。
[Talend API Tester](https://chrome.google.com/webstore/detail/talend-api-tester-free-ed/aejoelaoggembcahagimdiliamlcdmfm)

以下のようなURIで、POSTメソッドを発行します。token=には、上で確認したtokenを、channel=にはメッセージを送付したいチャンネル名を指定します。
``` https://slack.com/api/chat.postMessage?token=xoxb-xxx&channel=test01&text=aaaaa ```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132331/1f00056a-0a72-a314-f0b7-9fb159e7e651.png)

結果、以下のように意図したチャンネルにメッセージが送付されればOK!
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132331/c522afa7-d858-6365-c068-3f9c5c54e5ae.png)


## USS側スクリプト作成

Slack APIを用いてメッセージを送信するスクリプトを作成します。

スクリプトのインターフェース:
第1引数: 送付したいメッセージを含むテキストファイル(EBCDIC)
第2引数: Slack Channel名

Slackにメッセージとして送信したいテキストをファイルとして用意し、そのファイル名と送信したいSlackのChannel名を引数に指定してスクリプトを実行すると、指定したチャネルに指定したメッセージが出力されるようにします。

2パターンのスクリプトを用意しました。
1つ目は渡したテキストをメッセージ本文(装飾つき)で送付します。
2つ目はファイルとして添付して送付します。

どちらもメッセージを送付するというのは一緒ですが、本文として送付するかファイルとして送付するかという違いです。送付するデータのサイズが大きい場合はファイルとして送付したほうがよいでしょう。

ソース中の以下の箇所は、上で取得したSlackアクセス用のtoken、および、使用するProxyサーバーのURLでそれぞれ置き換えてください。
```token=xoxb-xxx```
```proxy=http://yyy:8080```

### (1)chat.postMessageメソッドによるメッセージ送信スクリプト

参考: [chat.postMessage](https://api.slack.com/methods/chat.postMessage)

```sh:slack_chat.sh
#!/bin/sh

fileName=$1
channelName=$2

if [[ ! -e "${fileName}" ]]; then
        echo "Error: file ${fileName} does noe exist."
        exit 1
fi

if [[ -z ${channelName} ]]; then
        channelName=general
fi

slackAPI="https://slack.com/api/chat.postMessage"
text="<!channel> Notification from z/OS !"
token=xoxb-xxx
proxy=http://yyy:8080


tempDir=/tmp
tempKey=SlackChat
pid=$$
tempFile=${tempDir}/${tempKey}_${pid}.json
tempFileUTF8=${tempDir}/${tempKey}_${pid}_utf8.json


##### Create JSON data
echo "{"                                        > ${tempFile}
echo " \"channel\": \"${channelName}\",  "      >> ${tempFile}
echo " \"text\": \"${text}\",  "                >> ${tempFile}

echo ' "attachments": [{
          "color": "good",
          "text": "'                            >> ${tempFile}

cat ${fileName} | sed -e 's/$/ \\n/g'           >> ${tempFile}

echo '"}]}'                                     >> ${tempFile}


##### Code Conversion
iconv -f IBM-1399 -t UTF-8 ${tempFile} > ${tempFileUTF8}


##### Issue Slack API
curl -H "Content-type: application/json" -H "Authorization: Bearer ${token}" -d @${tempFileUTF8}  ${slackAPI} -x ${proxy} -k
echo;


##### Clean up
if [[ -e ${tempFile} ]]; then
        rm ${tempFile}
fi

if [[ -e ${tempFileUTF8} ]]; then
        rm ${tempFileUTF8}
fi
```

### (2)files.uploadメソッドによるメッセージ送信スクリプト

参考: [files.upload](https://api.slack.com/methods/files.upload)

```sh:slack_file.sh
#!/bin/sh

fileName=$1
channelName=$2

if [[ ! -e "${fileName}" ]]; then
        echo "Error: file ${fileName} does noe exist."
        exit 1
fi

if [[ -z ${channelName} ]]; then
        channelName=general
fi

slackAPI=https://slack.com/api/files.upload
initialComment="'<!channel>' Notification from z/OS !"
token=xoxb-xxx
proxy=http://yyy:8080


tempDir=/tmp
tempKey=SlackFile
pid=$$
tempFileUTF8=${tempDir}/${tempKey}_${pid}_utf8.txt

##### Code Conversion
iconv -f IBM-1399 -t UTF-8 ${fileName} > ${tempFileUTF8}


##### Issue Slack API
curl -H "Authorization: Bearer ${token}" -F channels=${channelName} -F initial_comment="${initialComment}" -F file=@${tempFileUTF8}  ${slackAPI} -x ${proxy} -k
echo;


##### Clean up
if [[ -e ${tempFileUTF8} ]]; then
        rm ${tempFileUTF8}
fi
```

### 実行例

#### (1)chat.postMessage

スクリプト実行

```
$ cat test.txt
aaa
bbb
ccc

$ ./slack_chat.sh test.txt test01
{"ok":true,"channel":"CQLCGPXJA","ts":"1574243219.006200","message":{"type":"message","subtype":"bot_message","text":"<!channel> Notification from z\/OS !","ts":"1574243219.006200","username":"z\/OS bot","bot_id":"BQRA7JA4U","attachments":[{"text":"aaa \nbbb \nccc \n \n","id":1,"color":"2eb886","fallback":"aaa \nbbb \nccc \n \n"}]},"warning":"missing_charset","response_metadata":{"warnings":["missing_charset"]}}
```

結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132331/ef48c5f0-4213-32d1-75e2-67ffdeff6df3.png)

#### (2)files.upload

スクリプト実行

```
$ cat test.txt
aaa
bbb
ccc

$ ./slack_file.sh test.txt test01
{"ok":true,"file":{"id":"FQRBBAMRS","created":1574243630,"timestamp":1574243630,"name":"SlackFile_83952320_utf8.txt","title":"SlackFile 83952320 utf8","mimetype":"text\/plain","filetype":"text","pretty_type":"\u30d7\u30ec\u30fc\u30f3\u30c6\u30ad\u30b9\u30c8","user":"UQRPSGM08","editable":true,"size":12,"mode":"snippet","is_external":false,"external_type":"","is_public":true,"public_url_shared":false,"display_as_bot":false,"username":"","url_private":"https:\/\/files.slack.com\/files-pri\/TQL21SB0X-FQRBBAMRS\/slackfile_83952320_utf8.txt","url_private_download":"https:\/\/files.slack.com\/files-pri\/TQL21SB0X-FQRBBAMRS\/download\/slackfile_83952320_utf8.txt","permalink":"https:\/\/tomotagwork.slack.com\/files\/UQRPSGM08\/FQRBBAMRS\/slackfile_83952320_utf8.txt","permalink_public":"https:\/\/slack-files.com\/TQL21SB0X-FQRBBAMRS-58fbe5385d","edit_link":"https:\/\/tomotagwork.slack.com\/files\/UQRPSGM08\/FQRBBAMRS\/slackfile_83952320_utf8.txt\/edit","preview":"aaa\nbbb\nccc\n","preview_highlight":"<div class=\"CodeMirror cm-s-default CodeMirrorServer\" oncopy=\"if(event.clipboardData){event.clipboardData.setData('text\/plain',window.getSelection().toString().replace(\/\\u200b\/g,''));event.preventDefault();event.stopPropagation();}\">\n<div class=\"CodeMirror-code\">\n<div><pre>aaa<\/pre><\/div>\n<div><pre>bbb<\/pre><\/div>\n<div><pre>ccc<\/pre><\/div>\n<div><pre><\/pre><\/div>\n<\/div>\n<\/div>\n","lines":4,"lines_more":0,"preview_is_truncated":false,"comments_count":0,"is_starred":false,"shares":{"public":{"CQLCGPXJA":[{"reply_users":[],"reply_users_count":0,"reply_count":0,"ts":"1574243630.007500","channel_name":"test01","team_id":"TQL21SB0X"}]}},"channels":["CQLCGPXJA"],"groups":[],"ims":[],"has_rich_preview":false}}
```

結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132331/90301aa6-863d-157d-cfbd-02cd26580c97.png)

ファイルとしてデータが送付されているので、ダウンロードしてテキストファイルとして入手することができます。

※(1)のケースとアイコンが違うのは何故なんだろう？APIの出し方で違ってくるのかな？


### 補足
(1)のケースは、attachmentsという色々装飾ができる形式で本文中のメッセージとしてデータを送っています。color属性は"good"(Hexコードでの指定も可能)を固定で指定しているので緑色の縦棒がテキストの左側についていますが、Info/Warning/Errorなどのメッセージの種類に応じて色を制御する、みたいなこともできそうです。

curlコマンドにファイルでデータを渡す場合、UTF-8で渡す必要があるようです。渡すファイルはEBCDICの方が都合がよいと思うので、スクリプトの中でiconvによりUTF-8に変換するロジックを組み込んでいます(IBM-1399=>UTF-8変換)。
一応、DBCSを含むファイルを渡した場合も、きちんとSlack上に日本語として表示されていました。細かくテストできてませんが、slackに書き込むうえでエスケープが必要な特殊文字とか含まれていると、色々不都合あるかもしれません。

いずれも、送付先チャンネルの参加者全員に通知をするための```@channel```メンションを埋め込んでいます。スクリプト中では```<!channel>```と指定している部分です。

参考: [Formatting messages](https://api.slack.com/docs/message-formatting)

# Slack連携の具体例

以前、Lightweight Command Utility on z/OS USSというツールを作りました。
参考: [z/OSの新しい管理方法を探る - (4)Lightweight Command Utility on z/OS USS](https://qiita.com/tomotagwork/items/bb7f9592658389d2c5c6)
これの拡張のイメージで今回Slack連携部品をUSS上のシェルスクリプトとして作成してますので、このユーティリティーを利用して、少し具体的なSlack連携例を実装してみたいと思います。
このユーティリティーで提供しているスクリプトを一部利用しているので、このユーティリティーが動くことが前提です。
また、上で作成したSlackメッセージ送信用のシェルスクリプトを配置しているディレクトリをPATHに追加しておく必要があります。

※Slackに送付されるデータに、セキュリティ上問題がある内容のものが含まれる可能性が無いかは充分注意しましょう！

## SYSLOGの監視

一定間隔でSYSLOGをチェックし、事前に指定した文字列(特定のIDなど)が含まれるメッセージが出力されたらその列のみを抽出してSlackのメッセージとして送信します。
例えば、Spool使用率のメッセージを拾ってSlack経由で通知するとか、ロングランニングの処理の終了をSYSLOGメッセージから拾ってそれを通知するとか、その他特定のクリティカルなエラーが出た場合にそれを通知するとか、そんな利用方法をイメージしています。

### ソース
ユーティリティで提供している[syslog.sh](https://github.com/tomotagwork/Lightweight_Command_Utility_on_USS/blob/master/USS_Resources/syslog.sh)を元にして、カスタマイズしました(なのでちょっと余計なコード残っちゃってると思いますが)。内部的にはユーティリティで提供しているsyslog取得用のrex([syslog.rex](https://github.com/tomotagwork/Lightweight_Command_Utility_on_USS/blob/master/USS_Resources/syslog.rex))使ってます。

```sh:syslog2slack.sh
#!/bin/bash

scriptName=$(basename $0)
rexxFile=syslog.rex

######################################
# Function
######################################
. commonFunctions.sh

showHelp(){
        echo "Usage: ${scriptName}  [-h] | [-w [n] ] [-c <channel>] -f <fileName> "
        echo "  -h:              Show this help"
        echo "  -w [n]:          Watch(tail) syslog with specifed interval in second. n=1-300 (default: 2sec)"
        echo "  -c <channel>:    Slack channel name (default general)"
        echo "  -f <fileName>:   specify fileName including list of keyword to send to Slack"
        echo "  without option   Display syslog of last 1 minute."
        echo ""
        exit 0
}


getRelativeTime(){
        var=$1

        if [[ ${thisMinute} -ge ${var} ]] ; then
                min=$(expr ${thisMinute} - ${var})
                if [[ ${min} -lt 10 ]] ; then
                        min=0${min}
                fi
                result=${thisHour}:${min}:${thisSecond}
        else
                if [[ ${thisHour} -eq 0 ]] ; then
                        result=00:00:00
                else
                        hour=$(expr ${thisHour} - 1)
                        if [[ ${hour} -lt 10 ]]; then
                                hour=0${hour}
                        fi

                        min=$(expr 60 + ${thisMinute} - ${var})
                        if [[ ${min} -lt 10 ]] ; then
                                min=0${min}
                        fi

                        result=${hour}:${min}:${thisSecond}
                fi

        fi

        echo ${result}

}

createGrepCommand(){
        # arg1: fileName
        fileName=$1

        commandString="grep"
        while read line
        do
                commandString="${commandString} -e ${line}"
        done<${fileName}

        echo ${commandString}
}

signalHandler(){

        if [[ -e ${tempFile} ]]; then
                rm ${tempFile}
        fi
        exit 1

}


#######################################
# Main Logic
#######################################

trap signalHandler 1 2 3 15

set -A arrayDateTime $(date '+%Y %m %d %H %M %S')
thisYear=${arrayDateTime[0]}
thisMonth=${arrayDateTime[1]}
thisDay=${arrayDateTime[2]}
thisHour=${arrayDateTime[3]}
thisMinute=${arrayDateTime[4]}
thisSecond=${arrayDateTime[5]}


#echo ${thisHour}:${thisMinute}:${thisSecond}

#fromRelativeTime=$(getRelativeTime 1)
#echo ${fromRelativeTime}


arg_w=2
arg_c=general
arg_f=
flag_w=1
flag_c=0
flag_f=0

arg_l=1
arg_fd=
arg_ft=
arg_td=
arg_tt=
flag_l=0
flag_fd=0
flag_ft=0
flag_td=0
flag_tt=0

for option in "$@"
do
        case "$option" in
                '-h')
                        showHelp
                        exit 0
                        ;;
                '-w')
                        flag_w=1
                        if [[ ${flag_fd} -eq 1 ]] || [[ ${flag_ft} -eq 1 ]] || [[ ${flag_td} -eq 1 ]] || [[ ${flag_tt} -eq 1 ]] ; then
                                echo "Error: -w option can not be specified with -fd, -ft, -td, -tt."
                                showHelp
                                exit 0
                        elif [[ -z "$2" ]] || [[ $(checkOption "$2") -ne 0 ]] ; then
                                # use arg_w default value
                                shift 1
                        elif [[ $(checkNumber "$2") -eq 0 ]] ; then
                                echo "Error: Argument of -w must be number"
                                showHelp
                                exit 0
                        elif [[ $2 -gt 300 ]] ; then
                                echo "Error: set 1-300 sec in -w option"
                                showHelp
                                exit 0
                        else
                                arg_w=$2
                                shift 2
                        fi
                        ;;
                '-c')
                        flag_c=1
                        if [[ -z "$2" ]] || [[ $(checkOption "$2") -ne 0 ]] ; then
                                echo "Error: Argument is required for -f"
                                showHelp
                                exit 1
                        else
                                arg_c=$2
                                shift 2
                        fi
                        ;;
                '-f')
                        flag_f=1
                        if [[ -z "$2" ]] || [[ $(checkOption "$2") -ne 0 ]] ; then
                                echo "Error: Argument is required for -f"
                                showHelp
                                exit 1
                        elif [[ ! -e "$2" ]]; then
                                echo "Error: file $2 does noe exist."
                                showHelp
                                exit 1
                        else
                                arg_f="$2"
                                shift 2
                        fi
                        ;;
                -*)
                        echo "Error: Unsupported option: $1"
                        showHelp
                        exit 0
                        ;;
                *)
                        if [[ ! -z "$1" ]] && [[ $(checkOption "$1") -eq 0 ]]; then
                                shift 1
                        fi
                        ;;
        esac
done


#echo arg_w: ${arg_w}
#echo arg_c: ${arg_c}
#echo arg_f: ${arg_f}
#echo arg_fd: ${arg_fd}
#echo arg_ft: ${arg_ft}
#echo arg_td: ${arg_td}
#echo arg_tt: ${arg_tt}

if [[ ${flag_f} -eq 0 ]]; then
        echo "Error: -f option is required"
        showHelp
        exit 1
fi

grepCommand=$(createGrepCommand ${arg_f})
#echo ${grepCommand}


tempDir=/tmp
tempKey=SlackTest
pid=$$
tempFile=${tempDir}/${tempKey}_${pid}.txt

if [[ ${flag_w} -eq 1 ]]; then
        fromDate=${toDate}
        fromTime=${toTime}

        sleep 1

        while true
        do
                toDate=$(date '+%Y/%m/%d')
                toTime=$(date '+%T')

                ${rexxFile} ${fromDate} ${fromTime}.01 ${toDate} ${toTime}.00 | ${grepCommand}  >  ${tempFile}
                rc=$?
                if [[ ${rc} -eq 0 ]]; then
                        timeStamp=$(date)
                        echo ${timeStamp} Send message to Slack - ${arg_c}
                        cat ${tempFile}
                        #slack_file.sh ${tempFile} ${arg_c}
                        slack_chat.sh ${tempFile} ${arg_c}

                elif [[ ${rc} -eq 1 ]]; then
                        # no action

                else
                        exit ${rc}

                fi

                sleep ${arg_w}

                fromDate=${toDate}
                fromTime=${toTime}

        done

fi

exit
```

### 使い方

```xxx:ヘルプ
$ syslog2slack.sh -h
Usage: syslog2slack.sh  [-h] | [-w [n] ] [-c <channel>] -f <fileName>
  -h:              Show this help
  -w [n]:          Watch(tail) syslog with specifed interval in second. n=1-300 (default: 2sec)
  -c <channel>:    Slack channel name (default general)
  -f <fileName>:   specify fileName including list of keyword to send to Slack
```

-f オプションで、ウォッチしたいキーワードのリストを含むファイル名を指定します <必須>
-c オプションで、送信したいチャンネル名を指定します (デフォルトはgeneral)
-w は、SYSLOGをチェックする間隔を秒単位で指定します (デフォルト 2sec)

### 実行例

監視対象のリストを準備(以下は、CICSの起動と停止のメッセージID)

```txt:slackMonitoringMessage.txt
DFHSI1517
DFHKE1799
```

スクリプト実行(無限ループするスクリプトなので、バックグラウンドで起動します)

```
[CICS004@EPLEX1:/u/cics004/Shell] syslog2slack.sh -c test01 -f slackMonitoringMessage.txt > output.log 2>&1 &
[1]     65884
```

上の例では、プロセスID: 65884でバックグラウンドでスクリプトが稼働し、2分間隔でSYSLOGをチェックするループが動いている状態となります。
ここで、CICSのリージョンを1つ起動してみます。(例: ```S CT54D4A1```)
すると、Slackに以下のメッセージが通知されました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132331/a6cb8c12-30ca-d37c-4bda-67412afdefdd.png)

次に、このCICSリージョンを停止させてみます(例: ```F CT54D4A1,CEMT P SHUT```)。
すると、Slackに以下のメッセージが通知されました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132331/3699b799-f2d2-1c6a-a6e9-d6acf1dd95e5.png)


SYSLOG監視用スクリプトを終了させる場合は、先ほどのプロセスIDに対してkillコマンドでSIGTERMのシグナルを送付して停止させます。

```
[CICS004@EPLEX1:/u/cics004/Shell] kill 65884
[CICS004@EPLEX1:/u/cics004/Shell]
[1] + Done(1) syslog2slack.sh -c test01 -f slackMonitoringMessage.txt > output.log 2>&1 &
```

## JOBLOG送付
ここでは、JCLをサブミットしたらそのJOBLOGを丸ごとファイルとしてSlackに送付するということをやります。
例えば、長時間かかるようなジョブを複数流して、あとからまとめてJOBLOG確認、みたいなケースをイメージしています。

### ソース

この例では単純にJOBLOGまるごと送付してしまっていますが、手を加えればDDごとに分けたり指定したDDだけ出すというのもできると思います。JCLも手っ取り早くUSS上のファイルとして用意する前提ですが、ユーティリティで提供している[sumbmitJcl.sh](https://github.com/tomotagwork/Lightweight_Command_Utility_on_USS/blob/master/USS_Resources/submitJcl.sh)のように、MVS上のJCLを使用するようにカスタマイズも可能です。
内部的にはユーティリティで提供しているjoblog取得用のrex([joblog.rex](https://github.com/tomotagwork/Lightweight_Command_Utility_on_USS/blob/master/USS_Resources/joblog.rex))使ってます。

```sh:submit2slack.sh
#!/bin/sh

jclFileName=$1
channelName=$2

tempDir=/tmp
tempKey=submit2slack
pid=$$
tempFile=${tempDir}/${tempKey}_${pid}.txt


data=$(submitJcl.sh -s -f ${jclFileName})

while read var1 var2
do
        #echo "${var1} / ${var2}"
        if [[ "${var1}" = "JobID:" ]]; then
                JobID=${var2}
        fi

done <<-END
${data}
END

echo JobID: ${JobID}
joblog.rex ${JobID}  > ${tempFile}

slack_file.sh ${tempFile} ${channelName}

if [[ -e ${tempFile} ]]; then
        rm ${tempFile}
fi
```

### 使い方

実行するJCLをUSS上のファイルとして用意し、第1引数にそのファイル名、第2引数に送信先のチャンネル名を指定します。

Syntax:
```submit2slack.sh <jclFileName> <channelName>```

### 実行例

実行したいJCLを用意(ここでは、USERCATALOGをリストするだけの単純なJCLを使用)

```jcl:listc2.jcl
//LISTC    JOB   MSGCLASS=X,CLASS=A                                     00012038
//*-----------------------------------------------------                00013038
//*                                                                     00014038
//GO       EXEC PGM=IDCAMS,REGION=0M                                    00018038
//SYSPRINT DD SYSOUT=*                                                  00019038
//SYSIN DD *                                                            00020038
 LISTC USERCATALOG ALL                                                  00021038
/*                                                                      00022038
```

このJCLを指定してスクリプトを実行

```
[CICS004@EPLEX1:/u/cics004/Shell] submit2slack.sh ../JCL/listc2.jcl test01
JobID: JOB06488
{"ok":true,"file":{"id":"FQYLGH9RU","created":1574476908,"timestamp":1574476908,"name":"SlackFile_33620071_utf8.txt","title":"SlackFile 33620071 utf8","mimetype":"text\/plain","filetype":"text","pretty_type":"\u30d7\u30ec\u30fc\u30f3\u30c6\u30ad\u30b9\u30c8","user":"UQRPSGM08","editable":true,"size":98103,"mode":"snippet","is_external":false,"external_type":"","is_public":true,"public_url_shared":false,"display_as_bot":false,"username":"","url_private":"https:\/\/files.slack.com\/files-pri\/TQL21SB0X-FQYLGH9RU\/slackfile_33620071_utf8.txt","url_private_download":"https:\/\/files.slack.com\/files-pri\/TQL21SB0X-FQYLGH9RU\/download\/slackfile_33620071_utf8.txt","permalink":"https:\/\/tomotagwork.slack.com\/files\/UQRPSGM08\/FQYLGH9RU\/slackfile_33620071_utf8.txt","permalink_public":"https:\/\/slack-files.com\/TQL21SB0X-FQYLGH9RU-4644981ba7","edit_link":"https:\/\/tomotagwork.slack.com\/files\/UQRPSGM08\/FQYLGH9RU\/slackfile_33620071_utf8.txt\/edit","preview":"isfmsg2.1 is: ISF767I Request completed.\nLISTC JOB06488 CICS004\n1                      J E S 2  J O B  L O G  --  S Y S T E M  Z O S 1  --  N O D E  E P L E X           \n0 \n 11.42.04 JOB06488 ---- SATURDAY,  23 NOV 2019 ----","preview_highlight":"<div class=\"CodeMirror cm-s-default CodeMirrorServer\" oncopy=\"if(event.clipboardData){event.clipboardData.setData('text\/plain',window.getSelection().toString().replace(\/\\u200b\/g,''));event.preventDefault();event.stopPropagation();}\">\n<div class=\"CodeMirror-code\">\n<div><pre>isfmsg2.1 is: ISF767I Request completed.<\/pre><\/div>\n<div><pre>LISTC JOB06488 CICS004<\/pre><\/div>\n<div><pre>1                      J E S 2  J O B  L O G  --  S Y S T E M  Z O S 1  --  N O D E  E P L E X           <\/pre><\/div>\n<div><pre>0 <\/pre><\/div>\n<div><pre> 11.42.04 JOB06488 ---- SATURDAY,  23 NOV 2019 ----<\/pre><\/div>\n<\/div>\n<\/div>\n","lines":2868,"lines_more":2863,"preview_is_truncated":true,"comments_count":0,"is_starred":false,"shares":{"public":{"CQLCGPXJA":[{"reply_users":[],"reply_users_count":0,"reply_count":0,"ts":"1574476908.000900","channel_name":"test01","team_id":"TQL21SB0X"}]}},"channels":["CQLCGPXJA"],"groups":[],"ims":[],"has_rich_preview":false}}
```

Slackには以下のメッセージが届きました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132331/541069af-e900-9864-9655-0d7bcd80d93b.png)

JOBLOGがファイルとして添付されているので、Slack上でインラインで確認もできますし、ファイルをダウンロードしてローカルのテキストエディター(Notepadやサクラエディタ)などで参照することができます。


# おわりに

Slackで情報共有するってのは最近結構はやりなので、今回試してみました。

ここでは、USSのシェルスクリプトからcurlでAPI発行してますが、REST API発行できればよいので、他にも色々バリエーションは考えられます。冒頭のリンクにあったJavaしかり、その他では、例えばCICSは外部のWebサービスを呼び出すためのAPIを提供しているので、CICS-COBOLアプリからSlackにメッセージ出すこともできるはず。

z/OSの環境はネットワーク的に外部との接続がかなり制限されるので、なかなか実際の運用で活用するのは厳しいかもしれませんが、開発/テスト環境などで、通知や情報共有をSlackにつなげられると効率化図れる部分もあるかもしれません。
まぁ必ずしもz/OSから直接やらなくてもいいとは思いますけど、バリエーションの一つとして組み込める余地はありますよ、ということで。