【小ネタ】PythonでCloudFormationテンプレートをパースする時に組み込み関数の短縮系構文に邪魔されない方法
サーバーレス開発部@大阪の岩田です。
サーバーレスアプリの開発ではDynamoDBのテーブル定義をSAMやCloudFormationのテンプレート等で管理することが多いと思います。 DynamoDB LocalやLocalStackを使ってテストを回す際はテンプレートをパースして、よしなにテスト用のテーブルを作りたいところですが、単純にテンプレートをパースするとCloudFormationの組み込み関数の短縮系構文に邪魔をされることがあります。
組み込み関数の短縮系構文を使用していても、うまくテンプレートをパースする方法について調べたので、その手法についてご紹介します。
テスト用テンプレート
下記のテンプレートで試してみます。 DynamoDBのテーブルを1つ作るだけのシンプルなCloudFormationテンプレートです。 ParametersでStageNameというパラメータを定義しており、受け取った値をテーブル名に反映するようにしています。 実際のテンプレートだと、この中にさらにLambdaやAPI Gatewayの定義が乗っかってくることになります。
AWSTemplateFormatVersion: 2010-09-09 Description: Create DynamoDB Table Parameters: StageName: Type: String Default: dev AllowedValues: - dev - stg - prd Description: Environment Resources: TestTable: Type: "AWS::DynamoDB::Table" Properties: TableName: !Sub ${StageName}-test-table AttributeDefinitions: - AttributeName: "id" AttributeType: "S" KeySchema: - AttributeName: "id" KeyType: "HASH" ProvisionedThroughput: ReadCapacityUnits: 5 WriteCapacityUnits: 5
普通にパースしてみる
テスト実行時に、先ほどのテンプレートをPythonから読み込んで、テスト用のテーブルを作成してみます。 なお、Pythonのバージョンは3.6.5を使用しています。
import yaml def main(): with open("dynamo.yml") as file: data = yaml.load(file) print(data) # テスト用のテーブルを作成する処理 # .... if __name__ == '__main__': main()
試しに実行すると・・・
Traceback (most recent call last): File "test.py", line 11, in <module> main() File "test.py", line 5, in main data = yaml.load(file) File "/Users/xxxxxx/Documents/xxxxxx/lib/python3.6/site-packages/yaml/__init__.py", line 72, in load return loader.get_single_data() File "/Users/xxxxxx/Documents/xxxxxx/lib/python3.6/site-packages/yaml/constructor.py", line 37, in get_single_data return self.construct_document(node) File "/Users/xxxxxx/Documents/xxxxxx/lib/python3.6/site-packages/yaml/constructor.py", line 46, in construct_document for dummy in generator: File "/Users/xxxxxx/Documents/xxxxxx/lib/python3.6/site-packages/yaml/constructor.py", line 398, in construct_yaml_map value = self.construct_mapping(node) File "/Users/xxxxxx/Documents/xxxxxx/lib/python3.6/site-packages/yaml/constructor.py", line 204, in construct_mapping return super().construct_mapping(node, deep=deep) File "/Users/xxxxxx/Documents/xxxxxx/lib/python3.6/site-packages/yaml/constructor.py", line 129, in construct_mapping value = self.construct_object(value_node, deep=deep) File "/Users/xxxxxx/Documents/xxxxxx/lib/python3.6/site-packages/yaml/constructor.py", line 86, in construct_object data = constructor(self, node) File "/Users/xxxxxx/Documents/xxxxxx/lib/python3.6/site-packages/yaml/constructor.py", line 414, in construct_undefined node.start_mark) yaml.constructor.ConstructorError: could not determine a constructor for the tag '!Sub' in "dynamo.yml", line 16, column 18
Fn::Sub
の短縮表記!Sub
が邪魔をしてYAMLとして正しくパース出来ません。
AWS CLIの力を借りてパースしてみる
先ほどのエラーに遭遇した時疑問に思ったのが、「AWS CLIのcloudformation validate
コマンドはどうやってテンプレートを検証してるんだろう?」ということでした。
AWS CLIの中身を追いかけていくとyamlhelperというファイルの中に、yaml_parseというそれらしき関数を見つけました。
試しにこの関数を使ってパースしてみます。
from awscli.customizations.cloudformation.yamlhelper import yaml_parse def main(): with open("dynamo.yml") as file: data = yaml_parse(file.read()) print(data) # テスト用のテーブルを作成する処理 # .... if __name__ == '__main__': main()
{'AWSTemplateFormatVersion': datetime.date(2010, 9, 9), 'Description': 'Create DynamoDB Table', 'Parameters': {'StageName': {'Type': 'String', 'Default': 'dev', 'AllowedValues': ['dev', 'stg', 'prd'], 'Description': 'Environment'}}, 'Resources': {'TestTable': {'Type': 'AWS::DynamoDB::Table', 'Properties': {'TableName': {'Fn::Sub': '${StageName}-test-table'}, 'AttributeDefinitions': [{'AttributeName': 'id', 'AttributeType': 'S'}], 'KeySchema': [{'AttributeName': 'id', 'KeyType': 'HASH'}], 'ProvisionedThroughput': {'ReadCapacityUnits': 5, 'WriteCapacityUnits': 5}}}}}
今度はパース出来ました! 出力結果をよく見ると、
{'Fn::Sub': '${StageName}-test-table'}
と短縮表記!Sub
で書いていたところがFn::Sub
に展開されています。
これでパース出来るようになったので、後はテストのFixture内でパースした内容からテーブルを作成すればOKです!
まとめ
以上 PythonでCloudFormationテンプレートをパースする時のちょっとしたテクニックでした。 テストコードの中でいちいちテーブル定義を書いてしまうと、テーブル定義に変更があった場合にテストコードの修正が漏れ、本来落ちるべきテストが通ってしまう。 といった事態も想定されます。 今回紹介したようなテクニックをうまく活用して、設定を一元管理出来れば幸せになれるのではないでしょうか。