zeroweb
zeroweb

Reputation: 2752

Terraform using count with list inside list variable?

I am trying to create few storage account and some containers in each account. I need to create this as a module so that I can reuse it. The way that I am thinking to do this is by creating a variable such as

storageaccounts = [
    {
      name       = "testbackupstorage11"
      containers = ["logs", "web", "backups"]
    },
    {
      name       = "testbackupstorage12"
      containers = ["logs-1", "web-1"]
    }
  ]

I've created the following code. However, I think this line

count                 = length(var.storageaccounts.*.containers)

is giving me error. I want to loop through the storageaccount array, get the containers and assign the 'length' of the containers key to the 'count' inside the 'azurerm_storage_container' so that this block creates multiple storage account.

However, this doesn't work as expected, most likely because of * I also tested with

count                 = length(var.storageaccounts[count.index].containers)

when I do this, I get the error

 on ..\modules\storage\main.tf line 21, in resource "azurerm_storage_container" "this":
  21:   count                 = length(var.storageaccounts[count.index].containers)

The "count" object can be used only in "resource" and "data" blocks, and only
when the "count" argument is set.

How can I accomplish this? Or is there any better way?

Here is the full code.

resource "random_id" "this" {
  count = length(var.storageaccounts)
  keepers = {
    storagename = 1
  }

  byte_length = 6
  prefix      = var.storageaccounts[count.index].name
}

resource "azurerm_storage_account" "this" {
  count                    = length(var.storageaccounts)
  name                     = substr(lower(random_id.this[count.index].hex), 0, 24)
  resource_group_name      = var.resourcegroup
  location                 = var.location
  account_tier             = "Standard"
  account_replication_type = "LRS"
}

resource "azurerm_storage_container" "this" {
  count                 = length(var.storageaccounts.*.containers)
  name                  = var.storageaccounts[count.index].containers[count.index]
  storage_account_name  = azurerm_storage_account.this[count.index].name
  container_access_type = "private"
}

provider "random" {
  version = "2.2"
}
locals {
  storageaccounts = [
    {
      name       = "testbackupstorage11"
      containers = ["logs", "web", "backups"]
    },
    {
      name       = "testbackupstorage12"
      containers = ["logs-1", "web-1"]
    }
  ]

}

module "storage" {
  source          = "../modules/storage"
  resourcegroup   = "my-test"
  location        = "eastus"
  storageaccounts = local.storageaccounts

}

provider "azurerm" {
  version = "=2.0.0"
  features {}
}

//variable "prefix" {}
variable "location" {}
variable "resourcegroup" {}
variable "storageaccounts" {
  default = []
  type = list(object({
    name       = string
    containers = list(string)
  }))
}

Upvotes: 0

Views: 18753

Answers (2)

matt45uk
matt45uk

Reputation: 83

length(var.storageaccounts.*.containers)

Your count doesn't make sense, you asking for a list of storageaccounts with the attribute containers. So it would be looking for

    [
        {
          name       = "testbackupstorage11"
          containers = ["logs", "web", "backups"]
        },
        {
          name       = "testbackupstorage12"
          containers = ["logs-1", "web-1"]
        }
    ].containers

Try using a locals to merge all into one list:

    locals{
        storageaccounts = [for x in var.storageaccounts: x.containers]
    } // Returns list of lists

Then

count = length(flatten(local.storageaccounts)) //all one big list

https://www.terraform.io/docs/configuration/functions/flatten.html

Sorry, not had a chance to test the code, but I hope this helps.

Upvotes: 0

sevillo
sevillo

Reputation: 195

count = length(var.storageaccounts.*.containers) will return the length of var.storageaccounts which is 2.

count = length(var.storageaccounts[count.index].containers) would fail because you can't reference something that hasn't been declared.

What you can do is flatten the lists.

For example:

variables.tf

variable "storageaccounts" {
  default = []
  type = list(object({
    name       = string
    containers = list(string)
  }))
}

main.tf

resource "null_resource" "cluster" {
  count = length(flatten(var.storageaccounts.*.containers))
  provisioner "local-exec" {
    command = "echo ${flatten(var.storageaccounts.*.containers)[count.index]}"
  }
}

variables.tfvars

storageaccounts = [
    {
      name       = "testbackupstorage11"
      containers = ["logs", "web", "backups"]
    },
    {
      name       = "testbackupstorage12"
      containers = ["logs-1", "web-1"]
    }
  ]

The plan

terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.


------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # null_resource.cluster[0] will be created
  + resource "null_resource" "cluster" {
      + id = (known after apply)
    }

  # null_resource.cluster[1] will be created
  + resource "null_resource" "cluster" {
      + id = (known after apply)
    }

  # null_resource.cluster[2] will be created
  + resource "null_resource" "cluster" {
      + id = (known after apply)
    }

  # null_resource.cluster[3] will be created
  + resource "null_resource" "cluster" {
      + id = (known after apply)
    }

  # null_resource.cluster[4] will be created
  + resource "null_resource" "cluster" {
      + id = (known after apply)
    }

Plan: 5 to add, 0 to change, 0 to destroy.

------------------------------------------------------------------------

This plan was saved to: /path/plan

To perform exactly these actions, run the following command to apply:
    terraform apply "/path/plan"

The application

terraform apply  
/outputs/basics/plan
null_resource.cluster[1]: Creating...
null_resource.cluster[4]: Creating...
null_resource.cluster[3]: Creating...
null_resource.cluster[0]: Creating...
null_resource.cluster[2]: Creating...
null_resource.cluster[3]: Provisioning with 'local-exec'...
null_resource.cluster[1]: Provisioning with 'local-exec'...
null_resource.cluster[4]: Provisioning with 'local-exec'...
null_resource.cluster[0]: Provisioning with 'local-exec'...
null_resource.cluster[2]: Provisioning with 'local-exec'...
null_resource.cluster[3] (local-exec): Executing: ["/bin/sh" "-c" "echo logs-1"]
null_resource.cluster[2] (local-exec): Executing: ["/bin/sh" "-c" "echo backups"]
null_resource.cluster[4] (local-exec): Executing: ["/bin/sh" "-c" "echo web-1"]
null_resource.cluster[1] (local-exec): Executing: ["/bin/sh" "-c" "echo web"]
null_resource.cluster[0] (local-exec): Executing: ["/bin/sh" "-c" "echo logs"]
null_resource.cluster[2] (local-exec): backups
null_resource.cluster[2]: Creation complete after 0s [id=3936346761857660500]
null_resource.cluster[4] (local-exec): web-1
null_resource.cluster[3] (local-exec): logs-1
null_resource.cluster[0] (local-exec): logs
null_resource.cluster[1] (local-exec): web
null_resource.cluster[4]: Creation complete after 0s [id=3473332636300628727]
null_resource.cluster[3]: Creation complete after 0s [id=8036538301397331156]
null_resource.cluster[1]: Creation complete after 0s [id=8566902439147392295]
null_resource.cluster[0]: Creation complete after 0s [id=6115664408585418236]

Apply complete! Resources: 5 added, 0 changed, 0 destroyed.

The state of your infrastructure has been saved to the path
below. This state is required to modify and destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.

State path: terraform.tfstate

Upvotes: 0

Related Questions