AWS SSM Change Calendarの仕組み

# はじめに
本記事はSSM Change Calendarの仕様を深堀して確認したときの内容をまとめたものです。
今後Change Calendarを使用する方のご参考になればと思います。

# SSM ChangeCalendarとは
Change Calendarとは、カレンダーのステータスを管理によってAWSのイベントを管理できるSystems Managerのサービスです。

Change Calendaではカレンダーイベントを設定することができ、それに沿ってカレンダーのステータスが変更されます。変更されるタイミングでAWSのイベントを発するため、これをEventBridgeなどで待ち構えて、起動契機として活用することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/918398/422c8b0a-66fb-434a-a4f7-24d40a648a12.png)

https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/systems-manager-change-calendar.html

カレンダーのステータスによって発せられるイベントの例です。以下の例ではCLOSE → OPENになった時の例を示しています。

```json:Change Calendar Event
{
    "version": "0",
    "id": "aa48eca7-a8fa-1f85-6ac0-bc48039695cd",
    "detail-type": "Calendar State Change",
    "source": "aws.ssm",
    "account": "<ACCOUNT_ID>",
    "time": "2024-06-20T12:55:00Z",
    "region": "ap-northeast-1",
    "resources": [
        "arn:aws:ssm:ap-northeast-1:<ACCOUNT_ID>:document/SampleChangeCalendar"
    ],
    "detail": {
        "state": "OPEN",
        "atTime": "2024-06-20T12:55:00Z",
        "nextTransitionTime": "2024-06-26T00:00:00Z",
        "event": "SampleEvent"
    }
}
```

また、Change Calendarには記事執筆現在、`GetCalendarState` APIが用意されています。(記事執筆現在使用できるAPIはこの1つです。)

`GetCalendarState` APIは特定の時刻におけるカレンダーの状態（OPEN/CLOSE）を取得することが可能なAPIです。工場として一つ休日カレンダーを作成しておき、工場内のシステムはジョブの実行前にそのカレンダーを一度参照することで休日を判断できるようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/918398/30d3d3c9-6909-6bc3-dc4d-6c37a7ca601f.png)

https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/change-calendar-getstate.html


# ChangeCalendarの仕組み
## GUIによるカレンダー生成
カレンダーをGUIから作成するときには以下のAPIをリクエストしていることがCroudtrailから確認することができます。

**① PutCalendar**
**② CreateDocument**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/918398/eb28ef2e-487d-d276-19c5-aaf5a1d893ef.png)


**①PutCalendar**
GUI上からカレンダーの作成ボタンをクリックすると、ユーザーから`PutCalendar`のリクエストがSSMに対して送られます。ここにはカレンダーの更新内容（Putリクエスト）が含まれており、この情報をもとにSSm側でSSMドキュメントの作成を行います。

:::note warn
本記事執筆現在、`PutCalendar` APIは現在マネジメントコンソール操作からのリクエストのみ対応しています。AWS CLIやSDKなどからコールをすることはできません。
:::

```json:PutCalendar
{
    :
    "eventName": "PutCalendar",
    :
    "requestParameters": {
        "name": "SampleChangeCalendar",
        "type": "DEFAULT_CLOSED",
        "description": "Sample",
        "cMEvents": "DISABLED",
        "vEvents": []
    },
    :
    "resources": [
        {
            "accountId": "<ACCOUNT_ID>",
            "ARN": "arn:aws:ssm:ap-northeast-1:<ACCOUNT_ID>:document/SampleChangeCalendar"
        }
    ],
    :
}
```


**②CreateDocument**
`DocumentType:ChangeCalendar`のSSMドキュメントを作成します。

```json:CreateDocument
{
    :
    "eventName": "CreateDocument",
    :
    "requestParameters": {
        "content": "HIDDEN_DUE_TO_SECURITY_REASONS",
        "name": "SampleChangeCalendar",
        "documentFormat": "TEXT"
    },
    "responseElements": {
        "documentDescription": {
            "hash": "08dbe9fbdb123c7d826e468103368044feecd857bbcfdbf8d2bfd53a33563f76",
            "hashType": "Sha256",
            "name": "SampleChangeCalendar",
            "owner": "<ACCOUNT_ID>",
            "createdDate": "Jun 20, 2024 1:02:26 AM",
            "status": "Creating",
            "documentVersion": "1",
            "description": "Sample",
            "platformTypes": [],
            "documentType": "ChangeCalendar",
            "schemaVersion": "",
            "latestVersion": "1",
            "defaultVersion": "1",
            "documentFormat": "TEXT",
            "tags": [],
            "documentId": "7f209fd2-4868-4aa9-a355-3025dc90d90f"
        }
    },
    :
}
```


## GUIによるカレンダーイベント生成
カレンダーイベントを作成するときには以下の4つのAPIを順番にリクエストしていることがCroudtrailから確認することができます。

**①PutCalendar**
**②CreateDocument**
**③UpdateDocument**
**④UpdateDocumentDefaultVersion**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/918398/ba842691-2c56-5c52-9b45-a42971d0f5cb.png)



**①PutCalendar**
カレンダー作成時と同様に`PutCalendar`APIが使用されます。`vEVENT`要素にGUIで入力したカレンダーイベント情報が含まれていることが確認できます。

```json:PutCalendar
{
    :
    "eventName": "PutCalendar",
    :
    "requestParameters": {
        "name": "SampleChangeCalendar",
        "type": "DEFAULT_CLOSED",
        "description": "Sample",
        "cMEvents": "DISABLED",
        "vEvents": [
            {
                "eventID": "e9ab8ac1-bda1-4ab5-8d8f-8f7c0d5930fa",
                "summary": "SampleCalendarEvent",
                "description": "Sample Calendar Event",
                "startTime": "2024-06-20T00:00:00",
                "startTimeZone": "Etc/UTC",
                "endTime": "2024-06-20T00:10:00",
                "endTimeZone": "Etc/UTC",
                "type": "STANDARD"
            }
        ]
    },
    :
    "resources": [
        {
            "accountId": "820356253015",
            "ARN": "arn:aws:ssm:ap-northeast-1:820356253015:document/SampleChangeCalendar"
        }
    ],
    :
}
```

**②CreateDocument**
次もカレンダー作成と同様に`CreateDocument`のAPIがリクエストされます。しかしながら、カレンダー作成の時点で既にDocumentは生成されているので、エラーとして処理されます。この処理が一連の流れの中に含まれていることは現段階では謎です...
（あくまで挙動からの推測ですが、SSM内でChangeCalendarを担う処理では、このAPIがエラー応答のときは後続の処理を実行するといったアルゴリズムになっているように見えます。）

ちなみにCloutrail上ではセキュリティ上の観点から、`Content`要素の内容は`HIDDEN_DUE_TO_SECURITY_REASONS`というようにマスク化された状態で記録されるようです。

```json:CreateDocument
{
    :
    "eventName": "CreateDocument",
    :
    "errorCode": "DocumentAlreadyExists",
    "errorMessage": "Document with same name SampleChangeCalendar already exists",
    "requestParameters": {
        "content": "HIDDEN_DUE_TO_SECURITY_REASONS",
        "name": "SampleChangeCalendar",
        "documentFormat": "TEXT"
    },
    :
}
```

**③UpdateDocument**
``UpdateDocument``の操作によってICS形式ファイルをChangeCalendarドキュメントに反映を行ってい行きます。前述の通り、CloudTrail上ではカレンダーイベントの内容は`HIDDEN_DUE_TO_SECURITY_REASONS`としてマスク化されています。
更新を行ったことで`responseElements`の`latestVersion`に2が格納されています。

```json:UpdateDocument
{
    :
    "eventName": "UpdateDocument",
    "requestParameters": {
        "content": "HIDDEN_DUE_TO_SECURITY_REASONS",
        "name": "SampleChangeCalendar",
        "documentVersion": "$LATEST",
        "documentFormat": "TEXT"
    },
    "responseElements": {
        "documentDescription": {
            "hash": "6e40d4d99d97753afd39073dae4fd7a23587f82c8afc2ad68a75896ae6de5c05",
            "hashType": "Sha256",
            "name": "SampleChangeCalendar",
            "owner": "<ACCOUNT_ID>",
            "createdDate": "Jun 20, 2024 1:03:51 AM",
            "status": "Updating",
            "documentVersion": "2",
            "description": "Sample",
            "platformTypes": [],
            "documentType": "ChangeCalendar",
            "schemaVersion": "",
            "latestVersion": "2",
            "defaultVersion": "1",
            "documentFormat": "TEXT",
            "tags": [],
            "documentId": "7f209fd2-4868-4aa9-a355-3025dc90d90f"
        }
    },
    :
}
```

*④*UpdateDocumentDefaultVersion**
更新したドキュメントのバージョンをデフォルトバージョンとして設定します。

```json:UpdateDocumentDefaultVersion
{
    :
    "eventName": "UpdateDocumentDefaultVersion",
    :
    "requestParameters": {
        "name": "SampleChangeCalendar",
        "documentVersion": "2"
    },
    "responseElements": {
        "description": {
            "name": "SampleChangeCalendar",
            "defaultVersion": "2"
        }
    },
    :
}
```



## 生成されたカレンダーとイベントの確認
前節までのGUI操作によってChange Calendarならびにカレンダーイベントを作成しました。
その登録内容を確認すると以下の通りです。
`DocumentType`が`ChangeCalendar`のSSMドキュメントが作成されていることがわかります。

```bash
$ aws ssm get-document --name SampleChangeCalendar --document-format TEXT
{
    "Name": "SampleChangeCalendar",
    "CreatedDate": "2024-06-20T01:03:51.367000+00:00",
    "DocumentVersion": "2",
    "Status": "Active",
    "Content": "BEGIN:VCALENDAR\r\nPRODID:-//AWS//Change Calendar 1.0//EN\r\nVERSION:2.0\r\nX-CALENDAR-TYPE:DEFAULT_CLOSED\r\nX-WR-CALDESC:Sample\r\nX-CALENDAR-CMEVENTS:DISABLED\r\nBEGIN:VEVENT\r\nDTSTAMP:20240620T010351Z\r\nUID:e9ab8ac1-bda1-4ab5-8d8f-8f7c0d5930fa\r\nSEQUENCE:0\r\nSUMMARY:SampleCalendarEvent\r\nDESCRIPTION:Sample Calendar Event\r\nDTSTART;TZID=Etc/UTC:20240620T000000\r\nDTEND;TZID=Etc/UTC:20240620T001000\r\nX-EVENT-TYPE:STANDARD\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n",
    "DocumentType": "ChangeCalendar",
    "DocumentFormat": "TEXT"
}
```

`Content`部分についてはICS形式で記述が行われており、ここでカレンダーイベントが定義されていることが確認できます。

```yaml:Content部分抽出(特殊文字反映済)
BEGIN:VCALENDAR
PRODID:-//AWS//Change Calendar 1.0//EN
VERSION:2.0
X-CALENDAR-TYPE:DEFAULT_CLOSED
X-WR-CALDESC:Sample
X-CALENDAR-CMEVENTS:DISABLED
BEGIN:VEVENT
DTSTAMP:20240620T010351Z
UID:e9ab8ac1-bda1-4ab5-8d8f-8f7c0d5930fa
SEQUENCE:0
SUMMARY:SampleCalendarEvent
DESCRIPTION:Sample Calendar Event
DTSTART;TZID=Etc/UTC:20240620T000000
DTEND;TZID=Etc/UTC:20240620T001000
X-EVENT-TYPE:STANDARD
END:VEVENT
END:VCALENDAR
```



https://qiita.com/inagacky/items/89dd781f7cba15f54048

| 項目 | 概要 | 必須 |
| :-- | :-- | :--: |
| BEGIN, END| 開始終了タグとして使用される。`VCALENDAR`や`VEVENT`を設定しタグ内に定義を行う。| 〇 |
| PROID | カレンダーの作成者を表す。基本的には`-//AWS//Change Calendar 1.0//EN` を設定する。Googleカレンダーなど他ツールで生成したカレンダーを使用するときには変更する必要があるので注意。| 〇 |
| VERSION | ICS形式のバージョンを示す。| 〇 |
| X-CALENDAR-TYPE | ChangeCalendarの設定値。構築時に設定したデフォルトステータスを表す。 | |
| X-WR-CALDESC | ChangeCalendarの設定値。構築時に設定した説明を表す。| |
| X-CALENDAR-CMEVENTS | ChangeCalendarの設定値。構築時に設定した`Add change management events to the calendar`の有無。 ||
| DTSTAMP | イベント作成時のタイムスタンプを表す。||
| UID | ユニークIDを表す。 | 〇 |
| SEQUENCE | シーケンスを表す。 ||
| SUMMARY | カレンダーイベントのタイトルを表す。| 〇 |
| DESCRIPTION | カレンダーイベントに設定した説明を表す。||
| DTSTAMP | カレンダーイベントの開始日時を表す。ISO8601形式に従って記述される。| 〇 |
| DTEND | カレンダーイベントの終了日時を表す。ISO8601形式に従って記述される。開始時刻から5分以上の間隔が必要である。| 〇 |
| X-EVENT-TYPE | カレンダーイベントのタイプを表す。`Standard`か`Advisory`を設定することが可能。アドバイザリーイベントには、カレンダー上での機能はなく、カレンダーを表示しているユーザーに情報を提供するためだけのもの。||

以下の公式サンプルでは`VEVENT`ではなく`VTODO`を使用していますが、記事執筆現在ChangeCalendarでは`VTODO`は対応していませんでしたのでお気を付けください。

https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ssm-document.html

# おわりに
Change Calendarはステータスが変わるタイミングでEventBridgeに連携をできることから、イベントスケジューラとしての活躍にも期待ができそうです。