Autifyでシナリオ実行が失敗した際に通知するWebhookを作ってみた

※※この記事は[「Autifyアドベントカレンダー2022」](https://qiita.com/advent-calendar/2022/autify)の4日目記事です。

こんにちは！
Autifyに今年の10月に入社した、カスタマーサポートエンジニアのMamiです。
今回はAutifyでシナリオ実行が失敗した際に、Slackに通知するWebhookを作成してみましたので、ご紹介いたします。

## 作った背景
せっかく入社したので、何かつくってみようーと思い、実際にお問合せを受ける中で、ニーズがありそうだった失敗時の結果通知用Webhookを作ることにしました:smile:
※現状Autifyでは、テスト実行が完了した際にテスト結果を含む通知メッセージをSlackに送信するSlack Appを提供していますので、常に結果を受け取りたい場合は、[こちら](https://help.autify.com/docs/ja/integrate-with-slack)からご利用方法をご確認くださいませ。

## 構成
Webhookは、AWS Lambdaを利用して作成しました。
WebhookシークレットやSlackのWebhookURLの保存にはAWS Secret Managerを利用しました。
![autify-webhook.drawio (3).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/416416/470c7715-8a17-5952-761b-1c43dcae35f4.png)

## 実際の作成方法
### 事前準備
事前に以下の準備をしてください。
* AWSアカウント
* 投稿用のSlackチャンネル
* Hattyの絵文字登録（社内のSlackチャンネルに登録いただくと、Hattyが結果をお知らせするように作成できます。）
    * HattyのImageファイルは[こちら](https://drive.google.com/drive/folders/1gyrsutJp5Bp0SSey5GZv2Wqdm6Sy79QZ)からダウンロード可能です。
    * 今回は、失敗通知なので、「hatty_bow」という名前でHattyがお辞儀している絵文字を登録して利用しました。
### 利用したコード
今回Webhook作成に利用したコードはこちらです。
<details><summary>Webhook用のコード</summary><div>

```python
from email import header
import json, hmac, hashlib, base64, urllib,boto3,ast
from urllib.request import Request, urlopen
from botocore.exceptions import ClientError

def is_valid_signature(secret, signature, event):
    body = event['body']
    secret_key = secret['autify-webhook-secret']
    new_hmac = "sha1=" + hmac.new(bytes(secret_key, "utf-8"), bytes(body, "utf-8"), hashlib.sha1).hexdigest()
    return hmac.compare_digest(signature, new_hmac)

def get_secret():
    secret_name = "autify-webhook"
    region_name = "ap-northeast-1"

    # Create a Secrets Manager client
    session = boto3.session.Session()
    client = session.client(
        service_name='secretsmanager',
        region_name=region_name
    )
    try:
        get_secret_value_response = client.get_secret_value(
            SecretId=secret_name
        )
    except ClientError as e:
        # For a list of exceptions thrown, see
        # https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_GetSecretValue.html
        raise e
    # Decrypts secret using the associated KMS key.
    secret = get_secret_value_response['SecretString']
    return secret

def post_slack_results(secret, event, context):
    slack_body = json.loads(event['body'])
    if slack_body['status'] == "failed":
        if 'scenario_name' in slack_body:
            send_data = {
                "channel":secret['slack-channel'],
                "username": "webhook-test-bot",
                "text": ":hatty_bow: テスト失敗のお知らせです:hatty_bow: \n" + "シナリオ実行が失敗しました。URLから詳細を確認してください。\n" + "シナリオID：" + str(slack_body['scenario_id']) + "\nシナリオ名：" + slack_body['scenario_name'] + "\n実行結果：" + slack_body['url'] 
                }
            payload = "payload=" + json.dumps(send_data)
            request = Request(
                secret['slack-webhook-url'],
                data=payload.encode("utf-8"),
                method="POST"
                )
            with urlopen(request) as response:
                response_body = response.read().decode("utf-8")
        else:
            return{
            'statusCode': 200,
            "body": "Test plan was executed."
        }
    else:
        return {
        'statusCode': 200,
        "body": "The test was successful.",
        }

def lambda_handler(event, context):
    signature = event['headers']['x-autify-signature']
    secret = ast.literal_eval(get_secret())
    if is_valid_signature(secret,signature,event):
        post_slack_results(secret,event,context)
        return {
            'statusCode': 200
        }
    else:
        return {
            "statusCode": 401, 
            "body": "Unauthorized" 
            }
```

</div></details>

### 1.SlackのWebhookURLを取得する。
はじめに通知用チャンネルにIncoming Webhookを追加します。
1. [Incoming Webhookのアプリページを開く](https://slack.com/services/new/incoming-webhook)
1. 投稿用のチャンネルを選択し、Incoming Webhookインテグレーションを追加する
![スクリーンショット 2022-11-16 15.20.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/416416/c106791b-aa23-9401-13c1-d60372dca612.png)
1. 追加後に表示されるWebhookURLをメモする。
![スクリーンショット 2022-11-16 15.21.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/416416/4c6c0952-d96c-67a4-94a8-ae2cbd67fe53.png)
### 2.AWSでWebhookを作成する。
次にいよいよWebhookを作成していきます。
1. AWS LambdaでWebhookを作成する。
    1. AWS Lambdaのサービスへ移動する。
    1. 関数の作成より、新規の関数を作成する。
※今回はPythonで実装したため、ランタイムはPython3.9を選択しています。
![スクリーンショット 2022-11-14 23.02.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/416416/0fc653eb-c46f-c4f9-8360-c11e65d14597.png)
    1. lambda functionタブにWebhook用のコードを貼り付け、Deployを行う。
![スクリーンショット 2022-11-14 23.07.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/416416/aaa07fcb-0397-372a-ed3f-dff107b00409.png)
1. 設定を開き、関数URLを作成する。
※Webhookはインターネット公開する必要があるため、今回は認証タイプは`NONE`を選択して作成します。
![スクリーンショット 2022-11-14 23.11.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/416416/5575794c-d730-e699-819b-47a074648f70.png)
![スクリーンショット 2022-11-14 23.12.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/416416/b025040d-67f0-1b3d-0f7d-43127cd19edc.png)
1. AWS Secret Managerに必要な情報を保存する。
    1. AWS Secret Managerのサービスへ移動する。
    1. その他のシークレットのタイプを選択し、3つのシークレットを保存する。
        * slack-webhook-url：SlcakのwebhookURLを設定してください。
        * slack-channel：投稿用のチャンネルを設定してください。
        * autify-webhook-secret：webhookシークレットを設定してください。
![スクリーンショット 2022-11-14 23.15.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/416416/7517bb93-6ec0-ca65-a77c-e33d643913ec.png)
1. AWS LambdaのIAMロールにAWS Sectet Managerの権限を付与する。
    1. IAMのサービスへ移動する
    1. ロールを開き、AWS Lambdaの名前を入力し作成したLambda関数用のロールの検索を行う
    1. ロールを選択し、以下ポリシーの追加を行う
```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "secretsmanager:GetSecretValue",
            "Resource": "*"
        }
    ]
}
```
### ３.AutifyにWebhookを設定する。
最後にWebhookをAutifyに設定します。
1. Autifyにログインする
1. ワークスペース設定ページに遷移
1. Webhookセクションで追加ボタンを押下
1. Webhook URLとWebhookシークレットを設定
![スクリーンショット 2022-11-16 14.52.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/416416/52aa5819-4042-2a09-a112-697d0bf9363c.png)

### 4.実際にシナリオを実施して通知を確認する。
実際にシナリオを実行しSlackに通知がされることを確認してください。
![スクリーンショット 2022-11-16 21.54.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/416416/2bbd56e4-b79f-43b7-d644-d7211d87a069.png)
※投稿されたものを見ると、おそらくHattyの絵文字部分が残念な感じになっていると思うので、適したものに変えてください:pray:

## おわりに
今回はとてもとてもシンプルな通知の内容になっておりますが、Webhookでは今回の通知に利用した内容以外も受け取っています。
[こちら](https://help.autify.com/docs/ja/integrate-with-slack)のページをご覧の上、必要な情報を追加してご利用いただければと思います！
また、実際作ってみるた感想としては、失敗だけの通知というのは中々切ないものがありました:joy:
作ってみたはものの、成功と失敗どちらも通知される方が精神衛生的にはいいかもしれない・・・と思ったりもしましたw
この記事がどなたかのお役に立てますと幸いです。
かなり早いですが、メリークリスマス:santa_tone1:

## 参考
今回SlackへのPostをする・Webhookシークレットを検証するという実装にあたって、以下の記事を参考にさせていただきました！ありがとうございました！
* [AWS LambdaでSlack通知してみる](https://qiita.com/suo-takefumi/items/b47922362366de897920)
* [【小ネタ】PythonでZendeskのWebhook署名検証を行う](https://dev.classmethod.jp/articles/zendesk-webhook-signature-verification-in-python/)
* [AWS Secrets Managerを使ってLambdaのシークレットを管理する](https://dev.classmethod.jp/articles/lambda-blueprint-slack-cloudwatch-with-secrets-manager/)