JoeyP
JoeyP

Reputation: 2702

Variable keys in terraform maps

In Terraform, I'm trying to create a module with that includes a map with variable keys. I'm not sure if this is possible but I've tried the following without success.

resource "aws_instance" "web" {
    ami = "${var.base_ami}"
    availability_zone = "${var.region_a}"
    instance_type = "${var.ec2_instance_size}"
    security_groups = ["sec1"]
    count = "${var.ec2_instance_count}"
    tags {
        Name = "${var.role} ${var_env}"
        role = "${var.app_role}"
        ${var.app_role} = "${var_env}"
    }
}

and this:

tags {
   Name = "${var.role} ${var_env}"
}
tags."${var.role}" = "${var.env}"

Any ideas? Is this not possible with Terraform currently?

Upvotes: 52

Views: 126706

Answers (6)

João Neto
João Neto

Reputation: 1840

Yet another (new) way of achieving this is via (var.myvar): "somevalue":

resource "kubernetes_secret" "redis" {
  metadata {
    name = local.redis_secret_name
    namespace = var.namespace
  }
  data = {
    (local.redis_secret_password_key): base64encode(random_password.redis.result)
  }
}

Upvotes: 4

gsaslis
gsaslis

Reputation: 3166

There's (now) a lookup function supported in the terraform interpolation syntax, that allows you to lookup dynamic keys in a map.

Using this, I can now do stuff like:

output "image_bucket_name" {
  value = "${lookup(var.image_bucket_names, var.environment, "No way this should happen")}"
}

where:

variable "image_bucket_names" {
  type = "map"

  default = {
    development = "bucket-dev"
    staging = "bucket-for-staging"
    preprod = "bucket-name-for-preprod"
    production = "bucket-for-production"
  }

}

and environment is a simple string variable.

Upvotes: 50

tavnab
tavnab

Reputation: 2734

Update

The accepted answer describes how to do dynamic lookups in an existing map. For constructing maps with dynamic keys, in HCL2 (0.12), you can use a quoted interpolation expression in the key:

resource "aws_instance" "web" {
  count = "${var.ec2_instance_count}"
  
  ami = "${var.base_ami}"
  availability_zone = "${var.region_a}"
  instance_type = "${var.ec2_instance_size}"
  security_groups = ["sec1"]

  tags = {
    Name              = "${var.role} ${var.env}"
    role              = "${var.app_role}"
    "${var.app_role}" = "${var.env}"               # <------ like this
  }
}

And once issue #21566 is fixed, you can replace "${var.app_role}" with (var.app_role), which is the method described in the documentation.

(The same caveat as below applies here as well: if var.app_role contains one of those literal keys as its value, then it will replace it.)

Old answer

The accepted answer describes how to do dynamic lookups in an existing map. For constructing maps with dynamic keys, in HCL2 (0.12), you have two ways:

For expressions + merge

You can use for expressions to dynamically build a map from one or more variables for your keys, and then use that in combination with the merge function to build a new map with a mix of static and dynamic keys:

variable "app_role" {
  type = string
}

locals {
  tags = merge(
    {
      Name = "${var.role} ${var.env}"
      role = "${var.app_role}"
    },
    {
      for k in [var.app_role]: k => "${var.env}"
    }
  )
}

zipmap

Alternatively, you can use zipmap to construct it in one shot:

locals {
  tags = zipmap(
    [
       "Name",
       "role",
       var.app_role
    ],
    [
       "${var.role} ${var.env}",
       var.app_role,
       var.env
    ]
  )
}

You can then use this map in a resource:

resource "aws_instance" "web" {
  count = "${var.ec2_instance_count}"
  
  ami = "${var.base_ami}"
  availability_zone = "${var.region_a}"
  instance_type = "${var.ec2_instance_size}"
  security_groups = ["sec1"]

  tags = local.tags // or inline the above here
}

One caveat is that if var.app_role is equal to either "Name" or "role", then it'll overwrite your static value. You can avoid this by swapping the arguments in merge or reordering the lists in zipmap, though such a collision would more likely be a configuration error that should be caught & fixed before applying.

Upvotes: 37

lander2k2
lander2k2

Reputation: 121

The following works with terraform version 0.11.7. This solution uses the map function.

resource "aws_instance" "web" {
  ...
  tags = "${map(
    "Name", "${var.role} ${var_env}",
    "role", "${var.app_role}",
    "${var.app_role}", "${var_env}"
  )}"
}

Upvotes: 7

Matt Lewin
Matt Lewin

Reputation: 502

I'm uncertain when it was added, but at least as of version 0.11.7, Terraform supports the use of variables as map keys. Here's an example of how I'm currently using it to select the AWS instance type:

In the .tf file:

variable "environment" {}

...

variable "instance_types_webserver" {
  type = "map"

  default = {
    testing    = "t2.small"
    qa         = "t2.medium"
    staging    = "t2.xlarge"
    production = "t2.xlarge"
  }
}

...

resource "aws_opsworks_instance" "verification" {
  stack_id      = "${aws_opsworks_stack.verification.id}"
  layer_ids     = ["${aws_opsworks_custom_layer.verification.id}"]
  instance_type = "${var.instance_types_webserver["${var.environment}"]}"
  state         = "running"
  count         = 1
}

In the .tfvars file:

...
environment = "testing"
...

Upvotes: 2

scottatron
scottatron

Reputation: 161

I recently needed to set a tag key dynamically too, and managed to do it using zipmap:

locals {
  ec2_tag_keys = ["some/prefix/${var.some_var}", "another_tag"]
  ec2_tag_vals = ["some value", "another value"]
}

resource "aws_instance", "foo" {
  ...
  tags = "${zipmap(local.ec2_tag_keys, local.ec2_tag_vals)}"
}

It's a little clunky, but it works.

Upvotes: 6

Related Questions