Systems Manager Automation(SSM Automation)の備忘録

<!-- ================================================= -->
# SSM Automationの実行方法
<!-- ================================================= -->
公式サイト：
[AWS Systems Manager Automation](https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/systems-manager-automation.html)

以下の点が公式ドキュメントやその他Blog情報からはわかりずらかったり、やってみて初めて気づいた点があるため、備忘録的に記録しておく。

1. Documentの入力値、出力値
    - Documentの入力パラメータでのStringMapやMapListの値の記述方法
    - Documentから各Stepへ入力値を渡す方法
    - Stepの出力値を次以降のStepの入力値やDocumentの出力値にする方法
1. **aws:branch**での分岐の記述方法
1. Documentの共有と**aws:executeScript**のStepを含むDocumentでのデフォルトの想定ロール
1. Automationでのboto3の注意事項

<!-- ================================================= -->
# Documentの入力値、出力値
<!-- ================================================= -->
- Documentの入力パラメータでのStringMapやMapListの値の記述方法
Documentの入力パラメータでタイプを指定できるが、特にStringMapやMapListの場合、かつ必須をNoにした際のデフォルト値をどのように記述して指定すればよいか。
結論から言うと、ここはJsonの書式で記載する必要がある。

| StringMap  | MapList                  |
|:-----------|:-------------------------|
|辞書型      |1組のキーバリュー辞書の配列|

```:StringMap
{ "key1":"value1", "key2":"value2", ... }
```
```:MapList
[ { "key11":"value11", "key12":"value12", ... }, { "key21":"value21", ... } ... ]
```

- Documentから各Stepへ入力値を渡す方法
Stepで**aws:executeScript**を実行させたい場合、Documentとして入力した値を当該Stepの入力値としたい場合がある。
その場合、当該Stepで**追加の入力**という項目があるが、ここで

| 入力名        | 入力値   |
|:-------------|:----------|
| InputPayload | 以下参照  |
```
parameter1name: '{{ documentinputparametername }}'
parameter2name: '{{ step1.stepoutputparametername }}'
:
```
とシングルクォーテーションと波括弧2つで囲って指定する。
この**parameter1name**という変数名はスクリプト内で

```python:python
def script_handler(events, context):

parameter1 = events['parameter1name']
:
```
のように、events引数の連想配列のキー名となる。
なお、Documentの入力値として指定した変数名と異なる変数名でスクリプトへ渡しても良い。~~(混乱を避ける意味では統一しておいたほうが無難)~~

- Stepの出力値を次以降のStepの入力値やDocumentの出力値にする方法
スクリプトの出力値をStepの出力値として抽出し、次以降のStepの入力値やDocumentの出力値としたい場合、スクリプト側で連想配列をreturnする。
```python:python
result = {
  'key1' : 'value1',
  'key2' : 'value2'
}

return result
```
また、Stepの**出力**で以下のように(抽出したい要素の数だけ)出力値の変数名とパラメータを指定する。
| 名前            | セレクター    | タイプ           |
|:----------------|:-------------|:-----------------|
| Stepの出力値として定義する変数名 | `$.Payload.`の後に、returnした連想配列のキー名を指定 |returnした連想配列のキー名に対応する値の型|

>- セレクター:例えば、キー名がkey1なら`$.Payload.key1`となる
>- タイプ:他のStepの入力値やDocumentの出力値に利用する場合、StringかStringListを指定するのが無難

Stepで**aws:executeAutomation**により他のドキュメントを関数のように呼び出すことが可能であるが、Documentの出力でStepの出力値等を指定しておくとDocument間で実行結果等の値をやり取り可能になる。
(ロールを想定のにある)**出力**に、特定のStepの出力値を指定したい場合は以下のように記述する。
```:出力
[ "step1.step1outputparametername","step2.step2outputparametername",... ]
```

<!-- ================================================= -->
# aws:branchでの分岐の記述方法
<!-- ================================================= -->
下記のように、**入力**の**Choice**の部分に**MapList**形式で分岐先と評価する変数、評価式を指定する。
評価は上から行われ、評価式(下記では**StringEquals**)が**true**になったMapのNextStepで記載したStepにジャンプする。

例えば、以下のMapListの場合
```json:Choice
[
  {
    "NextStep": "Step3"
    "Variable": "''{{documentinputparametername}}''"
    "StringEquals": "OutputValue1"
  },
  {
    "NextStep": "Step4"
    "Variable": "''{{step1.stepoutputparametername}}''"
    "StringEquals": "OutputValue2"
  },
  :
]
```
1. Documentを実行する際に入力した入力値**documentinputparametername**の値が評価式の値**OutputValue1**と等しい場合、Step3へ
1. そうではない場合、Step1の出力値である**step1.stepoutputparametername**が**OutputValue2**と等しい場合、Step4へ
1. いずれでもない場合、本Stepの次のStepへ実行が遷移する(Default動作を定義していた場合はDefault動作に従う)

という動作となる。
このため、**aws:executeScript**のStepの実行結果を元に分岐処理を行いたい場合(Documentの入力値や前Stepの**aws:executeScript**の実行結果で分岐処理を行いたい場合等)、**aws:executeScript**のStepの定義で**追加の入力**と**出力**をきちんと行っておく必要がある。

<!-- ================================================= -->
# Documentの共有と**aws:executeScript**のStepを含むDocumentでのデフォルトの想定ロール
<!-- ================================================= -->
AWS Organizationsによって複数のアカウントを管理している場合、特定のアカウントで作成したDocumentを他のアカウントへ共有したい場合、Documentの詳細で**アクセス許可**に共有させたいAWSアカウントIDを指定すれば良いが、以下の落とし穴がある。

- 共有する場合はDocumentの**デフォルトの想定ロール**を**空白**にしないとならない。<br>想定ロールはARNで指定するため、共有先のアカウントで当該Documentを実行しようとした場合、Documentに想定ロールが指定されていると**共有元のアカウントのIAMロールで実行**しようとしてしまって権限エラーで実行できないことになる。
- 一方、**aws:executeScript**のStepを含むDocumentの場合、想定ロールが指定されていない場合は**iam:PassRole**の権限エラーとなって実行できない。
- オートメーションドキュメントの実行で**シンプルな実行**ではなく**複数のアカウントとリージョン**で実行しようとしても、上記は回避できない。<br>一見**オートメーション実行ロール名**を指定し、実行させたいアカウント上に同名のIAMロールを用意すれば可能に見えるが、実際は**aws:executeScript**のStepが1つでも入っており、かつ想定ロールが指定されていない場合、実行させたいアカウント上でStepの処理が実行されるタイミングで**iam:PassRole**権限エラーで異常終了する。

>推測であるが、**aws:executeScript**のStepを実行しようとした段階で内部的に想定ロールへ**iam:PassRole**しようとしているようで、そこでエラーとなっている模様

よって、**aws:executeScript**のStepを含むDocumentは共有できず、実行させたいアカウント上に想定ロールだけ異なるDocumentをコピーで作成する必要がある。

<!-- ================================================= -->
# Automationでのboto3の注意事項
<!-- ================================================= -->
[boto3のリファレンス](https://boto3.amazonaws.com/v1/documentation/api/latest/index.html)
以下のスクリプトはboto3の各サービス(例は**ec2**であるが、各AWSサービス名に置換すること)毎のオブジェクトを作成し、そのメソッド一覧を表示するものである。
これを**aws:executeScript**のStep上で実行してみるとわかるが、実は一部のAWSサービスについてはboto3の一部のメソッドが実装されていないことが分かる。
(SSM Automationの実行環境上の問題であり、EC2等の通常のOS上に作成されたboto3の環境ではきちんと実装されている)

```python:boto3メソッド一覧表示
import boto3
import inspect

def script_handler(events, context):
  client = boto3.client('ec2')

  for x in inspect.getmembers(client, inspect.ismethod):
    print x[0]

  return
```

2023/3時点で確認している限りでは以下がある。
| AWSサービス名 | 未実装のメソッド |
|:-------------|:-----------------|
| SSOAdmin     | attach_customer_managed_policy_reference_to_permission_set |
|              | attach_managed_policy_to_permission_set |
|              | delete_inline_policy_from_permission_set |
|              | delete_permissions_boundary_from_permission_set |
|              | detach_customer_managed_policy_reference_from_permission_set |
|              | detach_managed_policy_from_permission_set |
|              | put_permissions_boundary_to_permission_set |

オブジェクトにメソッドが未実装の場合、**AttributeError**が発生するので、**AttributeError**で異常終了した場合は諦めるか、**aws:branch**と**aws:executeAwsApi**のStepを作成したりして回避するという緊急手段を取る必要がある。

<!-- ================================================= -->
## 履歴
<!-- ================================================= -->
- 2023/3/24 初稿
- 2023/3/24 Documentの出力について漏れていたので追記
- 2023/3/29 boto3のsso-adminに未実装のメソッドを追加で見つけたため追記