Note: this block was copy-pasted from terraform-provider-aws
0.9.5
variable "myvar" {
default = "myvalue"
}
variable "mymap" {
type = "map"
default = {
"internal_subnet_tag.key" = "associate_public_ip_address"
"internal_subnet_tag.value" = "no"
}
}
data "aws_subnet_ids" "our_vpc" {
vpc_id = "real-vpc-id"
tags {
"${var.myvar}" = "${var.mymap["internal_subnet_tag.value"]}"
# "${var.mymap["internal_subnet_tag.value"]}" = "${var.mymap["internal_subnet_tag.value"]}"
# "${var.mymap["internal_subnet_tag.key"]}" = "${var.mymap["internal_subnet_tag.value"]}"
# ----- Lines below work well
# "associate_public_ip_address" = "${var.mymap["internal_subnet_tag.value"]}"
# "associate_public_ip_address" = "no"
}
}
--Cut of bad debug
data.aws_subnet_ids.our_vpc: Refreshing state...
2017/05/15 15:49:12 [DEBUG] plugin: terraform: aws-provider (internal) 2017/05/15 15:49:12 [DEBUG] Matching ^aws: with ${var.myvar}
2017/05/15 15:49:12 [DEBUG] plugin: terraform: aws-provider (internal) 2017/05/15 15:49:12 [DEBUG] DescribeSubnets {
2017/05/15 15:49:12 [DEBUG] plugin: terraform: Filters: [{
2017/05/15 15:49:12 [DEBUG] plugin: terraform: Name: "vpc-id",
2017/05/15 15:49:12 [DEBUG] plugin: terraform: Values: ["real-vpc-id"]
2017/05/15 15:49:12 [DEBUG] plugin: terraform: },{
2017/05/15 15:49:12 [DEBUG] plugin: terraform: Name: "tag:${var.myvar}",
2017/05/15 15:49:12 [DEBUG] plugin: terraform: Values: [""]
2017/05/15 15:49:12 [DEBUG] plugin: terraform: }]
2017/05/15 15:49:12 [DEBUG] plugin: terraform: }
--Cut of good debug
data.aws_subnet_ids.our_vpc: Refreshing state...
2017/05/15 15:48:26 [DEBUG] plugin: terraform: aws-provider (internal) 2017/05/15 15:48:26 [DEBUG] Matching ^aws: with associate_public_ip_address
2017/05/15 15:48:26 [DEBUG] plugin: terraform: aws-provider (internal) 2017/05/15 15:48:26 [DEBUG] DescribeSubnets {
2017/05/15 15:48:26 [DEBUG] plugin: terraform: Filters: [{
2017/05/15 15:48:26 [DEBUG] plugin: terraform: Name: "vpc-id",
2017/05/15 15:48:26 [DEBUG] plugin: terraform: Values: ["real-vpc-id"]
2017/05/15 15:48:26 [DEBUG] plugin: terraform: },{
2017/05/15 15:48:26 [DEBUG] plugin: terraform: Name: "tag:associate_public_ip_address",
2017/05/15 15:48:26 [DEBUG] plugin: terraform: Values: ["no"]
2017/05/15 15:48:26 [DEBUG] plugin: terraform: }]
2017/05/15 15:48:26 [DEBUG] plugin: terraform: }
Tag name is not interpolated
terraform plan
Your AWS account should have vpc and subnet(s). At least one subnet with the specified tag key and value for the successful refresh
Hi @dvishniakov! Sorry things didn't work as expected here.
It is a limitation of the configuration language that attribute names must be constants. This is true for all attribute names, not specifically the ones in this tags
map.
However, it should be possible to work around this by dynamically producing a map using the map
function, thus avoiding this limitation of the configuration language:
tags = "${map(var.myvar, var.mymap["internal_subnet_tag.value"])}"
In future we may extend the configuration language to have dynamic keys for maps. At present this isn't possible because it doesn't have enough information during parsing to recognize the difference between a map (where keys are arbitrarily chosen by you) and other objects (where the keys are fixed, defined by the resource schema). Let's consider this issue to be a feature request for this working with the more intuitive syntax.
@apparentlymart thanks! This workaround helps at this point, however such feature still could be useful so please keep this enhancement open.
This is currently painful for Kubernetes clusters, since K8s typically uses tags like:
"kubernetes.io/cluster/myclustername" = "shared"
So what I really need is a set of tags like this:
resource "aws_subnet" "sn-backend" {
...
count = "${length(var.zones)}"
tags {
"Name" = "sn-backend-${element(var.zones, count.index)}"
"Contact" = "${var.contact}"
"Environment" = "${var.environment}-${var.region}"
"kubernetes.io/cluster/${var.cluster_name}" = "shared"
"kubernetes.io/role/internal-elb" = ""
}
}
This isn't possible of course. Producing these tags as a map is hard, because I have interpolation required for keys and values. This requires a lot of variables to be defined because if I use the syntax ${map(...)}
then I can't do any interpolation.
The best solution I've found is to define all tags that have interpolation in their value as a map in one var (subnettags
), then define each key that has interpolation as another var, then finally define tags as a merge like:
resource "aws_subnet" "sn-backend" {
...
tags = "${merge(var.subnettags, map(var.clustertag, "shared"))}"
}
but wow is this verbose. I feel like this issue should be reopened and fixed as it is really awkward to work around.
It seems like a simple workaround here would be to let people specify tags as lists of keys and values like:
resource "aws_subnet" "sn-backend" {
tags = [
{
key = "Name"
value = "sn-backend-${element(var.zones, count.index)}"
},
{
key = "kubernetes.io/cluster/${var.cluster_name}"
value = "shared"
}
]
}
It would avoid having to change the fundamentally static nature of keys in the configuration language.
Is that viable? Does terraform support lists of maps in its syntax?
Hi all,
The new improved parser/interpreter we are currently integrating has support for interpolation into map keys, so once that is released the configurations discussed in this issue should work as expected.
Integrating this is a big project that addresses a number of different configuration limitations, so I can't yet say exactly when it will be done but it _is_ the current focus for the Terraform team at HashiCorp.
For anyone who is still struggling with the best way to do this, and waiting for the map interpolation to be supported, here's the way I'm doing it now. This is about as clean as I've been able to get it, and is specifically for dealing with the kubernetes.io/cluster/<clustername>
challenge. Will also share on the related issues:
locals {
common_tags = "${map(
"Project", "openshift",
"KubernetesCluster", "${var.cluster_id}",
"kubernetes.io/cluster/${var.cluster_name}", "${var.cluster_id}"
)}"
}
resource "aws_instance" "master" {
// Use our common tags and add a specific name.
tags = "${merge(
local.common_tags,
map(
"Name", "OpenShift Master"
)
)}"
}
Reference:
Another option here is to use lifecycle ignore_changes to avoid unwanted plan diffs:
resource "aws_vpc" "vpc" {
...
# Ignore k8s tag and tag count
lifecycle {
ignore_changes = ["tags.kubernetes", "tags.%"]
}
tags {
# Other tags
}
}
Hi all! Sorry for the long silence here.
I took the example in the initial comment on this issue and adapted it a little to use a null_resource
just because I don't have any VPCs in my test AWS account right now:
variable "mymap" {
type = "map"
default = {
"internal_subnet_tag.key" = "associate_public_ip_address"
"internal_subnet_tag.value" = "no"
}
}
resource "null_resource" "example" {
triggers = {
(var.mymap["internal_subnet_tag.key"]) = var.mymap["internal_subnet_tag.value"]
}
}
This example uses the new ability for a map constructor to have arbitrary expressions for keys. Because we allow "naked" identifiers to be used as literal keys, a more complex expression like this one must be placed in parentheses so the parser can understand our intent. (It would also work to use a quoted string with an interpolation sequence as in the original example, but I used simple parentheses because they are less visually "noisy".)
I can see this working as expected in Terraform v0.12.0-alpha1:
$ terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.
------------------------------------------------------------------------
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# null_resource.example will be created
+ resource "null_resource" "example" {
+ id = (known after apply)
+ triggers = {
+ "associate_public_ip_address" = "no"
}
}
Plan: 1 to add, 0 to change, 0 to destroy.
------------------------------------------------------------------------
Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.
In order to make this work, the new language parser makes a stronger distinction between attributes that happen to have map/object values (where dynamic keys are allowed) and nested blocks (where only literal keys are allowed, so that they can be validated early). The practical implication of this is that it is now _required_ to use the equals sign to define a value for a map attribute, where before omitting this was just non-idiomatic.
As an example of what I mean, I've also included some of the examples shown in this issue updated to use the attribute definition syntax where previously they used nested block syntax:
data "aws_subnet_ids" "our_vpc" {
vpc_id = "real-vpc-id"
tags = {
"${var.myvar}" = "${var.mymap["internal_subnet_tag.value"]}"
}
}
resource "aws_subnet" "sn-backend" {
...
count = "${length(var.zones)}"
tags = {
"Name" = "sn-backend-${element(var.zones, count.index)}"
"Contact" = "${var.contact}"
"Environment" = "${var.environment}-${var.region}"
"kubernetes.io/cluster/${var.cluster_name}" = "shared"
"kubernetes.io/role/internal-elb" = ""
}
}
The configuration upgrade tool that will be included with the v0.12.0 final release will be able to fix this usage automatically across your whole configuration, if you wish.
The technique of merging a set of common tags with some override tags will still work, and can now be expressed more clearly due to first-class expression syntax:
tags = merge(
local.common_tags,
{
Name = "OpenShift Master"
}
)
With all of that said, it looks like the use-cases covered by this issue have been addressed in the master
branch and will be included in the subsequent v0.12.0 release, and so I'm going to close this. Thanks everyone for sharing your use-cases, and for your patience while we laid the groundwork to make this possible.
@apparentlymart - this is awesome, thank you! And thanks for the detailed update and usage examples. Really cool 😄
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.
Most helpful comment
For anyone who is still struggling with the best way to do this, and waiting for the map interpolation to be supported, here's the way I'm doing it now. This is about as clean as I've been able to get it, and is specifically for dealing with the
kubernetes.io/cluster/<clustername>
challenge. Will also share on the related issues:Reference: