Terraform variables not allowed - terraform

I have a simple use case where I am provisioning a key vault but want to define a variable as its only really needed here (local) but I get the error variable not allowed.
variable "secrets" {
type = map(string)
default = {
"price-cosmos-db-primary-key" = azurerm_cosmosdb_account.acc.primary_key
"price-cosmos-db-endpoint" = azurerm_cosmosdb_account.acc.endpoint
}
}
resource "azurerm_key_vault_secret" "keyvaultsecrets" {
count = length(local.secrets)
name = keys(local.secrets)[count.index]
value = values(local.secrets)[count.index]
key_vault_id = azurerm_key_vault.price_keyvault.id
depends_on = [
azurerm_cosmosdb_account.acc
]
}
Is it possible to do the equivalent but using locals?

Use locals, it will work.
locals {
secrets = {
"price-cosmos-db-primary-key" = azurerm_cosmosdb_account.acc.primary_key
"price-cosmos-db-endpoint" = azurerm_cosmosdb_account.acc.endpoint
}
}

Related

How to create Iterative Loop in Terraform

I have around 50 resources that I create in a Terraform script.
I need now to add diagnostic logging for each resource.
The following code is what I use:
data "azurerm_monitor_diagnostic_categories" "vnet-spoke01" {
resource_id = module.MOD-VNET-SPOKE01.id
}
resource "azurerm_monitor_diagnostic_setting" "vnet-spoke01" {
name = "diag-${module.MOD-VNET-SPOKE01.vnetName}"
target_resource_id = module.MOD-VNET-SPOKE01.id
log_analytics_workspace_id = module.MOD-LOG-ANALYTICS-WORKSPACE.id
dynamic "log" {
for_each = data.azurerm_monitor_diagnostic_categories.vnet-spoke01.logs
content {
category = log.value
retention_policy {
days = 0
enabled = false
}
}
}
dynamic "metric" {
for_each = data.azurerm_monitor_diagnostic_categories.vnet-spoke01.metrics
content {
category = metric.value
retention_policy {
days = 0
enabled = false
}
}
}
}
As you can see, I'm adding VNET of spoke-1 diagnostic settings.
Can someone kindly guide me as to how I can add a for-loop so it goes through each resource (that I'd put in an array or list) and run through it?
e.g.
variable "myResources" {
type = list(string)
default = ["module.MOD-VNET-SPOKE01", "module.MOD-VNET-SPOKE02" etc...]
}
for a in myResources
{
.... execute diagnostic routine
}
How could I do this?
Many thanks
Sadly you can't do that. You can't dynamically resolve strings (e.g. "module.MOD-VNET-SPOKE01") into resource identifiers (e.g. module.MOD-VNET-SPOKE01.id.
Your variable would already have to contain all ids for your loop to work:
variable "myResources" {
type = list(string)
default = ["<id-SPOKE01>", "<id-SPOKE02>", etc...]
}
or through locals:
locals {
myResources = [module.MOD-VNET-SPOKE01.id, module.MOD-VNET-SPOKE02.id etc...]
}

Terraform - cloud run multiple environment variables from list

I'm trying to set multiple environment variables on a cloud run module I've created. The example I'm following from Terraform is static. Is it possible to dynamically create these?
template {
spec {
containers {
image = "us-docker.pkg.dev/cloudrun/container/hello"
env {
name = "SOURCE"
value = "remote"
}
env {
name = "TARGET"
value = "home"
}
}
}
}
https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloud_run_service#example-usage---cloud-run-service-multiple-environment-variables
I've tried:
dynamic "env" {
for_each = var.envs
content {
name = each.key
value = each.value
}
}
But I get the following error:
A reference to "each.value" has been used in a context in which it unavailable, such as when the configuration no longer contains the value in
│ its "for_each" expression. Remove this reference to each.value in your configuration to work around this error.
Edit: Full code example
resource "google_cloud_run_service" "default" {
name = "cloudrun-srv"
location = "us-central1"
template {
spec {
containers {
image = "us-docker.pkg.dev/cloudrun/container/hello"
env {
name = "SOURCE"
value = "remote"
}
env {
name = "TARGET"
value = "home"
}
}
}
}
traffic {
percent = 100
latest_revision = true
}
autogenerate_revision_name = true
}
When you use dynamic blocks, you can't use each. It should be:
dynamic "env" {
for_each = var.envs
content {
name = env.key
value = env.value
}
}

Referencing value from module output (object map)

I'm trying to figure out a nifty way to generate randomised secrets in Azure Key vault using Terraform. Where i'm a bit stuck is when i want to use the generated secrets in other modules. The output of the secrets are as object map, and i cannot figure out the right syntax to reference the generated secrets in other modules.
For some specifics:
In the Keyvault module, i'm generating secrets like this, empty strings generates a new secret:
# Generate a random password
resource "random_password" "password" {
for_each = var.secrets
length = 20
min_upper = 2
min_lower = 2
min_numeric = 2
min_special = 2
override_special = "!##$*()-_=+[]{}<>:"
keepers = {
name = each.key
}
}
# Create Azure Key Vault secrets
resource "azurerm_key_vault_secret" "secret" {
for_each = var.secrets
key_vault_id = azurerm_key_vault.key-vault.id
name = each.key
value = lookup(each.value, "value") != "" ? lookup(each.value, "value") : random_password.password[each.key].result
tags = var.tags
depends_on = [
azurerm_key_vault.key-vault,
azurerm_key_vault_access_policy.default_policy,
]
}
The var.secrets is a map defined like this:
variable "secrets" {
type = map(object({
value = string
}))
description = "Define Azure Key Vault secrets"
default = {}
}
An populated like this:
kv-secrets = {
secret-x = {
value = ""
}
secret-y = {
value = ""
}
secret-z = {
value = ""
}
}
The secrets are defined as outputs for the Key vault module like this:
output "key-vault-secrets" {
value = values(azurerm_key_vault_secret.secret).*.value
}
Now let's say i would like to use a generated secret in other modules of my terraform project, a PSQL DB perhaps:
module "psql" {
source = "./modules/psql"
psql_server_name = var.psql_server_name
location = var.location
prefix = var.prefix
environment = var.environment
resource_group_name = azurerm_resource_group.rg.name
administrator_login = var.administrator_login
administrator_login_password = ?module.keyvault.key-vault-secrets?
..
..
}
Does anyone know how to reference the generated secret assigning the value to "administrator_login_password"? Or am i approaching this wrong.
cred: https://gmusumeci.medium.com/how-to-manage-azure-key-vault-with-terraform-943bf7251369
If you want to use module.keyvault.key-vault-secrets["secret-x"].value' syntax, then your output should be:
output "key-vault-secrets" {
value = azurerm_key_vault_secret.secret
}

Terraform: How to set variables in a module based on a conditional?

I would like to pass a variable that will allow me to specify the list of VPC and subnet settings for an AWS instance. There are fixed VPC and subnet settings that make sense so I just want to allow a user to pick one using a single variable, i.e. use A or B.
For instance, let's say I have two available VPCs, and these are specified in a variables.tf file for a module my_instance:
variable "a_vpc_cidr_block" { default = "105.191.44.0/22" }
variable "a_vpc_id" { default = "id_a"}
variable "a_vpc_name" { default = "vpc_a" }
variable "a_subnet_availability_zone" { default = "us-east-1a" }
variable "a_subnet_cidr_block" { default = "105.191.25.0/25" }
variable "a_subnet_name" { default = "instance_A" }
variable "b_vpc_cidr_block" { default = "105.191.45.0/22" }
variable "b_vpc_id" { default = "id_b"}
variable "b_vpc_name" { default = "vpc_b" }
variable "b_subnet_availability_zone" { default = "us-east-1a" }
variable "b_subnet_cidr_block" { default = "105.191.35.0/25" }
variable "b_subnet_name" { default = "instance_B" }
The my_instance module will take a single input variable that an environment will specify, with a value of either 'A' or 'B' (is there a way to limit options for a variable to a list of values such as options=['A', 'B']?), and will be called like so in the terraform.tf for a Terraform configuration with a single instance:
module "my_instance" {
source = "../../modules/my_instance"
option = "A"
}
I want to now implement some logic within the module's main file (modules/my_instance/my_instance.tf) where it decides on which of the two collections of VPC and subnet settings it should use from the ones in modules/my_instance/variables.tf. I want to something like this (pseudocode):
if var.option == 'A'
vpc_cidr_block = var.a_vpc_cidr_block
vpc_id = var.a_vpc_id
vpc_name = var.a_vpc_name
subnet_availability_zone = var.a_subnet_availability_zone
subnet_cidr_block = var.a_subnet_cidr_block
subnet_name = var.a_subnet_name
else if var.option == 'B'
vpc_cidr_block = var.b_vpc_cidr_block
vpc_id = var.b_vpc_id
vpc_name = var.b_vpc_name
subnet_availability_zone = var.b_subnet_availability_zone
subnet_cidr_block = var.b_subnet_cidr_block
subnet_name = var.b_subnet_name
else
raise an error
# get a data resource identified by the VPC variables
data "aws_vpc" "instance_vpc" {
cidr_block = var.vpc_cidr_block
tags = {
Name = var.vpc_name
}
}
# get a data resource identified by the VPC variables
data "aws_subnet" "instance_subnet" {
vpc_id = var.vpc_id
cidr_block = var.subnet_cidr_block
availability_zone = var.subnet_availability_zone
tags = {
Name = var.subnet_name
}
}
# create an AWS key pair resource
resource "aws_key_pair" "instance_aws_key_pair" {
key_name = "component_key_${terraform.workspace}"
public_key = file("~/.ssh/terraform.pub")
}
# create the AWS EC2 instance
resource "aws_instance" "my_aws_instance" {
key_name = aws_key_pair.instance_aws_key_pair.key_name
ami = "ami-b12345"
instance_type = "t2.micro"
subnet_id = data.aws_subnet.instance_subnet.id
connection {
type = "ssh"
user = "terraform"
private_key = file("~/.ssh/terraform")
host = self.public_ip
}
tags = {
"Name" : "my_instance_name"
"Terraform" : "true"
}
}
Is this a matter of somehow using a count, something like this:
count = var.option == 'A'? 1 : 0
Is there a way to do this, or is there a better approach? I am very new to Terraform so I may be missing something obvious.
You have a couple of questions here.
Firstly, you should be able to use the newer, experimental custom validation rules to assert that a value is in a specific list of values.
Secondly, for determining which set of variables to use, I'd recommend going with a good old map in a local value.
For example,
locals {
vpc_info = {
"A" = {
vpc_cidr_block = var.a_vpc_cidr_block
vpc_id = var.a_vpc_id
vpc_name = var.a_vpc_name
subnet_availability_zone = var.a_subnet_availability_zone
subnet_cidr_block = var.a_subnet_cidr_block
subnet_name = var.a_subnet_name
}
"B" = {
vpc_cidr_block = var.b_vpc_cidr_block
vpc_id = var.b_vpc_id
vpc_name = var.b_vpc_name
subnet_availability_zone = var.b_subnet_availability_zone
subnet_cidr_block = var.b_subnet_cidr_block
subnet_name = var.b_subnet_name
}
}
}
Then you should be able to reference a specific field, within the chose option like the following
local.vpc_info[var.option].vpc_name
Let me know if this hits all your questions.

Looping using for or For_each | Terraform 0.12

I need to create multiple subnets in GCP within a network. I am planning to use Terraform 0.12 syntax for the same as follows:
project_name = [
"order-dev",
"ship-dev"
]
variable "project_name" {
type = list(string)
description = "Name of the project"
}
resource "google_compute_subnetwork" "subnetwork" {
name = "${var.project_name}-subnetwork"
ip_cidr_range = var.subnet_ip_cidr_range
region = var.region
network = google_compute_network.network.self_link
}
Is there anyway to use for or for_each expression in this scenario, i am aware of using element and doing this. But want to try a different approach if possible?
variable "project_name" {
type = set(string)
}
resource "google_compute_subnetwork" "subnetwork" {
for_each = var.project_name
name = "${each.key}-subnetwork"
ip_cidr_range = var.subnet_ip_cidr_range
region = var.region
network = google_compute_network.network.self_link
}
Try using the count meta-argument
With your sample, something like this
project_name = [
"order-dev",
"ship-dev"
]
variable "project_name" {
type = list(string)
description = "Name of the project"
}
resource "google_compute_subnetwork" "subnetwork" {
count = length(var.project_name)
name = "${var.project_name[count.index]}-subnetwork"
ip_cidr_range = var.subnet_ip_cidr_range
region = var.region
network = google_compute_network.network.self_link
}
Another option is for_each with key-value pairs, but you only have access to one value and I don't think you can use a list variable like your sample.
resource "google_compute_subnetwork" "subnetwork" {
for_each = {
order = "order-dev"
ship = "ship-dev"
}
name = "${key.value}-subnetwork"
ip_cidr_range = var.subnet_ip_cidr_range
region = var.region
network = google_compute_network.network.self_link
}
Resources:
https://www.terraform.io/docs/configuration/resources.html#count-multiple-resource-instances-by-count
https://blog.gruntwork.io/terraform-tips-tricks-loops-if-statements-and-gotchas-f739bbae55f9
https://www.terraform.io/docs/configuration/resources.html#for_each-multiple-resource-instances-defined-by-a-map-or-set-of-strings

Resources