Reference outputs in another module

Hi,

Immediate apologies as this seems a common thread but i’m new to TF i’m struggling to grasp this after reading a plethora of forum posts about how to reference an output from one .tf file into another.
I’m used to AWS Cloudformation and when issuing an export value from a stack, you can reference the value of that export by use of an import function - can’t figure this out properly with TF so hoping someone can help.

What i want to do is build only the VPC components in this file and output the values required such as subnet id’s, security groups etc. This will build the foundation of the network whereby i can build separate resources outside of this such as ec2, alb’s and RDS mysql nodes.

Once these are output, i want to then import the respective value into say, the ec2 module for the subnet_id (for example).

Tree =
terraform
├── README.md
├── main.tf
├── modules
│ ├── alb
│ │ ├── alb.tf
│ │ └── variables.tf
│ ├── database
│ │ ├── db.tf
│ │ └── variables.tf
│ └── ec2
│ ├── ec2.tf
│ └── variables.tf
└── variables.tf

Firstly, am i correct with the structure in that main.tf is essentially my root module and that i have created child modules for ec2, database and alb?

The root main.tf has all the VPC configuration details.
I’ll remove the majority of this for readability. There is nothing in this file other than setting the provider, resources and outputs.

main.tf

....

###########################
# Create VPC and Internet Gateway #
###########################

resource "aws_vpc" "demo-vpc" {
    cidr_block = var.vpc_cidr
    instance_tenancy = "default"
    enable_dns_support = true
    enable_dns_hostnames = true
    
    tags = {
        Name = "demo-vpc"
    }
}
resource "aws_internet_gateway" "demo-igw" {
  vpc_id = aws_vpc.demo-vpc.id

  tags = {
    Name = "demo-vpc-igw"
  }
}

#################################
# Create two public subnets in AZ1 and AZ2 #
#################################

resource "aws_subnet" "demo-public-subnet-az1" {
  vpc_id     = aws_vpc.demo-vpc.id
  cidr_block = var.public_subnet1_cidr
  map_public_ip_on_launch = true
  availability_zone = "us-east-1a"

  tags = {
    Name = "Public-Subnet-AZ1"
  }
}

.....

output "vpc_id" {
    description = "ID of the VPC"
    value = aws_vpc.demo-vpc.id
}

output "public_subnet-az1" {
    description = "ID of the Public Subnet AZ1"
    value = aws_subnet.demo-public-subnet-az1.id
}

ec2.tf

....

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

######################
# Create Web Servers #
######################

resource "aws_instance" "instance_1" {
  ami                     = "ami-011899242bb902164"
  instance_type           = var.instance_type
  subnet_id               = module.vpcmodule.public_subnet-az1
  vpc_security_group_ids  = [module.vpcmodule.public_sg]
}

The above validates ok after running init and validate commands in ec2, but it seems that when running a plan in ec2, it runs ok but it states it will build all VPC components in the main.tf along with the ec2 instances in ec2.tf.
This kind of makes sense to a degree and is most likely by design as i’m calling the module two directories back (main.tf).

However, all i want to do is that when running the ec2.tf, it simply pulls the id of the “public_subnet-az1” output from the main.tf into the ‘subnet_id’ in the ec2.tf, not build both stacks.

I’ve left the variables.tf files out for the moment but if required to help, i can add these to the topic.

Thank you from another TF newby.

To update, i may have solved this, although i would appreciate advice on whether what i have done is correct and best practice.

So, instead of using modules, i used a data source instead.
I dug a bit further into some forums and documentation and although i had a decent idea, i now further understand what modules are essentially for which led me to the data sources.

Caveat = The main.tf in the root module must be applied or at least the values are in the state file to be referenced, otherwise you’ll receive an error when running the plan advising “Error: no matching ‘parameter’ found” which makes sense if it ain’t there yet.

The ec2.tf child module that simply holds the code to build 2 x EC2 instances in AZ1 and AZ2 that needs to reference the Security Groups and Subnets now has the following…
Note that i have now commented out the module.

Section 1 to define the data source attributes are as follows:

aws_subnet = standard TF call
public_subnet-az1 = used the output from the main.tf (not the name of the resource)
tags = using the name tag from the ‘resource’ block from main.tf so i can create a data reference for subnets in AZ1 and AZ2

Section 2 to Create Web Servers.

For the subnet_id for Instance1 i referenced the data block in this file and appended the id to the end to retrieve the ID itself.
For the vpc_security_group_ids, essentially the same to reference the public_sg data block and append the id to again reference the ID of this resource (list).

/*
module "vpcmodule" {
    source = "../../"
}
*/

data "aws_subnet" "public_subnet-az1" {
  tags = {
    Name = "Public-Subnet-AZ1"
  }
}

data "aws_subnet" "public_subnet-az2" {
  tags = {
    Name = "Public-Subnet-AZ2"
  }
}

data "aws_security_group" "public_sg" {
  tags = {
    Name = "Public_Security_Group"
  }
}

######################
# Create Web Servers #
######################

resource "aws_instance" "instance_1" {
  ami                     = "ami-011899242bb902164"
  instance_type           = var.instance_type
  subnet_id               = data.aws_subnet.public_subnet-az1.id
  vpc_security_group_ids  = [data.aws_security_group.public_sg.id]
}

resource "aws_instance" "instance_2" {
  ami                     = "ami-011899242bb902164"
  instance_type           = var.instance_type
  subnet_id               = data.aws_subnet.public_subnet-az2.id
  vpc_security_group_ids  = [data.aws_security_group.public_sg.id]
}

I ran a plan and it now came back that it was only going to build the two instances - happy days.
I then ran apply and it built the two instances in the respective AZ’s and subnets requested along with assigning the correct SG deployed in the main.tf VPC. This was confirmed in the AWS console UI.
It did not overwrite anything and the ID’s created from the main.tf apply matched in the state file.
Next, to carry on with the other modules and expanding this out.

I hope this helps anyone else with the same scenario but again, happy for anyone to give advice and let me know if i’m not performing this correctly.
Every day’s a school day!