Terraform: Alternatives to concat, which no longer works with strings

Created on 20 May 2016  ·  12Comments  ·  Source: hashicorp/terraform

Terraform Version

Terraform v0.7.0-dev (84a0a32b1947f52deeac344ec5e49393e8a432e1)

Affected Resource(s)

core

Terraform Configuration Files

resource "azurerm_storage_account" "public" {
  name                = "${format("%.24s", lower(sha1(concat(var.name, "-public"))))}"
  resource_group_name = "${azurerm_resource_group.dcos.name}"
  location            = "${var.location}"
  account_type        = "Standard_LRS"

  tags {
    environment = "${var.environment}"
  }
}

This no longer works since concat now only accepts list arguments. The commit where this change was introduced mentions using join as an alternative, or native HIL string concatenation. I haven't been able to get anything to work. I've tried the following:

${format("%.24s", lower(sha1(join("", var.name, "-public"))))}
${format("%.24s", lower(sha1(join("", [var.name, "-public"]))))}

Both are invalid. What is the new way of doing this? The examples in the interpolation docs for sha1 and sha256 still refer to the old form of concat

References

https://github.com/hashicorp/terraform/commit/f49583d25ae6d7dfca3640e71269e892c38ccefd

core documentation question

Most helpful comment

Hi @Bowbaq! Thanks for opening an issue. You're right that the documentation is lagging the actual features on `master - this will be corrected prior to a release.

The correct way to structure your interpolation in Terraform 0.7 is:

name = "${format("%.24s", lower(sha1("${var.name}-public")))}"

Running this for me gives:

$ cat main.tf
cat main.tf
variable "name" {
    default = "test"
}

output "test" {
    value = "${format("%.24s", lower(sha1("${var.name}-public")))}"
}

$ terraform apply

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

test = 7e3fbf8714494e0dece27e61

The join interpolation function is designed to join lists together. We had to become a lot stricter with output types in order to be able to effectively type check at the planning phase in order avoid runtime errors - previously this was not necessary as everything was in effect a string.

I'll go ahead and close this issue for now - if the above doesn't work for you please feel free to reopen!

All 12 comments

Thanks for the report, @Bowbaq - we did remove support in concat() for strings with the first class lists/maps work, but that was under the impression there were other ways to concatenate strings.

My initial fiddling isn't yielding the answer. Kicking to @jen20 so we can determine and document the correct solution here (or make it work if we're missing one). 👍

Hi @Bowbaq! Thanks for opening an issue. You're right that the documentation is lagging the actual features on `master - this will be corrected prior to a release.

The correct way to structure your interpolation in Terraform 0.7 is:

name = "${format("%.24s", lower(sha1("${var.name}-public")))}"

Running this for me gives:

$ cat main.tf
cat main.tf
variable "name" {
    default = "test"
}

output "test" {
    value = "${format("%.24s", lower(sha1("${var.name}-public")))}"
}

$ terraform apply

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

test = 7e3fbf8714494e0dece27e61

The join interpolation function is designed to join lists together. We had to become a lot stricter with output types in order to be able to effectively type check at the planning phase in order avoid runtime errors - previously this was not necessary as everything was in effect a string.

I'll go ahead and close this issue for now - if the above doesn't work for you please feel free to reopen!

@jen20 I think that'll do the trick. I'm a bit confused though, I didn't think it was legal to nest the ${} syntax. Certainly in many cases it's not required. Is ${sha1(var.name)} equivalent to ${sha1("${var.name}")} ?

@Bowbaq the key thing to notice here, which isn't immediately obvious from the docs, is that when there is a string literal _inside_ an interpolation (${ ... }) that string can _itself_ contain interpolations. These nested interpolations can then contain their own strings, with their own interpolations again.

So yes, in your example it is equivalent to say "${var.name}" and var.name within the expression inside an interpolation.

This example is working but I have similar that won't work any more.

Before terraform 0.7.0 I had something like this:

# this one is in separate file for dv env only
# production env have separate file
resource "openstack_compute_floatingip_v2" "fip_lb_dv" {
  pool = "some-pool"
}
variable "environment_short" {
  default = "dv"
}

# rest of files were common for both envs
# ex. lb vips
resource "openstack_lb_vip_v1" "lb_vip_http" {
  ...
  floating_ip = "${concat("openstack_compute_floatingip_v2.fip_lb_", environment_short, ".address")}"
  ...
}

This example was properly configuring floating_ip.

In version 0.7 I'm trying something like you proposed but I have only variable name but not interpolated:

# i want to get address falue of this fip to use in openstack_lb_vip_v1
output "fip" {
  value = "${format("openstack_compute_floatingip_v2.fip_lb_%s.address", var.environment_short)}"
  # or as in your example
  value = "${format("openstack_compute_floatingip_v2.fip_lb_%s.address", "${var.environment_short}")}"
  # or even
  value = "${"${format("openstack_compute_floatingip_v2.fip_lb_%s.address", "${var.environment_short}")}"}"
}

Every time with same result:

Outputs:

fip = openstack_compute_floatingip_v2.fip_lb_dv.address

Not what I want. How could I interpolate this string "again" to get proper IP here?

👍

Using format I was able to concat two strings. Thanks for the tip, @jen20.

Example for anyone wondering how:

variable "var1" {
  type = "string"
  default = "bar"
}

variable "var2" {
  type = "string"
  default = "baz"
}

variable "var3" {
  type = "map"
  default = {
    bar.baz = "dingo"
  }
}

data "template_file" "foo" {
  template = "${bong}"

  vars {
    bong = "${lookup(var.var3, format("%s.%s", var.var1, var.var2))}"
  }
}

output "bingo" {
  value = "${data.template_file.foo.rendered}"
}
terraform apply
data.template_file.foo: Refreshing state...

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

bingo = dingo

Does this still work? Specifically I've been trying

name     = "${format("%s%s", var.TEST_ID, var.TEST_ID)}"

~and in my plan, it's not interpolating the value of TEST_ID, it's using it literally.~
Does interpolation somehow only work if wrapped inside a lookup?

Update -- I accidentally quoted the var.TEST_ID in my posting, but my point still stands. When I try to use var.TEST_ID it doesn't interpolate it. E.g. if I do

name     = "${format("%s%s", "bar", var.TEST_ID)}"

then terraform will pick up the name as bar, without the value of TEST_ID.

@luhkevin You can just do "bar${var.TEST_ID}" for a simple string concatenation.

For the original question, the example
"${format("%.24s", lower(sha1(concat(var.name, "-public"))))}"
was provided the solution:
"${format("%.24s", lower(sha1("${var.name}-public")))}"

The format function in this case is used is to limit the sha1 hash to the first 24 characters, and convert to a string, and is not relevant to the question of "how to concat strings since concat is only for lists now".
The important part is

"${var.name}-public"

It's simple, but not immediately obvious, to concatenate the var.name variable with the literal -public

Indeed, the format function should be necessary only if you need an unusual formatting of the value that isn't the default string representation. If you're only using %s verbs in the format string then it's always possible to rewrite it just as normal iterpolation:

format("%s%s", "bar", var.test_id) -> "bar${var.test_id}"

The format function is handy for more complex situations like when you need to render a number with zero-padding up to a fixed number of digits, etc. The latest docs for format (which will be published to the main website along with the forthcoming v0.12.0 release) includes a similar example showing the equivalence between the %s and %d verbs and string interpolation, which I hope will make the relationship between these two features clearer.

hi i am new to terraform, can some help me ..
when ever i am tring to build a instance with same code.. it say security group already exits , so rather than changing security group name in main.tf and network creation i want to set a variable can you pls suggest is this the right format
for variable

variable "security_name" {

description = "test1"

}

for main:::::

vpc_security_group_ids = ["${aws_security_group."${var.security_name}".id}"]

for network :::::

resource "aws_security_group" "${var.security_name}" {

name = "${var.security_name}"

is this right format ? for security group..

::::::terraform validate

Error: Error loading /home//terraform/new/main.tf: Error reading config for aws_instance[test]: parse error at 1:22: expected "}" but found opening quote

You can't use variables to name a Terraform resource. Note there is a difference between the Terraform resource name and the name attribute inside the resource.

In your example below, the first appearance of your var is the name for referring to this resource within Terraform only. The second time you are assigning it to the name and the attribute becomes a property of the aws security group. AWS resources have 'arn' numbers that uniquely identify them, and Terraform AWS Provider is just mapping those to Terraform resource names.

If you get a message that the security group already exists then perhaps you shouldn't just keep creating more of them.

If it's the right security group, created outside of Terraform, and you want to use it then you can import existing aws things into terraform and then refer to them, using Terraform datasources instead of resources, like this one https://www.terraform.io/docs/providers/aws/d/security_group.html

resource "aws_security_group" "${var.security_name}" {
name = "${var.security_name}"

I'm going to lock this issue because it has been closed for _30 days_ ⏳. This helps our maintainers find and focus on the active issues.

If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

Was this page helpful?
0 / 5 - 0 ratings