AWS SSM Parameter StoreにあるNameのリストをフィルターして取得する

# この記事でできるようになること
どうも、IoTエンジニアのDangoと言います。AWSを用いたIoTシステムを構築していることが多いです。

今回はPythonでAWS SSM Parameter StoreにあるNameをフィルターして取得し、List化します。

# 前提
AWS SSM Parameter Storeの詳しい仕様については割愛します。
Python3 + boto3で操作する前提です(AWS CLIなどでもやることは大体同じですが)。
IAMの設定も済んでいる前提です。

## 基本操作
まずはboto3でssmを操作する方法を簡単に。
最初に、ssmのclientを作ります。

```python
import boto3
REGION = "ap-northeast-1"
ssm = boto3.client("ssm",region_name=REGION)
```

そして、次のように操作します。

```python
戻り値 = ssm.メソッド(引数)
```

#やりたいこと
AWS SSM Parameter Storeから読み出すとき、
Name(=パラメーター名)が決まっていれば、`ssm.get_parameters`にその値を入れて終わりなのですが、

「Nameの一部だけを知っている」場合に、同じ部分を持ったNameをListで取得できると、
`ssm.get_parameters`で一気にValue(=パラメーターの値)を取得できて便利です。

※ssm.get_parametersの使い方については、以下をご覧ください。
https://dev.classmethod.jp/articles/get-data-from-system-manager-parameter-store-using-boto3-ja/


##Nameの一部だけを知っている例
例えば、次のようなNameのparameterがあったとします。

```
hoge-sub
hoge-main
fuga-main
fuga-sub
```

これを、

- 「"hoge-"から始まる」
- 「"fuga-"から始まる」
- 「"main"を含む」
- 「"sub"を含む」

といった条件で検索できたら、該当するNameの中身を一度に読めて便利です。

どうすれば実現できるでしょうか。

## 実現方法
`ssm.describe_parameters`を使用します。

※補足
Nameが"-"などではなく"/"区切りになっている場合、`ssm.get_parameter_by_path`で済む話なのですが、命名規則に"/"が採用されていない場合でも、前方一致(BeginWith)や部分一致(Contains)で調べられます。

以下は、公式ドキュメントのdescribe_parametersの項目を解釈した内容です。
より詳しい情報は公式ドキュメントをご覧ください。
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ssm.html#SSM.Client.describe_parameters

### describe_parametersの基本
Nameが"hoge"で始まる。と検索する場合、次のように書きます。

```python
ret = ssm.describe_parameters(
    ParameterFilters=[
        {
            "Key":"Name",
            "Option":"BeginsWith",
            "Values":["hoge"]
        }
    ]
)
```

Nameに"main"を含む。という場合、次のように書きます。

```python
ret = ssm.describe_parameters(
    ParameterFilters=[
        {
            "Key":"Name",
            "Option":"Contains",
            "Values":["main"]
        }
    ]
)
```

Valuesの部分は、Listですので、複数検索語句を含めれば「or」で検索可能です。

### 戻り値

次のような構造の辞書が返ってきます(公式ドキュメントより)。


```python
{
    'Parameters': [
        {
            'Name': 'string',
            'Type': 'String'|'StringList'|'SecureString',
            'KeyId': 'string',
            'LastModifiedDate': datetime(2015, 1, 1),
            'LastModifiedUser': 'string',
            'Description': 'string',
            'AllowedPattern': 'string',
            'Version': 123,
            'Tier': 'Standard'|'Advanced'|'Intelligent-Tiering',
            'Policies': [
                {
                    'PolicyText': 'string',
                    'PolicyType': 'string',
                    'PolicyStatus': 'string'
                },
            ],
            'DataType': 'string'
        },
    ],
    'NextToken': 'string'
}
```

今はNameが欲しいので、こんな感じでListとして取り出しましょう。

```python
name_list=[]
for parameter in ret["Parameters"]:
    name_list.append(parameter["Name"])
```

### NextToken
しかし、話はこれで終わりではありません。
条件に一致するParameterが多い場合(確かデフォルトでは10より多い場合)、一回の`describe_parameters`ではパラメーターを全て読み出すことはできません。
一度に読み出せるパラメーター数がキーワード引数`MaxResults`によって決まっているからです。
以下のようにして変更できますが、最大でも50までです。


```python
ret = ssm.describe_parameters(
    ParameterFilters = [
        {
            "Key":"Name",
            "Option":"Contains",
            "Values":["main"]
        }
    ],
    MaxResults = 50 # 1~50 
)

```

51以上parameterの数が超える可能性がある場合、どうやって条件に当てはまるパラメータを全て取得するのでしょうか。
ここでNextTokenが登場します。
これは返り値の中に含まれています。

```python
next_token = ret['NextToken']
```

これは、いわば本の「しおり」のようなもので、
parameterのうち何件まで読み出したかをハッシュ値で返してくれています。
50件まで読み込んだのなら、51件以降はこのNextTokenを使って「続きから」読めます。

使い方は、次のような感じで`describe_parameters`の引数に指定します。

```python
ret = ssm.describe_parameters(
    ParameterFilters = [
        {
            "Key":"Name",
            "Option":"Contains",
            "Values":["main"]
        }
    ],
    MaxResults = 50, # 1~50 
    NextToken = next_token  # 追加
)
```

※補足
NextTokenやMaxResultsの機能は、もともとパラメーターの一覧をページネーションで切り替えられるようにした機能のようです。１ページ〇〇件と指定して、「次へ」を押すとNextTokenで次の〇〇件を取得する、という機能です。

#全体像
あとは、next_tokenが返って来なくなるまで`describe_parameters`し続ければ、全件読み出し完了、というわけです。
whileループで読み出しましょう。

```python
import boto3
REGION = "ap-northeast-1"
ssm = boto3.client("ssm",region_name=REGION)

names = [] # target

desc_args={
    "ParameterFilters":[
        {
            "Key":"Name",
            "Option":"BeginsWith",
            "Values":["hoge"]
        }
    ]
}

# NextTokenが空になるまでループを回す
while (True):
    ret = ssm.describe_parameters(**desc_args)  #キーワード引数の展開

    for parameter in ret["Parameters"]:
        names.append(parameter["Name"])

    if(not "NextToken" in ret):
        break
    else:
        desc_args["NextToken"]=ret["NextToken"]
```
なお、一回目のdescribeと二回目以降のdescribeでは、NextTokenに値を入れるかどうかで引数の使い方が異なるため、普通に書くとssm.describe_parametersを二回書くことになります。
**を使ってキーワード引数をアンパックすることにより、キーワード引数の数に関係なく一回のssm.describe_parametersで簡潔に記述できます。