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"]
}
}
Related
I am working with vm deployments over AWS with terraform(v1.0.9) as infrastructure as code. i have Terraform output.tf to print two lan a ips and code prints, list of lists like [["ip_a",],["ip_b",]] but i want a list like this ["ip_a", "ip_b"].
output.tf code
`output "foo" {
value = {
name = "xyz"
all_ips = tolist(aws_network_interface.vm_a_eni_lan_a.*.private_ips)
}
}`
printing -->
"name" = "xyz" "lan_a_ips" = tolist(\[ toset(\[ "10.0.27.116",\]), toset(\[ "10.0.28.201",\]), \])
but i want "lan_a_ips" = ["10.0.27.116", "10.0.28.201"]
I beleive tweaking output.tf can help. Any help is appreciated.
In your case, you have just set the splat expression [1] in a wrong place, i.e., instead of setting aws_network_interface.vm_a_eni_lan_a.private_ips[*] you set it to aws_network_interface.vm_a_eni_lan_a.*.private_ips. So you only need to change the output value:
output "foo" {
value = {
name = "xyz"
all_ips = aws_network_interface.vm_a_eni_lan_a.private_ips[*]
}
}
EDIT: The above applies when only a single instance of an aws_network_interface resource is created. For situations where there are multiple instance of this resource created with count meta-argument, the following can be used to get a list of IPs:
output "foo" {
value = {
name = "xyz"
all_ips = flatten([for i in aws_network_interface.test[*] : i.private_ips[*]])
}
}
Here, the for [2] loop is used to iterate over all the instances of a resource, hence the splat expression when referencing them aws_network_interface.test[*]. Additionally, since this will create a list of lists (as private_ips[*] returns a list), flatten [3] built-in function can be used to create a single list of IP addresses.
[1] https://www.terraform.io/language/expressions/splat
[2] https://www.terraform.io/language/expressions/for
[3] https://www.terraform.io/language/functions/flatten
I need to check the value that exists in a variable or not and based on that I need to create resources.
If value_list doesn't have these values('abc','def','ghi') it should not create the resource.
What I'm trying here is:
Converting the string variable to list
Check that list is having values 'abc' or 'def' or 'ghi'. If value_list contains any one of the values then proceed with the next steps to create resources.
If value_list doesn't have these values('abc','def','ghi') it should not create the resource.
variables.tf
variable "value_list" {
default = "abc,def,ghi"
type= string
}
resource.tf
resource "azurerm_kubernetes_cluster_node_pool" "user" {
value_list = ${split(",", var.value_list)}
count = "${contains(value_list,"abc") ? 1 : 0 || contains(value_list,"def") ? 1 : 0 || contains(value_list,"ghi") ? 1 : 0
}
Error:
This character is not used within the language.
Expected the start of an expression, but found an invalid expression token.
How to check if the value_list is having the desired value or not?
Terraform has functions that can help with that:
https://www.terraform.io/docs/language/functions/contains.html
https://www.terraform.io/docs/language/functions/setintersection.html
https://www.terraform.io/docs/language/functions/setsubtract.html
https://www.terraform.io/docs/language/functions/setunion.html
It looks like you are using contains, but in a strange way, if you need to split something you can do it in a local that way it is available to multiple resources, also the expression in your count does not look right you might want to look at the documentation for that:
https://www.terraform.io/docs/language/meta-arguments/count.html#using-expressions-in-count
Here is a sample usage:
variable "value_list" {
default = "abc,def,ghi"
type = string
}
locals {
vlist = split(",", var.value_list)
}
resource "null_resource" "test_abc" {
count = contains(local.vlist, "abc") ? 1 : 0
provisioner "local-exec" {
command = "echo FOUND;"
}
}
resource "null_resource" "test_xyz" {
count = contains(local.vlist, "xyz") ? 1 : 0
provisioner "local-exec" {
command = "echo FOUND;"
}
}
resource "null_resource" "test_abc_or_def" {
count = (contains(local.vlist, "abc") || contains(local.vlist, "def")) ? 1 : 0
provisioner "local-exec" {
command = "echo FOUND;"
}
}
See the count in that last resource:
count = (contains(local.vlist, "abc") || contains(local.vlist, "def")) ? 1 : 0
that is a conditional expression in the format:
<CONDITION> ? <TRUE VAL> : <FALSE VAL>
the condition is what looks strange in your sample code, you can have as many or in your condition as you want but don't mix the values there
( vlist contains "abc" OR vlist contains "def" )
( contains(local.vlist, "abc") || contains(local.vlist, "def") )
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"
}
I have a case where I have to create an aws_vpc resource if the user does not provide vpc id. After that I am supposed to create resources with that VPC.
Now, I am applying conditionals while creating an aws_vpc resource. For example, only create VPC if existing_vpc is false:
count = "${var.existing_vpc ? 0 : 1}"
Next, for example, I have to create nodes in the VPC. If the existing_vpc is true, use the var.vpc_id, else use the computed VPC ID from aws_vpc resource.
But, the issue is, if existing_vpc is true, aws_vpc will not create a new resource and the ternary condition is anyways trying to check if the aws_vpc resource is being created or not. If it doesn't get created, terraform errors out.
An example of the error when using conditional operator on aws_subnet:
Resource 'aws_subnet.xyz-subnet' not found for variable 'aws_subnet.xyz-subnet.id'
The code resulting in the error is:
subnet_id = "${var.existing_vpc ? var.subnet_id : aws_subnet.xyz-subnet.id}"
If both things are dependent on each other, how can we create conditional resources and assign values to other configuration based on them?
You can access dynamically created modules and resources as follows
output "vpc_id" {
value = length(module.vpc) > 0 ? module.vpc[*].id : null
}
If count = 0, output is null
If count > 0, output is list of vpc ids
If count = 1 and you want to receive a single vpc id you can specify:
output "vpc_id" {
value = length(module.vpc) > 0 ? one(module.vpc).id : null
}
The following example shows how to optionally specify whether a resource is created (using the conditional operator), and shows how to handle returning output when a resource is not created. This happens to be done using a module, and uses an object variable's element as a flag to indicate whether the resource should be created or not.
But to specifically answer your question, you can use the conditional operator as follows:
output "module_id" {
value = var.module_config.skip == true ? null : format("%v",null_resource.null.*.id)
}
And access the output in the calling main.tf:
module "use_conditionals" {
source = "../../scratch/conditionals-modules/m2" # << Change to your directory
a = module.skipped_module.module_id # Doesn't exist, so might need to handle that.
b = module.notskipped_module.module_id
c = module.default_module.module_id
}
Full example follows. NOTE: this is using terraform v0.14.2
# root/main.tf
provider "null" {}
module "skipped_module" {
source = "../../scratch/conditionals-modules/m1" # << Change to your directory
module_config = {
skip = true # explicitly skip this module.
name = "skipped"
}
}
module "notskipped_module" {
source = "../../scratch/conditionals-modules/m1" # << Change to your directory
module_config = {
skip = false # explicitly don't skip this module.
name = "notskipped"
}
}
module "default_module" {
source = "../../scratch/conditionals-modules/m1" # << Change to your directory
# The default position is, don't skip. see m1/variables.tf
}
module "use_conditionals" {
source = "../../scratch/conditionals-modules/m2" # << Change to your directory
a = module.skipped_module.module_id
b = module.notskipped_module.module_id
c = module.default_module.module_id
}
# root/outputs.tf
output skipped_module_name_and_id {
value = module.skipped_module.module_name_and_id
}
output notskipped_module_name_and_id {
value = module.notskipped_module.module_name_and_id
}
output default_module_name_and_id {
value = module.default_module.module_name_and_id
}
the module
# m1/main.tf
resource "null_resource" "null" {
count = var.module_config.skip ? 0 : 1 # If skip == true, then don't create the resource.
provisioner "local-exec" {
command = <<EOT
#!/usr/bin/env bash
echo "null resource, var.module_config.name: ${var.module_config.name}"
EOT
}
}
# m1/variables.tf
variable "module_config" {
type = object ({
skip = bool,
name = string
})
default = {
skip = false
name = "<NAME>"
}
}
# m1/outputs.tf
output "module_name_and_id" {
value = var.module_config.skip == true ? "SKIPPED" : format(
"%s id:%v",
var.module_config.name,
null_resource.null.*.id
)
}
output "module_id" {
value = var.module_config.skip == true ? null : format("%v",null_resource.null.*.id)
}
The current answers here are helpful when you are working with more modern versions of terraform, but as noted by OP here they do not work when you are working with terraform < 0.12 (If you're like me and still dealing with these older versions, I am sorry, I feel your pain.)
See the relevant issue from the terraform project for more info on why the below is necessary with the older versions.
but to avoid link rot, I'll use the OPs example subnet_id argument using the answers in the github issue.
subnet_id = "${element(compact(concat(aws_subnet.xyz-subnet.*.id, list(var.subnet_id))),0)}"
From the inside out:
concat will join the splat output list to list(var.subnet_id) -- per the background link 'When count = 0, the "splat syntax" expands to an empty list'
compact will remove the empty item
element will return your var.subnet_id only when compact recieves the empty splat output.
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)