過去記事からの派生案件で、Terraformで使うtfvarsファイルについて、繰り返しデータを多数投入する想定のため、これを自動生成したいと考えた。
Terraform loop処理の超シンプルな例
Python - Jinja2テンプレートで連続データを処理したい
実際に使用するファイル群は過去記事に記載しているがこんな想定で。(もちろん実際は他にもいろいろ必要)自動生成したいのは、以下の*印をつけたcodecommit.auto.tfvarsである。(この時点では手動で値を記述したもの)
work_dir/
├── codecommit.auto.tfvars *
├── codecommit.tf
├── config.tf
├── terraform.tfvars
├── variables.tf
└── vpc.tf
これとは別に、tfvars自動生成作業用ディレクトリの作業前はこの状態。以下3つのファイルを用意する。codecommit.tmplはテンプレートとなる。このファイル名はスクリプトから呼び出すので名称に注意。対象のAWSリソースによって変えるが、tfファイルの名称に合わせておけばよい。
script_dir/
├── codecommit.tmpl
├── create_vars.py
└── data.csv
codecommit.tmpl
param{{ num }} = {
repository_name = "{{ repo_name }}"
description = "{{ des }}"
}
data.csv (今回の例ではヘッダーありの前提)
num,repo_name,des
1,"my-repo001","my-repo001の説明"
2,"my-repo002","my-repo002の説明"
3,"my-repo003","my-repo003の説明"
create_vars.py
import sys
import pandas as pd
from jinja2 import Environment, FileSystemLoader
def main():
# テンプレート読み込み
env = Environment(loader=FileSystemLoader('./', encoding='utf8'))
tmpl = env.get_template(template)
# CSV読み込み
df = pd.read_csv("data.csv",
encoding='utf-8',
header=0
)
# CSVデータをJSONに変換
df.to_json('data.json')
df_js = pd.read_json("data.json")
# 代入処理。テンプレートにデータを投入後、変数ファイル(xxx.auto.vars)に書き出す。
def loop():
pre = n + '_param_list = {' + '\n'
end = '}'+ '\n'
with open(varsfile, 'w') as f:
f.write(pre)
for i in range(len(df_js)):
data = df_js.iloc[i]
config = tmpl.render(data)
f.write(config)
f.write('\n')
f.write(end)
loop()
if __name__ == "__main__":
args = sys.argv
if len(args) == 2:
n = args[1]
template = n + '.tmpl'
varsfile = n + '.auto.tfvars'
main()
else:
print ('Usage: Specify one of AWS resource name. e.g. s3,iam,cloudwatch...etc.')
sys.exit()
CSVファイルのヘッダー列の値と、xxxx.tmplファイル内の代入箇所の値は一致させる。すると代入処理時に特に指定しなくてもうまいことやってくれる。pandasのヘッダー行の扱いがよくわからなくてはまったが、一応上記で期待値になった。CSV読み込み時はheaderを指定しなくても勝手に判断してこれもうまいことやってくれるらしいが、一応作法としてheader=0を指定。
ヘッダーあり・なしで挙動が変わり、認識させるために複数の方法があるが、あれこれ考えるの面倒だから、CSVにヘッダ入れておくのが一番簡単だろうと思った。ちなみに当初はこんな記述にしてた。namesがカラム名となるのだが、これを定数ではなく自動で取得したいというモチベーションにより、結果的に上記の通りになった。
変更前のCSV読み込み処理(この時点ではCSVのヘッダーなし)
# CSV読み込み
df = pd.read_csv("data.csv",
encoding='utf-8',
names=("num","repo_name","des")
)
CSVのヘッダ仕様については以下参考にした。
CSVファイルから読み込みを行うには(pandas編)
read_csv でヘッダあり・なしCSVの読み込み
代入処理ではループの都度ファイルに書き出している。当初、一度値を全部配列に格納してそれを書き出そうかと思ったが、フォーマットが崩れるからそれを整形したりとか面倒だからやめた。
繰り返し代入処理の部分は以下記事が参考になった。助かった。
【jinja2】テンプレートエンジンでデータの連続差し込み
実行時は引数として作成対象のAWSリソース名を指定する。テンプレート(今回の場合codecommit.tmpl)のファイル名に合わせる。ここで指定した値が[リソース名].auto.tfvarsのリソース名になる。
$ python3 create_vars.py codecommit
生成されたcodecommit.auto.tfvars。
codecommit_param_list = {
param1 = {
repository_name = "my-repo001"
description = "my-repo001の説明"
}
param2 = {
repository_name = "my-repo002"
description = "my-repo002の説明"
}
param3 = {
repository_name = "my-repo003"
description = "my-repo003の説明"
}
}
tfvars生成後は不要となるが、参考までに中間地点で生成されたJSONの中身。
data.json
{"num":{"0":1,"1":2,"2":3},"repo_name":{"0":"my-repo001","1":"my-repo002","2":"my-repo003"},"des":{"0":"my-repo001\u306e\u8aac\u660e","1":"my-repo002\u306e\u8aac\u660e","2":"my-repo003\u306e\u8aac\u660e"}}
こんな短いコードだがめっちゃ紆余曲折した。まーでも、これでひと安心。これはサンプルだから3アイテムだけだけど、AWSリソースの単位で十数個とか数十個とかアイテムがあって、さらにそれが複数重なったら手動で記述なんてやってられない。いや、3アイテムだけでも十分面倒だし、ミスる可能性ありありだ。
今回のやり方はAWSリソース毎にCSVを分けて作っていく必要あるけど、それくらいならいいだろう。