Using nested maps in Terraform input variables

July 11, 2018

Here’s my working terraform file:

variable "create" {
  description = "Whether to the resources"
  default     = true
}

variable "config" {
  type        = "map"
  description = "Instances to create (map of maps keyed on 'name')"
}

data "template_file" "config_instance" {
  count    = "${length(var.config)}"
  template = "${element(keys(var.config), count.index)}"
}

data "template_file" "config_instance_type" {
  count    = "${length(var.config)}"
  template = "${lookup(var.config[element(keys(var.config), count.index)], "instance_type")}"
}

data "aws_ami" "ubuntu" {
  most_recent = true

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-amd64-server-*"]
  }

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }

  owners = ["099720109477"] # Canonical
}

resource "aws_instance" "instance" {
  count = "${var.create ? length(var.config) : 0}"

  ami           = "${data.aws_ami.ubuntu.id}"
  instance_type = "${element(data.template_file.config_instance_type.*.rendered, count.index)}"

  tags {
    Name = "${element(data.template_file.config_instance.*.rendered, count.index)}"
  }
}

output "instance_id" {
  value = "${length(aws_instance.instance.*.id) > 0 ? element(concat(aws_instance.instance.*.id, list("")), 0) : ""}"
}

Given the following variable file:

create = true

config = {
  "instance-01" = {
    instance_type = "t2.micro"
  }

  "instance-02" = {
    instance_type = "t2.nano"
  }
}

Terraform would produce the following plan:

$ terraform plan -var-file=test.tfvars
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.

data.template_file.config_instance_type[0]: Refreshing state...
data.template_file.config_instance[1]: Refreshing state...
data.template_file.config_instance[0]: Refreshing state...
data.template_file.config_instance_type[1]: Refreshing state...
data.aws_ami.ubuntu: Refreshing state...

------------------------------------------------------------------------

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:

  + aws_instance.instance[0]
      id:                           <computed>
      ami:                          "ami-5cc39523"
      associate_public_ip_address:  <computed>
      availability_zone:            <computed>
      ebs_block_device.#:           <computed>
      ephemeral_block_device.#:     <computed>
      get_password_data:            "false"
      instance_state:               <computed>
      instance_type:                "t2.micro"
      ipv6_address_count:           <computed>
      ipv6_addresses.#:             <computed>
      key_name:                     <computed>
      network_interface.#:          <computed>
      network_interface_id:         <computed>
      password_data:                <computed>
      placement_group:              <computed>
      primary_network_interface_id: <computed>
      private_dns:                  <computed>
      private_ip:                   <computed>
      public_dns:                   <computed>
      public_ip:                    <computed>
      root_block_device.#:          <computed>
      security_groups.#:            <computed>
      source_dest_check:            "true"
      subnet_id:                    <computed>
      tags.%:                       "1"
      tags.Name:                    "instance-01"
      tenancy:                      <computed>
      volume_tags.%:                <computed>
      vpc_security_group_ids.#:     <computed>

  + aws_instance.instance[1]
      id:                           <computed>
      ami:                          "ami-5cc39523"
      associate_public_ip_address:  <computed>
      availability_zone:            <computed>
      ebs_block_device.#:           <computed>
      ephemeral_block_device.#:     <computed>
      get_password_data:            "false"
      instance_state:               <computed>
      instance_type:                "t2.nano"
      ipv6_address_count:           <computed>
      ipv6_addresses.#:             <computed>
      key_name:                     <computed>
      network_interface.#:          <computed>
      network_interface_id:         <computed>
      password_data:                <computed>
      placement_group:              <computed>
      primary_network_interface_id: <computed>
      private_dns:                  <computed>
      private_ip:                   <computed>
      public_dns:                   <computed>
      public_ip:                    <computed>
      root_block_device.#:          <computed>
      security_groups.#:            <computed>
      source_dest_check:            "true"
      subnet_id:                    <computed>
      tags.%:                       "1"
      tags.Name:                    "instance-02"
      tenancy:                      <computed>
      volume_tags.%:                <computed>
      vpc_security_group_ids.#:     <computed>


Plan: 2 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.

Due to weirdness in HCL library if you wanted to pass input variables using JSON, you would need to wrap the nested map inside a list like so:

{
  "create": true,
  "config": {
    "instance-03": [
      {
        "instance_type": "t2.micro"
      }
    ],
    "instance-04": [
      {
        "instance_type": "t2.nano"
      }
    ]
  }
}

Terraform plan produced:

$ terraform plan -var-file=test.tfvars.json
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.

data.template_file.config_instance[1]: Refreshing state...
data.template_file.config_instance_type[1]: Refreshing state...
data.template_file.config_instance_type[0]: Refreshing state...
data.template_file.config_instance[0]: Refreshing state...
data.aws_ami.ubuntu: Refreshing state...

------------------------------------------------------------------------

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:

  + aws_instance.instance[0]
      id:                           <computed>
      ami:                          "ami-5cc39523"
      associate_public_ip_address:  <computed>
      availability_zone:            <computed>
      ebs_block_device.#:           <computed>
      ephemeral_block_device.#:     <computed>
      get_password_data:            "false"
      instance_state:               <computed>
      instance_type:                "t2.micro"
      ipv6_address_count:           <computed>
      ipv6_addresses.#:             <computed>
      key_name:                     <computed>
      network_interface.#:          <computed>
      network_interface_id:         <computed>
      password_data:                <computed>
      placement_group:              <computed>
      primary_network_interface_id: <computed>
      private_dns:                  <computed>
      private_ip:                   <computed>
      public_dns:                   <computed>
      public_ip:                    <computed>
      root_block_device.#:          <computed>
      security_groups.#:            <computed>
      source_dest_check:            "true"
      subnet_id:                    <computed>
      tags.%:                       "1"
      tags.Name:                    "instance-03"
      tenancy:                      <computed>
      volume_tags.%:                <computed>
      vpc_security_group_ids.#:     <computed>

  + aws_instance.instance[1]
      id:                           <computed>
      ami:                          "ami-5cc39523"
      associate_public_ip_address:  <computed>
      availability_zone:            <computed>
      ebs_block_device.#:           <computed>
      ephemeral_block_device.#:     <computed>
      get_password_data:            "false"
      instance_state:               <computed>
      instance_type:                "t2.nano"
      ipv6_address_count:           <computed>
      ipv6_addresses.#:             <computed>
      key_name:                     <computed>
      network_interface.#:          <computed>
      network_interface_id:         <computed>
      password_data:                <computed>
      placement_group:              <computed>
      primary_network_interface_id: <computed>
      private_dns:                  <computed>
      private_ip:                   <computed>
      public_dns:                   <computed>
      public_ip:                    <computed>
      root_block_device.#:          <computed>
      security_groups.#:            <computed>
      source_dest_check:            "true"
      subnet_id:                    <computed>
      tags.%:                       "1"
      tags.Name:                    "instance-04"
      tenancy:                      <computed>
      volume_tags.%:                <computed>
      vpc_security_group_ids.#:     <computed>


Plan: 2 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.

Please note that if default map is provided for the config variable, it would result in concatenation of those maps!


comments powered by Disqus