Terraform “count” gotcha

The problem

I was tasked with creating a resource, a storage account. However, we only needed it to be in two of our environments, not all of them. Although Terraform doesn’t technically support if statements, the workaround using the count meta parameter is quite well known.

To slightly complicate matters, I needed to place an output value from the resource into an Azure Key Vault. This proved to be the main part of the challenge. I found that I was experiencing errors with the Terraform init or validate steps, even when the count was resolving to zero and Terraform shouldn’t have been trying to create the resource.

The solution

After working on the problem for far longer than I’d like to admit to, I discovered the answer hidden in the official Terraform site’s documentation. The key part was in the “Referring to instances” section. What the documentation doesn’t say is that if you are using count then you must use indexing to refer to it. My mistake was thinking that, because I was using it as a false if statement, I was expecting to just be able to refer to the output value the same way that I would have done if I hadn’t had the count parameter there.

Example

The repository contains a variables.tf file with the following definition in it:

variable "create_resource" {
  description = "Create resource for this environment?"
  type        = bool
  default     = false
}

The environment-specific files specify this as true when required, e.g. development.tfvars

environment = "dev"
create_resource = true

and, the main.tf to use all of this:

data "azurerm_client_config" "current" {}

locals {
  default_resource_name = "notavailable"
  storage_resource_name = "${var.create_resource ? module.storage_resource[0].storage_account_name : local.default_resource_name}"
}

# resource group snipped

module "storage_resource" {  
  source              = "module_url"
  count               = var.create_resource ? 1 : 0
  name                = "${var.storage_name}${var.environment}"
  location            = module.resource_group.resource_group_location
  resource_group_name = module.resource_group.resource_group_name
}

module "key_vault" {
  source              = "module_url"
  name                = "${var.key_vault_name}-${var.environment}"
  location            = module.resource_group.resource_group_location
  resource_group_name = module.resource_group.resource_group_name
  tenant_id           = data.azurerm_client_config.current.tenant_id

  admin_ids = [
    data.azurerm_client_config.current.object_id
  ]
  secrets = [
    {
      name   = "storage-account-name"
      value  = local.storage_resource_name
    },
  ]
}

The principal thing to note here is the use of the [0] indexing in the storage_resource_name local. The ternary operator ensures that a secret is created in the vault even if the resource isn’t created.

Errors

The main error message that I encountered throughout this was “this value does not have any attributes”. Depending on what I had in the code, I also encountered “module.storage_resource is tuple with 1 element”. In retrospect, this should have been something that indicated that I needed to access it via an index.