I don't know if it is possible but I would like to take outputs from different modules as needed:
locals {
node_proyect =
[
["node_1", "project_A"],
["node_2", "project_B"],
...
["node_N", "project_N"],
]
}
Working modules:
module "node_1" {
...
}
[...]
module "node_N" {
...
}
trying to do:
module "outputs_sample" {
for_each = {for i,v in local.node_proyect: i=>v}
...
node_name = module.node_[each.value[0]].node_name
proyect_name = each.value[1]
...
}
What I want:
node_name --> module.node_node_1.node_name --> "string with the name with which the node has been created"
project_name --> "project_A"
Next for_each:
node_name --> module.node_node_2.node_name --> "string with the name with which the node has been created"
project_name --> "project_B"
But I get:
Error: Reference to undeclared module
│
│ on ....tf line ..., in module "outputs_sample":
│ 1073: node_name = module.node_[each.value[0]].node_name
│
│ No module call named "module.node_" is declared in module.....
The output of the modules exists and works perfectly, so if I do: module.node_node_1.node_name works.
I don't know how to make Terraform to interpret it like this, instead of literally as the error says: module.node_[each.value[0]].node_name
Given that you stated the modules are declared in the same config directory despite being absent from the config provided in the question, we can first fix the type and structure of the local variable:
locals {
node_proyect = {
"1" = "proyect_A",
"2" = "proyect_B"
}
}
Now we can use this for module iteration and fix the value accessors and string interpolation, and remove the unnecessary list constructor also causing issues:
module "outputs_sample" {
source = "./module"
for_each = local.node_proyect
...
node_name = "module.node_${each.key}.node_name"
proyect_name = each.value
...
}
But this will still causes issues due to a violation of the Terraform DSL. You need to restructure your module declarations (still absent from question so need to be hypothetical here):
module "node" {
...
for_each = local.node_proyect
...
}
and then the outputs can be accessed normally from the resulting map of objects:
module "outputs_sample" {
source = "./module"
for_each = local.node_proyect
...
node_name = module.node[each.key].node_name
proyect_name = each.value
...
}
After all of these fixes to your config you will achieve your goal.
Related
I am using the following resource
resource "aws_cloudwatch_metric_alarm" "example" {
for_each = toset(var.myenvironments_map[var.environment])
...
db_instance_identifier_key = each.key
dimensions = {
DBInstanceIdentifier = db_instance_identifier
}
}
and wanted to do something like the following
locals {
`db_instance_identifier` = var.db_instance_identifier_key == "myKey" ?
"${avariable}" : "${aMap[var.db_instance_identifier_key].identifier}"
}
variable "db_instance_identifier_key" {
type = string
}
but when I run terraform plan I get: "The root module input variable "db_instance_identifier_key" is not set, and
│ has no default value. Use a -var or -var-file command line argument to
│ provide a value for this variable."
Basically I want to compute db_instance_identifier based on each.key from the for_each.
This is a local variable declaration:
locals {
`db_instance_identifier` = var.db_instance_identifier_key == "myKey" ?
"${avariable}" : "${aMap[var.db_instance_identifier_key].identifier}"
}
This is a module input variable declaration:
variable "db_instance_identifier_key" {
type = string
}
You need to use one or the other, not both.
Is it possible to reference another locals value inside the creation of a locals value?
The example below was the smallest and simplest example I could come up with.
variable "size" {
default = 3
}
variable "infrastructure_version" {
default = 1
}
locals {
values = {
for n in range(var.size) : n => {
name = "instance_${n + 1}"
full_name = "test_${name}_v${var.infrastructure_version}"
}
}
}
When trying to access name within the for loop inside the locals block i get the following error:
│ Error: Invalid reference
│
│ on instances.tf line 13, in locals:
│ 13: full_name = "test_${name}_v${var.infrastructure_version}"
│
│ A reference to a resource type must be followed by at least one attribute access, specifying the resource name.
Other attempts:
(These were desperate attempts with no real likelihood of succeeding)
local.values[n].name which gives Error: Self-referencing local value
n.name which gives Error: Unsupported attribute
self.name which gives Error: Invalid "self" reference
Anyone know if this is possible? Or am I stuck repeating the creation of name inside full_name as well?
full_name = "test_instance_${n + 1}_v${var.infrastructure_version}"
Instead of doing variable interpolation every time a value should be used, it's possible to create a local module that acts as a function.
Where you can use local variables and reuse previously created variables.
Short example below, it's better suited when used for more complex and larger applications due to the amount of overhead.
main.tf:
From the main module I import the local module that will serve as a function
module "instance_conf" {
source = "./modules"
count = var.size
index = count.index
infra = var.infrastructure_version
}
locals {
values = {for idx, val in module.instance_conf: idx => val}
}
I send in index and infra to the module as input. The variable definitions in the other module must be matching those, here you could also provide descriptions if needed.
modules/func.tf:
variable "index" {
type = number
}
variable "infra" {
type = number
}
locals {
name = "instance_${var.index + 1}"
}
output "name" {
value = local.name
}
output "full_name" {
value = "test_${local.name}_v${var.infra}"
}
To get the desired output to the main module, calculate values either in the locals block or directly in the output block. When importing this module the values will be available as a list of maps, for each count.index in the main module. count = var.size
The list could look like this:
[
{
name: "instance_1",
full_name: "test_instance_1_v1"
},
{
name: "instance_2",
full_name: "test_instance_2_v1"
},
...
]
So in order to use the module output as previous with for_each I converted the list of map objects, to a map with the index of each map object as the key for that object.
locals {
values = {for idx, val in module.instance_conf: idx => val}
}
And now when using local.values it will look like this:
{
"1": {
name: "instance_1",
full_name: "test_instance_1_v1"
},
"2": {
name: "instance_2",
full_name: "test_instance_2_v1"
},
...
}
The project structure now looks like this:
.
├── main.tf
├── modules
│ └── values_function.tf
Hopefully this helps someone else out. When variable interpolation and recreation of a value every time it is used, is not an acceptable answer. Mainly because of the maintainability factor.
Your last attempt is correct. You can't make it different and it works:
full_name = "test_instance_${n + 1}_v${var.infrastructure_version}"
I create a cloud run service like so
terraform {
required_version = ">= 1.1.2"
required_providers {
google = {
source = "hashicorp/google"
version = "~> 4.1.0"
}
google-beta = {
source = "hashicorp/google-beta"
version = "~> 4.2.0"
}
}
}
provider "google" {
project = "main_project"
region = "us-central-1"
credentials = "<my-key-path>"
}
resource "google_cloud_run_service" "default" {
name = "cloudrun-srv"
location = "us-central1"
template {
spec {
containers {
image = "us-docker.pkg.dev/cloudrun/container/hello"
}
}
}
traffic {
percent = 100
latest_revision = true
}
}
I want to save the value of the service url that is created - https://default-hml2qtrgfq-uw.a.run.app using an output variable. something like
output "cloud_run_instance_url" {
value = google_cloud_run_service.default.url
}
This gives me an error:
terraform plan
╷
│ Error: Unsupported attribute
│
│ on main.tf line 40, in output "cloud_run_instance_url":
│ 40: value = google_cloud_run_service.default.url
│
│ This object has no argument, nested block, or exported attribute named "url".
╵
How do I get this output value and assign it to a variable so that other services like cloud scheduler can point to it?
If you declare an output for the url resource attribute like:
output "cloud_run_instance_url" {
value = google_cloud_run_service.default.status.0.url
}
then it will be available for resolution (such as for inputs to other modules) at the scope where the module is declared in the namespace module.<declared module name>.cloud_run_instance_url. For example, if this module is declared in the root module config, then it can be resolved at that namespace elsewhere in the root module config.
Terraform Version
Terraform v1.1.2
on windows_amd64
Terraform Configuration Files
child_module1.tf(C1):
# Create Resource Group
resource "aws_resourcegroups_group" "resourcegroups_group" {
name = "test"
resource_query {
query = <<JSON
{
"ResourceTypeFilters": [
"AWS::EC2::Instance"
],
"TagFilters": [
{
"Key": "project",
"Values": ["${var.ProjectName}"]
}
]
}
JSON
}
}
child_module1_variables.tf:
########
variable "ProjectName" {
type = string
description = "This name would be prefixed with the cluster names."
}
Now call this child module in another child module**(C2)**:
child_module2.tf:
module "prepare_aws_cloud" {
source = "./modules/aws/prepare_cloud_copy"
ProjectName = "${var.test.ProjectName}"
}
child_module2_variables.tf:
variable "test" {
type = object({
ProjectName = string
})
}
Now I create a root module(R1)** which calls the child_module2.tf:**
terraform {
backend "local" {
}
}
module "test_deploy" {
source = "D:\\REPO\\installer_v2.2.22.1\\installer\\aws"
test = {
#ProjectName = ""
}
}
So the dependency is as follows:
R1 calls >> C2 calls >> C1
ERROR
PS D:\tkgTest> terraform apply -input=true
╷
│ Error: Invalid value for module argument
│
│ on testing.tf line 21, in module "test_deploy":
│ 21: test= {
│ 22: #ProjectName = ""
│ 23: }
│
│ The given value is not suitable for child module variable "test" defined at .terraform\modules\test_deploy\variables.tf:108,1-15: attribute "ProjectName" is required.
Expected Behavior
I would have expected that the user input would be taken interactively by the console as I am passing the -input=true flag but it doesn't seem to work.
The interactive prompts for input variables are intended only to help with getting started with Terraform (e.g. following a simple tutorial) and so they are limited in the scope of what they support. The typical way to set root module input variables for routine use is to either create a .tfvars file and pass it to Terraform with -var-file or to set a variable directly using -var.
Note also that only root module input variables can be set directly as part of the planning options. Any child module variables are defined exclusively by the expressions written in the module block, and so if you want to be able to set a child module's input variable on a per-run basis you'll need to also declare it as a root module variable and then pass it through to the child module.
For example, in the root module:
variable "test" {
type = object({
ProjectName = string
})
}
module "test_deploy" {
source = "./installer/aws"
test = var.test
}
You can then create an example.tfvars file with the following to set a value for the variable:
test = {
ProjectName = "example"
}
Specify that file when you run Terraform:
terraform apply -var-file=example.tfvars
If you will always set the same values then you can avoid the need for the extra option by naming your file example.auto.tfvars and placing it in the same directory where you will run Terraform. Terraform will load .auto.tfvars files automatically without any explicit -var-file option.
I am writing terraform script to create ASG on AWS. I tried to create it with terraform module to have a more reusable code. The problem is when I want to use variable from common-variable.tfvars on the module tf files, it keeps saying it is undefined and need to be declared. This way, the module will be less reuseable .
Here is an example
root
|
|___project 1
| |_____ main.tf
| |_____ common-variable.tfvars
|
|___ modules
|
|_____ a-module
|______ main.tf
So inside project 1 common-variable.tfvars, basically it looks like this
variable "a" {
description = "a variable"
default = "a"
}
variable "b" {
description = "a variable"
default = "b"
}
Inside a-module / main.tf looks like this
variable "name" {}
resource "aws_autoscaling_group" "asg-1" {
name = "${var.a}"
...
}
When I do terraform init, it says
resource 'aws_autoscaling_group.asg-1' config: unknown variable
referenced: 'a'. define it with 'variable' blocks
Any idea how i can use this common variable from within the module main .tf?
Update
I managed to pass terraform init by redeclaring the variable in each module. However, when i run terraform plan, this kind of error appears invalid value "common-variable.tfvars" for flag -var-file: multiple map declarations not supported for variables
Wrong tfvars format, should be key / value only, such as:
a = "a"
b = "b"
Second, check how do you refer the module, should be something like this:
source = "../modules/a-module"
You need to declare the variables required by the module within the module, and then pass them in when instantiating the module from your project.
Example stolen from hashicorp documentation
In your project:
module "assets_bucket" {
source = "./publish_bucket"
name = "assets"
}
module "media_bucket" {
source = "./publish_bucket"
name = "media"
}
In your module:
# publish_bucket/bucket-and-cloudfront.tf
variable "name" {} # this is the input parameter of the module
resource "aws_s3_bucket" "the_bucket" {
# ...
}
resource "aws_iam_user" "deploy_user" {
# ...
}