AGEST Engineers Blog

株式会社AGESTのテックブログです。

外部アクセス制限がある環境下でAutifyからSlackに通知する

はじめまして。システムソリューショングループのしろです

テスト自動化ツールAutifyはテスト結果をSlackに通知することで、Failした結果を効率的に確認することができて便利です。ただ、企業のセキュリティポリシーによってはこの通知機能が有効に使えない場合があります。 ここでは以下のセキュリティ上の制限がある環境下でAutifyからSlackへ通知を行う方法をご紹介します。

  • Slackにアプリを自由に追加できない
  • CIツールがローカル環境にあり、外部からのデータを受け付けない

概要

環境

  • Jenkins(ローカル:2.319.2@Docker)
  • Autify(クラウド)
  • Slack(クラウド) ※Docker Desktop有償化されるので要注意(2022/02/09時点)

前提条件

  • Slackへアプリの作成が可能な権限である(Slack APIトークンが発行できる)
  • Jenkinsへログインできる
  • JenkinsにSlack Notification(プラグイン)がインストールされている

構成図

Slack通知については、Autifyの標準機能でSlack通知機能や、Jenkins Webhook機能で実現可能なのですが、先述した制限があるため、構成としてはJenkinsがAutifyのテスト結果取得APIでデータを取得し、Slackへ通知する構成となっています。 f:id:digitalhearts:20220303171410p:plain

結論

Slackに見事通知できました! f:id:digitalhearts:20220303171429p:plain

設定手順

Slackの設定

1.Slackのチャンネル一覧>対象のチャンネルを選択>右クリック>『チャンネル詳細を開く』をクリック
f:id:digitalhearts:20220303171449p:plain

2.インテグレーションタブ>App>アプリを追加する
f:id:digitalhearts:20220303171509p:plain

3.JenkinsCIもしくは、表示する>設定をクリック(ブラウザがひらく)
f:id:digitalhearts:20220303171538p:plain

4.Slackに追加
f:id:digitalhearts:20220303171600p:plain

5.対象のチャンネルを選択>『JenkinsCIインテグレーション追加』をクリック
f:id:digitalhearts:20220303171625p:plain

6.Step3のチームサブドメイン・インテグレーション用トークン認証情報IDを控える
f:id:digitalhearts:20220303171647p:plain

7.下の方にスクロールし、『インテグレーションの設定』を確認し、問題なければ『設定を保存する』をクリック
f:id:digitalhearts:20220303171712p:plain

Jenkins側(SSH)の作業

※root権限でディレクトリ作成などを行うとJenkinsユーザーで実行できない可能性もあるので、作業ユーザーに注意してください。

  • 中間ファイル出力、Shellスクリプト配置用のディレクトリを作成 例)/tmp/autify_notification/
  • Autifyのテスト結果取得用shellスクリプトを先ほど作成したディレクトリに配置
  • jqのインストール
  • curlコマンドで取得したJSONを解析するために使用
#!/bin/bash

readonly BASE_URL='https://app.autify.com/api/v1/projects'
test_pj_id=${1}
autify_token=${2}
tmp_file_path=${3}

# テスト結果一覧の先頭(たぶん最新)からテスト結果IDを取得
new_id=$(curl -L --request GET "$BASE_URL/$test_pf_id/results?per_page=1" -H "Authorization: Bearer $autify_token" | jq ".[].id")
old_id=$(cat $tmp_file_path/old_id.txt)

# 前回のテスト結果IDと、取得したテスト結果IDが異なる
if [ "$new_id" != "$old_id" ] ; then
  detail=$(curl -L --request GET "$BASE_URL/$test_pf_id/results/$new_id" -H "Authorization: Bearer $autify_token")
  status=$(echo "$detail" | jq -r ".status")
  if [ "$status" = "running" ] ; then
    echo 'ステータス:running'
    exit
  fi

  # Autifyは標準時間なので+9時間
  tmp_started_at=$(echo "$detail" | jq -r ".started_at")
  started_at=$(date -d ""$tmp_started_at" 9hours" '+%F %T')
  tmp_finished_at=$(echo "$detail" | jq -r ".finished_at")
  finished_at=$(date -d ""$tmp_finished_at" 9hours" '+%F %T')

  test_plan_name=$(echo "$detail" | jq -r ".test_plan.name")
  tp_id=$(echo "$detail" | jq -r ".test_plan_capability_results[].id")
  msgbody="通知テストです。\n テストプラン名:$test_plan_name\n ステータス:$status\n 開始日時:$started_at\n 終了時刻:$finished_at\n リザルトURL:https://app.autify.com/projects/xxx/results/$new_id/capabilities/$tp_id"

  echo "$status"> $tmp_file_path/status.txt
  echo "$new_id"> $tmp_file_path/old_id.txt
  echo -e "$msgbody"> $tmp_file_path/result.txt

  # SlackAPIで通知する場合
  #curl --location --request POST 'https://slack.com/api/chat.postMessage' \
  #  --header 'Content-Type: application/x-www-form-urlencoded' \
  #  --data-urlencode 'token={token}' \
  #  --data-urlencode 'channel={workspace channel}' \
  #  --data-urlencode "text=$(echo -e $msgbody)"
fi

Jenkins側(画面)の作業

1.Jenkinsへログイン>Jenkinsの管理>システム設定
f:id:digitalhearts:20220303171750p:plain

2.Slack>Workspaceに『チームサブドメイン』を入力

3.Slack>Credential>追加
f:id:digitalhearts:20220303171811p:plain

4.下記を入力し、『追加』をクリック 種類:Secret text
Secret:控えていた『インテグレーション用トークン認証情報ID』
ID:ユニークな任意の文字列
f:id:digitalhearts:20220303171830p:plain

5.Credentialを選択>『Test Connection』をクリック(Successが表示されることを確認)
f:id:digitalhearts:20220303171847p:plain

6.『保存』をクリック

通知ジョブの作成

1.新規ジョブ作成を選択>下記を入力し『OK』をクリック

  • ジョブ名:任意
  • パイプライン
    f:id:digitalhearts:20220303171905p:plain

2.ビルドトリガ>定期的に実行 ※5分間隔に設定した例 f:id:digitalhearts:20220303171920p:plain

1.パイプラインにご提供している[pipelineソース]をコピペし、環境変数書き換える>『保存』をクリック TEST_PJ_ID:テストのプロジェクトID
AUTIFY_TOKEN:Autifyで発行したユーザートークン
TMP_FILE_PATH:中間ファイル出力先・Shellスクリプトを配置したディレクトリパス
SLACK_CH:投稿したいSlackチャンネル
CREDENTIAL_ID:Jenkinsに登録した任意のID

pipeline {
    agent any
    environment {
        TEST_PJ_ID={AutifyのテストプロジェクトID}
        AUTIFY_TOKEN='{AutifyのUserトークン}'
        TMP_FILE_PATH='{設定したい}'
        SLACK_CH='{投稿したいSlackチャンネル}'
        CREDENTIAL_ID='{Jenkinsに登録した任意のID}'
    }
    stages {
        stage('AutifyResult') {
            steps {
                echo 'get autify result.'
                script {
                    sh(script:"$TMP_FILE_PATH/autify_notification.sh $TEST_PJ_ID $AUTIFY_TOKEN $TMP_FILE_PATH", returnStatus:true)
                    if (fileExists("$TMP_FILE_PATH/result.txt")) {
                        def res=readFile(file:"$TMP_FILE_PATH/result.txt")
                        String status=readFile(file:"$TMP_FILE_PATH/status.txt")
                        echo "$status"
                        if ( "$status" =~ 'passed'){
                            slackSend(channel: "$SLACK_CH", color: "good", message: "$res", tokenCredentialId: "$CREDENTIAL_ID")
                        }else{
                            slackSend(channel: "$SLACK_CH", color: "#ff0000", message: "$res", tokenCredentialId: "$CREDENTIAL_ID")
                        }
                        sh(script:"rm $TMP_FILE_PATH/result.txt", returnStatus:false)
                        sh(script:"rm $TMP_FILE_PATH/status.txt", returnStatus:false)
                    }
                }
            }
        }
    }
}

あとがき

正直、SlackとJenkinsの連携はめちゃくちゃ楽ちんでした。 あ、これで通知できちゃうんだって感じです。
ただ今回は楽ちんなやり方が封印されていたので、(たぶん)アクロバティックなやり方になった。という印象です。

特にハマったは↓コレ。

  • Jenkinsのローカル環境を作成する(OpenJavaムツカシイ)
  • JenkinsでPipeLine(Groovy)を記載したこと
  • Shellスクリプトの戻り値が取得できなかった

PipelineではGroovyっていう言語でプログラムかけるって記載を見つけたのですが、どうやら限定的な使い方しかできないようで、だいぶハマりましたね。 特にShellスクリプトの実行が・・・
戻り値を取得できれば一部の中間ファイルは不要の産物だったのですが、調査期間では実現できなかったのが心残りです。 あとは、IF文・・・。
『==』とか『===』で文字列比較できないんかーいって思いました。(未だに疑問)

だいぶ自由度が高いので今回掲載した以外でも色々と通知の仕方は考えられそうです。 で、色々ハマった点はありますが、全体的にJenkinsとSlackアプリに触れられたことは、私のなかでだいぶプラスになりました。

■ デジタルハーツは一緒に働くメンバーを募集しています! hrmos.co

  • f:id:zo_03:20211213095237p:plain
  • f:id:zo_03:20211213095237p:plain
©AGEST, Inc.