0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[Terraform] for_each で作ったインデックスつきリソースの一部を removed する

Last updated at Posted at 2024-11-20

TL;DR

Terraform の templatefile() と Terragunt の generate() を組み合わせることで下記のメリットを得られる

  • removed 対象が多くても転記ミスに強い
  • removed の結果を反映した plan 差分を確認できる

想定する状況

例えば下記のようにリソースを定義したとする:

locals {
  repeats = ["foo", "bar", "baz"]
}

resource "something" "this" {
  for_each = toset(local.repeats)
  name     = each.value
}

これを apply すると、下記のリソース管理することになる:

  • something.this["foo"]
  • something.this["bar"]
  • something.this["baz"]

これらのうち、foobar のリソースだけを rm しようとする場合、何度も moved {}removed {} を書かないといけないので面倒という話。

解決策

Terraform の templatefile()Terragunt1generate() を組み合わせることでうまくいく:

terragrunt.hcl
locals {
  something_removed = [
    "foo",
    "bar",
  ]
}

generate "removed" {
  path      = "removed.tf"
  if_exists = "overwrite"
  contents = templatefile("templates/removed.tmpl", {
    remove_targets = local.something_removed
  })
}
templates/removed.tmpl
%{ for target in remove_targets }
moved {
  from = something.this["${target}"]
  to = something.this_${target}
}

removed {
  from = something.this_${target}
  lifecycle {
    destroy = false
  }
}
%{ endfor }

.tmpl 中の変数( ${} で囲ったもの) は Terraform の変数ではなくテンプレートの変数なので注意。

バリエーション: Terraform の組み込み関数をはさむ

解決策

Terraform の replace() を使って下記のように書く:

terragrunt.hcl
locals {
  something_removed = [
    "something-1",
    "something-2",
  ]
  something_removed_sanitized = {
    for name in local.something_removed : name => replace(name, "-", "_")
  }
}

generate "removed" {
  path      = "removed.tf"
  if_exists = "overwrite"
  contents = templatefile("templates/removed.tmpl", {
    remove_target_names_map = local.something_removed_sanitized
  })
}
templates/removed.tmpl
%{ for name_hyphen, name_underscore in remove_target_names_map }
moved {
  from = something.this["${name_hyphen}"]
  to = something.this_${name_underscore}
}

removed {
  from = something.this_${name_underscore}
  lifecycle {
    destroy = false
  }
}
%{ endfor }

大事なのでもう一度言うが、.tmpl 中の変数 ${} は Terraform の変数ではなくテンプレート中の変数なので注意。

下記のようにしても、local.target_sanitized というテンプレート変数がない旨のエラーが出て動かない:

bad-example.tmpl
# does not work
%{ for target in remove_targets }

locals {
  target_sanitized = replace($target, "-", "_")
  }  
}

moved {
  from = something.this["${target}"]
  to = something.this_${local.target_sanitized}
}

removed {
  from = something.this_${local.target_sanitized}
  lifecycle {
    destroy = false
  }
}
%{ endfor }

Terragrunt が便利だよという補足

テンプレートを使ったループ展開は Terraform の機能なので、本記事で扱った問題は Terragrunt なしでも解決できそうにも見える。

下記のように書いてみると、一応は期待通りの removed.tf が生成されそうな動きをするのだが、実際に apply するまで removed.tf の実体はないため、plan 時点では removed の結果を反映した plan 差分は確認不可能。

locals {
  teams_removed = [
    "team-1",
  ]
  teams_removed_sanitized = {
    for name in local.teams_removed : name => replace(name, "-", "_")
  }
}

resource "local_file" "removed" {
  filename = "removed.tf"
  content = templatefile("templates/removed.tmpl", {
    team_names_map = {
      for name in local.teams_removed : name => replace(name, "-", "_")
    }
  })
}

一方、Terragrunt の場合には plan 時点で removed.tf が生成されるため removed {} の結果も反映した plan 差分を検討できる(removed.tf の ignore を忘れずに)。

参考にした情報

  1. しれっと別ツールを持ち出してしまったが、実務の IaC を助けてくれる便利機能(要件次第ではほぼ必須なこともある)がいろいろ揃っているのでおすすめ。本記事で扱った課題を解決するためだけに導入しようという意味ではない

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?