CloudFormationコード(yaml)からパラメータ一覧を作りたい

## はじめに

CloudFormationのyamlファイルから、パラメータに関する情報のみ抜き出して、一覧を作れるか挑戦しました。yamlの書き方によってはエラーになる未完成品ですが、個人的には及第点をとれたのでここに残します。

## 参考

今回のスクリプト作成にあたり、参考にさせて頂いたサイトです。

- [Python で YAML ファイルを扱う](https://maku77.github.io/python/io/yaml.html)
- [【小ネタ】PythonでCloudFormationテンプレートをパースする時に組み込み関数の短縮系構文に邪魔されない方法](https://dev.classmethod.jp/articles/dynamolocal-from-cfn/)


## スクリプト（Python3）

早速ですが、スクリプトを紹介します。

- スクリプトの流れ
    - （１）CloudFormationコード(yaml)をテキストファイルとして読み込む
    - （２）短縮構文を拡張する（例： `!Sub` -> `Fn::Sub` , `!Ref` -> `Fn::Ref`）
    - （３）短縮構文がない状態でyamlとして読み込む
    - （４）yamlの中身を確認し、Parameters配下の情報をリスト表示する

```python:paramlist.py
## command sample
## python paramlist.py test.yml

# yamlモジュールインストール `$ pip install pyyaml`
import yaml
import sys
import re

# 深堀りしない
exclusionStr = "|AWSTemplateFormatVersion|Description|Type|TemplateURL|DependsOn|Mappings|Outputs|"

args = sys.argv
path = args[1]

#（１）CloudFormationコード(yaml)をテキストファイルとして読み込む
f = open(path)
s0 = f.read()
f.close()

# （２）短縮構文を拡張する（例： `!Sub` -> `Fn::Sub` , `!Ref` -> `Fn::Ref`）
s1 = re.sub("!((Sub|Ref|Join|GetAtt|FindInMap))\s", r'Fn::\1 ', s0)

#（３）短縮構文がない状態でyamlとして読み込む
obj = yaml.safe_load(s1)

#（４）yamlの中身を確認し、Parameters配下の情報をリスト表示する
def readYaml( curObj, pathStr , exeFlg):
    try:
        if exeFlg == 0:
            for key in curObj:
                # 次の階層に進む
                curFlg = key in exclusionStr
                if not curFlg:
                    if key == "Parameters":
                        nxtFlg = 1
                    else:
                        nxtFlg = 0
                    pathStr += "/" + key
                    readYaml( curObj[key] , pathStr , nxtFlg)
        else:
            print("---- {0} ----".format( pathStr ) )
            # パラメータの項目と値を表示
            for key in curObj:
                print( "\t{0} - {1}".format(key , curObj[key] ) )
    except Exception as e:
        print("ERROR curObj = {0}, pathStr = {1}, exeFlg = {2}".format( curObj, pathStr, exeFlg ) )
        print(e)

#############################
## -------- START -------- ##
print("---- Parameter List ----" )
readYaml( obj , "" , 0 )
```

## 実行結果

以下のCloudFormationコードに対してスクリプトを実行します。

```yaml:test.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: cloudformation yaml sample

Parameters:
  hogePrefix:  { Type: String , Default: hogefuga123 }
  BucketUrl: { Type: String , Default: "https://hogefuga123.s3.amazonaws.com/" }
  AZName001: { Type: String , Default: ap-northeast-1a }
  AZName002: { Type: String , Default: ap-northeast-1c }
  VPCName: { Type: String , Default: vhoge01 }

Resources:
  VPC:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: !Sub "${BucketUrl}${VPCTemplate}"
      Parameters: 
        hogePrefix: !Ref hogePrefix
        BucketUrl: !Ref BucketUrl
        VPCName: !Ref VPCName
```

実行結果

```console
$ python paramlist.py test.yml
---- Parameter List ----
---- /Parameters ----
        hogePrefix - {'Type': 'String', 'Default': 'hogefuga123'}
        BucketUrl - {'Type': 'String', 'Default': 'https://hogefuga123.s3.amazonaws.com/'}
        AZName001 - {'Type': 'String', 'Default': 'ap-northeast-1a'}
        AZName002 - {'Type': 'String', 'Default': 'ap-northeast-1c'}
        VPCName - {'Type': 'String', 'Default': 'vhoge01'}
---- /Parameters/Resources/VPC/Properties/Parameters ----
        hogePrefix - Fn::Ref hogePrefix
        BucketUrl - Fn::Ref BucketUrl
        VPCName - Fn::Ref VPCName
```



## おまけ

#### [おまけ１] Cloud9 のセットアップ

本スクリプトを AWS Cloud9 で使うときの環境設定です。

```
# デフォルトを Python 2 -> Python 3 に切り替える
$ sudo alternatives --config python

$ pip -V
$ sudo pip install --upgrade pip
$ pip -V

# yamlモジュールをインストール
$ pip install pyyaml
```


#### [おまけ２] そのままyamlロードするとエラーになる

- [【小ネタ】PythonでCloudFormationテンプレートをパースする時に組み込み関数の短縮系構文に邪魔されない方法](https://dev.classmethod.jp/articles/dynamolocal-from-cfn/)

上のサイトで紹介されている通り、短縮系の構文を含むCloudFormationコードをyamlとしてロードするとエラーになります。

```
$ python sample.py test.yml
test.yaml
Exception occurred while loading YAML...
could not determine a constructor for the tag '!Sub'
  in "test.yaml", line 72, column 20
```

![line.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/3c2066a2-e34a-01cd-8941-303defe041e4.png)

作ったスクリプトは未完成、どんなyaml形式でもエラーがでないようにしたい。