1
votes

I am having trouble referencing an output from a module in another module. The resources in the first module was deployed using for_each. The resources in the second module is trying to reference the resources from first module

There are 2 modules created

  1. Security Group
  2. VM

The intention is to assign the Security Group to the VM

The following is the module for the Security Group


variable "configserver" {
  type = map(object({
    name              = string
    location          = string
    subnet            = string
    availability_zone = string
    vm_size           = string
    hdd_size          = string
  }))
}


module "configserver_nsg" {
  for_each = var.configserver

  source              = "../../../terraform/modules/azure-network-security-group"
  resource_group_name = var.resource_group_name
  tags                = var.tags
  location = each.value.location
  nsg_name = "${each.value.name}-nsg"

  security_rules = [
    {
      name              = "Office",
      priority          = "100"
      direction         = "Inbound"
      access            = "Allow"
      protocol          = "TCP"
      source_port_range = "*"
      destination_port_ranges = [
        "22"]
      source_address_prefix = "192.168.1.100"
      destination_address_prefixes = [
        module.configserver_vm[each.key].private_ip
      ]
    },
    

    {
      name                       = "Deny-All-Others"
      priority                   = 4096
      direction                  = "Inbound"
      access                     = "Deny"
      protocol                   = "*"
      source_port_range          = "*"
      destination_port_range     = "*"
      source_address_prefix      = "*"
      destination_address_prefix = "*"
    }

  ]
}

// Value


configserver = {
  config1 = {
    name              = "config1"
    location          = "eastus"
    subnet            = "services"
    availability_zone = 1
    vm_size           = "Standard_F2s_v2"
    hdd_size          = 30
  }
}

The security group module source has an output file which outputs the id of the nsg

output "nsg_id" {
  description = "The ID of the newly created Network Security Group"
  value       = azurerm_network_security_group.nsg.id
}

Generally, if there isn't a for_each, I could access the nsg_id like this

module.configserver_nsg.id

So far this is good, now the issue is that I am not able to access the nsg_id from another module

module "configserver_vm" {
  for_each = var.configserver

  source         = "../../../terraform/modules/azure-linux-vm"
  resource_group = module.resource_group.name
  ssh_public_key = var.ssh_public_key
  tags           = var.tags
  vm_name            = each.value.name
  location           = each.value.location
  subnet_id          = each.value.subnet
  availability-zones = each.value.availability_zone
  vm_size            = each.value.vm_size
  hdd-size           = each.value.hdd_size
  nsg_id             = module.configserver_nsg[each.key].nsg_id
}

Based on my research these, a number of posts (here, here, here say I should be able to loop through the map using each.key however

nsg_id             = module.configserver_nsg[each.key].nsg_id

this produces the error

Error: Cycle: module.configserver_nsg (close), module.configserver_vm.var.nsg_id (expand), module.configserver_vm.azurerm_network_interface_security_group_association.this, module.configserver_vm (close), module.configserver_nsg.var.security_rules (expand), module.configserver_nsg.azurerm_network_security_group.nsg, module.configserver_nsg.output.nsg_id (expand)

Is there any other way to reference the value?

1
Hi. Maybe it makes sense to pack the for_each logic inside of the module? I mean provide var.configserver and module.configserver_nsg as input variables for the module and instead use for_each for the resources inside of the "../../../terraform/modules/azure-linux-vm" module. Or is there a problem with this approach?Fedor Petrov
The error implies you have a cyclical dependency you need to unravel first.Matt Schuchard
@FedorPetrov Doesn't that mean we now have to create a single module for VM and Security group? Let me know if I am understanding this correct. We would prefer to have separated modules for VM and security groups so that these are reusable.zeroweb
@MattSchuchard yes correct, after reviewing the terraform statefile, I see some dependency and it looks like the dependency call is going as a loop which terraform is complaining. I am not quite sure how to tackle this. It works if I don't call the module within for_each loop. Still looking into itzeroweb
Any more updates on this question? Does it solve your problem? If it solves your problem please accept it.Charles Xu

1 Answers

2
votes

As I see the first problem is that you use the wrong way to quote the things from the module configserver_nsg for the NSG id, it should be like this:

nsg_id             = module.configserver_nsg[each.value.name].nsg_id

And the second problem has been said by @Matt. It's a cyclical dependency between the two modules. The thing that makes the cyclical dependency is the NSG rule, it seems the NSG rule needs the VM private IP address. According to my knowledge, you cannot solve the cyclical dependency if you do not make a change. So I recommend you make a change that separates the NSG rule from the module configserver_nsg and use the resource azurerm_network_security_rule instead after the two modules.

And finally, it seems like this:

variable "configserver" {
  type = map(object({
    name              = string
    location          = string
    subnet            = string
    availability_zone = string
    vm_size           = string
    hdd_size          = string
  }))
}


module "configserver_nsg" {
  for_each = var.configserver

  source              = "../../../terraform/modules/azure-network-security-group"
  resource_group_name = var.resource_group_name
  tags                = var.tags
  location = each.value.location
  nsg_name = "${each.value.name}-nsg"

  security_rules = [
    {
      
    },
    

    {
      name                       = "Deny-All-Others"
      priority                   = 4096
      direction                  = "Inbound"
      access                     = "Deny"
      protocol                   = "*"
      source_port_range          = "*"
      destination_port_range     = "*"
      source_address_prefix      = "*"
      destination_address_prefix = "*"
    }

  ]
}

// Value


configserver = {
  config1 = {
    name              = "config1"
    location          = "eastus"
    subnet            = "services"
    availability_zone = 1
    vm_size           = "Standard_F2s_v2"
    hdd_size          = 30
  }
}

module "configserver_vm" {
  for_each = var.configserver

  source         = "../../../terraform/modules/azure-linux-vm"
  resource_group = module.resource_group.name
  ssh_public_key = var.ssh_public_key
  tags           = var.tags
  vm_name            = each.value.name
  location           = each.value.location
  subnet_id          = each.value.subnet
  availability-zones = each.value.availability_zone
  vm_size            = each.value.vm_size
  hdd-size           = each.value.hdd_size
  nsg_id             = module.configserver_nsg[each.value.name].nsg_id
}

resource "azurerm_network_security_rule" "configserver_nsg" {
  for_each = var.configserver
  name              = "Office",
  priority          = "100"
  direction         = "Inbound"
  access            = "Allow"
  protocol          = "TCP"
  source_port_range = "*"
  destination_port_ranges = ["22"]
  source_address_prefix = "192.168.1.100"
  destination_address_prefixes = [
    module.configserver_vm[each.key].private_ip
  ]
  resource_group_name         = var.resource_group_name
  network_security_group_name = "${each.value.name}-nsg"
}