Given a series of modules whose names follow a pattern:
module "mod-a" {
// ...
}
module "mod-b" {
// ...
}
module "mod-b" {
// ...
}
and assuming each module defines an output called my_output, can I refer to a particular module on the basis of a dynamically resolved name?
e.g.
...
// some_module = "mod-a"
some_value = module[some_module].my_output
...
The syntax above gives the error:
The "module" object cannot be accessed directly. Instead, access one of its
attributes.
Is there another way to access a module whose name is only known at runtime?
To achieve this in Terraform today (Terraform 0.12.13), you'll need to construct a suitable map explicitly as a local value, and then index that map:
locals {
modules = {
mod_a = module.mod_a
mod_b = module.mod_b
mod_c = module.mod_c
}
}
Elsewhere in the configuration you can then use an expression like local.modules[local.dynamic_module_key], selecting the desired object from the map.
Terraform requires static references to objects like this so that it can properly construct a dependency graph. In this case, Terraform infers that local.modules depends on all of the outputs from all three of these modules, and thus anything that refers to local.modules must wait until all of the outputs from all of these modules are ready to ensure that the final index operation has a complete value to work with.
Related
I'm creating a terraform module which is working fine. However, when I use it multiple times it creates multiples roles and policies which are literally the same.
I'm thinking if there is a way for the module to create a role when I call it for the first time and keep using the same role for the subsequent modules
No, Terraform does not support this. Your best bet is to create the shared resources outside the module (or in a separate module), and then pass them in as input arguments into the module you're creating multiple times.
I like the approach of having a module for "shared" resources, because then you can pass that entire module in as an input argument into any module that uses those shared resources.
EDIT: Sample code for shared modules:
main.tf
module "mod1" {
source = "./mymodule1"
}
module "mod2" {
source = "./mymodule2"
input_module = module.mod1
}
output "mod2" {
value = module.mod2
}
mymodule1/main.tf
output "some_field" {
value = "foo"
}
mymodule2/main.tf
variable "input_module" {}
output "module_that_was_input" {
value = var.input_module
}
Result:
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
Outputs:
mod2 = {
"module_that_was_input" = {
"some_field" = "foo"
}
}
Is it possible to somehow create an arbitrary attribute from a variable? Here is what I am trying to achieve.
How I currently do it (now deprecated in 3.0.0):
resource "aws_lb_listener_rule" "example" {
condition {
field = var.condition_field
values = var.condition_values
}
}
The new syntax requires a nested block with the condition field. But my condition is stored in a variable:
resource "aws_lb_listener_rule" "example" {
condition {
var.condition_field {
values = var.condition_values
}
}
}
Is it possible to somehow create an arbitrary attribute from a variable?
or: Can I store a nested attribute block in a variable?
Background on my question: I am currently trying to upgrade from 2.70.0 to 3.0.0 and there are quite a few breaking changes in my system. One of them includes the aws_lb_listener_rule. If it is not possible to create the attribute from the variable I would have to either pin the version or change the module API used by a ton of projects.
It actually seems like it is not possible to do that. The closes thing I have found that allows me to use 3.0.0 without changing my module variables and with that all the Terraform scripts that use it are dynamic conditional blocks.
dynamic "condition" {
for_each = var.field == "path-pattern" ? [var.field] : []
content {
path_pattern {
values = var.patterns
}
}
}
This is repeated for all possible var.field values.
variable "cidr" {
type = map(string)
default = {
development = "x.1.0.0/16"
qa = "x.1.0.0/16"
default = "x.1.0.0/16"
}
}
variable "network_address_space" {
default = lookup(var.cidr, var.environment_name,"default")
}
Am getting error that "Error: Function calls not allowed"
variable "subnet_address_space": cidr_subnet2_address_space = cidrsubnet(var.network_address_space,8,1)
A Terraform Input Variable is analogous to a function argument in a general-purpose programming language: its value comes from an expression in the calling module, not from the current module.
The default mechanism allows us to substitute a value for when the caller doesn't specify one, but because variables are intended for getting data into a module from the outside, it doesn't make sense to set the default to something from inside that module: that would cause the result to potentially be something the caller of the module could never actually specify, because they don't have access to the necessary data.
Terraform has another concept Local Values which are roughly analogous to a local variable within a function in a general-purpose programming language. These can draw from function results and other objects in the current module to produce their value, and so we can use input variables and local values together to provide fallback behaviors like you've illustrated in your question:
var "environment_name" {
type = string
}
var "environment_default_cidr_blocks" {
type = map(string)
default = {
development = "10.1.0.0/16"
qa = "10.2.0.0/16"
}
}
var "override_network_range" {
type = string
default = null # If not set by caller, will be null
}
locals {
subnet_cidr_block = (
var.override_network_range != null ?
var.override_network_range :
var.environment_default_cidr_blocks[var.environment_name]
)
}
Elsewhere in the module you can use local.subnet_cidr_block to refer to the final CIDR block selection, regardless of whether it was set explicitly by the caller or by lookup into the table of defaults.
When a module uses computation to make a decision like this, it is sometimes useful for the module to export its result as an Output Value so that the calling module can make use of it too, similar to how Terraform resources also export additional attributes recording decisions made by the provider or by the remote API:
output "subnet_cidr_block" {
value = local.subnet_cidr_block
}
As stated in Interpolate variables inside .tfvars to define another variable by the Hashicorp person, it is intended to be constant by design.
Input variables are constant values passed into the root module, and so they cannot contain interpolations or other expressions that do not yield a constant value.
We cannot use variables in backend either as in Using variables in terraform backend config block.
These are the things we Terraform users tripped on at some point, I suppose.
I have a terraform project I am working on. In it, I want a file to contain many variables. I want these variables to be accessible from any module of the project. I have looked in the docs and on a udemy course but still don't see how to do this. How does one do this in terraform? Thanks!
I don't think this is possible. There are several discussions about this at Github, but this is not something the Hashicorp team wants.
In general we're against the particular solution of Global Variables, since it makes the input -> resources -> output flow of Modules less explicit, and explicitness is a core design goal.
I know, we have to repeat a lot of variables between different modules
I try to do this
Define a the set of common variables e.g. common_tags at the bottom/top of the variables.tf file for all modules. Usually, your tech ops admin/cloud admin will have a template project created for this.
For each module, I add the following as the last item
global_var = var.global_var_val
An example is common tags. In any root/child module I combine them using merge() function. I hope that makes sense.
You can use -var-file to pass the overrides. Example:
terraform -chdir=staging/000base apply -var-file ../../global.tfvars
Other way can be dedicated shared module.
For example you have module with:
output "my_var" {
value = "apollo440"
}
And in other modules you can use it like:
module "gvars" {
# source = "git#github.com:user/terraform-global-vars.git?ref=v0.0.1-alpha"
source="../../../modules/global_vars"
}
provider "something" {
name="${module.gvars.my_var}"
}
Third way could be to use https://registry.terraform.io/providers/hashicorp/http/latest/docs/data-sources/http to query some http endpoint.
Ouh... and forth way could be to utilize Vault Provider https://registry.terraform.io/providers/hashicorp/vault/latest/docs
For pathing constants between lots of modules we can use the following simple way.
/modules/global_constants/outputs.tf:
Describe module with global constants as outputs:
output "parameter_1" {
value = "value_1"
sensitive = true
}
output "parameter_2" {
value = "value_2"
sensitive = true
}
/example_1.tf
After we can use in any *.tf
module "global_settings" {
source = "./modules/global_constants"
}
data "azurerm_key_vault" "keyvault" {
name = module.global_settings.parameter_1
resource_group_name = module.global_settings.parameter_2
}
/modules/module2/main.tf
Or in other any modules:
module "global_settings" {
source = "../global_constants"
}
data "azurerm_key_vault" "keyvault" {
name = module.global_settings.parameter_1
resource_group_name = module.global_settings.parameter_2
}
I have inherited a project with two files:
a.tf
dir_name/b.tf
and each contains:
variable "region" {
default = "us-east-1"
}
Is there any reason why I can't delete the variable definition from dir_name/b.tf as it seems to be already defined?
UPDATE
a.tf contains a module definition that goes like this:
module "dir_name" {
source = "./dir_name"
}
No, you can't remove one or the other. Terraform works on the module level, where each module has an explicit set of input variables and output attributes; variables can't be passed implicitly from one script to a module.
If you're passing variables from one place to another, it does currently result in a lot of repetition, eg:
module "dir_name" {
source = "./dir_name"
region = "${var.region}"
}