EventBridgeのサンドボックスを使いイベントパターンのチェックを行う。

# はじめに
先日、`EventBridge`で`CloudTrail`に記録されたイベントを契機にするルールを作成した際、四苦八苦しながら少しだけサンドボックスの機能を使ってみたので、残しておこうと思います。

# EventBridgeのサンドボックス機能とは
`EventBridge`のルールを「**イベントパターンを持つルール**」の形式で作成する場合、各サービス等から出力されるイベントに合致するように「**イベントパターン**」を記載して、イベントに合致した場合にルールを実行する動作となりますが、「**イベントパターン**」の記載方法があっているかを確認するためには実際にイベントを発生させて確認する必要があります。

ただ、記載方法があっているかを確認するのに毎回イベントを発生させるのは非常に手間なので、2022年3月に新たに「**サンドボックス**」の機能が加わりました。

- 「[Amazon EventBridge がマネジメントコンソールでルールフィルタリングとイベント変換機能の強化を開始](https://aws.amazon.com/jp/about-aws/whats-new/2022/03/amazon-eventbridge-rule-filtering-transformation-console/)」

各サービスごとに用意されている「**サンプルイベント**」からサンプルを選択して、サンプルのイベントに対して、自分で作成した「**イベントパターン**」が合致するかを確認するか、もしくはイベント発生時に呼び出される**イベントオブジェクト**をサンプルとして登録して、「**イベントパターン**」が合致するかを確認する方法があります。

- サンプルイベントからサンプルを選択する場合
![Monosnap_20221120_194811.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/caa90fd1-a89b-be1c-8ec1-28884839eafc.png)

- 自分でサンプルを作成する場合
![Monosnap_20221120_195016.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/bad7fe55-93e3-0947-5e0b-16a287e550f0.png)

# 今回やりたかったこと
今回`EventBridge`で最終的にやりたかったこととしては、`Fargate`をブルーグリーンデプロイメントでデプロイ途中、デプロイを手動で停止した際のイベントを検知して通知することをやりたかったというのが本来の目的です。

![Monosnap_20221120_200350.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/782110ad-de0d-cb0f-f225-b5d5496ea242.png)

ただ、イベントパターンを作成する際に、イベントパターンの書式確認のため毎回`Fargate`のデプロイを実施するのは骨が折れるため、「**サンドボックス**」で書式があっていることを確認してみようと思います。

# カスタムサンプルイベントの作成
元々用意されているサンプルイベントを使うのもよいですが、今回は`CloudTrail`に記録されるイベントを使いたかったので、カスタムでサンプルイベントを作成しようと思います。

ただ、`CloudTrail`に記録されるイベントの履歴は、実際にイベントが発生した際に呼び出されるイベントオブジェクトの書式と異なるようで、`CloudTrail`に記録されるJSON形式のイベント履歴をそのままサンドボックスのサンプルイベントにコピペしてチェックしても「**サンプルイベントは無効です。**」と出てしまい、チェックに失敗してしまいます。

ちなみに以下が`CloudTrail`に記録されるイベント例。

<details><summary>CloudTrailに記録されるイベント例（展開してください）</summary>

```json:CloudTrailに記録されるイベント例
{
  "eventVersion": "1.08",
  "userIdentity": {
      "type": "IAMUser",
      "principalId": "XXXXXXXXXXXXXXXXXXXXX",
      "arn": "arn:aws:iam::123456789012:user/testuser",
      "accountId": "123456789012",
      "accessKeyId": "YYYYYYYYYYYYYYYYYYYY",
      "userName": "testuser",
      "sessionContext": {
          "sessionIssuer": {},
          "webIdFederationData": {},
          "attributes": {
              "creationDate": "2022-11-20T03:11:57Z",
              "mfaAuthenticated": "true"
          }
      }
  },
  "eventTime": "2022-11-20T11:27:02Z",
  "eventSource": "codedeploy.amazonaws.com",
  "eventName": "StopDeployment",
  "awsRegion": "ap-northeast-1",
  "sourceIPAddress": "AWS Internal",
  "userAgent": "AWS Internal",
  "requestParameters": {
      "deploymentId": "d-3LVK9RJSK",
      "autoRollbackEnabled": true
  },
  "responseElements": {
      "status": "Succeeded",
      "statusMessage": "No more commands will be scheduled for execution in the deployment instances"
  },
  "requestID": "2ed04e08-daee-4ace-b132-f61e802f6cd0",
  "eventID": "69c587a6-eb5c-4fa3-a7cb-b9e735f81ba1",
  "readOnly": false,
  "eventType": "AwsApiCall",
  "managementEvent": true,
  "recipientAccountId": "123456789012",
  "eventCategory": "Management",
  "sessionCredentialFromConsole": "true"
}
```

</details>

また、後述する方法で取得したイベントオブジェクト例。

<details><summary>イベント発生時に実行されるイベントオブジェクト例（展開してください）</summary>

```json:イベント発生時に実行されるAPI例
{
    "version": "0",
    "id": "fbc9cc14-052e-06c5-0b96-51965c1ca766",
    "detail-type": "AWS API Call via CloudTrail",
    "source": "aws.codedeploy",
    "account": "123456789012",
    "time": "2022-11-20T11:27:02Z",
    "region": "ap-northeast-1",
    "resources": [],
    "detail": {
        "eventVersion": "1.08",
        "userIdentity": {
            "type": "IAMUser",
            "principalId": "XXXXXXXXXXXXXXXXXXXXX",
            "arn": "arn:aws:iam::123456789012:user/testuser",
            "accountId": "123456789012",
            "accessKeyId": "YYYYYYYYYYYYYYYYYYYY",
            "userName": "testuser",
            "sessionContext": {
                "sessionIssuer": {},
                "webIdFederationData": {},
                "attributes": {
                    "creationDate": "2022-11-20T03:11:57Z",
                    "mfaAuthenticated": "true"
                }
            }
        },
        "eventTime": "2022-11-20T11:27:02Z",
        "eventSource": "codedeploy.amazonaws.com",
        "eventName": "StopDeployment",
        "awsRegion": "ap-northeast-1",
        "sourceIPAddress": "AWS Internal",
        "userAgent": "AWS Internal",
        "requestParameters": {
            "deploymentId": "d-3LVK9RJSK",
            "autoRollbackEnabled": true
        },
        "responseElements": {
            "status": "Succeeded",
            "statusMessage": "No more commands will be scheduled for execution in the deployment instances"
        },
        "requestID": "2ed04e08-daee-4ace-b132-f61e802f6cd0",
        "eventID": "69c587a6-eb5c-4fa3-a7cb-b9e735f81ba1",
        "readOnly": false,
        "eventType": "AwsApiCall",
        "managementEvent": true,
        "recipientAccountId": "123456789012",
        "eventCategory": "Management",
        "sessionCredentialFromConsole": "true"
    }
}
```

</details>

上記２つの`JSON`を比較したもの。
左が`CloudTrail`で記録されたJSON、右がイベント発生時に実行されるイベントオブジェクトのJSONです。

![Monosnap_20221120_204407.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/1eb15ac1-88ec-dc0d-4645-e9dfa946c13a.png)

## イベント発生時に実行されるAPI
`CloudTrail`で記録されたイベントの`JSON`と、イベント発生時に実行されるAPIの`JSON`を比較すると、イベント発生時に実行されるイベントオブジェクトは以下のように`detail`の箇所に、`CloudTrail`に記録されるイベントが含まれることがわかるかと思います。

```json:
{
    "version": "0",
    "id": "fbc9cc14-052e-06c5-0b96-51965c1ca766",
    "detail-type": "AWS API Call via CloudTrail",
    "source": "aws.codedeploy",
    "account": "123456789012",
    "time": "2022-11-20T11:27:02Z",
    "region": "ap-northeast-1",
    "resources": [],
    "detail": {
      "CloudTrail のイベント記録"
    }
}
```

サンドボックスで試すだけなら`detail`より上に記載されている箇所を自分で適当に作って書式を合わせてもできるかと思いますが、それはそれで手間なので、イベント発生時に実行されるイベントオブジェクトを記録する仕組みを仕込んだ上で一度デプロイ停止のテストを行い、確認してみようと思います。

【参考】
- 「[CloudTrail のイベントを EventBridge で検知するためのイベントパターンの設定を教えてください](https://dev.classmethod.jp/articles/tsnote-eventbridge-how-do-i-configure-event-patterns-for-eventbridge-to-detect-cloudtrail-events/)」

# イベントオブジェクトの出力
`Lambda`のデベロッパーガイドにイベントオブジェクトの内容をログに記録するコードが書かれていたため、こちらを使っていこうと思います。

- 「[Node.js の AWS Lambda 関数ハンドラー](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/nodejs-handler.html)」

```js:index.js
exports.handler =  async function(event, context) {
  console.log("EVENT: \n" + JSON.stringify(event, null, 2))
  return context.logStreamName
}
```

- 「[Lambda関数の作成](#lambda関数の作成)」
- 「[EventBridgeルールの作成](#eventbridgeルールの作成)」

## Lambda関数の作成
`AWS Lambda`のダッシュボードより「**関数の作成**」から、以下のように設定して「**関数の作成**」を選択します。

![Monosnap_20221120_212117.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/92a13512-8826-98f7-a478-d5bab04704ba.png)

## EventBridgeルールの作成
`Amazon EventBridge`のダッシュボードより「**ルール**」→「**ルールを作成**」から以下のように設定して次へ進みます。

![Monosnap_20221120_213150.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/eda0f4e4-c65f-e5e4-9026-c44afd60a82d.png)

そのまま下に進み、「**イベントパターン**」　の設定を以下の様に設定して次へ進みます。

今回の場合、`CloudTrail`に記録された情報を見ると、「**AWSのサービス**」、「**イベントタイプ**」は以下のような設定となるため、選択後、次へ進みます。

![Monosnap_20221120_213316.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/52aa83cb-5cdc-e823-444a-87f1bf9b57e6.png)

ターゲットは先程作成した`Lambda`を指定して次へ進みます。

![Monosnap_20221120_213425.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/63e238c1-98b6-b8ee-d672-3baa168d1768.png)

そのまま次へ進み、「**ルールの作成**」ができたら完了です。

## イベントオブジェクトの出力
`EventBridge`のイベントが作成終わったら、実際にイベントを発生させてイベントオブジェクトを出力させます。

私の場合は、ブルーグリーンデプロイを停止したタイミングで出力されるため事象を発生させてみます。

今回`Lambda`でイベント発生時のイベントオブジェクトを記録するようにしているので、ログは`CloudWatch Logs`に出力され、`output_event_object`と言った名前で設定した場合は「`/aws/lambda/output_event_object`」といったロググループに出力されております。

ログストリームに`detail`等の項目を含んだ`JSON`形式のログが出力されていれば成功です。

![Monosnap_20221123_092131.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/3132df77-ae04-28df-d9b6-e6e0429ba83a.png)

## サンドボックスへのログ貼り付け
`EventBridge`に戻り、「**サンドボックス**」→「**イベントパターン**」を選択し、以下のようにして先程`CloudWatch Logs`に出力されたログをサンプルイベントにコピペします。
※１行目のCloudWatch Logsのログメッセージは不要

|項目|設定|
|:--|:--|
|イベントソース|AWSイベントまたはEventBridgeパートナーイベント|
|サンプルイベント|ご自身名前を入力|
|入力欄|CloudWatch Logsに出力されたログのコピペ|

![Monosnap_20221121_093600.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/4d5e5e8e-282a-9202-99bf-6f6e4818d3fc.png)

## イベントパターンの作成
いよいよイベントに合致させるイベントパターンを作成し、書式があっているかを確認してみようと思います。

イベントパターンは自分で最初から作成することもできますが、それだと大変なので、以下のようにメニューから選択して雛形を作ってから必要な部分を自分で追加してみます。

今回は「**特定のオペレーション**」に、デプロイを停止した際のオペレーションとなる「`StopDeployment`」を指定して、デプロイ停止時のアクションをトリガーとするイベントパターンを作成してみます。

![Monosnap_20221121_221931.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/05826ded-a987-c835-78e2-dd8eeaf5ae8f.png)

入力し終わって「**テストパターン**」ボタンを押すとサンプルイベントとイベントパターンのチェックが行われ、イベントパターンに一致すれば以下のように表示されます。

:::note info
イベントパターンに一致したサンプルイベント
:::

イベントパターンの内容が一致しなければ以下のように表示されます。

:::note alert
サンプルイベントがイベントパターンと一致しませんでした
:::

そもそもサンプルイベントの書式が誤っていたりする場合は以下のように表示されます。

:::note alert
サンプルイベントは無効です。
:::

最初、`CloudTrail`のログそのままコピペすれば良いものだと思って試していた際は、`JSON`のフォーマットチェックは通るのにイベントパターンに何を書いても失敗していたので、ここに何を書けば通るのかわからずだいぶ悩みました。

## イベントパターンの編集
上記では`eventName`を`StopDeployment`に指定してチェックしましたが、「**パターンを編集**」を選択することで、以下のように直接編集できる画面に切り替わります。

イベントパターンに記載される内容は`CloudWatch Logs`に出力された`JSON`形式のログのうち、マッチさせたい内容を記載すればOKです。

但し、`CloudWatch Logs`のログをそのままイベントパターンに記載するのではなく、階層構造はそのままで、`Value`（値の部分）は`[]`でくくる必要があります。

以下は`autoRollbackEnabled`と`status`の値もパターンマッチの条件にした場合の例。

![Monosnap_20221121_222949.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/69cc8bdc-8ef9-d4ec-8c4d-f25c5e8a3245.png)

# おわりに
いちいち事象を発生させてトライアンドエラーで進めていかなくても、一度イベントログを出力してからサンドボックスで確認すれば、イベントパターンがマッチするかを試せるのは非常にやりやすいと感じました。