1

I added a piece of code to support enable/disable to the feature - I used the count. Now I have the following problem:

ERROR:
on modules/eventhub/main.tf line 110, in resource "azurerm_eventhub" "events":

110: namespace_name = azurerm_eventhub_namespace.eventhub.*.name |---------------- | azurerm_eventhub_namespace.eventhub is tuple with 1 element

Inappropriate value for attribute "namespace_name": string required.

Create EventHub namespace

resource "azurerm_eventhub_namespace" "eventhub" { count = var.enable_eh ? 1 : 0 name = "${var.name}-ns" location = var.location resource_group_name = var.resource_group sku = var.sku capacity = var.capacity dedicated_cluster_id = azurerm_eventhub_cluster.eventhub[count.index].id #for eventHub Cluster auto_inflate_enabled = var.auto_inflate != null ? var.auto_inflate.enabled : null maximum_throughput_units = var.auto_inflate != null ? var.auto_inflate.maximum_throughput_units : null

dynamic "network_rulesets" {
for_each = var.network_rules != null ? ["true"] : []
content {
  default_action = "Deny"
  dynamic "ip_rule" {
    for_each = var.network_rules.ip_rules
    iterator = iprule
    content {
      ip_mask = iprule.value
    }
  }

  dynamic "virtual_network_rule" {
    for_each = var.network_rules.subnet_ids
    iterator = subnet
    content {
      subnet_id = subnet.value
    }
  }
}

}

tags = var.tags }

resource "azurerm_eventhub_namespace_authorization_rule" "events" { for_each = local.authorization_rules name = each.key namespace_name = azurerm_eventhub_namespace.eventhub.*.name resource_group_name = var.resource_group

listen = each.value.listen send = each.value.send manage = each.value.manage

}

resource "azurerm_eventhub" "events" { for_each = local.hubs name = each.key namespace_name = azurerm_eventhub_namespace.eventhub.*.name <------ problematic line

resource_group_name = var.resource_group partition_count = each.value.partitions message_retention = each.value.message_retention

}

I don't know what the issue is.

Sinan Ünür
  • 103
  • 2
Nava Bar
  • 13
  • 1
  • 3

2 Answers2

1

I believe you should be able to reference the namespace name like so:

namespace_name = azurerm_eventhub_namespace.eventhub[0].name

However, if the namespace_name field is a required parameter on the resources you are passing it to, you might run into other issues while you are trying to set enabled to false, as the resources depend on the output of the resource you are setting to count[0]. If you want them all to be conditional on the creation of the namespace, i would recommend continuing down your approach with the for_each over the local values.

If you need to the other resources to not be created unless the namespace is created you can create a condition on your local value like this (assuming map):

locals{
    hub = var.enable_eh ? {foo = "bar", baz = "qux"} : {}
}

The for_each will omit the resources that are empty.

zm31
  • 381
  • 1
  • 6
0

I think you should migrate from count to for_each, here is a wider context and howto:

Terraform provides a 'for_each' iterator (aka set/map or key named list) and a 'count' iterator (aka indexed list with strict ordering) which allows you to loop over elements of a list and perform multi resource deployments. What many people fail to realize is that when using indexed list with the 'count' iterator, any change to the list ordering (like removing an item) causes replacement of all items in the list, those can be important cloud resources like databases, instances, buckets etc.

High level overview here: https://www.hashicorp.com/blog/new-terraform-tutorials-on-count-and-for-each

With ‘count’, even when one of the list items needs just reconfiguration of substantial attributes, terraform might plan to destroy and/or replace the entire list of resources, making this update a clear 'no-go' in production environments. Those resources would have better support (yet not perfect) for updating its items, if the list would have been implemented using the 'for-each' iterator, simply because the difference between a simple indexed list and a set or a map is that the item names (keys) are all guaranteed to be unique, and also do not have any particular ordering.

But many times, for various reasons, one would already implement a 'count' iterator in production and face the need to migrate it later to 'for each' iterator without destroying any item and while keeping terraform states clean and synchronized with the cloud.

here is a stable workaround for anyone facing such requirement, i think this one is not well documented yet.

terraform init -var-file=./tfvars/.tfvars -reconfigure terraform state list → to grab the count list items

Change ‘count’ instructions to ‘for-each’ instructions (example):

resource "google_container_node_pool" "nodes" {

  • count = length(var.node_pools)
  • for_each = {for i, v in var.node_pools: i => v}
  • initial_node_count = var.node_pools[count.index].initial_node_count
  • initial_node_count = each.value.initial_node_count

etc ….

Now, obviously at this point the terraform plan would show it's intention to destroy and re-create everything under that list of resources.

terraform plan -var-file=./tfvars/.tfvars → not looking good

Here is the golden transformation steps (example):

terraform state mv 'google_container_node_pool.nodes[0]' 'google_container_node_pool.nodes["0"]'

In this example we manually, yet safely, transformed one of the items from numbered (indexed) list to named item in the list, keeping the old ordering as needed (although will not be important post transformation it is important during transformation).

After doing the above on all items in the list you can initiate plan again and watch the results:

terraform plan -var-file=./tfvars/.tfvars → “infrastructure matches the configuration”

Now you can safely commit, merge and apply your new terraform infrastructure code and continue working with a new key/named list, with the ‘for-each’ iterator handling item updates much better (although - nor perfect)