20
votes

I'm just getting to grips with Terraform (so apologies if this is a stupid question).

I'm setting up an azure vnet with a set of subnets, each subnet has a routing table that sends traffic via a firewall.

It looks like the subnet and route table combination would make a good re-useable module.

By convention, I'd like to create the subnet and the route table in the same resource group and location as the parent vnet.

If I provide all the values needed in the module as individual values, the module works ok :)

What I'd rather do is effectively pass the resource that represents the parent vnet into the module as a "parameter" and have the module read things like the resource group name, location and vnet name directly from the vnet resource, so that: - I type less (I create the module instance with just the vnet, rather than seperate values for vnet name, resource group name and location) - it removes the opportunity for error when setting the location and resource group names on the route table and subnet (if I read the values from the parent vnet in the module then the values will be the same as the parent vnet)

Currently the module variables are defined as:

variable "parent-vnet-name" {}

variable "parent-vnet-resource-group-name" {}

variable "parent-vnet-location" {}

variable "subnet-name" {
  type = "string"
}

variable "subnet-address-prefix" {}

variable "firewall-ip-private" {}

and the module as:

resource "azurerm_route_table" "rg-mgt-network__testtesttest" {
   name                = "${var.subnet-name}"
  location            = "${var.parent-vnet-location}"
  resource_group_name = "${var.parent-vnet-resource-group-name}"

  route {
    name                   = "Default"
    address_prefix         = "0.0.0.0/0"
    next_hop_type          = "VirtualAppliance"
    next_hop_in_ip_address = "${var.firewall-ip-private}"
  }
}

Really what I'd like to do is more like variables:

variable "parent-vnet" {}

variable "subnet-name" {
  type = "string"
}

variable "subnet-address-prefix" {}

variable "firewall-ip-private" {}

with module doing something like:

resource "azurerm_route_table" "rg-mgt-network__testtesttest" {
  name                = "${var.subnet-name}"
  location            = "${var.parent-vnet.location}"
  resource_group_name = "${var.parent-vnet.resource-group-name}"

  route {
    name                   = "Default"
    address_prefix         = "0.0.0.0/0"
    next_hop_type          = "VirtualAppliance"
    next_hop_in_ip_address = "${var.firewall-ip-private}"
  }
}

I've played around with a variety of things (such as trying to pass the vnet without specifying an attribute name (validation failure) or using a data source to pull back the vnet (the data sources doesn't have the resource group info) ) but haven't got anywhere so I'm wondering if I've missed something?

Cheers, Andy

3
Can you share your code so far? If you have a few different alternative show a minimal example of each and hwy they don't work for you.ydaetskcoR
Examples added. Cheers, Andyuser2926169
Are you creating the route table and the virtual network at the same time? Or do you build them both with separate apply commands?ydaetskcoR
new subnets will be added over time - so they will be seperate apply commandsuser2926169
There doesn't seem to be an obvious link between the route table and the virtual network in your example (and I don't know Azure well) but are you not just passing in the resource group name and location that the virtual network happens to be in? But then I don't understand why the route table resource even needs the location specifying instead of extracting it from the resource group and simplifying the API.ydaetskcoR

3 Answers

3
votes

This isn't currently possible - according to the Terraform Creating Modules page, a module's input variables can only be standard variable types: string, list, and map.

I've encountered this same use case and have had to provide large lists of inputs to modules, which isn't ideal but seems to be the current state of the world.

22
votes

This is possible with terraform > 0.12. You can use the object type, butt you have to explicitly list fields that you use within your module.

# module's variables.tf
variable "parent_vnet" {
  # List each field in `azurerm_route_table` that your module will access
  type = object({
    name = string
    location = string
    resource_group_name = string
  })
} 

# caller
resource "azurerm_route_table" "my_parent_vnet" {
  # ...
}

module "my-module" {
  parent_vnet = azurerm_route_table.my_parent_vnet
}
1
votes

If you want to avoid passing long lists of attributes of other resources you can use the data resources to avoid that.

Say you need all resources "provider" attributes within a module "consumer", pass in an identifier of the data source and then fetch all attributes using the data source within the module.

resource "producer" {
id = "id"
}

module "consumer" {
 second-module-id = resource.producer.id
}

Within the "consuming" module you can now call access all attributes of the "producer" using the data source (provided your cloud provider has a data source):

data "producer-data" {
id = second-module-id
}

resource "resource" {
  prop1 = data.prop1
  prop2 = data.prop2
  ...
}