In Terraform, can i have variables in Data Sources? - terraform

i will like to know if I can use variables while calling Data sources in terraform:
Instead of having:
data.terraform_remote_state.dev_vpc
I want to have a variable call dev like:
data.terraform_remote_state.${var.stage}_vpc
I tried to use the variable as it is above but got the error:
Error: Invalid attribute name
on locals.tf line 21, in locals:
21: for n in data.terraform_remote_state.${var.stage}_vpc :
An attribute name is required after a dot.
Any help will be appreciated.
Thanks in advance.

You can't template an identifier like that, but there are a few other ways you could do it.
Use a variable in the remote state definition:
data "terraform_remote_state" "rs" {
backend = "local"
config = {
path = local.remote_path
}
}
This is a little simpler, and lets you template in your remote config using config files, locals, or variables as you want.
Define multiple remote states, and index:
locals {
remote_paths = { dev = "./dev/terraform.tfstate", prod = "./prod/terraform.tfstate" }
}
data "terraform_remote_state" "rs" {
for_each = local.remote_paths
backend = "local"
config = {
path = each.value
}
}
You can now reference dev and prod at data.terraform_remote_state.rs["dev"] or data.terraform_remote_state.rs["prod"]

You can do something like this:
locals {
stage_map = {
¨variable1 name¨ = data.terraform_remote_state.variable1_value
¨variable2 name¨ = data.terraform_remote_state.variable2_value
}
}
resource "xxxx" "this" {
name = var.scp_name
content = local.stage_map[var.stage]
}

Related

terraform access variable using module variable key

If I have a variables in my terraform module, such as:
variable "environment" {
type = string
}
within my module, I'm using locals to define some items specific to environments:
locals {
dev = {
foo=bar
}
}
Within the module where locals is, how can I use the passed in environment variable to access the corresponding key in locals?
locals.${var.environment}.foo is what I'm going to, where var.environment will evaluate to dev.
Something like this?
local[var.environment]["foo"]
You can't access the locals object directly. So this won't work
local[var.environment]["foo"]
And it will produce this error:
│ The "local" object cannot be accessed directly. Instead, access one of its attributes.
Instead, you can create a local map with your environments as keys:
locals {
a_meaningful_name = {
dev = {
greeting = "Welcome to DEV"
}
uat = {
salutations = "Hello from UAT"
}
}
}
variable "environment" {
type = string
}
output "rs" {
value = local.a_meaningful_name[var.environment]
}

Creating a dynamic secret variable block within Terraform for Cloud Run

I'm trying to create the following block dynamically based on a list of strings
env {
name = "SECRET_ENV_VAR"
value_from {
secret_key_ref {
name = google_secret_manager_secret.secret.secret_id
key = "1"
}
}
}
Based off documentation: https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloud_run_service#example-usage---cloud-run-service-secret-environment-variables
I would like to dynamically add Secrets, and have defined the following dynamic block:
dynamic "env" {
for_each = toset(var.secrets)
content {
name = each.value
value_from {
secret_key_ref {
name = each.value
key = "1"
}
}
}
}
Where secrets is a variable of type list(string)
However, this throws an error: Blocks of type "value_from" are not expected here.
I'm not sure what I'm missing, or where I have incorrectly specified the value_from block.
Could someone point me in the right direction for fixing this up?
UPDATE;
I have also tried to implement this variable as a map, as per the suggestion in the comments on this post. (https://www.terraform.io/docs/language/expressions/dynamic-blocks.html#multi-level-nested-block-structures)
dynamic "env" {
for_each = var.secrets
content {
name = each.key
dynamic "value_from" {
for_each = env.value.name
secret_key_ref {
name = value_from.value.name
key = value_from.value.version
}
}
}
}
However, this also gives the same error. Blocks of type "value_from" are not expected here.
In this example, the secrets variable is defined as a list(any) with this value:
secrets = [
{
name = "SECRET"
version = "1"
}
]
You have to upgrade your gcp provider. Support for secrets in google_cloud_run_service was added in v3.67.0. Current version is v4.1.0, which means that you must be using very old gcp provider.
In the end, I solved this by changing the variable type to a map(any):
secrets = {
"SECRET" = "1"
}
This allowed me to create the "dynamic" env block, without needing to implement the nested dynamic block.

Common variables for multiple modules

Is this the right way to share variables across modules or am I doing it wrong?
In a base module I add a variable:
variable "aws_region" {
description = "Region to use"
default = "eu-west-1"
}
Then I expose it using output:
output "AwsRegion" {
value = var.aws_region
}
Then in the module I want to use it from, I import the base module and refer to it:
module "base" {
source = "../base"
}
provider "aws" {
version = "~> 2.37"
region = module.base.AwsRegion
}
Is there a way to define the variable and export it as output in one go? Does Terraform have the concept of access modifiers? It would be great to be able to do public variable or output variable...
I'm not positive it's the best way, but I've been passing them in at the main.tf file. By doing this I can see the new variables being defined as well as the values that are populating the modules' variables.
module "linuxvm" {
source = "./linuxvm"
vmprefix = var.prefix
resourcegroup = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
vmos = var.os
password = var.vmpassword
subnetid = module.network.subnetid
}

Iterate Through Map of Maps in Terraform 0.12

I need to build a list of templatefile's like this:
templatefile("${path.module}/assets/files_eth0.nmconnection.yaml", {
interface-name = "eth0",
addresses = element(values(var.virtual_machines), count.index),
gateway = element(var.gateway, count.index % length(var.gateway)),
dns = join(";", var.dns_servers),
dns-search = var.domain,
}),
templatefile("${path.module}/assets/files_etc_hostname.yaml", {
hostname = element(keys(var.virtual_machines), count.index),
}),
by iterating over a map of maps like the following:
variable templatefiles {
default = {
"files_eth0.nmconnection.yaml" = {
"interface-name" = "eth0",
"addresses" = "element(values(var.virtual_machines), count.index)",
"gateway" = "element(var.gateway, count.index % length(var.gateway))",
"dns" = "join(";", var.dns_servers)",
"dns-search" = "var.domain",
},
"files_etc_hostname.yaml" = {
"hostname" = "host1"
}
}
}
I've done something similar with a list of files:
file("${path.module}/assets/files_90-disable-console-logs.yaml"),
file("${path.module}/assets/files_90-disable-auto-updates.yaml"),
...but would like to expand this to templatefiles (above).
Here's the code I've done for the list of files:
main.tf
variable files {
default = [
"files_90-disable-auto-updates.yaml",
"files_90-disable-console-logs.yaml",
]
}
output "snippets" {
value = flatten(module.ingition_snippets.files)
}
modules/main.tf
variable files {}
resource "null_resource" "files" {
for_each = toset(var.files)
triggers = {
snippet = file("${path.module}/assets/${each.value}")
}
}
output "files" {
value = [for s in null_resource.files: s.triggers.*.snippet]
}
Appreciate any help!
Both of these use-cases can be met without using any resource blocks at all, because the necessary features are built in to the Terraform language.
Here is a shorter way to write the example with static files:
variable "files" {
type = set(string)
}
output "files" {
value = tomap({
for fn in var.files : fn => file("${path.module}/assets/${fn}")
})
}
The above would produce a map from filenames to file contents, so the calling module can more easily access the individual file contents.
We can adapt that for templatefile like this:
variable "template_files" {
# We can't write down a type constraint for this case
# because each file might have a different set of
# template variables, but our later code will expect
# this to be a mapping type, like the default value
# you shared in your comment, and will fail if not.
type = any
}
output "files" {
value = tomap({
for fn, vars in var.template_files : fn => templatefile("${path.module}/assets/${fn}", vars)
})
}
Again, the result will be a map from filename to the result of rendering the template with the given variables.
If your goal is to build a module for rendering templates from a source directory to publish somewhere, you might find the module hashicorp/dir/template useful. It combines fileset, file, and templatefile in a way that is hopefully convenient for static website publishing and similar use-cases. (At the time I write this the module is transitioning from being in my personal GitHub account to being in the HashiCorp organization, so if you look at it soon after you may see some turbulence as the docs get updated, etc.)

Terraform dynamic variable

I'm creating subnets as part of a seperate terraform template and exporting the IDs as follows.
output "subnet-aza-dev" {
value = "${aws_subnet.subnet-aza-dev.id}"
}
output "subnet-azb-dev" {
value = "${aws_subnet.subnet-azb-dev.id}"
}
output "subnet-aza-test" {
value = "${aws_subnet.subnet-aza-test.id}"
}
output "subnet-azb-test" {
value = "${aws_subnet.subnet-azb-test.id}"
}
...
I'm then intending to lookup these IDs in another template which is reused to provision multiple environments. Example below shows my second template is calling a module to provision an EC2 instance and is passing through the subnet_id.
variable "environment" {
description = "Environment name"
default = "dev"
}
module "sql-1-ec2" {
source = "../modules/ec2winserver_sql"
...
subnet_id = "${data.terraform_remote_state.env-shared.subnet-aza-dev}"
}
What I'd like to do is pass the environment variable as part of the lookup for the subnet_id e.g.
subnet_id = "${data.terraform_remote_state.env-shared.subnet-aza-${var.environment}"
However I'm aware that variable interpolation isn't supported. I've tried using a map inside of the first terraform template to export them all to a 'subnet' which I could then use to lookup from the second template. This didn't work as I was unable to output variables inside of the map.
This sort of design pattern is something I've used previously with CloudFormation, however I'm much newer to terraform. Am I missing something obvious here?
Worked out a way to do this using data sources
variable "environment" {
description = "Environment name"
default = "dev"
}
module "sql-1-ec2" {
source = "../modules/ec2winserver_sql"
...
subnet_id = "${data.aws_subnet.subnet-aza.id}"
}
data "aws_subnet" "subnet-aza" {
filter {
name = "tag:Name"
values = ["${var.product}-${var.environment}-${var.environmentno}-subnet-aza"]
}
}
data "aws_subnet" "subnet-azb" {
filter {
name = "tag:Name"
values = ["${var.product}-${var.environment}-${var.environmentno}-subnet-azb"]
}
}
Whilst this works and fulfils my original need, I'd like to improve on this by moving the data blocks to within the module, so that there's less repetition. Still working on that one though...

Resources