Terraform:如何删除从数组/列表创建的用户资源?

时间:2019-08-01 15:01:51

标签: terraform

我有一个文件users.tf文件,它为aws创建了管理员用户。通过定义列表(例如users = ["bob", "john", "tom"])来做到这一点

,然后使用aws_iam_user资源在terraform中使用count资源对其进行迭代,如下所示:

resource "aws_iam_user" "user" {
  count = length(local.users)
  name  = local.users[count.index]
}

这里的问题是,如果我删除数组的第一个元素(上述示例中的“ bob”),那么在发出terraform plan(而不是删除bob)之后,terraform建议执行的操作是更改将bob更改为john,将john更改为tom,然后删除tom。

像这样:

  # aws_iam_user.user[0] will be updated in-place
  ~ resource "aws_iam_user" "user" {
        arn           = "arn:aws:iam::5555555:user/bob"
        force_destroy = false
        id            = "bob"
      ~ name          = "bob" -> "john"
        path          = "/"
        tags          = {}
        unique_id     = "BLABLABLA11111"
    }

  # aws_iam_user.user[1] will be updated in-place
  ~ resource "aws_iam_user" "user" {
        arn           = "arn:aws:iam::5555555:user/john"
        force_destroy = false
        id            = "john"
      ~ name          = "john" -> "tom"
        path          = "/"
        tags          = {}
        unique_id     = "BLABLABLA22222"
    }

  # aws_iam_user.user[2] will be destroyed
  - resource "aws_iam_user" "user" {
      - arn           = "arn:aws:iam::5555555:user/tom" -> null
      - force_destroy = false -> null
      - id            = "tom" -> null
      - name          = "tom" -> null
      - path          = "/" -> null
      - tags          = {} -> null
      - unique_id     = "BLABLABLA3333" -> null

这将导致john获得bob的知识,而tom获得john的知识。这是不可取的。

我尝试使用for_each循环的very new feature(在写此问题之前19小时发布)而不是count,并将键定义为原始索引号,希望terraform将它们视为相同资源。

是的,没有这样的运气:

...
# aws_iam_user.user[1] will be destroyed
...
# aws_iam_user.user["1"] will be created
...

我将总结我的问题:

通过遍历列表创建资源时,是否有任何方法可以删除该资源(特别是aws_iam_user),以便所有其余资源保持原样?

1 个答案:

答案 0 :(得分:3)

您在这里看到的是the count documentation在其最后一段中警告的情况:

  

请注意,由count创建的单独资源实例仍由其 index 标识,而不是由给定列表中的字符串值标识。这意味着,如果将元素从列表的中间删除,则所有索引实例之后都将看到其subnet_id值发生变化,这将导致远程对象更改超出预期范围。应谨慎使用从列表生成多个实例的做法,并应格外小心,如果稍后更改列表会发生什么情况。

幸运的是,这正是for_each功能旨在解决的问题。不过,为了有效使用它,在传递给for_each的地图中选择有意义的唯一键很重要:

resource "aws_iam_user" "user" {
  for_each = { for name in local.users : name => name }

  name  = each.value
}

这将导致Terraform跟踪实例标识符,例如aws_iam_user.user["john"]而不是aws_iam_user.user[1]

尽管您的状态中已有基于count的实例,所以要采取一些迁移步骤即可。不幸的是,Terraform没有足够的信息来自动将现有的基于索引的地址与新的基于名称的地址相关联,但是通过将现有列表与单独的一次性脚本一起使用,您可以告诉Terraform如何通过运行命令来翻译这些地址对于列表中的每个条目都是这样的:

terraform state mv 'aws_iam_user.user[1]' 'aws_iam_user.user["john"]'

之后,Terraform将按名称跟踪这些对象,因此添加和删除名称将仅影响与您更改的名称相关的对象。


如果您现在还不准备完全切换到for_each,则可以使用类似的策略和一次性脚本来“治愈”通过从列表中删除项目而创建的漏洞:

# First, Terraform must "forget" the user that you removed
terraform state rm 'aws_iam_user.user[0]'

# Then renumber the subsequent items to correlate with their new
# positions in the list.
terraform state mv 'aws_iam_user.user[1]' 'aws_iam_user.user[0]'
terraform state mv 'aws_iam_user.user[2]' 'aws_iam_user.user[1]'
# etc, etc

如果您的用户数量不多,这当然是一个很繁琐且容易出错的过程,因此最好编写一个小的程序来生成脚本。