When I change count to for_each, the resource will recreate?

terraform version: 0.12.6
aws version: v2.22.0

new main.tf

resource "aws_instance" "this" {
  for_each = {
    1: ""
  }

  ami                    = var.ami
  instance_type          = var.instance_type
  subnet_id              = var.subnet_id

  tags = merge(
    var.tags,
    {
      "Name" = format("%s-%s-%s-%s", var.service, var.environment, var.region, each.key)
    },
  )
  ......

}

result:

  # module.ec2.aws_instance.this[1] will be destroyed
  - resource "aws_instance" "this" {
      - ami                          = "ami-**********" -> null
      ..........
    }

  # module.ec2.aws_instance.this["1"] will be created
  + resource "aws_instance" "this" {
      + ami                          = "ami-*********"
      ..........
    }

How can the resources not be recreate? Or how to replace index by string ?

Looking forward to your reply!

Thanks

Hi @lyc0221!

In order to get the benefits of for_each you’ll need to use string keys that are different than the integer indices you had before. Otherwise, for_each will behave essentially the same as count, including the issues with adding and removing items in the middle of the sequence.

Because Terraform therefore can’t automatically know which indices in your old configuration relate to which string keys in your new configuration, unfortunately a more manual migration process is needed to tell Terraform how to rename each instance.

If you were to use the index and key that you showed in your example output, you’d move the existing object from one address to the other like this:

terraform state mv 'module.ec2.aws_instance.this[1]' 'module.ec2.aws_instance.this["1"]'

(the above uses Unix-style shell quoting syntax. If you’re on Windows then you’ll need to use Windows-style escaping instead, like module.ec2.aws_instance.this[\"1\"].)

terraform state mv directly modifies the Terraform state to move the object associated with the first address address to instead be correlated with the second address. Future runs of terraform plan should therefore see that module.ec2.aws_instance.this["1"] already exists and either leave it unchanged or plan to update it in-place.

As I mentioned above, in practice you’ll likely want to use a more meaningful key to identify each element. Since your question doesn’t include context about what real-world concept this instance represents I’m going to just call it "foo" for the sake of example:

resource "aws_instance" "this" {
  for_each = {
    "foo": ""
  }

  # ...
}

In this case, you’d move to module.ec2.aws_instance.this["foo"] instead.

1 Like

Hi @apparentlymart

terraform state list

data.aws_ami.ubuntu_xenial
data.aws_subnet_ids.all
data.aws_vpc.default
null_resource.provisioning
module.ec2.data.template_file.userdata
module.ec2.aws_instance.this
module.security_group.aws_security_group.this
module.security_group.aws_security_group_rule.egress_rules
module.security_group.aws_security_group_rule.ingress_rules[0]
module.security_group.aws_security_group_rule.ingress_rules[1]
module.security_group.aws_security_group_rule.ingress_rules[2]

terraform state mv ‘module.ec2.aws_instance.this’ ‘module.ec2.aws_instance.this[“1”]’

Move "module.ec2.aws_instance.this" to "module.ec2.aws_instance.this[\"1\"]"

Error: Invalid target address

Cannot move to module.ec2.aws_instance.this["1"]: module.ec2.aws_instance.this
does not exist in the current state.

Why can I not move to module.ec2.aws_instance.this that is exist ?