Terraform: resource-instance-scoped named local values

Created on 17 Sep 2015  路  8Comments  路  Source: hashicorp/terraform

Sometimes I find that I repeat myself in resources. For example:

resource "aws_subnet" "foo" {
  count = 3
  availability_zone = "${var.region}${element(split(",", var.availability_zones), count.index)}"
  tags = {
    Name = "private-${var.region}${element(split(",", var.availability_zones), count.index)}-subnet"
  }
}

I was thinking it would be great to have self references here, as sometimes these interpolations can be somewhat complex and I end up having to copy and paste. For example:

resource "aws_subnet" "foo" {
  count = 3
  availability_zone = "${var.region}${element(split(",", var.availability_zones), count.index)}"
  tags = {
    Name = "private-${self.availability_zone}-subnet"
  }
}

Not sure if it's possible to do this in a nice manner at the moment?

config enhancement

Most helpful comment

There are no plans to include this in Terraform v0.12. The scope of Terraform v0.12 is now closed, because its release process is already underway.

As I described in my earlier comment, there are some design challenges to solve before this could be implemented. So far we've not had time to work on those, so work here is blocked until there is time to think through what restrictions are needed to make this behave in a deterministic way while still being useful, and how best to implement it since today the arguments inside a block can be evaluated in any order while implementing this feature would effectively make the arguments themselves a dependency graph, which is a considerably more complex design.

We implemented local values several versions ago to address use-cases like this in a different way, by allowing repeated expressions to be hoisted out into a named symbol that can then be used in various locations:

locals {
  bucket_name = "blog.example.org"
}

resource "aws_s3_bucket" "example_org" {
  bucket = "${local.bucket_name}"
  logging {
    target_bucket = "logs_bucket"
    target_prefix = "logs/${local.bucket_name}/"
  }
}

The above is the recommended way to address this use-case for now.

One thing that we _cannot_ do with local values as currently implemented is instance-specific local values that can refer to count.index, within a resource block that has count set. For 0.12 we've reserved the name locals when used inside resource blocks. It doesn't do anything yet because there's a lot more implementation work to do to make it work, but our intent was to eventually support resource-scoped local values, which might look something like this:

resource "aws_s3_bucket" "example_org" {
  count = 5
  locals {
    bucket_name = "example${count.index}"
  }

  bucket = "${local.bucket_name}"
  logging {
    target_bucket = "logs_bucket"
    target_prefix = "logs/${local.bucket_name}/"
  }
}

The idea here would be that because this bucket_name local is defined inside the resource block we can evaluate it separately for each instance of the resource, and thus allow count.index to be used to generate a different value each time. This would be considerably simpler from an implementation standpoint because it separates the idea of re-usable values (which need to be evaluated in a dependency-aware fashion) from resource attributes (which can then still be evaluated in any order). I think (subjectively) it's also a simpler user model too, because it doesn't require any unusual restrictions on what can and cannot be used inside that block but rather behaves the same way as expressions elsewhere in the resource block.

Reserving the locals name as a "meta-argument" is the first step down this path for 0.12. I don't know yet when we'll be able to attack the rest of this, since once 0.12 is done we will need to devote some time to non-language-related parts of Terraform that have been more neglected during the 0.12 development cycle, but this scoped-locals design seems more promising (in terms of us knowing what an implementation of it might look like) and something we want to investigate further in a later release.

All 8 comments

Implementing something like what you described could potentially be allowed as long as the attribute in question is not _computed_, which is to say that its value is a known constant at plan time. Variables and interpolation functions applied to variables would fit into that rule, as could count.index since repetition of resources is also handled at plan time.

Currently the only place where there is special handling for plan-time-available values is in the special count argument itself, where only variables are allowed. Perhaps this concept could be generalized so that self can use it too. I know I've wanted to do stuff like this as well, rather than duplicating complex expressions all over the place.

A tricky part of this is that it creates dependencies within the attributes of the resource themselves. While it's not true in your example, you could easily create a situation where there is a cycle of dependencies between attributes, which Terraform would then need to detect and report. This would likely require the interpolator or config processor to retain some more state than it does today.

Maybe having self references which would work in the context of the resource itself and would be interpreted in the Update phase of each resource may be the way forward...

Ain't saying it makes things easier for the actual implementation as I can imagine the amount of changes we'd have to make in the lifecycle & interpolation, but it's just some food :hamburger: for thought :thought_balloon: .

This would really be nice. Here's an example with aws_s3_bucket resources:

resource "aws_s3_bucket" "example_org" {
    bucket = "blog.example.org"
    logging {
        target_bucket = "logs_bucket"
        target_prefix = "logs/${self.bucket}/"
    }
}

resource "aws_s3_bucket" "www_example_org" {
    bucket = "www.example.org"
    website {
        redirect_all_requests_to = "${aws_s3_bucket.example_org.bucket}"
    }
}

This is related to #5805

This would make a _lot_ of our stuff cleaner - I don't need full-blown self access, but I'd at least like to be able to refer back to the resource name within that resource's block, otherwise it makes for a ton of error-prone duplication across tags, descriptions, and collections of related resources.

Variables only help a little with that, because you can't interpolate resource names either.

yes, definitely, ${self.tags.Name} would be a common and useful case (in a remote-exec provisioner, for example, to netdom renamecomputer).

@apparentlymart Are there plans to include this in 0.12 ? It would be quite useful.

There are no plans to include this in Terraform v0.12. The scope of Terraform v0.12 is now closed, because its release process is already underway.

As I described in my earlier comment, there are some design challenges to solve before this could be implemented. So far we've not had time to work on those, so work here is blocked until there is time to think through what restrictions are needed to make this behave in a deterministic way while still being useful, and how best to implement it since today the arguments inside a block can be evaluated in any order while implementing this feature would effectively make the arguments themselves a dependency graph, which is a considerably more complex design.

We implemented local values several versions ago to address use-cases like this in a different way, by allowing repeated expressions to be hoisted out into a named symbol that can then be used in various locations:

locals {
  bucket_name = "blog.example.org"
}

resource "aws_s3_bucket" "example_org" {
  bucket = "${local.bucket_name}"
  logging {
    target_bucket = "logs_bucket"
    target_prefix = "logs/${local.bucket_name}/"
  }
}

The above is the recommended way to address this use-case for now.

One thing that we _cannot_ do with local values as currently implemented is instance-specific local values that can refer to count.index, within a resource block that has count set. For 0.12 we've reserved the name locals when used inside resource blocks. It doesn't do anything yet because there's a lot more implementation work to do to make it work, but our intent was to eventually support resource-scoped local values, which might look something like this:

resource "aws_s3_bucket" "example_org" {
  count = 5
  locals {
    bucket_name = "example${count.index}"
  }

  bucket = "${local.bucket_name}"
  logging {
    target_bucket = "logs_bucket"
    target_prefix = "logs/${local.bucket_name}/"
  }
}

The idea here would be that because this bucket_name local is defined inside the resource block we can evaluate it separately for each instance of the resource, and thus allow count.index to be used to generate a different value each time. This would be considerably simpler from an implementation standpoint because it separates the idea of re-usable values (which need to be evaluated in a dependency-aware fashion) from resource attributes (which can then still be evaluated in any order). I think (subjectively) it's also a simpler user model too, because it doesn't require any unusual restrictions on what can and cannot be used inside that block but rather behaves the same way as expressions elsewhere in the resource block.

Reserving the locals name as a "meta-argument" is the first step down this path for 0.12. I don't know yet when we'll be able to attack the rest of this, since once 0.12 is done we will need to devote some time to non-language-related parts of Terraform that have been more neglected during the 0.12 development cycle, but this scoped-locals design seems more promising (in terms of us knowing what an implementation of it might look like) and something we want to investigate further in a later release.

Was this page helpful?
0 / 5 - 0 ratings