Embedded Dynamic Blocks with YAMLDecode

I’m trying to allow developers to update a YAML file with a configuration of an S3 bucket that they are attempting to deploy. This YAML file will then be passed to an S3 module that we loop over using for_each.

I’m able to create multiple buckets from a YAML file if I comment out the dynamic blocks. However when attempting to iterate over the lifecycle_rules I get an error saying that the lifecycle_rule object does not have an attribute named ID.

What’s the best route to handle iterating over these multi-layer embedded objects? I’ve been attempting to follow the discussion from this thread https://discuss.hashicorp.com/t/pattern-to-handle-optional-dynamic-blocks/2384, to no luck.

Terraform Code:

locals {
  s3_data_raw = yamldecode(file("${path.module}/s3_data.yaml"))
}

resource "aws_s3_bucket" "s3" {
  for_each = { for b in local.s3_data_raw.buckets : b.bucket_name => b }

  acl = local.s3_data_raw.acl
  bucket = each.value.bucket_name
  force_destroy = each.value.force_destroy
  
  dynamic "lifecycle_rule" {
    for_each = each.value.lifecycle_rule[*]

    content {
      id = each.value.id
      enabled = each.value.enabled
      prefix = each.value.prefix

      dynamic "transition" {
        for_each = each.value.transition[*]

        content {
          days = each.value.days
          storage_class = each.value.storage_class
        }
      }
    }
  }
}

YAML File:

acl: "private"
buckets:
- bucket_name: "test-yaml-bucket"
  force_destroy: true
  lifecycle_rule:
  - enabled: true
    id: "IA"
    prefix: "data-lake/"
    transition:
    - days: 30
      storage_class: "STANDARD_IA"
  - enabled: true
    id: "Glacier"
    prefix: "data-lake/"
    transition:
    - days: 60
      storage_class: "GLACIER"

- bucket_name: "test-yaml-bucket-2"
  force_destroy: false
  lifecycle_rule:
  - enabled: true
    id: "IA"
    prefix: "data-lake/"
    transition:
    - days: 30
      storage_class: "STANDARD_IA"

Error:

Error: Unsupported attribute

  on s3.tf line 37, in resource "aws_s3_bucket" "s3":
  37:       id = each.value.id
    |----------------
    | each.value is object with 3 attributes

This object does not have an attribute named "id".

Hi @cbergeron1!

I think the problem here is that each is the iterator for the top-level for_each in the resource block, and so it’s looking for id in the main bucket object, rather than in the nested lifecycle_rule object.

The default iterator symbol for a dynamic block is named after the block type you’re declaring, so in your case it would be lifecycle_rule, like this:

  dynamic "lifecycle_rule" {
    for_each = each.value.lifecycle_rule[*]

    content {
      id = lifecycle_rule.value.id
      # ...etc...
    }
  }

Terraform has this default so that in most cases you’ll have access to all levels of this nested repetition from inside the deepest block. You don’t need it in this particular case, but notice that you could in principle refer to each.value.bucket_name or lifecycle_rule.value.id even inside the dynamic "transition"'s content block, because all of these symbols are in scope there.

Hi @apparentlymart !

That solution worked, perfectly the first try. For anyone who stumbles across this thread, the updated code to work in this case is below.

dynamic "lifecycle_rule" {
    for_each = each.value.lifecycle_rule[*]

    content {
      id = lifecycle_rule.value.id
      enabled = lifecycle_rule.value.enabled
      prefix = lifecycle_rule.value.prefix

      dynamic "transition" {
        for_each = lifecycle_rule.value.transition[*]

        content {
          days = transition.value.days
          storage_class = transition.value.storage_class
        }
      }
    }
  }

Thanks, working perfectly for me now!

Was just trying to solve this today and stumbled upon this thread.