How to work with block syntax

I’m not sure if I have the right channel – I’ve spent all of my cognitive resources today trying to construct terraform for building s3 buckets … When I ran across this topic: https://github.com/hashicorp/terraform/issues/21458, I decided to openly say something.

I have a project where an application that I’m building TF for uses about 10 s3 buckets. Some have lifecycle_rules, some are public facing, some have default encryption enabled. This translates to about 2-3 resource blocks per use-case, or I suppose I could define a resource per s3 bucket, which I also don’t want to do, given that they’re all so similar. Either of these approaches seem far less readable and maintainable than it would be if I were able to pass objects in as blocks.

So maybe I’m doing something wrong.

… And while I’m here, is there a good resource for TF 12? I find myself repeatedly trying to make the TF 12 preview article fit every new use-case. (it seems to always be the same article that I’m trying to squeeze some new experience out of.)

Is this what you’re looking for?

locals {
  default_versioning_enabled = true
  default_acl = "private"
  buckets = {
      bucket_1 = {},
      bucket_2 = {
          versioning_enabled = false,
          acl = "public"
      }
   }
}

resource "aws_s3_bucket" "this" {
  for_each = locals.buckets
  bucket = each.key
  acl    = lookup(each.value, "acl", local.default_acl)

  versioning {
    enabled = lookup(each.value, "versioning_enabled", local.default_versioning_enabled)
  }
}

You can expand on this to support all the available attributes for S3 buckets and place them in the locals.buckets or accept them as a variable. We use this technique quite a bit in our environment,

It had not occurred to me to consider the default value and use that in a block, so I’ll remember this approach for other things … but I don’t think this is the sticking point for me – my issue is what to do if there should be an empty block, as I think is the case with lifecycle_rule. How do you specify an entire rule block could be either set or unset within the same construct as the example that was provided?

Part of my issue lies in the nesting of blocks, such as this:

server_side_encryption_configuration {
    rule {
      apply_server_side_encryption_by_default {
        sse_algorithm     = "AES256"
      }
    }
}

For reference, here is the other kind of block that I need to be able to do the same kind of thing with:

lifecycle_rule {
    id = each.value.lifecycle_rl.id
    expiration {
      days = each.value.lifecycle_rl.expiration_days
    }
    enabled = each.value.lifecycle_rl.enabled
    abort_incomplete_multipart_upload_days = each.value.lifecycle_rl.abort_incomplete_multipart_upload_days
}

I figured out how to do what I wanted to do. Since it took a while, and I didn’t get any direct answers, I blogged about it here. I would have liked to have the syntax be a bit more nice (not need to use length(), but I’ve spent too much time just trying to get it work, so I’m kind of done with it.)