mukul
mukul

Reputation: 49

The "for_each" map includes keys derived from resource attributes that cannot be determined until apply

I'm working on an infrastructure setup using Terraform where I've encountered an issue related to using the for_each meta-argument within a resource block. I have a proxy module structured as follows:

locals {
  artifacts_downloads_path        = "${path.cwd}/artifacts"
  proxy_zip_file_name             = "${var.proxy_name}.${var.proxy_version}.zip"
  local_proxy_artifact_directory  = "${local.artifacts_downloads_path}/${var.proxy_name}"
  script_file_directory           = "${path.cwd}/scripts"
  terraform_state_output          = data.terraform_remote_state.terraform_state_values.outputs
}

module "target_server" {
  for_each        = {
    for targetServer in var.list_of_target_servers_entries : targetServer => targetServer
  }
  source          = "../target_server"
  apigee_env      = var.apigee_env
  target_server_json = jsondecode(element(values(local.terraform_state_output), index(keys(local.terraform_state_output), each.value)))
  depends_on      = [data.terraform_remote_state.terraform_state_values]
}

module "kvm" {
  for_each        = {
    for kvm in var.list_of_kvm_entries : kvm => kvm
  }
  source          = "../kvm"
  apigee_env      = var.apigee_env
  proxy_name      = var.proxy_name
  kvm_json        = jsondecode(element(values(local.terraform_state_output), index(keys(local.terraform_state_output), each.value)))["entries"]
}

resource "apigee_proxy" "proxy" {
  count            = fileexists("${local.artifacts_downloads_path}/${local.proxy_zip_file_name}") ? 1 : 0
  name             = var.proxy_name
  bundle           = "${local.artifacts_downloads_path}/${local.proxy_zip_file_name}"
  bundle_hash      = filebase64sha256("${local.artifacts_downloads_path}/${local.proxy_zip_file_name}")
  depends_on       = [module.target_server, module.kvm]
}

resource "apigee_proxy_deployment" "proxy_deployment" {
  count            = fileexists("${local.artifacts_downloads_path}/${local.proxy_zip_file_name}") ? 1 : 0
  proxy_name       = apigee_proxy.proxy[0].name
  environment_name = var.apigee_env
  revision         = apigee_proxy.proxy[0].revision
  service_account  = var.service_account
  depends_on       = [apigee_proxy.proxy[0]]
}

resource "null_resource" "clean_up" {
  count            = fileexists("${local.artifacts_downloads_path}/${local.proxy_zip_file_name}") ? 1 : 0
  triggers         = {
    value = filebase64sha256("${local.artifacts_downloads_path}/${local.proxy_zip_file_name}")
  }
  provisioner "local-exec" {
    command = <<EOF
      cd ${local.script_file_directory}
      bash ./delete_old_APIGEE_resource_revisions.sh ${var.apigee_gcp_project_id} ${var.proxy_name} ${var.access_token} ${var.num_of_proxy_revisions_to_be_retained} "apis"
      rm -rf ${local.artifacts_downloads_path}/${local.proxy_zip_file_name}
    EOF
  }
  depends_on       = [apigee_proxy_deployment.proxy_deployment[0]]
}

In this module, I'm using a data block to fetch remote state data:

data "terraform_remote_state" "terraform_state_values" {
  backend = "gcs"
  config = {
    bucket = var.terraform_state_bucket_name        #local.terraform_state_bucket
    prefix = var.terraform_state_bucket_path_prefix #local.terraform_state_bucket_prefix
  }
}

I'm trying to consume the output of this terraform_remote_state in the target_server module:

resource "apigee_target_server" "target_server" {
  for_each = {
    for index, entry in jsondecode(var.target_server_json) : index => entry
  }
  environment_name = var.apigee_env
  name             = each.value.name
  host             = strcontains(each.value.host, "https") ? split("https://", each.value.host)[1] : each.value.host
  port             = each.value.port
  is_enabled       = each.value.enabled ? true : false
  protocols        = each.value.protocol != [] ? [each.value.protocol] : []
}

However, upon running terraform plan, I encounter the following error: var.target_server_json will be known only after apply The "for_each" map includes keys derived from resource attributes that cannot be determined until apply, and so Terraform cannot determine the full set of keys that will identify the instances of this resource.

I tried refactoring it to use count instead. Here's the attempt I made:

locals {
items_count = length(jsondecode(var.target_server_json))
}
resource "apigee_target_server" "target_server" {
  count = local.items_count

  # Access elements dynamically using the index
  environment_name = var.apigee_env
  name = var.target_server_json[count.index].name
 host             = strcontains(var.target_server_json[count.index].host, "https") ? split("https://", var.target_server_json[count.index].host)[1] : var.target_server_json[count.index].host
 port             = var.target_server_json[count.index].port
 is_enabled       = var.target_server_json[count.index].enabled ? true : false
 protocols        = var.target_server_json[count.index].protocol != [] ? [var.target_server_json[count.index].protocol] : []
}

Upvotes: 4

Views: 3468

Answers (1)

Marcin
Marcin

Reputation: 238081

target_server_json must be known at plan time. Its not possibile to iterate in TF, regardless of for_each or count over uknown number of items.

So to fix your error make sure that target_server_json is known at plan time, which means you have to pass it as an input argument to your configuration files. This way, TF will know exactly how many elements is in target_server_json

Upvotes: 4

Related Questions