Search code examples
amazon-web-servicesterraformterraform-provider-awsaws-iotaws-iot-core

Terraform does not keep state if resources are generated dynamically


I have a variables.tf file that has the following contents:

variable "thing_configuration_set" {
  default = {
    "name" = "customer1"
    "projects" = [
      {
        "name" = "project1"
        "things" = [
          {
            "name"           = "device1"
            "fw_version"     = "1.0"
            "fw_type"        = "generic_device"
            "thing_type"     = "default_device"
          }
        ]
      }
    ]
  }
}

variable "iot_policy" {
  type = string
  sensitive = true
}

locals {
  customer_list = distinct(flatten([for idx, customer in var.thing_configuration_set :
    {
      "customer" : customer.name
    }
  ]))

  project_list = distinct(flatten([for idx, customer in var.thing_configuration_set :
    flatten([for project_idx, project in customer.projects :
      {
        "customer" = customer.name
        "project"  = project.name
      }
    ])
  ]))

  thing_list = flatten([for idx, customer in var.thing_configuration_set :
    flatten([for project_idx, project in customer.projects :
      flatten([for thing in project.things :
        {
          "customer" = customer.name
          "project"  = project.name
          "thing"    = thing
        }
      ])
    ])
  ])

  thing_types = distinct(flatten([for idx, record in local.thing_list :
    {
      "thing_type" = record.thing.thing_type
  }]))

  iot_policy_json = base64decode(var.iot_policy)
}

And then another tf file that defines all the resources needed to setup an IoT thing in aws:

resource "aws_iot_thing_group" "customer" {
  for_each = { for idx, record in local.customer_list : idx => record }

  name = each.value.customer
}

resource "aws_iot_thing_group" "project" {
  for_each = { for idx, record in local.project_list : idx => record }

  name              = each.value.project
  parent_group_name = each.value.customer
}

resource "aws_iot_thing" "thing" {
  for_each = { for idx, record in local.thing_list : idx => record }
  name     = "${each.value.customer}_${each.value.project}_${each.value.thing.name}"
  attributes = {
    bms_fw_version = each.value.thing.bms_fw_version
    bms_type       = each.value.thing.bms_fw_type
  }
  thing_type_name = each.value.thing.thing_type
}

resource "aws_iot_thing_group_membership" "thing_group_membership" {
  for_each         = { for idx, record in local.thing_list : idx => record }
  thing_name       = "${each.value.customer}_${each.value.project}_${each.value.thing.name}"
  thing_group_name = each.value.project
}

resource "aws_iot_thing_type" "thing_type" {
  for_each   = { for idx, record in local.thing_types : idx => record }
  name       = "${each.value.thing_type}"
}

resource "aws_iot_certificate" "things_cert" {
  active = true
}

resource "aws_iot_thing_principal_attachment" "cert_attachment" {
  for_each  = { for idx, record in local.thing_list : idx => record }
  principal = aws_iot_certificate.things_cert.arn
  thing     = aws_iot_thing.thing[each.key].name
}

resource "aws_iot_policy" "policy" {
  name = "connect_subscribe_publish_any"
  policy = local.iot_policy_json
}

resource "aws_iot_policy_attachment" "thing_policy_attachment" {
  policy = aws_iot_policy.tf_policy.name
  target = aws_iot_certificate.things_cert.arn
}

Since we have quite a few resources in AWS already I tried importing them. But when I do terraform plan it still wants to created these 'successfully' imported resources.

For example:

terraform import aws_iot_thing_group.customer Customer1

Would return:

Import successful!

The resources that were imported are shown above. These resources are now in
your Terraform state and will henceforth be managed by Terraform.

If I then run terraform plan it will still list that it will create this customer:

  # aws_iot_thing_group.customer["0"] will be created
  + resource "aws_iot_thing_group" "customer" {
      + arn      = (known after apply)
      + id       = (known after apply)
      + metadata = (known after apply)
      + name     = "Customer1"
      + tags_all = (known after apply)
      + version  = (known after apply)
    }

What am I doing wrong? Is this a bug in terraform?

From what I've seen (very new to terraform) this state only works when you define the resource directly, without any generated stuff (like for-each etc).


Solution

  • As per @luk2302 (h/t) comment and documentation [1], the correct import command is (since it is being run in PowerShell):

    terraform import 'aws_iot_thing_group.customer[\"0\"]' Customer1
    

    [1] https://developer.hashicorp.com/terraform/cli/commands/import#example-import-into-resource-configured-with-for_each