AppConfigにデプロイされたときに設定内容をslackに通知する

この記事は[モチベーションクラウドシリーズ Advent Calendar 2022](https://qiita.com/advent-calendar/2022/mcs) 9日目の投稿です。

# はじめに

AWSにはAppConfigというアプリケーションの設定などを管理するサービスがあり、さらに今年の3月に機能フラグとしての利用に特化した機能もリリースされました。

https://docs.aws.amazon.com/ja_jp/appconfig/latest/userguide/what-is-appconfig.html

https://aws.amazon.com/jp/about-aws/whats-new/2022/03/aws-appconfig-feature-flags/

機能フラグ(Feature Flag)についてはこちらを参考にしてください。

https://www.atlassian.com/ja/continuous-delivery/principles/feature-flags

弊社サービスにおいてもAppConfigを機能フラグとして活用しているのですが、設定内容が変更された際に変更内容をSlackに通知したかったため仕組みを作りました。
やっていることはシンプルですがググってもあまり似たようなケースがヒットしなかったため、参考になれば幸いです。

# 構成

![appconfig.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2348276/907d53f4-7cf2-de0c-ea18-c7234869f8d0.png)

ざっくり上記の構成になります。
Lambdaの代わりにAWS Chatbotを使用してSlackへメッセージ投稿するパターンもありますが、その場合リソース名とイベント名しか取得できず具体の設定内容を通知できなかったため、間にLambdaを挟んでPythonのプログラムからSlackへ投稿しています。

投稿されるメッセージは下記のようなイメージです。
※ アイコンは事前にSlack側でカスタム絵文字を登録しています。

![スクリーンショット 2022-12-06 18.17.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2348276/cf25b774-ef36-8de9-3490-e1bc5c802e92.png)

# 実装

## AppConfig

まず公式手順に則ってアプリケーション、プロファイル/機能フラグ、環境を構築します。

https://docs.aws.amazon.com/ja_jp/appconfig/latest/userguide/appconfig-working.html

構築した後にAppConfigの拡張機能を設定します。

https://docs.aws.amazon.com/ja_jp/appconfig/latest/userguide/working-with-appconfig-extensions-about-predefined-notification-eventbridge.html

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2348276/888a9b3f-937d-4ab5-b6cf-064f52ee4d34.png)

AppConfigの拡張機能タブから"AppConfig deployment events to Amazon EventBridge"を選択します。

右上の"リソースに追加"から通知したいリソースを選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2348276/a713d761-cf54-af00-ad0b-9d00b3daefa5.png)

リソースはアプリケーション/プロファイル/環境から選択することができ、また複数選択も可能です。
ただしアプリケーションとプロファイルの両方に紐づけてしまうと通知も2回来てしまうのでお気をつけください。

## Lambda

次にLambdaの設定をします。

一から作成を選択し、ランタイムはPython3.9にして関数作成します。

実行ロールにAppConfigを参照するための権限を付与します。
AppConfig関連についてはなぜかAWS管理のポリシーがないため下記のポリシーを作成して付与しました。
おそらく全ては不要ですがlist/Getにプラスして `StartConfigurationSession`が必要になります。

<details><summary>Policy設定</summary>


```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "appconfig:ListEnvironments",
                "appconfig:ListTagsForResource",
                "appconfig:GetExtensionAssociation",
                "appconfig:GetLatestConfiguration",
                "appconfig:ListDeployments",
                "appconfig:GetExtension",
                "appconfig:GetDeployment",
                "appconfig:ListExtensions",
                "appconfig:GetEnvironment",
                "appconfig:GetDeploymentStrategy",
                "appconfig:GetHostedConfigurationVersion",
                "appconfig:ListConfigurationProfiles",
                "appconfig:ListApplications",
                "appconfig:ListExtensionAssociations",
                "appconfig:ListDeploymentStrategies",
                "appconfig:GetConfiguration",
                "appconfig:GetApplication",
                "appconfig:GetConfigurationProfile",
                "appconfig:ListHostedConfigurationVersions",
                "appconfig:StartConfigurationSession"
            ],
            "Resource": "*"
        }
    ]
}
```
</details>


実際の使用するコードは下記です。

なおコードについては下記のブログを参考にしました。

https://aws.amazon.com/jp/blogs/mt/using-aws-appconfig-feature-flags/

<details><summary>サンプルコード</summary>

:::note info 
SlackのWebHookやチャンネル名、投稿する文面などは適宜変更してください。
:::

```python
import json
import boto3
import os
import urllib.request

SLACK_URL = os.environ['SLACK_URL']
SLACK_CHANNEL = os.environ['SLACK_CHANNEL']


def post_slack(msg):
    data = {
        'text': msg,
        'username': 'AppConfig',
        'icon_emoji': ':appconfig:',
        'channel': SLACK_CHANNEL
    }

    method = 'POST'
    request_headers = {'Content-Type': 'application/json; charset=utf-8'}
    body = json.dumps(data).encode("utf-8")
    request = urllib.request.Request(
        url=SLACK_URL,
        data=body,
        method=method,
        headers=request_headers
    )
    urllib.request.urlopen(request)
    print("Slackにメッセージ投稿しました")
    print(f"Message: {msg}")


def get_environment_name(application_id, environment_id):
    client = boto3.client("appconfig")
    environment = client.get_environment(
        ApplicationId=application_id,
        EnvironmentId=environment_id
    )
    print(f"環境名: {environment['Name']}")
    return environment['Name']


def get_appconfig_configuration(application_id, environment_id, profile_id):
    client = boto3.client("appconfigdata")
    session = client.start_configuration_session(
        ApplicationIdentifier=application_id,
        EnvironmentIdentifier=environment_id,
        ConfigurationProfileIdentifier=profile_id
    )
    config_token = session['InitialConfigurationToken']
    configuration = client.get_latest_configuration(
        ConfigurationToken=config_token
        )['Configuration'].read()
    print(f"config: {configuration.decode('utf-8')}")
    return configuration.decode('utf-8')


def lambda_handler(event, context):
    application_id = event['detail']['Application']['Id']
    environment_id = event['detail']['Environment']['Id']
    profile_id = event['detail']['ConfigurationProfile']['Id']
    profile_name = event['detail']['ConfigurationProfile']['Name']
    environment_name = get_environment_name(
        application_id,
        environment_id
    )
    configuration = get_appconfig_configuration(
        application_id,
        environment_id,
        profile_id
    )
    msg = f"env: {environment_name}にprofile: {profile_name}がデプロイされました。\n"
    msg += f"``` {configuration} ```"
    post_slack(msg)

```

</details>


AppConfigのeventのサンプルだとApplication/Environment/ConfigurationProfileのそれぞれIdとNameを通知してくれるみたいですが、動作確認した際何故かApplicationとEnvironmentはNameがeventに含まれていなかったです。
そのためEnvironmentについてはコード内でIDを元に取得するようにしました。

<details><summary>公式Docのeventサンプル</summary>


```json
{
   "version":"0",
   "id":"c53dbd72-c1a0-2302-9ed6-c076e9128277",
   "detail-type":"On Deployment Complete",
   "source":"aws.appconfig",
   "account":"111122223333",
   "time":"2022-07-09T01:44:15Z",
   "region":"us-east-1",
   "resources":[
      "arn:aws:appconfig:us-east-1:111122223333:extensionassociation/z763ff5"
   ],
   "detail":{
      "InvocationId":"5tfjcig",
       "Parameters":{
         
      },
      "Type":"OnDeploymentComplete",
      "Application":{
         "Id":"ba8toh7",
         "Name":"MyApp"
      },
      "Environment":{
         "Id":"pgil2o7",
         "Name":"MyEnv"
      },
      "ConfigurationProfile":{
         "Id":"ga3tqep",
         "Name":"MyConfigProfile"
      },
      "DeploymentNumber":1,
      "ConfigurationVersion":"1"
   }
}
```
</details>


## EventBridge

最後にEventBridge側の設定をします。

公式の手順に則ってルールの作成をします。

https://docs.aws.amazon.com/ja_jp/eventbridge/latest/userguide/eb-get-started.html

イベントパターンの箇所については下記のように設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2348276/a6e7789d-159c-c54e-922c-da6805a6240f.png)

AWSのサービス: AppConfig
イベントタイプ: AppConfig Extensions Event Notification

特定のイベントを選択すると下記の３つから通知の対象を選択することができるので適宜変更してください。
任意のイベントを選択すると全てのイベントが対象になります。

- On Deployment Rollback
- On Deployment Complete
- On Deployment Start

その後にターゲットに先ほど作成したLambdaを指定して完了です。

# まとめ

簡単なコードを書く必要はありましたが、全体含めても数時間で設定しきれたので良かったと思います。
AppConfigの拡張機能は自作することも可能なようなので機会があれば試してみたいです。

https://aws.amazon.com/jp/about-aws/whats-new/2022/07/aws-announces-appconfig-extensions/