TheWellington
TheWellington

Reputation: 255

Using terraform to create multiple resources based on a set of variables

I have a set of variables in terraform.tfvars:

resource_groups = {
    cow = { 
        name     = "Cow"
        location = "eastus" 
    },    
    horse = { 
        name     = "Horse"
        location = "eastus" 
    },    
    chicken = { 
        name     = "Chicken"
        location = "westus2" 
    },    
}

my main.tf looks like this:


...
module "myapp" {
 source = "./modules/myapp"
 resource_groups = var.resource_groups
}


variable "resource_groups" {}
...

./modules/myapp.main.tf look like this:

module "resource_group" {
  source = "../myapp.resource_group"
  resource_groups = var.resource_groups

  for_each = {
  for key, value in try(var.resource_groups, {}) : key => value
  if try(value.reuse, false) == false
  }
  
}

variable "resource_groups" {}

and ../myapp.resource_group looks like this:

resource "azurerm_resource_group" "resource_group" {
name      = var.resource_groups.cow.name
location  = var.resource_groups.cow.location

}

variable "resource_groups" {}

My hope is that after terraform plan I would see that three new RGs would be set for addition. Infact I do get three new ones, but they all use the name and location of the cow RG, because I specified var.resource_groups.cow.name The problem is I have tried all kinds of different iterators in place of .cow. and I can't get terraform to use the other variables in the terraform.tfvars file. I have tried square brackets, asterisks, and other wildcards. I am stuck.

I am looking to define a resource in one place and then use that to create multiple instances of that resource per the map of variables.

Guidance would be much appreciated.

Thanks.

Bill

Upvotes: 1

Views: 11132

Answers (1)

Martin Atkins
Martin Atkins

Reputation: 74055

For this situation you'll need to decide whether your module represents one resource group or whether it represents multiple resource groups. For a module that only contains one resource anyway that decision is not particularly important, but I assume you're factoring this out into a separate module because there is something more to this than just the single resource group resource, and so you can decide between these two based on what else this module represents: do you want to repeat everything in this module, or just the resource group resource?


If you need the module to represent a single resource group then you should change its input variables to take the data about only a single resource group, and then pass the current resource group's data in your calling module block.

Inside the module:

variable "resource_group" {
  type = object({
    name     = string
    location = string
  })
}

resource "azurerm_resource_group" "resource_group" {
  name      = var.resource_group.name
  location  = var.resource_group.location
}

When calling the module:

variable "resource_groups" {
  type = map(
    object({
      name     = string
      location = string
    })
  )
}

module "resource_group" {
  source   = "../myapp.resource_group"
  for_each = var.resource_groups

  # each.value is the value of the current
  # element of var.resource_groups, and
  # so it's just a single resource group.
  resource_group = each.value
}

With this strategy, you will declare resource instances with the following addresses, showing that the repetition is happening at the level of the whole module rather than the individual resources inside it:

  • module.resource_group["cow"].azurerm_resource_group.resource_group
  • module.resource_group["horse"].azurerm_resource_group.resource_group
  • module.resource_group["chicken"].azurerm_resource_group.resource_group

If you need the module to represent the full set of resource groups then the module would take the full map of resource groups as an input variable instead of using for_each on the module block. The for_each argument will then belong to the nested resource instead.

Inside the module:

variable "resource_groups" {
  type = map(
    object({
      name     = string
      location = string
    })
  )
}

resource "azurerm_resource_group" "resource_group" {
  for_each = var.resource_groups

  name      = each.value.name
  location  = each.value.location
}

When calling the module:

variable "resource_groups" {
  type = map(
    object({
      name     = string
      location = string
    })
  )
}

module "resource_group" {
  source   = "../myapp.resource_group"

  # NOTE: No for_each here, because we need only
  # one instance of this module which will itself
  # then contain multiple instances of the resource.

  resource_group = var.resource_groups
}

With this strategy, you will declare resource instances with the following addresses, showing that there's only one instance of the module but multiple instances of the resource inside it:

  • module.resource_group.azurerm_resource_group.resource_group["cow"]
  • module.resource_group.azurerm_resource_group.resource_group["horse"]
  • module.resource_group.azurerm_resource_group.resource_group["chicken"]

It's not clear from the information you shared which of these strategies would be more appropriate in your case, because you've described this module as if it is just a thin wrapper around the azurerm_resource_group resource type and therefore it isn't really clear what this module represents, and why it's helpful in comparison to just writing an inline resource "azurerm_resource_group" block in the root module.

When thinking about which of the above designs is most appropriate for your use-case, I'd suggest considering the advice in When to Write a Module in the Terraform documentation. It can be okay to write a module that contains only a single resource block, but that's typically for more complicated resource types where the module hard-codes some local conventions so that they don't need to be re-specified throughout an organization's Terraform configurations.

If you are just passing the values through directly to the resource arguments with no additional transformation and no additional hard-coded settings then that would suggest that this module is not useful, and that it would be simpler to write the resource block inline instead.

Upvotes: 5

Related Questions