how to fetch secret key from multiple keyvaults? terraform - azure

I am trying to figure this one out and struggling to get it right.
So i have 3 keyvaults, 1 for each region, US, Europe, Asia so i am passing this via data blocks
data "azurerm_key_vault" "existing" {
name = "mykeyvault"
resource_group_name = "myrg"
}
data "azurerm_key_vault_secret" "userlist1" {
name = "secret1"
key_vault_id = "${data.azurerm_key_vault.existing.id}"
}
data "azurerm_key_vault_secret" "userlist2"{
name = "secret2"
key_vault_id = "${data.azurerm_key_vault.existing.id}"
}
output "secret_value1" {
value = "${data.azurerm_key_vault_secret.userlist1.value}"
}
output "secret_value2" {
value = "${data.azurerm_key_vault_secret.userlist2.value}"
}
now what am struggling to put together is, if my pipeline is set to run on region = europe, how can i pass the secret value below?
module "testmod" {
source = "./test
password = "${data.azurerm_key_vault_secret.**IFREGIONISEUROPETHENPASSTHISSECRET**.value}"
}

First off, the approach you have planned to use puts your secrets in your statefile, so make sure your statefile is encrypted and well secured.
Because you want to dynamically reference the values, what you want is probably a data structure like an object, rather than a sequence of variables.
Collect your values in an object using locals:
locals {
secret_by_region = {
"asia" = data.azurerm_key_vault_secret.userlist1.value,
"europe" = data.azurerm_key_vault_secret.userlist2.value
}
}
Then reference the value from the local variable:
module "testmod" {
source = "./test
password = local.secret_by_region[var.region]
}

Related

Terraform: referencing each.key or each.value in module when calling variables

I'm trying to achieve (maybe by wrong means) something like that. I'd like to be able to create few types of endpoints in Azure (KV, SA for example).
module "endpoints" {
source = "./modules/private_endpoint"
for_each = toset(var.endpoint_type)
private_connection_resource_id = "var.${each.value}.private_connection_resource_id"
Where:
Endpoint_type is a list of endpoints (its value is "storage_account"),
private_connection_resource_id is in map(any) which looks like (there are other values, but I don't think they're important at this point):
storage_account = {
private_connection_resource_id = #VALUE
...
private_connection_resource_id = "var.${each.value}.private_connection_resource_id" --- this gets translated to literal string (var.storage_account.private_connection_resource_id), where I'd like it to get translated to exact value - the id of storage account (it's hardcoded in tfvars).
Thank you in advance for any tips!
Edit: It appears that Im as dumb as they come. Should've changed the map a bit:
endpoint_type = {
storage_account = {
private_connection_resource_id = #VALUE
...
And ref in module calling to: each.value.private_connection_resource_id
You cannot construct an expression inside a string and then evaluate it. Terraform always parses the full configuration first and then executes it once already parsed.
If you want to look up values dynamically based on a key then a map is the most appropriate data structure to use for that purpose. For example, you could define a input variables endpoint_types and endpoints like this:
variable "endpoint_types" {
type = map(object({
private_connection_resource_id = string
}})
}
variable "endpoints" {
type = map(object({
type = string
}))
}
My intent with the above example is that the type attribute inside the endpoints objects is a lookup key for the other map in endpoint_types.
When you then define your module block with for_each you will first refer to var.endpoints and then look up an appropriate endpoint type in var.endpoint_types based on its selected key:
module "endpoints" {
source = "./modules/private_endpoint"
for_each = var.endpoints
private_connection_resource_id = var.endpoint_types[each.value.type].private_connection_resource_id
}
The user of the outer module would need to provide both a map of endpoints and a map describing all of the possible types those endpoints might have, like this:
endpoints = {
storage_1 = {
type = "storage"
}
storage_2 = {
type = "storage"
}
other_1 = {
type = "other"
}
}
endpoint_types = {
storage = {
private_connection_resource_id = "something"
}
other = {
private_connection_resource_id = "something_else"
}
}

snowflake terraform create multiple table in one tf files

I am trying to create multiple table through tf in snowflake.
Below are the sample code.
resource "snowflake_table" "table" {
database = "AMAYA"
schema = "public"
name = "info"
comment = "A table."
column {
name = "id"
type = "int"
nullable = true
default {
sequence = snowflake_sequence.sequence.fully_qualified_name
}
}
column {
name = "identity"
type = "NUMBER(38,0)"
nullable = true
identity {
start_num = 1
step_num = 3
}
}
resource "snowflake_table" "table" {
database = "AMAYA"
schema = "public"
name = "arch_info"
comment = "A table."
column {
name = "id"
type = "int"
nullable = true
default {
sequence = snowflake_sequence.sequence.fully_qualified_name
}
}
column {
name = "identity"
type = "NUMBER(38,0)"
nullable = true
identity {
start_num = 1
step_num = 3
}
}
}
When I run this script I get the error.
A snowflake_procedure resource named "table" was already declared at str.tf:16,1-38. Resource names must be unique per type in each module.
The only solution I have tried and worked is to create different files for different table. however I have 100 of tables to create, and was wondering if there is simpler way of putting all in one file and run the script
You can't use the same name for a resource more than once, like tablebelow:
resource "snowflake_table" "table" {
Use different names:
resource "snowflake_table" "table_1" {
You should look into for_each and dynamic functions when needing to create lots of the same resource with different parameters:
Terraform for_each
Terraform dynamic
With those, you can create complex maps that are defined on input and automatically create the required amount of resources, something like below (just an example with a couple of parameters):
locals {
snowflake_tables = {
info = {
database = "AMAYA"
...
columns = {
identity = {
type = "NUMBER(38,0)"
nullable = true
...
}
}
}
}
}
resource "snowflake_table" "table" {
for_each = local.snowflake_tables
name = each.key # info
database = each.value.database # AMAYA
...
dynamic "column" {
for_each = each.value.columns
content {
name = setting.key
type = setting.value["type"]
nullable = setting.value["nullable"]
...
}
}
}
With this technique, all you do is add more objects to the map for tables and columns. I've set the example in locals but you could have this as a variable input instead in a .tfvars file etc.

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.

Is there any method for looping over all variables in Terraform?

I'd like to have a resource block that can loop over every variable that I've defined with variable blocks. Is this possible?
For Example: Assuming I set myfirstvar and mysecondvar in a tfvar file. I'm searching for a solution that would take the following template and deploy all the names, values and descriptions to Terraform Cloud.
variable "myfirstvar" {
type = string
description = "a var to upload"
}
variable "mysecondvar" {
type = string
description = "another var to upload"
}
resource "tfe_variable" "test" {
for_each = var
key = currentvar.key
value = currentvar.value
category = "terraform"
workspace_id = tfe_workspace.test.id
description = currentvar.description
}
So far the only solutions I can think of are to put all tfvars in a single list type variable, but that wouldn't have the additional info from the variable blocks. Or I could do some further parsing in another program on the variable blocks and tfvars files gather all the info necessary. Neither is ideal.
The the answer to your direct question is no, because var is not actually an object in Terraform. Instead, it's just a prefix that all variable symbols have. This distinction is important in Terraform because it works by creating a dependency graph based on your references between objects, and there is no node in the dependency graph representing "all variables" and so there's no way for Terraform to represent a reference to it.
With that said, the typical way to achieve a similar result is to write a single variable that has a map type, like this:
variable "tfe_variables" {
type = map(object({
value = string
description = string
}))
}
resource "tfe_variable" "test" {
for_each = var.tfe_variables
key = each.key
value = each.value.value
category = "terraform"
workspace_id = tfe_workspace.test.id
description = each.value.description
}
Then when you define this variable you can set it to a map of objects, like this:
tfe_variables = {
myfirstvar = {
value = "example"
description = "a var to upload"
}
mysecondvar = {
value = "example"
description = "another var to upload"
}
}
It seems like you'd prefer to have the variables of this configuration match the variables defined on the workspace you're managing with it. There's no direct way to do that, because the variable definitions of the current configuration are metadata for outside consumption and not directly accessible by the module itself. The closest you could get to it is to construct the necessary map explicitly, so that Terraform can see all of the necessary dependency edges in order to execute the program:
variable "myfirstvar" {
type = string
}
variable "mysecondvar" {
type = string
}
locals {
variables = tomap({
myfirstvar = {
value = var.myfirstvar
description = "a var to upload"
}
mysecondvar = {
value = var.mysecondvar
description = "another var to upload"
}
})
}
resource "tfe_variable" "test" {
for_each = local.variables
key = each.key
value = each.value.value
category = "terraform"
workspace_id = tfe_workspace.test.id
description = each.value.description
}

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