korenlios
korenlios

Reputation: 11

Terraform replacement of resources prob due to index_key changes

I'm creating Cloudflare's records with Terraform using a module I created from the resource's documentation- https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/record.

I created a key value variable in which the key is the record's region and the value is the tenants in this region. I read this variable from a JSON file by jsondecode function. The module reads this variable and runs inside for loops in order to get the variable separated by region:tenant, region:tenant.

First running region_tenants_ids.json:

{
    "eu": [
        "test1"
    ],
    "us": [
        "test2"
    ]
}

Module:

 locals {
  tenants = flatten([
    for region, tenant_list in var.region_tenants_ids : [
      for tenant in tenant_list : {
        region = region
        tenant = tenant
      }
    ]
  ])
}

resource "cloudflare_record" "records" {
  count           = length(local.tenants)
  allow_overwrite = false
  zone_id         = var.zone_id
  type            = var.record_type
  proxied         = var.proxied_record

  name  = local.tenants[count.index].tenant
  value = "${var.prefix_lb_name}-${local.tenants[count.index].region}.${var.zone_name}"
}

resource "cloudflare_regional_hostname" "regional_hostname" {
  depends_on = [cloudflare_record.records]
  count      = length(local.tenants)
  zone_id    = var.zone_id
  hostname   = "${local.tenants[count.index].tenant}.${var.zone_name}"
  region_key = local.tenants[count.index].region
}

Every time I add another tenant to the region's list, the running forces replace of existing resources claiming the reason is because of the name, and creating the records from the list.

Second-running-adding-another-tenant:

{
    "eu": [
        "test1",
        "lasttest3"
    ],
    "us": [
        "test2"
    ]
}

I looked at the terraform.tfstate and could see that every time I add a new tenant the index_key is updated and messes up the order based on the alphabetic order, I assume this is why it's trying to replace the resources.

Plan- replacement error enforcement:

  # module.router_records.cloudflare_record.records[1] must be replaced
-/+ resource "cloudflare_record" "records" {
      ~ created_on      = "2023-08-30T21:29:11.355402Z" -> (known after apply)
      ~ hostname        = "test2.testcloudflare.dev.com" -> (known after apply)
      ~ id              = "5a9d8e71f668981e337a0cda4d259924" -> (known after apply)
      ~ metadata        = {
          - "auto_added"             = "false"
          - "managed_by_apps"        = "false"
          - "managed_by_argo_tunnel" = "false"
          - "source"                 = "primary"
        } -> (known after apply)
      ~ modified_on     = "2023-08-30T21:29:11.355402Z" -> (known after apply)
      ~ name            = "test2" -> "lasttest3" # forces replacement
      ~ proxiable       = true -> (known after apply)
      - tags            = [] -> null
      ~ ttl             = 1 -> (known after apply)
      ~ value           = "lb-us.testcloudflare.dev.com" -> "lb-eu.testcloudflare.dev.com"
        # (4 unchanged attributes hidden)
    }
 # module.router_records.cloudflare_record.records[2] will be created
  + resource "cloudflare_record" "records" {
      + allow_overwrite = false
      + created_on      = (known after apply)
      + hostname        = (known after apply)
      + id              = (known after apply)
      + metadata        = (known after apply)
      + modified_on     = (known after apply)
      + name            = "test2"
      + proxiable       = (known after apply)
      + proxied         = true
      + ttl             = (known after apply)
      + type            = "CNAME"
      + value           = "lb-us.testcloudflare.dev.com"
      + zone_id         = "xxx"
    }

  # module.router_records.cloudflare_regional_hostname.regional_hostname[1] will be updated in-place
  ~ resource "cloudflare_regional_hostname" "regional_hostname" {
      ~ hostname   = "test2.testcloudflare.dev.com" -> "lasttest3.testcloudflare.dev.com"
        id         = "test2.testcloudflare.dev.com"
      ~ region_key = "us" -> "eu"
        # (2 unchanged attributes hidden)
    }

  # module.router_records.cloudflare_regional_hostname.regional_hostname[2] will be created
  + resource "cloudflare_regional_hostname" "regional_hostname" {
      + created_on = (known after apply)
      + hostname   = "test2.testcloudflare.dev.com"
      + id         = (known after apply)
      + region_key = "us"
      + zone_id    = "xxx"
    }

Plan: 3 to add, 1 to change, 1 to destroy.

Changes to Outputs:
  ~ router_records_names = [
        "test1",
      + "lasttest3",
        "test2",
    ]

Could someone say how I'll be able to keep the index key/ stop this replacement enforcement due to adding another tenant to the region? Would really appreciate your help, even if you aren't familiar with the solution vote it up will be amazing, thanks!

The Code in Github Repo.

P.S.

  1. I'm adding it through variable from type- map(any) because one of the requirements was to get only the tenant name for each region since the record's value is always the same.

  2. I know I could add them 1 by 1, but then I'll have to repeat on the many parameters(like value which is static for each region, record type which will always be CNAME, etc.) many times which is losing the point of using terraform.

  3. I tried running the code, The Code in Github Repo., then adding another tenant to the JSON. I expected that the recreation enforcement would not happen.

Upvotes: 0

Views: 571

Answers (1)

korenlios
korenlios

Reputation: 11

I found the solution for me was to update the variable type from map(any) to list and finally to list(object). Then update the JSON format accordingly(list(object)), and update the module to use for_each as @marcin and @lxop suggested.

The variable:

variable "region_tenants_ids" {
  type        = list(object({
    region = string
    tenant = string
  }))
  default     = []
  description = "All tenant names by regions."
}

The JSON file:

[
    { "region": "eu", "tenant": "test" },
    { "region": "eu", "tenant": "zzz" },
    { "region": "us", "tenant": "bbb" },
    { "region": "au", "tenant": "aaa" }
]

The module:

resource "cloudflare_record" "records" {
  for_each = { for rt in var.region_tenants_ids : "${rt.region}-${rt.tenant}" => rt }

  allow_overwrite = false
  zone_id         = var.zone_id
  type            = var.record_type
  proxied         = var.proxied_record

  name  = "${each.value.tenant}"
  value = "${var.prefix_lb_name}-${each.value.region}.${var.zone_name}"
}

resource "cloudflare_regional_hostname" "regional_hostname" {
  depends_on = [cloudflare_record.records]

  for_each   = { for rt in var.region_tenants_ids : "${rt.region}-${rt.tenant}" => rt }

  zone_id    = var.zone_id
  hostname   = "${each.value.tenant}.${var.zone_name}"
  region_key = each.value.region
}

Upvotes: 0

Related Questions