Pass nested block as input

I would like to have same configuration for each dynamodb table. Although I thought I should use module, but how to put the nested blocks (e.g. “attribute” in this case for modules)?

// abstract
resource "aws_dynamodb_table" "abstract" {
    server_side_encryption {
        enabled = true
    }
    point_in_time_recovery {
        enabled = true
    }
}

// concrete
resource "user-score" {
    attribute {
        name = "userId"
        type = "S"
    }

    attribute {
        name = "region"
        type = "S"
    }

    // merge with the abstract resource
}

Hi @franzwong!

Unfortunately I don’t really understand what you’re trying to do here, based on your example. I’m not sure what you mean by the aws_dynamodb_table being “abstract”, and the resource you labelled “concrete” doesn’t have a valid resource block header so I’m not sure how to understand your intent for it.

If you could perhaps write a complete example by hand of what you’d like each of these resources to look like in the remote system, written how you would write it if you were not writing a generic module, then hopefully I can understand the pattern you’re trying to describe and thus offer some suggestions on how to create a module abstraction around it.

Thanks!

@apparentlymart Thanks for the reply and sorry for my description.

Suppose I have a few tables like these, you can see I keep repeating the properties “server_side_encryption” and “point_in_time_recovery”. I am thinking if it is possible to do something like “Object Oriented Programming”, putting the common properties to a parent “class”.

resource "aws_dynamodb_table" "user-score" {
    attribute {
        name = "userId"
        type = "S"
    }

    attribute {
        name = "region"
        type = "S"
    }

    server_side_encryption {
        enabled = true
    }
    point_in_time_recovery {
        enabled = true
    }
}

resource "aws_dynamodb_table" "user-profile" {
    attribute {
        name = "userId"
        type = "S"
    }

    server_side_encryption {
        enabled = true
    }
    point_in_time_recovery {
        enabled = true
    }
}

Hi @franzwong! Thanks for sharing that additional context.

Terraform does not support inheritence in the sense that you are thinking of here. However, I think you can get a similar result to what you are imagining using some other Terraform features:

variable "tables" {
  type = map(object({
    attributes = map(string)

    local_secondary_indices = map(object({
      range_key          = string
      projection_type    = string
      non_key_attributes = set(string)
    }))

    global_secondary_indices = map(object({
      hash_key           = string
      range_key          = string
      project_type       = string
      write_capacity     = number
      read_capacity      = number
      projection_type    = string
      non_key_attributes = set(string)
    }))
  }))
}

resource "aws_dynamodb_table" "all" {
  for_each = var.tables

  name = each.key

  dynamic "attribute" {
    for_each = each.value.attributes
    content {
      name = attribute.key
      type = attribute.value
    }
  }

  dynamic "local_secondary_index" {
    for_each = each.value.local_secondary_indices
    content {
      name               = local_secondary_index.key
      range_key          = local_secondary_index.value.range_key
      projection_type    = local_secondary_index.value.projection_type
      non_key_attributes = local_secondary_index.value.non_key_attributes
    }
  }

  dynamic "global_secondary_index" {
    for_each = each.value.global_secondary_indices
    content {
      name               = local_secondary_index.key
      write_capacity     = local_secondary_index.value.write_capacity
      read_capacity      = local_secondary_index.value.read_capacity
      hash_key           = local_secondary_index.value.hash_key
      range_key          = local_secondary_index.value.range_key
      projection_type    = local_secondary_index.value.projection_type
      non_key_attributes = local_secondary_index.value.non_key_attributes
    }
  }

  server_side_encryption {
    enabled = true
  }

  point_in_time_recovery {
    enabled = true
  }
}

This uses a variable provided by the caller to define the tables at a slightly higher level of abstraction than the aws_dynamodb_table resource directly, which then allows you to encode the rule that all tables must have encryption and point in time snapshots enabled while allowing some other attributes to be set by the caller.

A calling module could write the following value for tables in order to get a result similar to what you showed in your example:

  tables = {
    UserScore = {
      attributes = {
        userId = "S"
        region = "S"
      }
      local_secondary_indices  = []
      global_secondary_indices = []
    }
  }

That would then produce a resource instance with the address aws_dynamodb_table.all["UserScore"], using the key from the tables map as the table name.

Thanks @apparentlymart. Your answer solves my question and I’ve learnt some new tricks from you.