🙆‍♀️

Terragruntのパス指定を簡単にしよう!

に公開

Terragruntのパス指定、めんどくさくないですか?

ぱっと調べて、AIに聞いたけどいいのがなさそう。

下記のような相対パス指定がめんどくさすぎる。

locals {
}
terraform {
  source = "../../../../modules/schema_obj/stage"
}
dependency "s3" {
  config_path = "../../../../../../../../AWS/envs/dev/s3"
}
dependency "storage_integration" {
  config_path = "../../../../../account_obj/storage_integration"
}

ならパスをyamlにしてしまえばいいじゃないかということでスクリプトを作成。rootにおいて実行すると下記のようにrootからの相対パスをまとめたyamlができる。

module:
  iam_role: AWS/modules/iam_role
  ・・・
terragrunt:
  iam_role: AWS/envs/dev/iam_role
  ・・・

上記出力結果はrootで実行し、同じパスに保存されているはずなので下記のように呼び出せる。
便利!

呼ばれる側

locals {
  root_folder = dirname(find_in_parent_folders("folders.yaml"))
  relative_path_from_route = yamldecode(file(find_in_parent_folders("folders.yaml")))
}

呼ぶ側

include {
  path = find_in_parent_folders("root.hcl")
}

locals {
  ns_vars = yamldecode(file(find_in_parent_folders("namespace_vars.yaml")))
  parent = read_terragrunt_config(find_in_parent_folders("root.hcl")).locals
}

terraform {
  source =  "${local.parent.root_folder}/${local.parent.relative_path_from_route.module.s3_bucket}"
}

inputs = {
  bucket_name = local.ns_vars.S3.bucket_name
  environment = local.ns_vars.S3.environment
  s3_objects = [
    for obj in local.ns_vars.S3.objects : {
      bucket_name = local.ns_vars.S3.bucket_name
      key = obj.key
      source =  lookup(obj, "source", null) != null ? "${local.parent.root_folder}/${obj.source}" : null 
    }
  ]
}

懸念点

yamlのキーを末端のフォルダ名にしてるからこれが一意じゃないとどうなるかわかんないけどね!まあどのリソースかわかんなくなるし多分一意でしょヘーキヘーキ。
yamlの更新忘れそう?それは...そうね...
実行時間が考慮されてない?...それは...そうね...

../s3

で呼べる奴が

"${local.parent.root_folder}\\${local.parent.relative_path_from_route.terragrunt.s3}"

になる。だめかもしれん。

chatgpt君と格闘して作ったスクリプト

import os
import yaml

root_path = os.path.dirname(os.path.abspath(file))

module は単純に格納

modules = {}

terragrunt は env ごとに格納

terragrunts = {"dev": {}, "prod": {}}

for dirpath, dirnames, filenames in os.walk(root_path):
dirnames[:] = [d for d in dirnames if not d.startswith(".")]

folder_name = os.path.basename(dirpath)
rel_path = os.path.relpath(dirpath, root_path)

# パス区切りを / に統一
rel_path = rel_path.replace(os.sep, "/")

# module 側: main.tf がある場合のみ
if "main.tf" in filenames:
    modules[folder_name] = rel_path

# terragrunt 側: terragrunt.hcl がある場合のみ
if "terragrunt.hcl" in filenames:
    env_key = None
    for key in ["dev", "env", "prod"]:
        if key in rel_path.split("/"):  # os.sep → "/" に変更
            env_key = key
            break
    if env_key:
        terragrunts[env_key][folder_name] = rel_path
    else:
        terragrunts.setdefault("others", {})[folder_name] = rel_path

value(パス)でソート

def sort_dict_by_value(d):
sorted_dict = {}
for k, v in sorted(d.items(), key=lambda x: x[1] if not isinstance(x[1], dict) else ""):
if isinstance(v, dict):
sorted_dict[k] = sort_dict_by_value(v)
else:
sorted_dict[k] = v
return sorted_dict

modules_sorted = sort_dict_by_value(modules)
terragrunts_sorted = sort_dict_by_value(terragrunts)

output = {
"module": modules_sorted,
"terragrunt": terragrunts_sorted
}

yaml_path = os.path.join(root_path, "folders.yaml")
with open(yaml_path, "w", encoding="utf-8") as f:
yaml.dump(output, f, allow_unicode=True, sort_keys=False)

print(f"YAML出力完了: {yaml_path}")

ちなみにrootのフォルダを特定するだけならこれとかあるぞ!たぶんGitリポジトリで管理するだろうからこっちのほうがいいかもな!

Discussion