Terraform output defined are empty [duplicate] - terraform

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}"
}

Related

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
}

Terraform: How can I have one common output for all declared modules?

I have got something like this in my terraform file:
main.tf
module "airflow_tenant_one" {
source = "../modules/airflow_tenant"
name = "one-airflow"
project = var.project
cluster_name = var.cluster_name
region = var.region
kubernetes_endpoint = var.kubernetes_endpoint
tenant_domain = "one-airflow.${var.domain_name}"
}
module "airflow_tenant_two" {
source = "../modules/airflow_tenant"
name = "two-airflow"
project = var.project
cluster_name = var.cluster_name
region = var.region
kubernetes_endpoint = var.kubernetes_endpoint
tenant_domain = "two-airflow.${var.domain_name}"
}
How can I do one common output for all declared modules?
Currently, it looks like:
outputs.tf
output "chart_name_one" {
description = "The name of the chart"
value = module.airflow_tenant_one.chart_name
}
output "chart_name_two" {
description = "The name of the chart"
value = module.airflow_tenant_two.chart_name
}
I asked because in a future it is possible that I will add more modules in my main.tf file. Will be better to have one output declaration for all of them.
The module blocks you shared seem like they are all systematically configured based on some client names, so you can potentially do this using a single module block with for_each if you are using Terraform 0.13.0 or later.
locals {
tenants = toset([
"one",
"two",
])
}
module "airflow_tenant" {
for_each = local.tenants
name = "${each.key}-airflow"
project = var.project
cluster_name = var.cluster_name
region = var.region
kubernetes_endpoint = var.kubernetes_endpoint
tenant_domain = "${each.key}-airflow.${var.domain_name}"
}
output "tenant_chart_names" {
value = {
for name, tenant in module.airflow_tenant : name => tenant.chart_name
}
}
The above will cause there to be one instance of the airflow_tenant module per element of local.tenants, with addresses like this:
module.airflow_tenant["one"]
module.airflow_tenant["two"]
The for_each makes the module behave as a map of instances when you refer to it elsewhere, which is why we're able to project that map in output "tenant_chart_names" to derive a map from tenant name to chart names.
You can add and remove elements of local.tenants over time, in which case Terraform will understand that as an intent to either create a instances or destroy an instances of all of the objects described inside that module.
You can read more about this feature in Multiple Instances of a Module.

Pass terraform output from one file to another

I have following structure:
modules
|_ test1
| |_vpc.tf
|_test2
|_subnet.tf
I have created a vpc in test1/vpc.tf
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
}
I am getting vpc id in output like:
output "vpc_id" {
value = aws_vpc.main.id
}
How can I pass this id to test2/subnet.tf file? I am searching online and can't seem to find an answer for this.
Create a variable in subnet.tf:
variable "vpc_id" {
type = string
}
Then in your main terraform file where you are utilizing both of these modules, you would take the output from the vpc module and pass it to the input of the subnet module:
module "vpc" {
source = "modules/test1"
}
module "subnet" {
source = "modules/test2"
vpc_id = module.vpc.vpc_id
}

How to use remote state data sources within child modules

I am trying to call data from a remote state to reference a vpc_id for a network acl. When I run plan/apply, I receive the error "This object has no argument, nested block, or exported attribute named "vpc_id"."
I've tried using "data.terraform_remote_state.*.vpc_id", as well as "${}" syntax. I tried defining the data.remote info in the variables.tf for the child module, and the parent module.
I ultimately need to be able to call this module for different VPCs/subnets dynamically.
The relevant VPC already exists and all modules are initialized.
s3 bucket stage/network/vpc/terraform.tfstate:
"outputs": {
"vpc_id": {
"value": "vpc-1234567890",
"type": "string"
}
},
enter code here
modules/network/acl/main.tf:
data "terraform_remote_state" "stage-network" {
backend = "s3"
config = {
bucket = "bucket"
key = "stage/network/vpc/terraform.tfstate"
}
}
resource "aws_network_acl" "main" {
vpc_id = data.terraform_remote_state.stage-network.vpc_id
# acl variables here
stage/network/acl/main.tf:
data "terraform_remote_state" "stage-network" {
backend = "s3"
config = {
bucket = "bucket"
key = "stage/network/vpc/terraform.tfstate"
}
}
module "create_acl" {
source = "../../../modules/network/acl/"
vpc_id = var.vpc_id
# vpc_id = data.terraform_remote_state.stage-network.vpc_id
# vpc_id = "${data.terraform_remote_state.stage-network.vpc_id}"
# vpc_id = var.data.terraform_remote_state.stage-network.vpc_id
I am expecting the acl parent module to be able to associate to the VPC, and from there the child module to be able to configure the variables.
This is one of the breaking changes that the 0.12.X version of Terraform introduce.
The terraform_remote_state data source has changed slightly for the v0.12 release to make all of the remote state outputs available as a single map value, rather than as top-level attributes as in previous releases.
In previous releases, a reference to a vpc_id output exported by the remote state data source might have looked like this:
data.terraform_remote_state.vpc.vpc_id
This value must now be accessed via the new outputs attribute:
data.terraform_remote_state.vpc.outputs.vpc_id
Source: https://www.terraform.io/upgrade-guides/0-12.html#remote-state-references
In first state:
.....
output "expose_vpc_id" {
value = "${module.network.vpc_id}"
}
In another state, to share between terraform configs:
data "terraform_remote_state" "remote" {
backend = "s3"
config = {
bucket = "terraform-ex1"
key = "tera-ex1.tfstate"
region = "us-east-1"
}
}
output "vpc_id" {
value = "${data.terraform_remote_state.remote.outputs.expose_vpc_id}"
}

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