Uwe Krüger
Uwe Krüger

Reputation: 423

map list of maps to a list of selected field values in terraform

If resources use a count parameter to specify multi resources in terraform there is a simple syntax for providing a list/array of dedicated fields for the resource instances.

for example

aws_subnet.foo.*.id

Since quite a number of versions it is possible to declare variables with a complex structure, for example lists of maps.

variable "data" {
  type = "list"
  default = [
    {
      id = "1"
      ...
    },
    {
      id = "10"
      ...
    }
  ]
}

I'm looking for a possibility to do the same for varaibles I can do for multi resources: a projection of an array to an array of field values of the array elements.

Unfortunately

var.data.*.id

does not work as for resources. Is there any possibility to do this?

Upvotes: 22

Views: 46228

Answers (4)

Martin Atkins
Martin Atkins

Reputation: 74789

NOTE: This answer and its associated question are very old at this point, and this answer is now totally stale. I'm leaving it here for historical reference, but nothing here is true of modern Terraform.


At the time of writing, Terraform doesn't have a generalized projection feature in its interpolation language. The "splat syntax" is implemented as a special case for resources.

While deep structure is possible, it is not yet convenient to use, so it's recommended to still keep things relatively flat. In future it is likely that new language features will be added to make this sort of thing more usable.

Upvotes: 4

Vanna
Vanna

Reputation: 2322

A potentially simpler answer is to use the zipmap function.

Starting with an environment variable map compatible with ECS template definitions:

locals {
  shared_env = [
    {
      name  = "DB_CHECK_NAME"
      value = "postgres"
    },
    {
      name  = "DB_CONNECT_TIMEOUT"
      value = "5"
    },
    {
      name  = "DB_DOCKER_HOST_PORT"
      value = "35432"
    },
    {
      name  = "DB_DOCKER_HOST"
      value = "localhost"
    },
    {
      name  = "DB_HOST"
      value = "my-db-host"
    },
    {
      name  = "DB_NAME"
      value = "my-db-name"
    },
    {
      name  = "DB_PASSWORD"
      value = "XXXXXXXX"
    },
    {
      name  = "DB_PORT"
      value = "5432"
    },
    {
      name  = "DB_QUERY_TIMEOUT"
      value = "30"
    },
    {
      name  = "DB_UPGRADE_TIMEOUT"
      value = "300"
    },
    {
      name  = "DB_USER"
      value = "root"
    },
    {
      name  = "REDIS_DOCKER_HOST_PORT"
      value = "6380"
    },
    {
      name  = "REDIS_HOST"
      value = "my-redis"
    },
    {
      name  = "REDIS_PORT"
      value = "6379"
    },
    {
      name  = "SCHEMA_SCRIPTS_PATH"
      value = "db-scripts"
    },
    {
      name  = "USE_LOCAL"
      value = "false"
    }
  ]
}

In the same folder launch terraform console for testing built-in functions. You may need to terraform init if you haven't already.

terraform console

Inside the console type:

zipmap([for m in local.shared_env: m.name], [for m in local.shared_env: m.value])

Observe the output of each list-item-map being a name-value-pair of a single map:

{
  "DB_CHECK_NAME" = "postgres"
  "DB_CONNECT_TIMEOUT" = "5"
  "DB_DOCKER_HOST" = "localhost"
  "DB_DOCKER_HOST_PORT" = "35432"
  "DB_HOST" = "my-db-host"
  "DB_NAME" = "my-db-name"
  "DB_PASSWORD" = "XXXXXXXX"
  "DB_PORT" = "5432"
  "DB_QUERY_TIMEOUT" = "30"
  "DB_UPGRADE_TIMEOUT" = "300"
  "DB_USER" = "root"
  "REDIS_DOCKER_HOST_PORT" = "6380"
  "REDIS_HOST" = "my-redis"
  "REDIS_PORT" = "6379"
  "SCHEMA_SCRIPTS_PATH" = "db-scripts"
  "USE_LOCAL" = "false"
}

Upvotes: 3

Bruce
Bruce

Reputation: 607

UPDATE

Massive fancy features have been added into terraform since Terraform 0.12 was released, e.g., list comprehension, with which the solution is super easy.

locals {
  ids = [for d in var.data: d.id]
  #ids = [for d in var.data: d["id"]]  #same
}

# Then you could get the elements this way,
#     local.ids[0]

Solution before terraform 0.12

template_file can help you out.

data "template_file" "data_id" {
  count = "${length(var.data)}"
  template = "${lookup(var.data[count.index], "id")}"
}

Then you get a list "${data.template_file.data_id.*.rendered}", whose elements are value of "id".

You can get its element by index like this

"${data.template_file.data_id.*.rendered[0]}"

or through function element()

"${element(data.template_file.data_id.*.rendered, 0)}"

Upvotes: 32

Boeboe
Boeboe

Reputation: 2170

If have found a working solution using template rendering to by-pass the list of map's issue:

resource "aws_instance" "k8s_master" {
  count                       = "${var.master_count}"
  ami                         = "${var.ami}"
  instance_type               = "${var.instance_type}"
  vpc_security_group_ids      = ["${aws_security_group.k8s_sg.id}"]
  associate_public_ip_address = false
  subnet_id                   = "${element(var.subnet_ids,count.index % length(var.subnet_ids))}"
  user_data                   = "${file("${path.root}/files/user_data.sh")}"
  iam_instance_profile        = "${aws_iam_instance_profile.master_profile.name}"

  tags = "${merge(
    local.k8s_tags,
    map(
      "Name", "k8s-master-${count.index}",
      "Environment", "${var.environment}"
    )
  )}"
}

data "template_file" "k8s_master_names" {
  count    = "${var.master_count}"
  template = "${lookup(aws_instance.k8s_master.*.tags[count.index], "Name")}"
}

output "k8s_master_name" {
  value = [
    "${data.template_file.k8s_master_names.*.rendered}",
  ]
}

This will result in the following output:

k8s_master_name = [
    k8s-master-0,
    k8s-master-1,
    k8s-master-2
]

Upvotes: 3

Related Questions