How to reference a resource created by a Terraform module - terraform

I'm using the AWS VPC Terraform module to create a VPC. Additionally, I want to create and attach an Internet Gateway to this VPC using the aws_internet_gateway resource.
Here is my code:
module "vpc" "vpc_default" {
source = "terraform-aws-modules/vpc/aws"
name = "${var.env_name}-vpc-default"
cidr = "10.0.0.0/16"
enable_dns_hostnames = true
}
resource "aws_internet_gateway" "vpc_default_igw" {
vpc_id = "${vpc.vpc_default.id}"
tags {
Name = "${var.env_name}-vpc-igw-vpcDefault"
}
}
When I run terraform init, I get the following error:
Initializing modules...
- module.vpc
Error: resource 'aws_internet_gateway.vpc_default_igw' config: unknown resource 'vpc.vpc_default' referenced in variable vpc.vpc_default.id
How can I reference a resource created by a Terraform module?

Since you're using a module, you need to change the format of the reference slightly. Module Outputs use the form ${module.<module name>.<output name>}. It's also important to note, you can only reference values outputted from a module.
In your specific case, this would become ${module.vpc.vpc_id} based on the VPC Module's Outputs.

Related

How do I get vpc_id so I can create a VPC subnet in Terraform?

I am creating an AWS VPC with a single public subnet in a brand-new Terraform project, consisting only of a main.tf file. In that file I am using two resource blocks, aws_vpc and aws_subnet. The second resource must be attached to the first using the vpc_id attribute. The value of this attribute is created only upon apply, so it cannot be hard-coded. How do I get the ID of the resource I just created, so I can use it in the subsequent block?
resource "aws_vpc" "my_vpc" {
cidr_block = "102.0.0.0/16"
tags = {
Name = "My-VPC"
}
}
resource "aws_subnet" "my_subnet" {
vpc_id = # what goes here?
cidr_block = "102.0.0.0/24"
tags = {
Name = "My-Subnet"
}
}
The docs give the example of data.aws_subnet.selected.vpc_id for vpc_id. The value of this appears to depend on two other blocks, variable and data. I am having a hard time seeing how to wire these up to my VPC. I tried copying them directly from the docs. Upon running terraform plan I get the prompt:
var.subnet_id
Enter a value:
This is no good; I want to pull the value from the VPC I just created, not enter it at the command prompt. Where do I specify that the data source is the resource that I just created in the previous code block?
I have heard that people create a separate file to hold Terraform variables. Is that what I should to do here? It seems like it should be so basic to get an ID from one resource and use it in the next. Is there a one-liner to pass along this information?
You can just call the VPC in the subnet block by referencing Terraform's pointer. Also, doing this tells Terraform that the VPC needs to be created first and destroyed second.
resource "aws_vpc" "my_vpc" {
cidr_block = "102.0.0.0/16"
tags = {
Name = "My-VPC"
}
}
resource "aws_subnet" "my_subnet" {
vpc_id = aws_vpc.my_vpc.id
cidr_block = "102.0.0.0/24"
tags = {
Name = "My-Subnet"
}
}
I want to pull the value from the VPC I just created,
You can't do this. You can't dynamically populate variables from data sources. But you could use local instead:
locals {
subnet_id = data.aws_subnet.selected.id
}
and refer to it as local.subnet_id.

issues with aws_vpc_dhcp_options usage

I am creating a two VPC deployment. Both VPCs are being deployed via modules. In module VPC01 I am defining the dhcp options using:
resource "aws_vpc_dhcp_options" "dhcp_domain_name" {
domain_name = var.domain_name
tags = {
Name = var.domain_name
Creator = var.creator_name
}
}
resource "aws_vpc_dhcp_options_association" "dns_resolver" {
vpc_id = aws_vpc.infra-vpc.id
dhcp_options_id = aws_vpc_dhcp_options.dhcp_domain_name.id
}
So this will define the vpc dhcp options when this vpc is deployed. Now when I want to deploy my send vpc how do I associate this vpc with the same vpc dhcp option set created?
I was trying to use:
resource "aws_vpc_dhcp_options_association" "dns_resolver" {
vpc_id = aws_vpc.infra-vpc.id
dhcp_options_id = aws_vpc_dhcp_options.dhcp_domain_name.id
}
When I do this I get this error:
Error: Reference to undeclared resource
on modules/vpc-intapp/infra-vpc.tf line 173, in resource "aws_vpc_dhcp_options_association" "dns_resolver":
173: dhcp_options_id = aws_vpc_dhcp_options.dhcp_domain_name.id
A managed resource "aws_vpc_dhcp_options" "dhcp_domain_name" has not been
declared in module.vpc-intapp.
I need to somehow get the value of the vpc dhcp option into my second module. So how do I go about this?
Modules can't directly reference resources created in other modules. If you want a resource to be shared/referenced by multiple modules, you either need to create it outside the modules, and pass it as an input variable to both modules, or you need to define it as an output from one module, and pass it as an input to the other module.
Since you've already created the first set of resources, I would go with the second option. Add this to the first VPC module:
output "vpc_dhcp_options_id" {
value = aws_vpc_dhcp_options.dhcp_domain_name.id
}
Add this to the second VPC module:
variable "vpc_dhcp_options_id" {}
And change the second VPC module to use the module's input variable:
resource "aws_vpc_dhcp_options_association" "dns_resolver" {
vpc_id = aws_vpc.infra-vpc.id
dhcp_options_id = var.vpc_dhcp_options_id
}
Finally, pass the output value from the first module as an input value to the second module:
module "my_first_vpc" {
source = "..."
}
module "my_second_vpc" {
source = "..."
vpc_dhcp_options_id = module.my_first_vpc.vpc_dhcp_options_id
}

Reference multiple aws_instance in terraform for template output

We want to deploy services into several regions.
Looks like because of the aws provider, we can't just use count or for_each, as the provider can't be interpolated. Thus I need to set this up manually:
resource "aws_instance" "app-us-west-1" {
provider = aws.us-west-1
#other stuff
}
resource "aws_instance" "app-us-east-1" {
provider = aws.us-east-1
#other stuff
}
I would like when running this to create a file which contains all the IPs created (for an ansible inventory).
I was looking at this answer:
https://stackoverflow.com/a/61788089/169252
and trying to adapt it for my case:
resource "local_file" "app-hosts" {
content = templatefile("${path.module}/templates/app_hosts.tpl",
{
hosts = aws_instance[*].public_ip
}
)
filename = "app-hosts.cfg"
}
And then setting up the template accordingly.
But this fails:
Error: Invalid reference
on app.tf line 144, in resource "local_file" "app-hosts":
122: hosts = aws_instance[*].public_ip
A reference to a resource type must be followed by at least one attribute
access, specifying the resource name
I am suspecting that I can't just reference all the aws_instance defined as above like this. Maybe to refer to all aws_instance in this file I need to use a different syntax.
Or maybe I need to use a module somehow. Can someone confirm this?
Using terraform v0.12.24
EDIT: The provider definitions use alias and it's all in the same app.tf, which I was naively assuming to be able to apply in one go with terraform apply (did I mention I am a beginner with terraform?):
provider "aws" {
alias = "us-east-1"
region = "us-east-1"
}
provider "aws" {
alias = "us-west-1"
region = "us-west-1"
}
My current workaround is to not do a join but simply listing them all individually:
{
host1 = aws_instance.app-us-west-1.public_ip
host2 = aws_instance.app-us-east-1.public_ip
# more hosts
}

Terraform output defined are empty [duplicate]

I'm trying to setup some IaC for a new project using Hashicorp Terraform on AWS. I'm using modules because I want to be able to reuse stuff across multiple environments (staging, prod, dev, etc.)
I'm struggling to understand where I have to set an output variable within a module, and how I then use that in another module. Any pointers to this would be greatly appreciated!
I need to use some things created in my VPC module (subnet IDs) when creating EC2 machines. My understanding is that you can't reference something from one module in another, so I am trying to use an output variable from the VPC module.
I have the following in my site main.tf
module "myapp-vpc" {
source = "dev/vpc"
aws_region = "${var.aws_region}"
}
module "myapp-ec2" {
source = "dev/ec2"
aws_region = "${var.aws_region}"
subnet_id = "${module.vpc.subnetid"}
}
dev/vpc simply sets some values and uses my vpc module:
module "vpc" {
source = "../../modules/vpc"
aws_region = "${var.aws_region}"
vpc-cidr = "10.1.0.0/16"
public-subnet-cidr = "10.1.1.0/24"
private-subnet-cidr = "10.1.2.0/24"
}
In my vpc main.tf, I have the following at the very end, after the aws_vpc and aws_subnet resources (showing subnet resource):
resource "aws_subnet" "public" {
vpc_id = "${aws_vpc.main.id}"
map_public_ip_on_launch = true
availability_zone = "${var.aws_region}a"
cidr_block = "${var.public-subnet-cidr}"
}
output "subnetid" {
value = "${aws_subnet.public.id}"
}
When I run terraform plan I get the following error message:
Error: module 'vpc': "subnetid" is not a valid output for module "vpc"
Outputs need to be passed up through each module explicitly each time.
For example if you wanted to output a variable to the screen from a module nested below another module you would need something like this:
child-module.tf
output "child_foo" {
value = "foobar"
}
parent-module.tf
module "child" {
source = "path/to/child"
}
output "parent_foo" {
value = "${module.child.child_foo}"
}
main.tf
module "parent" {
source = "path/to/parent"
}
output "main_foo" {
value = "${module.parent.parent_foo}"
}

How to create provide modules that support multiple AWS regions?

We are trying to create Terraform modules for below activities in AWS, so that we can use them where ever that is required.
VPC creation
Subnets creation
Instance creation etc.
But while creating these modules we have to define the provider in all above listed modules. So we decided to create one more module for provider so that we can call that provider module in other modules (VPC, Subnet, etc.).
Issue in above approach is that it is not taking provider value, and asking for the user input for region.
Terraform configuration is as follow:
$HOME/modules/providers/main.tf
provider "aws" {
region = "${var.region}"
}
$HOME/modules/providers/variables.tf
variable "region" {}
$HOME/modules/vpc/main.tf
module "provider" {
source = "../../modules/providers"
region = "${var.region}"
}
resource "aws_vpc" "vpc" {
cidr_block = "${var.vpc_cidr}"
tags = {
"name" = "${var.environment}_McD_VPC"
}
}
$HOME/modules/vpc/variables.tf
variable "vpc_cidr" {}
variable "environment" {}
variable "region" {}
$HOME/main.tf
module "dev_vpc" {
source = "modules/vpc"
vpc_cidr = "${var.vpc_cidr}"
environment = "${var.environment}"
region = "${var.region}"
}
$HOME/variables.tf
variable "vpc_cidr" {
default = "192.168.0.0/16"
}
variable "environment" {
default = "dev"
}
variable "region" {
default = "ap-south-1"
}
Then when running terraform plan command at $HOME/ location it is not taking provider value and instead asking for the user input for region.
I need help from the Terraform experts, what approach we should follow to address below concerns:
Wrap provider in a Terraform module
Handle multiple region use case using provider module or any other way.
I knew a long time back that it wasn't possible to do this because Terraform built a graph that required a provider for any resource before it included any dependencies and it didn't used to be possible to force a dependency on a module.
However since Terraform 0.8 it is now possible to set a dependency on modules with the following syntax:
module "network" {
# ...
}
resource "aws_instance" "foo" {
# ...
depends_on = ["module.network"]
}
However, if I try that with your setup by changing modules/vpc/main.tf to look something like this:
module "aws_provider" {
source = "../../modules/providers"
region = "${var.region}"
}
resource "aws_vpc" "vpc" {
cidr_block = "${var.vpc_cidr}"
tags = {
"name" = "${var.environment}_McD_VPC"
}
depends_on = ["module.aws_provider"]
}
And run terraform graph | dot -Tpng > graph.png against it it looks like the graph doesn't change at all from when the explicit dependency isn't there.
This seems like it might be a potential bug in the graph building stage in Terraform that should probably be raised as an issue but I don't know the core code base well enough to spot where the change needs to be.
For our usage we use symlinks heavily in our Terraform code base, some of which is historic from before Terraform supported other ways of doing things but could work for you here.
We simply define the provider in a single .tf file (such as environment.tf) along with any other generic config needed for every place you would ever run Terraform (ie not at a module level) and then symlink this into each location. That allows us to define the provider in a single place with overridable variables if necessary.
Step 1
Add region alias in the main.tf file where you gonna execute the terraform plan.
provider "aws" {
region = "eu-west-1"
alias = "main"
}
provider "aws" {
region = "us-east-1"
alias = "useast1"
}
Step 2
Add providers block inside your module definition block
module "lambda_edge_rule" {
providers = {
aws = aws.useast1
}
source = "../../../terraform_modules/lambda"
tags = var.tags
}
Step 3
Define "aws" as providers inside your module. ( source = ../../../terraform_modules/lambda")
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 2.7.0"
}
}
}
resource "aws_lambda_function" "lambda" {
function_name = "blablabla"
.
.
.
.
.
.
.
}
Note: Terraform version v1.0.5 as of now.

Resources