Nested Loop Struggle

Good evening,

We have a Terraform configuration that starts with an aws_servicecatalog_provisioned_product resource to create an EC2 instance. Upon provisioning this resource returns the below outputs (truncated).

    outputs                                = [
        {
            description = "Amazon Machine Image (AMI) used to create the EC2 instance."
            key         = "ImageId"
            value       = "ami-9999"
        },
        {
            description = "The InstanceId of the created EC2 instance."
            key         = "InstanceId"
            value       = "i-9999"
        },
    ]

In the same configuration we’re wanting to tag the volumes attached to the instance using aws_ec2_tag. We understand this is not ideal, but we do not have any influence on the SC product that is provisioning the compute.

How would we go about adding a set of required tags to the instance’s volume(s)? We tried the below, but ran into issues due to our for_each key using attributes that aren’t known until apply. I expect there is probably an easier solution that I’m missing.

data "aws_ebs_volumes" "this" {
  filter {
    name   = "attachment.instance-id"
    values = [for instance in aws_servicecatalog_provisioned_product.this.outputs : instance.value if instance.key == "InstanceID"]
  }
}

locals {
  required_tags = {
    tag1                = var.value1
    tag2                = var.value2
    managed_by = "Terraform"
  }
  volume_tags = flatten(
    [for vol_id in data.aws_ebs_volumes.this.ids :
      [for tag_key, tag_value in local.required_tags : {
        vol_id = vol_id,
        tag_key = tag_key,
        tag_value = tag_value
        }
      ]
  ])
}

resource "aws_ec2_tag" "this" {
  for_each = { for vol_tag in local.volume_tags : "${vol_tag.vol_id}.${vol_tag.tag_key}" => vol_tag }

  resource_id = each.value.vol_id
  key = each.value.tag_key
  value = each.value.tag_value
}

Thank you for your time.

@apparentlymart would you happen to have any design recommendations?

Assuming I have no choice but to utilize -target to provision the SC product first so the volume IDs are no longer unknown.

Hi @Kimmel,

If the data source must depend on the creation of a managed resource before it can be read (the aws_servicecatalog_provisioned_product in this case), then there is no way to use its output during the initial plan operation.

If the aws_ebs_volumes already exist outside the configuration, perhaps there is another way to lookup their data without depending on the result of a managed resource?

If the aws_ebs_volumes are already managed within the configuration, then you should not be using a data source, and should access the required values from the managed resource.

If those cases don’t apply, then multiple configurations or the use of -target would be the workaround for now.

Good afternoon @jbardin ,

Thank you for the reply.

Unfortunately the EBS volumes are managed via the AWS SC product, and we are unable to pass tags to the product to attach to the volumes.

Thankfully the product is only able to create 1-2 volumes with static device names. I’ve broken the aws_ec2_tag resource into two separate resources with the non-root volume being conditionally added. This allows me to utilize the tag key as the for_each key since there will be no duplication.

resource "aws_ec2_tag" "dev_sda1" {
  for_each = local.required_tags

  resource_id = data.aws_ebs_volume.dev_sda1.id
  key         = each.key
  value       = each.value
}

resource "aws_ec2_tag" "xvdf" {
  for_each = var.sc_params.DataVolumeSize == 0 ? {} : local.required_tags

  resource_id = data.aws_ebs_volume.xvdf[0].id
  key         = each.key
  value       = each.value
}

It’s not necessarily elegant, but it works.

It would be nice if the original attempt would work with a distinct() or similar thrown into the mix to inform TF that unique keys were guaranteed.

Thanks again!