Assistance with calling module output

Hello everyone. I am relatively new to Terraform. I have recently begun to work with modules. I suspect my issue lays with my lack of fully understanding modules, however, I am eager to learn.
I am encountering an issue (probably of my own fault) where Terraform is creating duplicate VPC resources. Namely, 3 duplicate VPC’s, 3 duplicate subnets, 3 duplicate route tables, etc.
I have my root main.tf that calls three modules that I have created. Those modules are ‘vpc’, ‘routes’, and ‘watchguard.’ The ‘watchguard’ module is for a layer 7 cloud firewall.

In my Watchguard output.tf I am outputting the ID of the ENI on the private subnet.

#Outputs Watchguard private ENI id of the Firebox
  output "wg_private" {
  value = aws_network_interface.Watchguard_Private.id
}

The ‘routes’ module is calling the ‘watchguard’ module.

module "watchguard" {
  source = "../watchguard"
}

Inside of the ‘routes’ module i have several route tables that use the ENI of Watchguard for their 0 route. This same ‘route’ module also calls on the ‘vpc’ module for the VPC id output.

resource "aws_route_table" "primary_private_route_table" {
 vpc_id = module.vpc.prod_vpc_id
 route {
    cidr_block = "0.0.0.0/0"
    network_interface_id = module.watchguard.wg_private
  }
 tags = {
        Name = "Primary Private"
  }
} # end resource

As I mentioned at the beginning of my post, when i execute a terraform plan, it shows that Terraform will create (and it does) 3 duplicates of each VPC resource. I am assuming that this is because both the ‘routes’ and ‘watchguard’ module both call the ‘vpc’ module. Is this behavior expected? How do I go about just pulling in the outputs that i need and not the entire module again?

Thank you so much for your help!

Hi @JacksonBain,

Indeed, each time you refer to a module in a module block that asks Terraform to create another “copy” of its contents, and so I think you’ve determined the correct explanation for what you’re seeing.

Most of the time, a Terraform configuration will only have one “layer” of child modules. That is, the root module (the current directory when you run Terraform) will contain module blocks but the other modules it refers to will not.

The root module’s job is then to connect output values from one module into the input variables of another so that the necessary data can flow through the configuration while still only declaring each object once.

It sounds like your “routes” and “watchguard” modules both make use of the VPC, and so a typical design for that would be for them to have one or more input variables declared to get the data they need and then for the root module to assign the relevant data into those, which might look like this:

module "vpc" {
  source = "../vpc"

  # (vpc settings)
}

module "routes" {
  source = "../routes"

  vpc = module.vpc
  # (and any other settings)
}

module "watchguard" {
  source = "../watchguard"

  vpc = module.vpc
  # (and any other settings)
}

In your ../vpc module it looks like you already have a prod_vpc_id output value declared, which I’ll assume for what’s next. Inside both the ../routes and ../watchguard modules you can match that by declaring an input variable like this:

variable "vpc" {
  type = object({
    prod_vpc_id = string
  })
}

Then inside those modules you can refer to var.vpc.prod_vpc_id to get the VPC id passed from the VPC module, without having to redeclare it.

The type argument for the variable above is a type constraint, which says that this variable expects an object with at least an attribute named prod_vpc_id. It doesn’t matter if there are other attributes, because Terraform will discard those when passing the value into the module.

There’s some more on this topic, including some example use-cases, in the docs page Module Composition.

This worked like a champ. I scratched my head on this almost all day. I wasn’t able to find any information on this anywhere! Thank you so much. I hope this post helps someone else that also ran into issues.

Also, I just noticed you live in Portland too. Thanks neighbor!