How can I use a variable as an attribute name in terraform 3.0? - terraform

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.

Related

Terraform variable to assign using function

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.

Pass complex, non-primitive data types to Terraform template provider

Having a more complex list object like this
variable "proxy" {
type = list(object({
enabled = bool
host = string
port = number
user = string
password = string
}))
default = [
{
enabled = false
host = ""
port = 0
user = ""
password = ""
}
]
}
I want to use this in a external template (cloudinit in my case). The template_file directive allows passing variables to a template. Sadly, not for more complex types:
Note that variables must all be primitives. Direct references to lists or maps will cause a validation error.
So something like this
data "template_file" "cloudinit_data" {
template = file("cloudinit.cfg")
vars = {
proxy = var.proxy
}
}
cause the error
Inappropriate value for attribute "vars": element "proxy": string required.
This leads me to two questions:
How can I pass the variable to the template? I assume that I need to convert it to a primitive type like this:
vars = {
proxy_host = var.proxy.host
}
This doesn't work:
This value does not have any attributes.
Is there an alternative way to pass this object directly to the template?
I'm using v0.12.17.
The template_file data source continues to exist only for compatibility with configurations written for Terraform 0.11. Since you are using Terraform 0.12, you should use the templatefile function instead, which is a built-in part of the language and supports all value types.
Because templatefile is a function, you can call it from anywhere expressions are expected. If you want to use the rendered result multiple times then you could define it as a named local value, for example:
locals {
cloudinit_data = templatefile("${path.module}/cloudinit.cfg", {
proxy = var.proxy
})
}
If you only need this result once -- for example, if you're using it just to populate the user_data of a single aws_instance resource -- then you can just write this expression inline in the resource block, to keep everything together and make the configuration (subjectively) easier to read:
resource "aws_instance" "example" {
# ...
user_data = templatefile("${path.module}/cloudinit.cfg", {
proxy = var.proxy
})
}

How to define global variables in terraform?

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
}

Terraform: can module's name be resolved dynmically?

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.

Access parameter from a resource defined using create_resources

I would like to know if it is possible to access a parameter from a class being instantiated using the create_resources function. I want to use that parameter in other class to conditionally install some things or not.
This is my scenario:
define myResource($myParam) { ... }
create_resources(myResource, $hashResources)
$hashResources = { "MyResource1" : { "myParam" : 1 },
"MyResource2" : { "myParam" : 2 }
}
myFancyPuppetClass($nameOfResource) {
if(******myParam from the resource defined with name $nameOfResource in the same catalog******) { ... }
}
Is this possible? If it is, how can I do the reference? Thank you!
Since the resources you are attempting to create are defined types, and the parameters in a defined resource are not accessible, this is not possible in the latest version of Puppet.
See a previous answer of mine regarding accessing parameters in defined resources for an alternative.

Resources