From d6f5958e20b0e5334e80d344b3928e979168f84d Mon Sep 17 00:00:00 2001 From: magreenbaum Date: Tue, 22 Apr 2025 19:05:15 -0400 Subject: [PATCH 1/4] s3 table bucket --- README.md | 2 + examples/table-bucket/README.md | 70 ++++++++++++++++++ examples/table-bucket/main.tf | 108 +++++++++++++++++++++++++++ examples/table-bucket/outputs.tf | 64 ++++++++++++++++ examples/table-bucket/variables.tf | 0 examples/table-bucket/versions.tf | 14 ++++ modules/table-bucket/README.md | 65 ++++++++++++++++ modules/table-bucket/main.tf | 115 +++++++++++++++++++++++++++++ modules/table-bucket/outputs.tf | 64 ++++++++++++++++ modules/table-bucket/variables.tf | 53 +++++++++++++ modules/table-bucket/versions.tf | 10 +++ wrappers/table-bucket/README.md | 100 +++++++++++++++++++++++++ wrappers/table-bucket/main.tf | 15 ++++ wrappers/table-bucket/outputs.tf | 5 ++ wrappers/table-bucket/variables.tf | 11 +++ wrappers/table-bucket/versions.tf | 10 +++ 16 files changed, 706 insertions(+) create mode 100644 examples/table-bucket/README.md create mode 100644 examples/table-bucket/main.tf create mode 100644 examples/table-bucket/outputs.tf create mode 100644 examples/table-bucket/variables.tf create mode 100644 examples/table-bucket/versions.tf create mode 100644 modules/table-bucket/README.md create mode 100644 modules/table-bucket/main.tf create mode 100644 modules/table-bucket/outputs.tf create mode 100644 modules/table-bucket/variables.tf create mode 100644 modules/table-bucket/versions.tf create mode 100644 wrappers/table-bucket/README.md create mode 100644 wrappers/table-bucket/main.tf create mode 100644 wrappers/table-bucket/outputs.tf create mode 100644 wrappers/table-bucket/variables.tf create mode 100644 wrappers/table-bucket/versions.tf diff --git a/README.md b/README.md index 7a139a1c..cc438fea 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ These features of S3 bucket configurations are supported: - ALB/NLB log delivery bucket policy - Account-level Public Access Block - S3 Directory Bucket +- S3 Table Bucket ## Usage @@ -124,6 +125,7 @@ Users of Terragrunt can achieve similar results by using modules provided in the - [S3 Inventory](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/tree/master/examples/s3-inventory) - S3 bucket Inventory configuration. - [S3 Account-level Public Access Block](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/tree/master/examples/account-public-access) - Manage S3 account-level Public Access Block. - [S3 Directory Bucket](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/tree/master/examples/directory-bucket) - S3 Directory Bucket configuration. +- [S3 Table Bucket](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/tree/master/examples/table-bucket) - S3 Table Bucket configuration. ## Requirements diff --git a/examples/table-bucket/README.md b/examples/table-bucket/README.md new file mode 100644 index 00000000..78043742 --- /dev/null +++ b/examples/table-bucket/README.md @@ -0,0 +1,70 @@ +# S3 bucket with Cross-Region Replication (CRR) enabled + +Configuration in this directory creates S3 bucket in one region and configures CRR to another bucket in another region. + +Please check [complete example](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/tree/master/examples/complete) to see all other features supported by this module. + +## Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 5.83 | +| [random](#requirement\_random) | >= 2.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 5.83 | +| [random](#provider\_random) | >= 2.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [table\_bucket](#module\_table\_bucket) | ../../modules/table-bucket | n/a | + +## Resources + +| Name | Type | +|------|------| +| [aws_s3tables_namespace.namespace](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3tables_namespace) | resource | +| [random_pet.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet) | resource | +| [aws_caller_identity.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [owner\_account\_id](#output\_owner\_account\_id) | Account ID of the account that owns the table bucket. | +| [s3\_table\_arns](#output\_s3\_table\_arns) | The table ARNs. | +| [s3\_table\_bucket\_arn](#output\_s3\_table\_bucket\_arn) | ARN of the table bucket. | +| [s3\_table\_bucket\_created\_at](#output\_s3\_table\_bucket\_created\_at) | Date and time when the bucket was created. | +| [s3\_table\_created\_at](#output\_s3\_table\_created\_at) | Dates and times when the tables were created. | +| [s3\_table\_created\_by](#output\_s3\_table\_created\_by) | Account IDs of the accounts that created the tables | +| [s3\_table\_metadata\_locations](#output\_s3\_table\_metadata\_locations) | Locations of table metadata. | +| [s3\_table\_modified\_at](#output\_s3\_table\_modified\_at) | Dates and times when the tables was last modified. | +| [s3\_table\_modified\_by](#output\_s3\_table\_modified\_by) | Account IDs of the accounts that last modified the tables. | +| [s3\_table\_owner\_account\_ids](#output\_s3\_table\_owner\_account\_ids) | Account IDs of the accounts that owns the tables. | +| [s3\_table\_types](#output\_s3\_table\_types) | Types of the tables. One of customer or aws. | +| [s3\_table\_version\_tokens](#output\_s3\_table\_version\_tokens) | Identifiers for the current version of table data. | +| [s3\_table\_warehouse\_locations](#output\_s3\_table\_warehouse\_locations) | S3 URIs pointing to the S3 Bucket that contains the table data. | + diff --git a/examples/table-bucket/main.tf b/examples/table-bucket/main.tf new file mode 100644 index 00000000..5371bacb --- /dev/null +++ b/examples/table-bucket/main.tf @@ -0,0 +1,108 @@ +provider "aws" { + region = "us-east-1" # CloudFront expects ACM resources in us-east-1 region only + + # Make it faster by skipping something + skip_metadata_api_check = true + skip_region_validation = true + skip_credentials_validation = true +} + +locals { + bucket_name = "s3-table-bucket-${random_pet.this.id}" +} + +data "aws_caller_identity" "this" {} + +resource "random_pet" "this" { + length = 2 +} + +module "table_bucket" { + source = "../../modules/table-bucket" + + table_bucket_name = local.bucket_name + + maintenance_configuration = { + iceberg_unreferenced_file_removal = { + status = "enabled" + + settings = { + non_current_days = 7 + unreferenced_days = 3 + } + } + } + + create_table_bucket_policy = true + + table_bucket_policy_statements = [ + { + effect = "Allow" + principals = [{ + type = "AWS" + identifiers = [data.aws_caller_identity.this.account_id] + }] + actions = [ + "s3tables:GetTableData", + "s3tables:GetTableMetadataLocation" + ] + s3_paths = ["table/*"] + } + ] + + + tables = { + table1 = { + format = "ICEBERG" + namespace = aws_s3tables_namespace.namespace.namespace + + maintenance_configuration = { + iceberg_compaction = { + status = "enabled" + settings = { + target_file_size_mb = 64 + } + } + iceberg_snapshot_management = { + status = "enabled" + settings = { + max_snapshot_age_hours = 40 + min_snapshots_to_keep = 3 + } + } + } + + create_table_policy = true + policy_statements = [ + { + sid = "DeleteTable" + effect = "Allow" + principals = [{ + type = "AWS" + identifiers = [data.aws_caller_identity.this.account_id] + }] + actions = [ + "s3tables:DeleteTable", + "s3tables:UpdateTableMetadataLocation", + "s3tables:PutTableData", + "s3tables:GetTableMetadataLocation" + ] + } + ] + } + table2 = { + format = "ICEBERG" + name = "table2" + namespace = aws_s3tables_namespace.namespace.namespace + } + table3 = { + format = "ICEBERG" + namespace = aws_s3tables_namespace.namespace.namespace + } + } +} + +resource "aws_s3tables_namespace" "namespace" { + namespace = "example_namespace" + table_bucket_arn = module.table_bucket.s3_table_bucket_arn +} diff --git a/examples/table-bucket/outputs.tf b/examples/table-bucket/outputs.tf new file mode 100644 index 00000000..637e493a --- /dev/null +++ b/examples/table-bucket/outputs.tf @@ -0,0 +1,64 @@ +output "s3_table_bucket_arn" { + description = "ARN of the table bucket." + value = module.table_bucket.s3_table_bucket_arn +} + +output "s3_table_bucket_created_at" { + description = "Date and time when the bucket was created." + value = module.table_bucket.s3_table_bucket_created_at +} + +output "owner_account_id" { + description = "Account ID of the account that owns the table bucket." + value = module.table_bucket.s3_table_bucket_owner_account_id +} + +output "s3_table_arns" { + description = "The table ARNs." + value = module.table_bucket.s3_table_arns +} + +output "s3_table_created_at" { + description = "Dates and times when the tables were created." + value = module.table_bucket.s3_table_created_at +} + +output "s3_table_created_by" { + description = "Account IDs of the accounts that created the tables" + value = module.table_bucket.s3_table_created_by +} + +output "s3_table_metadata_locations" { + description = "Locations of table metadata." + value = module.table_bucket.s3_table_metadata_locations +} + +output "s3_table_modified_at" { + description = "Dates and times when the tables was last modified." + value = module.table_bucket.s3_table_modified_at +} + +output "s3_table_modified_by" { + description = "Account IDs of the accounts that last modified the tables." + value = module.table_bucket.s3_table_modified_by +} + +output "s3_table_owner_account_ids" { + description = "Account IDs of the accounts that owns the tables." + value = module.table_bucket.s3_table_owner_account_ids +} + +output "s3_table_types" { + description = "Types of the tables. One of customer or aws." + value = module.table_bucket.s3_table_types +} + +output "s3_table_version_tokens" { + description = "Identifiers for the current version of table data." + value = module.table_bucket.s3_table_version_tokens +} + +output "s3_table_warehouse_locations" { + description = "S3 URIs pointing to the S3 Bucket that contains the table data." + value = module.table_bucket.s3_table_warehouse_locations +} diff --git a/examples/table-bucket/variables.tf b/examples/table-bucket/variables.tf new file mode 100644 index 00000000..e69de29b diff --git a/examples/table-bucket/versions.tf b/examples/table-bucket/versions.tf new file mode 100644 index 00000000..9ef2d5d8 --- /dev/null +++ b/examples/table-bucket/versions.tf @@ -0,0 +1,14 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.83" + } + random = { + source = "hashicorp/random" + version = ">= 2.0" + } + } +} diff --git a/modules/table-bucket/README.md b/modules/table-bucket/README.md new file mode 100644 index 00000000..03e0b2b1 --- /dev/null +++ b/modules/table-bucket/README.md @@ -0,0 +1,65 @@ +# S3 Table Bucket + +Creates S3 Table Bucket and Tables with various configurations. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 5.83 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 5.83 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_s3tables_table.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3tables_table) | resource | +| [aws_s3tables_table_bucket.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3tables_table_bucket) | resource | +| [aws_s3tables_table_bucket_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3tables_table_bucket_policy) | resource | +| [aws_s3tables_table_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3tables_table_policy) | resource | +| [aws_iam_policy_document.table_bucket_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.table_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [create](#input\_create) | Whether to create s3 table resources | `bool` | `true` | no | +| [create\_table\_bucket\_policy](#input\_create\_table\_bucket\_policy) | Whether to create s3 table bucket policy | `bool` | `false` | no | +| [maintenance\_configuration](#input\_maintenance\_configuration) | Map of table bucket maintenance configurations | `any` | `{}` | no | +| [table\_bucket\_name](#input\_table\_bucket\_name) | Name of the table bucket. Must be between 3 and 63 characters in length. Can consist of lowercase letters, numbers, and hyphens, and must begin and end with a lowercase letter or number | `string` | `null` | no | +| [table\_bucket\_override\_policy\_documents](#input\_table\_bucket\_override\_policy\_documents) | List of IAM policy documents that are merged together into the exported document. In merging, statements with non-blank `sid`s will override statements with the same `sid` | `list(string)` | `[]` | no | +| [table\_bucket\_policy](#input\_table\_bucket\_policy) | Amazon Web Services resource-based policy document in JSON format | `string` | `null` | no | +| [table\_bucket\_policy\_statements](#input\_table\_bucket\_policy\_statements) | A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage | `any` | `{}` | no | +| [table\_bucket\_source\_policy\_documents](#input\_table\_bucket\_source\_policy\_documents) | List of IAM policy documents that are merged together into the exported document. Statements must have unique `sid`s | `list(string)` | `[]` | no | +| [tables](#input\_tables) | Map of table configurations | `any` | `{}` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [s3\_table\_arns](#output\_s3\_table\_arns) | The table ARNs. | +| [s3\_table\_bucket\_arn](#output\_s3\_table\_bucket\_arn) | ARN of the table bucket. | +| [s3\_table\_bucket\_created\_at](#output\_s3\_table\_bucket\_created\_at) | Date and time when the bucket was created. | +| [s3\_table\_bucket\_owner\_account\_id](#output\_s3\_table\_bucket\_owner\_account\_id) | Account ID of the account that owns the table bucket. | +| [s3\_table\_created\_at](#output\_s3\_table\_created\_at) | Dates and times when the tables were created. | +| [s3\_table\_created\_by](#output\_s3\_table\_created\_by) | Account IDs of the accounts that created the tables | +| [s3\_table\_metadata\_locations](#output\_s3\_table\_metadata\_locations) | Locations of table metadata. | +| [s3\_table\_modified\_at](#output\_s3\_table\_modified\_at) | Dates and times when the tables was last modified. | +| [s3\_table\_modified\_by](#output\_s3\_table\_modified\_by) | Account IDs of the accounts that last modified the tables. | +| [s3\_table\_owner\_account\_ids](#output\_s3\_table\_owner\_account\_ids) | Account IDs of the accounts that owns the tables. | +| [s3\_table\_types](#output\_s3\_table\_types) | Types of the tables. One of customer or aws. | +| [s3\_table\_version\_tokens](#output\_s3\_table\_version\_tokens) | Identifiers for the current version of table data. | +| [s3\_table\_warehouse\_locations](#output\_s3\_table\_warehouse\_locations) | S3 URIs pointing to the S3 Bucket that contains the table data. | + diff --git a/modules/table-bucket/main.tf b/modules/table-bucket/main.tf new file mode 100644 index 00000000..05a5c26b --- /dev/null +++ b/modules/table-bucket/main.tf @@ -0,0 +1,115 @@ +resource "aws_s3tables_table_bucket" "this" { + count = var.create ? 1 : 0 + + name = var.table_bucket_name + maintenance_configuration = var.maintenance_configuration +} + +resource "aws_s3tables_table_bucket_policy" "this" { + count = var.create && var.create_table_bucket_policy ? 1 : 0 + + resource_policy = var.table_bucket_policy != null ? var.table_bucket_policy : data.aws_iam_policy_document.table_bucket_policy[0].json + table_bucket_arn = aws_s3tables_table_bucket.this[0].arn +} + +data "aws_iam_policy_document" "table_bucket_policy" { + count = var.create && var.create_table_bucket_policy && var.table_bucket_policy == null ? 1 : 0 + + source_policy_documents = var.table_bucket_source_policy_documents + override_policy_documents = var.table_bucket_override_policy_documents + + dynamic "statement" { + for_each = var.table_bucket_policy_statements + + content { + sid = try(statement.value.sid, null) + actions = try(statement.value.actions, null) + not_actions = try(statement.value.not_actions, null) + effect = try(statement.value.effect, null) + resources = try(statement.value.resources, [for path in try(statement.value.s3_paths, ["*"]) : "${aws_s3tables_table_bucket.this[0].arn}/${path}"]) + not_resources = try(statement.value.not_resources, null) + + dynamic "principals" { + for_each = try(statement.value.principals, []) + + content { + type = principals.value.type + identifiers = principals.value.identifiers + } + } + + dynamic "not_principals" { + for_each = try(statement.value.not_principals, []) + + content { + type = not_principals.value.type + identifiers = not_principals.value.identifiers + } + } + + dynamic "condition" { + for_each = try(statement.value.conditions, []) + + content { + test = condition.value.test + values = condition.value.values + variable = condition.value.variable + } + } + } + } +} + +resource "aws_s3tables_table" "this" { + for_each = { for k, v in var.tables : k => v if var.create } + + format = each.value.format + name = try(each.value.table_name, each.key) + namespace = each.value.namespace + table_bucket_arn = aws_s3tables_table_bucket.this[0].arn + maintenance_configuration = try(each.value.maintenance_configuration, null) +} + +resource "aws_s3tables_table_policy" "this" { + for_each = { for k, v in var.tables : k => v if var.create && try(v.create_table_policy, false) } + + name = aws_s3tables_table.this[each.key].name + namespace = each.value.namespace + resource_policy = data.aws_iam_policy_document.table_policy[each.key].json + table_bucket_arn = aws_s3tables_table_bucket.this[0].arn +} + +data "aws_iam_policy_document" "table_policy" { + for_each = { for k, v in var.tables : k => v.policy_statements if var.create && try(v.create_table_policy, false) && can(v.policy_statements) } + + dynamic "statement" { + for_each = each.value + + content { + sid = try(statement.value.sid, null) + actions = try(statement.value.actions, null) + not_actions = try(statement.value.not_actions, null) + effect = try(statement.value.effect, null) + resources = [aws_s3tables_table.this[each.key].arn] + + dynamic "principals" { + for_each = try(statement.value.principals, []) + + content { + type = principals.value.type + identifiers = principals.value.identifiers + } + } + + dynamic "condition" { + for_each = try(statement.value.conditions, []) + + content { + test = condition.value.test + values = condition.value.values + variable = condition.value.variable + } + } + } + } +} diff --git a/modules/table-bucket/outputs.tf b/modules/table-bucket/outputs.tf new file mode 100644 index 00000000..fb952f0e --- /dev/null +++ b/modules/table-bucket/outputs.tf @@ -0,0 +1,64 @@ +output "s3_table_bucket_arn" { + description = "ARN of the table bucket." + value = try(aws_s3tables_table_bucket.this[0].arn, null) +} + +output "s3_table_bucket_created_at" { + description = "Date and time when the bucket was created." + value = try(aws_s3tables_table_bucket.this[0].created_at, null) +} + +output "s3_table_bucket_owner_account_id" { + description = "Account ID of the account that owns the table bucket." + value = try(aws_s3tables_table_bucket.this[0].owner_account_id, null) +} + +output "s3_table_arns" { + description = "The table ARNs." + value = { for k, v in aws_s3tables_table.this : k => v.arn } +} + +output "s3_table_created_at" { + description = "Dates and times when the tables were created." + value = { for k, v in aws_s3tables_table.this : k => v.created_at } +} + +output "s3_table_created_by" { + description = "Account IDs of the accounts that created the tables" + value = { for k, v in aws_s3tables_table.this : k => v.created_by } +} + +output "s3_table_metadata_locations" { + description = "Locations of table metadata." + value = { for k, v in aws_s3tables_table.this : k => v.metadata_location } +} + +output "s3_table_modified_at" { + description = "Dates and times when the tables was last modified." + value = { for k, v in aws_s3tables_table.this : k => v.modified_at } +} + +output "s3_table_modified_by" { + description = "Account IDs of the accounts that last modified the tables." + value = { for k, v in aws_s3tables_table.this : k => v.modified_by } +} + +output "s3_table_owner_account_ids" { + description = "Account IDs of the accounts that owns the tables." + value = { for k, v in aws_s3tables_table.this : k => v.owner_account_id } +} + +output "s3_table_types" { + description = "Types of the tables. One of customer or aws." + value = { for k, v in aws_s3tables_table.this : k => v.type } +} + +output "s3_table_version_tokens" { + description = "Identifiers for the current version of table data." + value = { for k, v in aws_s3tables_table.this : k => v.version_token } +} + +output "s3_table_warehouse_locations" { + description = "S3 URIs pointing to the S3 Bucket that contains the table data." + value = { for k, v in aws_s3tables_table.this : k => v.warehouse_location } +} diff --git a/modules/table-bucket/variables.tf b/modules/table-bucket/variables.tf new file mode 100644 index 00000000..3d332407 --- /dev/null +++ b/modules/table-bucket/variables.tf @@ -0,0 +1,53 @@ +variable "create" { + description = "Whether to create s3 table resources" + type = bool + default = true +} + +variable "table_bucket_name" { + description = "Name of the table bucket. Must be between 3 and 63 characters in length. Can consist of lowercase letters, numbers, and hyphens, and must begin and end with a lowercase letter or number" + type = string + default = null +} + +variable "maintenance_configuration" { + description = "Map of table bucket maintenance configurations" + type = any + default = {} +} + +variable "create_table_bucket_policy" { + description = "Whether to create s3 table bucket policy" + type = bool + default = false +} + +variable "table_bucket_policy" { + description = "Amazon Web Services resource-based policy document in JSON format" + type = string + default = null +} + +variable "table_bucket_source_policy_documents" { + description = "List of IAM policy documents that are merged together into the exported document. Statements must have unique `sid`s" + type = list(string) + default = [] +} + +variable "table_bucket_override_policy_documents" { + description = "List of IAM policy documents that are merged together into the exported document. In merging, statements with non-blank `sid`s will override statements with the same `sid`" + type = list(string) + default = [] +} + +variable "table_bucket_policy_statements" { + description = "A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage" + type = any + default = {} +} + +variable "tables" { + description = "Map of table configurations" + type = any + default = {} +} diff --git a/modules/table-bucket/versions.tf b/modules/table-bucket/versions.tf new file mode 100644 index 00000000..e0d68841 --- /dev/null +++ b/modules/table-bucket/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.83" + } + } +} diff --git a/wrappers/table-bucket/README.md b/wrappers/table-bucket/README.md new file mode 100644 index 00000000..57f82ce7 --- /dev/null +++ b/wrappers/table-bucket/README.md @@ -0,0 +1,100 @@ +# Wrapper for module: `modules/table-bucket` + +The configuration in this directory contains an implementation of a single module wrapper pattern, which allows managing several copies of a module in places where using the native Terraform 0.13+ `for_each` feature is not feasible (e.g., with Terragrunt). + +You may want to use a single Terragrunt configuration file to manage multiple resources without duplicating `terragrunt.hcl` files for each copy of the same module. + +This wrapper does not implement any extra functionality. + +## Usage with Terragrunt + +`terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/s3-bucket/aws//wrappers/table-bucket" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git//wrappers/table-bucket?ref=master" +} + +inputs = { + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Usage with Terraform + +```hcl +module "wrapper" { + source = "terraform-aws-modules/s3-bucket/aws//wrappers/table-bucket" + + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Example: Manage multiple S3 buckets in one Terragrunt layer + +`eu-west-1/s3-buckets/terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/s3-bucket/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git//wrappers?ref=master" +} + +inputs = { + defaults = { + force_destroy = true + + attach_elb_log_delivery_policy = true + attach_lb_log_delivery_policy = true + attach_deny_insecure_transport_policy = true + attach_require_latest_tls_policy = true + } + + items = { + bucket1 = { + bucket = "my-random-bucket-1" + } + bucket2 = { + bucket = "my-random-bucket-2" + tags = { + Secure = "probably" + } + } + } +} +``` diff --git a/wrappers/table-bucket/main.tf b/wrappers/table-bucket/main.tf new file mode 100644 index 00000000..b33d64b0 --- /dev/null +++ b/wrappers/table-bucket/main.tf @@ -0,0 +1,15 @@ +module "wrapper" { + source = "../../modules/table-bucket" + + for_each = var.items + + create = try(each.value.create, var.defaults.create, true) + create_table_bucket_policy = try(each.value.create_table_bucket_policy, var.defaults.create_table_bucket_policy, false) + maintenance_configuration = try(each.value.maintenance_configuration, var.defaults.maintenance_configuration, {}) + table_bucket_name = try(each.value.table_bucket_name, var.defaults.table_bucket_name, null) + table_bucket_override_policy_documents = try(each.value.table_bucket_override_policy_documents, var.defaults.table_bucket_override_policy_documents, []) + table_bucket_policy = try(each.value.table_bucket_policy, var.defaults.table_bucket_policy, null) + table_bucket_policy_statements = try(each.value.table_bucket_policy_statements, var.defaults.table_bucket_policy_statements, {}) + table_bucket_source_policy_documents = try(each.value.table_bucket_source_policy_documents, var.defaults.table_bucket_source_policy_documents, []) + tables = try(each.value.tables, var.defaults.tables, {}) +} diff --git a/wrappers/table-bucket/outputs.tf b/wrappers/table-bucket/outputs.tf new file mode 100644 index 00000000..ec6da5f4 --- /dev/null +++ b/wrappers/table-bucket/outputs.tf @@ -0,0 +1,5 @@ +output "wrapper" { + description = "Map of outputs of a wrapper." + value = module.wrapper + # sensitive = false # No sensitive module output found +} diff --git a/wrappers/table-bucket/variables.tf b/wrappers/table-bucket/variables.tf new file mode 100644 index 00000000..a6ea0962 --- /dev/null +++ b/wrappers/table-bucket/variables.tf @@ -0,0 +1,11 @@ +variable "defaults" { + description = "Map of default values which will be used for each item." + type = any + default = {} +} + +variable "items" { + description = "Maps of items to create a wrapper from. Values are passed through to the module." + type = any + default = {} +} diff --git a/wrappers/table-bucket/versions.tf b/wrappers/table-bucket/versions.tf new file mode 100644 index 00000000..e0d68841 --- /dev/null +++ b/wrappers/table-bucket/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.83" + } + } +} From 5762792e1c8868a87249885ed72c203902c19d84 Mon Sep 17 00:00:00 2001 From: magreenbaum Date: Tue, 22 Apr 2025 19:59:08 -0400 Subject: [PATCH 2/4] adjustments --- examples/table-bucket/main.tf | 13 +++++-------- modules/table-bucket/main.tf | 11 ++++++++++- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/examples/table-bucket/main.tf b/examples/table-bucket/main.tf index 5371bacb..b69dcfb5 100644 --- a/examples/table-bucket/main.tf +++ b/examples/table-bucket/main.tf @@ -1,5 +1,5 @@ provider "aws" { - region = "us-east-1" # CloudFront expects ACM resources in us-east-1 region only + region = "eu-west-1" # Make it faster by skipping something skip_metadata_api_check = true @@ -13,10 +13,6 @@ locals { data "aws_caller_identity" "this" {} -resource "random_pet" "this" { - length = 2 -} - module "table_bucket" { source = "../../modules/table-bucket" @@ -34,7 +30,6 @@ module "table_bucket" { } create_table_bucket_policy = true - table_bucket_policy_statements = [ { effect = "Allow" @@ -46,11 +41,9 @@ module "table_bucket" { "s3tables:GetTableData", "s3tables:GetTableMetadataLocation" ] - s3_paths = ["table/*"] } ] - tables = { table1 = { format = "ICEBERG" @@ -102,6 +95,10 @@ module "table_bucket" { } } +resource "random_pet" "this" { + length = 2 +} + resource "aws_s3tables_namespace" "namespace" { namespace = "example_namespace" table_bucket_arn = module.table_bucket.s3_table_bucket_arn diff --git a/modules/table-bucket/main.tf b/modules/table-bucket/main.tf index 05a5c26b..0d4d0be3 100644 --- a/modules/table-bucket/main.tf +++ b/modules/table-bucket/main.tf @@ -26,7 +26,7 @@ data "aws_iam_policy_document" "table_bucket_policy" { actions = try(statement.value.actions, null) not_actions = try(statement.value.not_actions, null) effect = try(statement.value.effect, null) - resources = try(statement.value.resources, [for path in try(statement.value.s3_paths, ["*"]) : "${aws_s3tables_table_bucket.this[0].arn}/${path}"]) + resources = try(statement.value.resources, ["${aws_s3tables_table_bucket.this[0].arn}/table/*"]) not_resources = try(statement.value.not_resources, null) dynamic "principals" { @@ -101,6 +101,15 @@ data "aws_iam_policy_document" "table_policy" { } } + dynamic "not_principals" { + for_each = try(statement.value.not_principals, []) + + content { + type = not_principals.value.type + identifiers = not_principals.value.identifiers + } + } + dynamic "condition" { for_each = try(statement.value.conditions, []) From 66febfd7539286ac3c6a000c6e65e157bf1b2e15 Mon Sep 17 00:00:00 2001 From: magreenbaum Date: Wed, 23 Apr 2025 19:10:28 -0400 Subject: [PATCH 3/4] update conditional --- modules/table-bucket/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/table-bucket/main.tf b/modules/table-bucket/main.tf index 0d4d0be3..f2eb65f1 100644 --- a/modules/table-bucket/main.tf +++ b/modules/table-bucket/main.tf @@ -80,7 +80,7 @@ resource "aws_s3tables_table_policy" "this" { } data "aws_iam_policy_document" "table_policy" { - for_each = { for k, v in var.tables : k => v.policy_statements if var.create && try(v.create_table_policy, false) && can(v.policy_statements) } + for_each = { for k, v in var.tables : k => v.policy_statements if var.create && try(v.create_table_policy, false) } dynamic "statement" { for_each = each.value From 9913e654b3029b270b7b64348c3575e519373e2d Mon Sep 17 00:00:00 2001 From: magreenbaum Date: Wed, 23 Apr 2025 19:15:41 -0400 Subject: [PATCH 4/4] update readme description --- examples/table-bucket/README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/examples/table-bucket/README.md b/examples/table-bucket/README.md index 78043742..874bd561 100644 --- a/examples/table-bucket/README.md +++ b/examples/table-bucket/README.md @@ -1,8 +1,6 @@ -# S3 bucket with Cross-Region Replication (CRR) enabled +# S3 Table Bucket -Configuration in this directory creates S3 bucket in one region and configures CRR to another bucket in another region. - -Please check [complete example](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/tree/master/examples/complete) to see all other features supported by this module. +Configuration in this directory creates S3 table bucket with bucket policy and S3 Tables with table policies. ## Usage