I would like to convert a simple list of string in terraform to a map with the keys as indexes.
I want to go from something like this:
locals {
keycloak_secret = [
"account-console",
"admin-cli",
"broker",
"internal",
"realm-management",
"security-admin-console",
]
}
To something like
map({0:"account-console", 1:"admin-cli"}, ...)
My goal is to take advantage of the new functionality of terraform 0.13 to use loop over map on terraform module.
I didn't find any solution, may something help me, thank you.
If I understand correctly, you want to convert your list into map. If so, then you can do this as follows:
locals {
keycloak_secret_map = {for idx, val in local.keycloak_secret: idx => val}
}
which produces:
{
"0" = "account-console"
"1" = "admin-cli"
"2" = "broker"
"3" = "internal"
"4" = "realm-management"
"5" = "security-admin-console"
}
I've come up with another solution, which is uglier than #Marcin 's answer.
locals = {
keycloak_secret_map = for secret_name in local.keycloak_secret : index(local.keycloak_secret, secret_name) => secret_name
}
Which gives
{
0 = "account-console"
1 = "admin-cli"
2 = "broker"
3 = "internal"
4 = "realm-management"
5 = "security-admin-console"
}
Related
I am having child module for Windows virtual machine.
Then I have root module (main.tf file), where I am using that child module
module "vm-win-resource" {
source = "./Modules/ServerWindows"
count = 2
vm-name = "vm-win-${random_string.rnd.result}" #OR "vm-win-${module.rnd-num.rnd-result}"
vm-rg = module.rg-resouce.rg-name
vm-location = module.rg-resouce.rg-location
nic-name = "vm-win-${random_string.rnd.result}-nic1" #OR "vm-win-${module.rnd-num.rnd-result}-nic1"
nic-rg = module.rg-resouce.rg-name
nic-location = module.rg-resouce.rg-location
nic-ip-subnet = "HERE IS SUBNET ID"
}
In same main.tf file, if I use random_string provider directly
resource "random_string" "rnd" {
length = 4
min_numeric = 4
special = false
lower = true
}
or if I create module, for random number and use it in module for virtual machine, result is same.
module "rnd-num" {
source = "./Modules/RandomNumber"
}
I get same name (generated number for both)
+ vm-win-name = [
+ [
+ "vm-win-6286",
+ "vm-win-6286",
],
]
So in both cases, value is generated only once.
Question is how can I generate random number for every loop in module for virtual machine?
Thank you for any help!
UPDATE
As workaround, I have placed provider to generate random number into virtual machine resource/module specification
resource "azurerm_windows_virtual_machine" "vm-resource" {
name = "${var.vm-name}-${random_string.rnd.result}"
resource_group_name = var.vm-rg
location = var.vm-location
size = var.vm-size
admin_username = var.vm-admin
admin_password = var.vm-adminpwd
network_interface_ids = [
azurerm_network_interface.nic-resource.id,
]
os_disk {
caching = "ReadWrite"
storage_account_type = var.vm-os-disk-type
}
source_image_reference {
publisher = var.vm-os-image.publisher
offer = var.vm-os-image.offer
sku = var.vm-os-image.sku
version = var.vm-os-image.version
}
tags = var.resource-tags
}
resource "random_string" "rnd" {
length = 4
min_numeric = 4
special = false
lower = true
}
it does the job but I would prefer to use it in main.tf file and not directly in resource/module specification, if it is possible.
A few words about how Terraform random_string works:
random_string generates a random string from specific characters. This string is generated once. Referencing its result attribute in multiple places will provide you the same output. Using it as random_string.rnd.result will not act as a function call, this means that it will provide the same value in every place.
The result value of a random_string will not change after consecutive applies. This is obvious, if we think about it. If it would change, the usage of random_string would be dangerous, since it would result in re-provisioning the resources which are referencing it.
If we want to have multiple different random strings, we have to define multiple random_string resources. For example:
resource "random_string" "rnd" {
count = 2
length = 4
min_numeric = 4
special = false
lower = true
}
module "vm-win-resource" {
source = "./Modules/ServerWindows"
count = 2
vm-name = "vm-win-${random_string.rnd[count.index].result}"
vm-rg = module.rg-resouce.rg-name
vm-location = module.rg-resouce.rg-location
nic-name = "vm-win-${random_string.rnd[count.index].result}-nic1"
nic-rg = module.rg-resouce.rg-name
nic-location = module.rg-resouce.rg-location
nic-ip-subnet = "HERE IS SUBNET ID"
}
Please note, we are using a count for the random_string resource as well.
I have a code snippet that sets the node pool name for a GKE cluster, it is very much unreadable. I would appreciate any help in making it more presentable and easy to understand what is happening.
output "test" {
value = regex("(?:[a-z](?:[-a-z0-9]{0,38}[a-z0-9])?)", lower(var.node_pool_version != "" ? var.node_pool_name != "" ? "${var.node_pool_name}-v${replace("${var.node_pool_version}",".","-")}" : "${var.name_prefix}-v${replace("${var.node_pool_version}",".","-")}" : var.node_pool_name != "" ? var.node_pool_name : "${var.name_prefix}-standard"))
}
variable "node_pool_version" {
description = "Override node_version for cluster upgrades"
type = string
default = ""
}
variable "node_pool_name" {
type = string
default = ""
}
variable "name_prefix" {
type = string
default = "develop"
}
Outputs:
❯ terraform apply
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
Outputs:
test = develop-standard
❯ terraform apply -var node_pool_version=1.16.15-gke.7800
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
Outputs:
test = develop-v1-16-15-gke-7800
❯ terraform apply -var node_pool_version=1.16.15-gke.7800 -var node_pool_name=develop-standard
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
Outputs:
test = develop-standard-v1-16-15-gke-7800
Edit:
My concern is with the extensive usage of String Functions & Conditional Expressions to generate the output. My use-case is if the user doesn't pass either node_pool_version or node_pool_name, my output should still manage to generate a node-name that can be easily identifiable. Is there a better way to re-write this code and make it more readable for anyone new to Terraform?
I suppose the best you could do is use a locals block:
variable "node_pool_version" {
description = "Override node_version for cluster upgrades"
type = string
default = ""
}
variable "node_pool_name" {
type = string
default = ""
}
variable "name_prefix" {
type = string
default = "develop"
}
locals {
version = replace("${var.node_pool_version}", ".", "-")
prefix = var.node_pool_name != "" ? var.node_pool_name : var.name_prefix
id = var.node_pool_name != "" ? local.prefix : "${local.prefix}-standard"
versioned_id = local.version != "" ? "${local.prefix}-v${local.version}" : local.id
clean_versioned_id = regex("(?:[a-z](?:[-a-z0-9]{0,38}[a-z0-9])?)", lower(local.versioned_id))
}
output "test" {
value = local.clean_versioned_id
}
Trying to put something together to get passed a limitation of the tfe plugin.
I have 200+ workspaces that I manage with a variable in Terraform Cloud that I need to update. All workspaces that I need to update start with "dev-workspace" in this case.
I have a data block with the following:
data "tfe_workspace_ids" "all" {
names = ["*"]
organization = "myorganization"
}
I can't do a wildcard search for these workspaces due to a limitation of the module. This data block returns a map of strings that include all of my workspaces:
aa = {
"dev-workspace-1" = "ws-anonymized"
"dev-workspace-2" = "ws-ws-anonymized"
"dev-workspace-3" = "ws-ws-anonymized"
"test-workspace-1" = "ws-ws-anonymized"
"prod-workspace-1" = "ws-ws-anonymized"
}
My problem is that I need to take this map of strings and filter it down to just return the ones that have "dev-workspace" in the key. I've tried something like the following:
resource "tfe_variable" "dev-workspace" {
for_each = contains(data.tfe_workspace_ids.all.ids, "dev-workspace")
key = "access_key"
value = "XXXX"
category = "terraform"
workspace_id = each.value
sensitive = true
description = "AWS IAM secret access key."
}
But it doesn't look like you can use contains in this manner with for_each:
Error: Error in function call
on main.tf line 16, in resource "tfe_variable" "dev-workspace":
16: for_each = contains(data.tfe_workspace_ids.all.ids, "dev-workspace")
|----------------
| data.tfe_workspace_ids.all.ids is map of string with 284 elements
Call to function "contains" failed: argument must be list, tuple, or set.
I'm not really sure what to do here, but have tried this several ways and can't figure it out. Thanks for any help.
If you want to filter, your resource could be (you have to change var.aa to the value of data.tfe_workspace_ids which produces the input map):
variable "aa" {
default = {
"dev-workspace-1" = "ws-anonymized"
"dev-workspace-2" = "ws-ws-anonymized"
"dev-workspace-3" = "ws-ws-anonymized"
"test-workspace-1" = "ws-ws-anonymized"
"prod-workspace-1" = "ws-ws-anonymized"
}
}
resource "tfe_variable" "dev-workspace" {
for_each = {for k, v in var.aa:
k => v if length(regexall("dev-workspace", k)) > 0}
key = "access_key"
value = "XXXX"
category = "terraform"
workspace_id = each.value
sensitive = true
description = "AWS IAM secret access key."
}
I must be being incredibly stupid but I can't figure out how to do simple string concatenation in Terraform.
I have the following data null_data_source:
data "null_data_source" "api_gw_url" {
inputs = {
main_api_gw = "app.api.${var.env_name == "prod" ? "" : var.env_name}mydomain.com"
}
}
So when env_name="prod" I want the output app.api.mydomain.com and for anything else - let's say env_name="staging" I want app.api.staging.mydomain.com.
But the above will output app.api.stagingmydomain.com <-- notice the missing dot after staging.
I tried concating the "." if the env_name was anything but "prod" but Terraform errors:
data "null_data_source" "api_gw_url" {
inputs = {
main_api_gw = "app.api.${var.env_name == "prod" ? "" : var.env_name + "."}mydomain.com"
}
}
The error is __builtin_StringToInt: strconv.ParseInt: parsing ""
The concat() function in TF appears to be for lists not strings.
So as the title says: How do you do simple string concatenation in Terraform?
I can't believe I'm asking how to concat 2 strings together XD
Update:
For anyone that has a similar issue I did this horrific workaround for the time being:
main_api_gw = "app.api.${var.env_name == "prod" ? "" : var.env_name}${var.env_name == "prod" ? "" : "."}mydomain.com"
I know this was already answered, but I wanted to share my favorite:
format("%s/%s",var.string,"string2")
Real world example:
locals {
documents_path = "${var.documents_path == "" ? format("%s/%s",path.module,"documents") : var.documents_path}"
}
More info:
https://www.terraform.io/docs/configuration/functions/format.html
so to add a simple answer to a simple question:
enclose all strings you want to concatenate into one pair of ""
reference variables inside the quotes with ${var.name}
Example: var.foo should be concatenated with bar string and separated by a dash
Solution: "${var.foo}-bar"
Try Below data resource :
data "null_data_source" "api_gw_url" {
inputs = {
main_api_gw = "app.api${var.env_name == "prod" ? "." : ".${var.env_name}."}mydomain.com"
}
}
For Terraform 0.12 and later, you can use join() function:
join(separator, list)
Example:
> join(", ", ["foo", "bar", "baz"])
foo, bar, baz
> join(", ", ["foo"])
foo
If you just want to concatenate without a separator like "foo"+"bar" = "foobar", then:
> join("", ["foo", "bar"])
foobar
Reference: https://www.terraform.io/docs/configuration/functions/join.html
Use the Interpolation Syntax for versions < 0.12
Here is a simple example:
output "s3_static_website_endpoint" {
value = "http://${aws_s3_bucket.bucket_tf.website_endpoint}"
}
Reference the Terraform Interpolation docs:
https://developer.hashicorp.com/terraform/language/expressions/strings#string-templates
after lot of research, It finally worked for me. I was trying to follow https://www.hashicorp.com/blog/terraform-0-12-preview-first-class-expressions/, but it did not work. Seems string can't be handled inside the expressions.
data "aws_vpc" "vpc" {
filter {
name = "tag:Name"
values = ["${var.old_cluster_fqdn == "" ? "${var.cluster_fqdn}" : "${var.old_cluster_fqdn}"}-vpc"]
}
}
My module takes a possibly-empty-list as input, and if that list is non-empty, creates some resources and returns a specific attribute that I need outside of the module, like so:
variable contexts {
type = "list"
}
resource "pagerduty_service" "p1" {
count = "${length(var.contexts)}"
name = "p1-${element(var.contexts, count.index)}"
description = "p1-${element(var.contexts, count.index)}"
auto_resolve_timeout = 14400
acknowledgement_timeout = 1800
escalation_policy = "${pagerduty_escalation_policy.p1.id}"
alert_creation = "create_alerts_and_incidents"
incident_urgency_rule {
type = "constant"
urgency = "high"
}
}
data "pagerduty_vendor" "cloudwatch" {
name = "Cloudwatch"
}
resource "pagerduty_service_integration" "p1_cloudwatch" {
count = "${length(var.contexts)}"
name = "Amazon Cloudwatch"
vendor = "${data.pagerduty_vendor.cloudwatch.id}"
service = "${element(pagerduty_service.p1.*.id, count.index)}"
}
output "integration_keys" {
value = "${pagerduty_service_integration.*.integration_keys}"
}
The trouble I am having is that when this module is run first with a non-empty list, thus creating the resources, it works fine. If I run it again, it fails with this exception:
* module.pagerduty.output.integration_keys: Resource 'pagerduty_service_integration.possibly_empty_resource_list' does not have attribute 'integration_key' for variable 'pagerduty_service_integration.possibly_empty_resource_list.*.integration_key'
I can't figure out a nice way to have this output return an empty list if the possibly_empty_resource_list is empty.
Any ideas?
EDIT:
I tried performing a ternary check on the output, but for some reason, using a list is not supported so this won't work however I hope it illustrates what I am trying to do:
"${length(var.contexts) > 0 ? pagerduty_service_integration.*.integration_keys : list()}"
Solution:
output "instance_id" {
value = "${element(concat(aws_instance.example.*.id, list("")), 0)}"
}
There's a section at the very bottom of the terraform upgrade to 0.11 guide here: https://www.terraform.io/upgrade-guides/0-11.html that shows what I use for counted resources
ex:
output "instance_id" { value = "${element(concat(aws_instance.example.*.id, list("")), 0)}" }
(moved over from a comment)