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
}
Related
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
}
}
I am trying to create multiple resources with different suffix. I have two insight resources which i need in same resource group.
I am using HMAC key to create random_id's.
Although it generates them, i am not able to use them in resource function.
The error that i'm getting:
Because random_id.application_insights_unique_suffix has "count" set, its attributes must be accessed on specific instances.
and here i'm getting an error
${random_id.application_insights_unique_suffix.hex}
locals {
hmac_key = "${var.environmentshort}${var.hmac_key}"
}
resource "random_id" "application_insights_unique_suffix" {
count = "${length(var.application_insights)}"
keepers = {
rand = sha256(local.hmac_key)
}
byte_length = 4
}
resource "azurerm_application_insights" "appi" {
for_each = var.application_insights
name = "${var.productname}-${var.environmentshort}-${var.regionshort}-${random_id.application_insights_unique_suffix.hex}"
location = var.resourcelocation
resource_group_name = var.resourcegpname
application_type = each.value["application_type"]
retention_in_days = each.value["retention_in_days"]
daily_data_cap_in_gb = each.value["daily_data_cap_in_gb"]
tags = merge(var.rgtags,var.ars_tags)
}
would be glad if you could help me find an alternate solution.
Thank you
Since the same variable is used in the random_id resource and the azurerm_application_insights resource, for_each can be used in both cases. That will require a small bit of refactoring:
locals {
hmac_key = "${var.environmentshort}${var.hmac_key}"
}
resource "random_id" "application_insights_unique_suffix" {
for_each = var.application_insights
keepers = {
rand = sha256(local.hmac_key)
}
byte_length = 4
}
resource "azurerm_application_insights" "appi" {
for_each = var.application_insights
name = "${var.productname}-${var.environmentshort}-${var.regionshort}-${random_id.application_insights_unique_suffix[each.key].hex}"
location = var.resourcelocation
resource_group_name = var.resourcegpname
application_type = each.value["application_type"]
retention_in_days = each.value["retention_in_days"]
daily_data_cap_in_gb = each.value["daily_data_cap_in_gb"]
tags = merge(var.rgtags,var.ars_tags)
}
Note that you cannot use count[1] and for_each [2] meta-arguments interchangeably:
Note: A given resource or module block cannot use both count and for_each.
[1] https://www.terraform.io/language/meta-arguments/count
[2] https://www.terraform.io/language/meta-arguments/for_each
How do I do a for_each loop for the following?
I want to create a tfe_variable node_count & vm_size.
I need both these tfe_variables in both wksp1 and wksp2
variable "custom_variables" {
type = map(object({
node_count = number
vm_size = string
}))
default = {
wksp1 = {
node_count = 2
vm_size = "Standard_D2_v3"
},
wksp2 = {
node_count = 5
vm_size = "Standard_D2_v5"
}
}
}
resource "tfe_variable" "custom" {
for_each = {
# for each workspace & variable in var.custom_variables create a tfe_variable
}
key = each.value.name
value = each.value.value
category = "terraform"
workspace_id = each.value.workspace_id
}
You're really close! Here are a couple of things to consider:
Option 1: Multiple tfe_variable resources
Create a tfe_variable resource for each variable you want to create
Make sure the key in you custom_variables map is the workspace ID.
variable "custom_variables" {
type = map(object({
node_count = number
vm_size = string
}))
default = {
wksp1_id = {
node_count = 2
vm_size = "Standard_D2_v3"
},
wksp2_id = {
node_count = 5
vm_size = "Standard_D2_v5"
}
}
}
resource "tfe_variable" "node_count" {
for_each = var.custom_variables
key = "node_count"
value = each.value.node_count
category = "terraform"
workspace_id = each.key
}
resource "tfe_variable" "vm_size" {
for_each = var.custom_variables
key = "vm_size"
value = each.value.vm_size
category = "terraform"
workspace_id = each.key
}
The drawback to this option is that you'll need an additional resource for each variable.
Option 2: A list of variable objects
Define a list of the keys, values, and workspace IDs of each variable
Use count to iterate the list
variable "custom_variables" {
type = list(object({
key = string
value = string
workspace_id = string
}))
default = [
{
key = "node_count"
value = "2"
workspace_id = "wksp1_id"
},
{
key = "node_count"
value = "5"
workspace_id = "wksp2_id"
},
{
key = "vm_size"
value = "Standard_D2_v3"
workspace_id = "wksp1_id"
},
{
key = "vm_size"
value = "Standard_D2_v5"
workspace_id = "wksp2_id"
}
]
}
resource "tfe_variable" "custom" {
count = length(var.custom_variables)
key = var.custom_variables[count.index].key
value = var.custom_variables[count.index].value
workspace_id = var.custom_variables[count.index].workspace_id
category = "terraform"
}
There are a couple of drawbacks to this approach as well:
There is a fair amount of duplicated code in the variable definition
The value must always be of the same type
If you're struggling with loop concepts in Terraform, this blog post might help you.
The main requirement to keep in mind for for_each is that we always need to create a map that has one element for each instance of the resource we want to create. In this case, that means you need a map with one element per workspace per variable, because tfe_variable describes a single variable on a single workspace.
Our job then is to write an expression to project the map-of-objects value coming in via the variable to a collection that has a separate element per variable. Here's one way to get that done, using the flatten function in a way similar to an example in its documentation:
locals {
workspace_variables = flatten([
for ws_name, ws in var.custom_variables : [
for var_name, value in ws : {
workspace = ws_name
variable = var_name
value = value
}
]
])
}
The above should produce a local.workspace_variables that looks like this:
[
{ workspace = "wksp1", variable = "node_count", value = 2 },
{ workspace = "wksp1", variable = "vm_size", value = "Standard_D2_v3" },
{ workspace = "wksp2", variable = "node_count", value = 5 },
{ workspace = "wksp2", variable = "vm_size", value = "Standard_D2_v5" },
]
This now meets the requirement of having one element per desired tfe_variable instance, so our only remaining job is to project this into a map to provide unique identifiers for each element and describe how to populate the tfe_variable arguments based on these objects:
resource "tfe_variable" "custom" {
for_each = {
for wsv in local.workspace_variables : "${wsv.workspace}.${wsv.variable}" => wsv
}
key = each.value.variable
value = each.value.value
category = "terraform"
workspace_id = each.value.workspace
}
One thing I didn't contend with above, because it wasn't directly you question, is the value of workspace_id in tfe_variable. If I recall correctly, that argument is expecting a workspace id rather than a workspace name, in which case you might need a slightly more complicated expression for the workspace_id argument. If you already have a tfe_workspace resource using the workspace names as keys then something like this might work, for example:
workspace_id = tfe_workspace.example[each.value.workspace].id
If your workspaces are created in a different way then you may have to do something more complicated here, but that's getting far off the topic of your original question so I won't try to dig into that here. I'm happy to try to help with it in a separate question on this site though, if you like.
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.
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