Terraform refactoring and state move

2022-01-03 - An example replacing a count with a for_each
Tag: terraform

Introduction

When I initialised my oracle cloud free tier infrastructure in a previous blog article, I made a mistake of using a count to iterate on a list of names for the instances I wished to spawn. The drawback of doing this is that I cannot reorder the items in this list, and deleting one instance could affect the other.

The solution to this is to change this count construct to a for_each. This way the state objects will no longer be indexed by the instance position in the list, they will be indexed by their names.

What changes in the terraform code

Since in one of the resources I used the index to infer a fault domain id, I rewrote the list from this :

oracle_amd64_instances_names = ["dalinar", "kaladin"]

to this :

oracle_amd64_instances = {
  dalinar = { "fault_domain_id" = 0 },
  kaladin = { "fault_domain_id" = 1 },
}

Note that I renamed the variable in order to not miss anywhere it was used. Now for each resource that used this list with a count like the following :

resource "oci_core_instance" "amd64-vms" {
  count                = length(var.oracle_amd64_instances_names)
  compartment_id       = var.oracle_tenancy_ocid
  availability_domain  = data.oci_identity_availability_domains.ads.availability_domains[0].name
  fault_domain         = data.oci_identity_fault_domains.fd.fault_domains[
    count.index % length(data.oci_identity_fault_domains.fd.fault_domains)].name
  display_name         = var.oracle_amd64_instances_names[
    count.index % length(var.oracle_amd64_instances_names)]
  shape                = "VM.Standard.E2.1.Micro"
  preserve_boot_volume = false
  create_vnic_details {
    subnet_id      = oci_core_subnet.adyxax-production.id
    hostname_label = var.oracle_amd64_instances_names[count.index]
    display_name   = var.oracle_amd64_instances_names[count.index]
}

Such entries now becomes :

resource "oci_core_instance" "amd64-vms" {
  for_each             = var.oracle_amd64_instances
  compartment_id       = var.oracle_tenancy_ocid
  availability_domain  = data.oci_identity_availability_domains.ads.availability_domains[0].name
  fault_domain         = data.oci_identity_fault_domains.fd.fault_domains[each.value["fault_domain_id"]].name
  display_name         = each.key
  shape                = "VM.Standard.E2.1.Micro"
  preserve_boot_volume = false
  create_vnic_details {
    subnet_id      = oci_core_subnet.adyxax-production.id
    hostname_label = each.key
    display_name   = each.key
}

How to migrate the state

To see which resources need to be migrated you can use terraform state list :

julien@nas ~/git/adyxax/adyxax.org/02-permanent-hosts (master *$%) $ terraform state list
data.cloudflare_zones.adyxax-eu
data.cloudflare_zones.adyxax-org
data.cloudflare_zones.asj-fr
data.hcloud_ssh_key.adyxax
data.oci_core_image.ol8
data.oci_core_vnic_attachments.amd64-vms-vnics[0]
data.oci_core_vnic_attachments.amd64-vms-vnics[1]
data.oci_identity_availability_domains.ads
data.oci_identity_fault_domains.fd
cloudflare_record.adyxax-org-oracle-amd64-vms-ipv4[0]
cloudflare_record.adyxax-org-oracle-amd64-vms-ipv4[1]
cloudflare_record.adyxax-org-oracle-amd64-vms-ipv6[0]
cloudflare_record.adyxax-org-oracle-amd64-vms-ipv6[1]
oci_core_instance.amd64-vms[0]
oci_core_instance.amd64-vms[1]
oci_core_internet_gateway.gw
oci_core_ipv6.amd64-vms-ipv6s[0]
oci_core_ipv6.amd64-vms-ipv6s[1]
oci_core_route_table.default-via-gw
oci_core_security_list.allow-all
oci_core_subnet.adyxax-production
oci_core_vcn.adyxax

Here we are interested with all the resources indexed with 0 and 1. I migrated the state using the following commands :

terraform state mv data.oci_core_vnic_attachments.amd64-vms-vnics[0] \
                   data.oci_core_vnic_attachments.amd64-vms-vnics[\"dalinar\"]
terraform state mv data.oci_core_vnic_attachments.amd64-vms-vnics[1] \
                   data.oci_core_vnic_attachments.amd64-vms-vnics[\"kaladin\"]
terraform state mv cloudflare_record.adyxax-org-oracle-amd64-vms-ipv4[0] \
                   cloudflare_record.adyxax-org-oracle-amd64-vms-ipv4[\"dalinar\"]
terraform state mv cloudflare_record.adyxax-org-oracle-amd64-vms-ipv4[1] \
                   cloudflare_record.adyxax-org-oracle-amd64-vms-ipv4[\"kaladin\"]
terraform state mv cloudflare_record.adyxax-org-oracle-amd64-vms-ipv6[0] \
                   cloudflare_record.adyxax-org-oracle-amd64-vms-ipv6[\"dalinar\"]
terraform state mv cloudflare_record.adyxax-org-oracle-amd64-vms-ipv6[1] \
                   cloudflare_record.adyxax-org-oracle-amd64-vms-ipv6[\"kaladin\"]
terraform state mv oci_core_instance.amd64-vms[0] \
                   oci_core_instance.amd64-vms[\"dalinar\"]
terraform state mv oci_core_instance.amd64-vms[1] \
                   oci_core_instance.amd64-vms[\"kaladin\"]
terraform state mv oci_core_ipv6.amd64-vms-ipv6s[0] \
                   oci_core_ipv6.amd64-vms-ipv6s[\"dalinar\"]
terraform state mv oci_core_ipv6.amd64-vms-ipv6s[1] \
                   oci_core_ipv6.amd64-vms-ipv6s[\"kaladin\"]

Note the escaping of the quotes so that the shell does not interpret (and remove) these. We can make sure we did not do any mistake by running a plan and seeing that terraform does not report any changes :

No changes. Your infrastructure matches the configuration.