From 855b4c68a5d8141644ad3df079cab2cf0e2133d5 Mon Sep 17 00:00:00 2001 From: magreenbaum Date: Sat, 1 Feb 2025 10:37:59 -0500 Subject: [PATCH 1/8] feat/support directory bucket --- examples/directory-bucket/main.tf | 111 +++++++++++++++++++ examples/directory-bucket/outputs.tf | 9 ++ examples/directory-bucket/variables.tf | 0 examples/directory-bucket/versions.tf | 14 +++ modules/directory-bucket/main.tf | 144 +++++++++++++++++++++++++ modules/directory-bucket/outputs.tf | 9 ++ modules/directory-bucket/variables.tf | 77 +++++++++++++ modules/directory-bucket/versions.tf | 10 ++ wrappers/directory-bucket/README.md | 100 +++++++++++++++++ wrappers/directory-bucket/main.tf | 19 ++++ wrappers/directory-bucket/outputs.tf | 5 + wrappers/directory-bucket/variables.tf | 11 ++ wrappers/directory-bucket/versions.tf | 10 ++ 13 files changed, 519 insertions(+) create mode 100644 examples/directory-bucket/main.tf create mode 100644 examples/directory-bucket/outputs.tf create mode 100644 examples/directory-bucket/variables.tf create mode 100644 examples/directory-bucket/versions.tf create mode 100644 modules/directory-bucket/main.tf create mode 100644 modules/directory-bucket/outputs.tf create mode 100644 modules/directory-bucket/variables.tf create mode 100644 modules/directory-bucket/versions.tf create mode 100644 wrappers/directory-bucket/README.md create mode 100644 wrappers/directory-bucket/main.tf create mode 100644 wrappers/directory-bucket/outputs.tf create mode 100644 wrappers/directory-bucket/variables.tf create mode 100644 wrappers/directory-bucket/versions.tf diff --git a/examples/directory-bucket/main.tf b/examples/directory-bucket/main.tf new file mode 100644 index 00000000..d7956486 --- /dev/null +++ b/examples/directory-bucket/main.tf @@ -0,0 +1,111 @@ +locals { + region = "eu-west-1" + zone_id = "euw1-az1" +} + +provider "aws" { + region = local.region + + # Make it faster by skipping something + skip_metadata_api_check = true + skip_region_validation = true + skip_credentials_validation = true +} + +data "aws_caller_identity" "current" {} + +module "directory_bucket" { + source = "../../modules/directory-bucket" + + bucket_name_prefix = random_pet.this.id + availability_zone_id = local.zone_id + + server_side_encryption_configuration = { + sse_algorithm = "aws:kms" + kms_master_key_id = aws_kms_key.objects.id + } + + lifecycle_rules = { + all = { + id = "test" + status = "Enabled" + abort_incomplete_multipart_upload = { + days_after_initiation = 7 + } + expiration = { + days = 7 + } + }, + logs = { + status = "Enabled" + expiration = { + days = 5 + } + filter = { + prefix = "logs/" + object_size_less_than = 10 + } + }, + other = { + id = "other" + status = "Enabled" + expiration = { + days = 2 + } + filter = { + prefix = "other/" + } + } + } + + create_bucket_policy = true + policy_statements = { + write = { + sid = "ReadWriteAccess" + effect = "Allow" + + actions = [ + "s3express:CreateSession", + ] + + principals = [ + { + type = "AWS" + identifiers = [data.aws_caller_identity.current.account_id] + } + ] + } + readonly = { + sid = "ReadOnlyAccess" + effect = "Allow" + + actions = [ + "s3express:CreateSession", + ] + + principals = [ + { + type = "AWS" + identifiers = [data.aws_caller_identity.current.account_id] + } + ] + + conditions = [ + { + test = "StringEquals" + values = ["ReadOnly"] + variable = "s3express:SessionMode" + } + ] + } + } +} + +resource "random_pet" "this" { + length = 2 +} + +resource "aws_kms_key" "objects" { + description = "KMS key is used to encrypt bucket objects" + deletion_window_in_days = 7 +} diff --git a/examples/directory-bucket/outputs.tf b/examples/directory-bucket/outputs.tf new file mode 100644 index 00000000..6ccf6a99 --- /dev/null +++ b/examples/directory-bucket/outputs.tf @@ -0,0 +1,9 @@ +output "directory_bucket_name" { + description = "Name of the directory bucket." + value = module.directory_bucket.directory_bucket_name +} + +output "directory_bucket_arn" { + description = "ARN of the directory bucket." + value = module.directory_bucket.directory_bucket_arn +} diff --git a/examples/directory-bucket/variables.tf b/examples/directory-bucket/variables.tf new file mode 100644 index 00000000..e69de29b diff --git a/examples/directory-bucket/versions.tf b/examples/directory-bucket/versions.tf new file mode 100644 index 00000000..9ef2d5d8 --- /dev/null +++ b/examples/directory-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/directory-bucket/main.tf b/modules/directory-bucket/main.tf new file mode 100644 index 00000000..f8f4db14 --- /dev/null +++ b/modules/directory-bucket/main.tf @@ -0,0 +1,144 @@ +locals { + bucket_name = "${var.bucket_name_prefix}--${var.availability_zone_id}--x-s3" +} + +resource "aws_s3_directory_bucket" "this" { + count = var.create ? 1 : 0 + + bucket = local.bucket_name + data_redundancy = var.data_redundancy + force_destroy = var.force_destroy + type = var.type + + location { + name = var.availability_zone_id + type = var.location_type + } +} + +resource "aws_s3_bucket_server_side_encryption_configuration" "this" { + count = var.create && length(var.server_side_encryption_configuration) > 0 ? 1 : 0 + + bucket = aws_s3_directory_bucket.this[0].bucket + + rule { + bucket_key_enabled = true + + apply_server_side_encryption_by_default { + sse_algorithm = var.server_side_encryption_configuration.sse_algorithm + kms_master_key_id = try(var.server_side_encryption_configuration.kms_master_key_id, null) + } + } +} + +resource "aws_s3_bucket_lifecycle_configuration" "this" { + count = var.create && length(var.lifecycle_rules) > 0 ? 1 : 0 + + bucket = aws_s3_directory_bucket.this[0].bucket + + dynamic "rule" { + for_each = var.lifecycle_rules + + content { + id = try(rule.value.id, rule.key) + status = try(rule.value.enabled ? "Enabled" : "Disabled", tobool(rule.value.status) ? "Enabled" : "Disabled", title(lower(rule.value.status))) + + # Max 1 block - abort_incomplete_multipart_upload + dynamic "abort_incomplete_multipart_upload" { + for_each = try([rule.value.abort_incomplete_multipart_upload_days], []) + + content { + days_after_initiation = try(rule.value.abort_incomplete_multipart_upload_days, null) + } + } + + # Max 1 block - expiration + dynamic "expiration" { + for_each = try(flatten([rule.value.expiration]), []) + + content { + days = try(expiration.value.days, null) + } + } + + # Max 1 block - filter - with one key argument or a single tag + dynamic "filter" { + for_each = [for v in try(flatten([rule.value.filter]), []) : v if max(length(keys(v))) == 1] + + content { + object_size_greater_than = try(filter.value.object_size_greater_than, null) + object_size_less_than = try(filter.value.object_size_less_than, null) + prefix = try(filter.value.prefix, null) + } + } + + # Max 1 block - filter - with more than one key arguments or multiple tags + dynamic "filter" { + for_each = [for v in try(flatten([rule.value.filter]), []) : v if max(length(keys(v))) > 1] + + content { + and { + object_size_greater_than = try(filter.value.object_size_greater_than, null) + object_size_less_than = try(filter.value.object_size_less_than, null) + prefix = try(filter.value.prefix, null) + } + } + } + } + } +} + +resource "aws_s3_bucket_policy" "this" { + count = var.create && var.create_bucket_policy ? 1 : 0 + + bucket = aws_s3_directory_bucket.this[0].bucket + policy = data.aws_iam_policy_document.this[0].json +} + +data "aws_iam_policy_document" "this" { + count = var.create && var.create_bucket_policy ? 1 : 0 + + source_policy_documents = var.source_policy_documents + override_policy_documents = var.override_policy_documents + + dynamic "statement" { + for_each = var.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, [aws_s3_directory_bucket.this[0].arn]) + 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 + } + } + } + } +} diff --git a/modules/directory-bucket/outputs.tf b/modules/directory-bucket/outputs.tf new file mode 100644 index 00000000..8e768d59 --- /dev/null +++ b/modules/directory-bucket/outputs.tf @@ -0,0 +1,9 @@ +output "directory_bucket_name" { + description = "Name of the directory bucket." + value = try(aws_s3_directory_bucket.this[0].bucket, null) +} + +output "directory_bucket_arn" { + description = "ARN of the directory bucket." + value = try(aws_s3_directory_bucket.this[0].arn, null) +} diff --git a/modules/directory-bucket/variables.tf b/modules/directory-bucket/variables.tf new file mode 100644 index 00000000..54a25555 --- /dev/null +++ b/modules/directory-bucket/variables.tf @@ -0,0 +1,77 @@ +variable "create" { + description = "Whether to create directory bucket resources" + type = bool + default = true +} + +variable "bucket_name_prefix" { + description = "Bucket name prefix" + type = string + default = null +} + +variable "data_redundancy" { + description = "Data redundancy. Valid values: `SingleAvailabilityZone`" + type = string + default = null +} + +variable "force_destroy" { + description = "Boolean that indicates all objects should be deleted from the bucket when the bucket is destroyed so that the bucket can be destroyed without error. These objects are not recoverable" + type = bool + default = null +} + +variable "type" { + description = "Bucket type. Valid values: `Directory`" + type = string + default = "Directory" +} + +variable "availability_zone_id" { + description = "Availability Zone ID or Local Zone ID" + type = string + default = null +} + +variable "location_type" { + description = "Location type. Valid values: `AvailabilityZone` or `LocalZone`" + type = string + default = null +} + +variable "server_side_encryption_configuration" { + description = "Map containing server-side encryption configuration." + type = any + default = {} +} + +variable "lifecycle_rules" { + description = "List of maps containing configuration of object lifecycle management." + type = any + default = {} +} + +variable "create_bucket_policy" { + description = "Whether to create a directory bucket policy." + type = bool + default = false +} + +variable "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 "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 "policy_statements" { + description = "A map of IAM policy statements for custom permission usage" + type = any + default = {} +} diff --git a/modules/directory-bucket/versions.tf b/modules/directory-bucket/versions.tf new file mode 100644 index 00000000..e0d68841 --- /dev/null +++ b/modules/directory-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/directory-bucket/README.md b/wrappers/directory-bucket/README.md new file mode 100644 index 00000000..1efcca77 --- /dev/null +++ b/wrappers/directory-bucket/README.md @@ -0,0 +1,100 @@ +# Wrapper for module: `modules/directory-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/directory-bucket" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git//wrappers/directory-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/directory-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/directory-bucket/main.tf b/wrappers/directory-bucket/main.tf new file mode 100644 index 00000000..6546b651 --- /dev/null +++ b/wrappers/directory-bucket/main.tf @@ -0,0 +1,19 @@ +module "wrapper" { + source = "../../modules/directory-bucket" + + for_each = var.items + + availability_zone_id = try(each.value.availability_zone_id, var.defaults.availability_zone_id, null) + bucket_name_prefix = try(each.value.bucket_name_prefix, var.defaults.bucket_name_prefix, null) + create = try(each.value.create, var.defaults.create, true) + create_bucket_policy = try(each.value.create_bucket_policy, var.defaults.create_bucket_policy, false) + data_redundancy = try(each.value.data_redundancy, var.defaults.data_redundancy, null) + force_destroy = try(each.value.force_destroy, var.defaults.force_destroy, null) + lifecycle_rules = try(each.value.lifecycle_rules, var.defaults.lifecycle_rules, []) + location_type = try(each.value.location_type, var.defaults.location_type, null) + override_policy_documents = try(each.value.override_policy_documents, var.defaults.override_policy_documents, []) + policy_statements = try(each.value.policy_statements, var.defaults.policy_statements, {}) + server_side_encryption_configuration = try(each.value.server_side_encryption_configuration, var.defaults.server_side_encryption_configuration, {}) + source_policy_documents = try(each.value.source_policy_documents, var.defaults.source_policy_documents, []) + type = try(each.value.type, var.defaults.type, "Directory") +} diff --git a/wrappers/directory-bucket/outputs.tf b/wrappers/directory-bucket/outputs.tf new file mode 100644 index 00000000..ec6da5f4 --- /dev/null +++ b/wrappers/directory-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/directory-bucket/variables.tf b/wrappers/directory-bucket/variables.tf new file mode 100644 index 00000000..a6ea0962 --- /dev/null +++ b/wrappers/directory-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/directory-bucket/versions.tf b/wrappers/directory-bucket/versions.tf new file mode 100644 index 00000000..e0d68841 --- /dev/null +++ b/wrappers/directory-bucket/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.83" + } + } +} From ef6f159a81575bbd9dca1582e003cf0c6cb11382 Mon Sep 17 00:00:00 2001 From: magreenbaum Date: Sat, 1 Feb 2025 10:45:22 -0500 Subject: [PATCH 2/8] wrappers --- wrappers/directory-bucket/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wrappers/directory-bucket/main.tf b/wrappers/directory-bucket/main.tf index 6546b651..80ab6c88 100644 --- a/wrappers/directory-bucket/main.tf +++ b/wrappers/directory-bucket/main.tf @@ -9,7 +9,7 @@ module "wrapper" { create_bucket_policy = try(each.value.create_bucket_policy, var.defaults.create_bucket_policy, false) data_redundancy = try(each.value.data_redundancy, var.defaults.data_redundancy, null) force_destroy = try(each.value.force_destroy, var.defaults.force_destroy, null) - lifecycle_rules = try(each.value.lifecycle_rules, var.defaults.lifecycle_rules, []) + lifecycle_rules = try(each.value.lifecycle_rules, var.defaults.lifecycle_rules, {}) location_type = try(each.value.location_type, var.defaults.location_type, null) override_policy_documents = try(each.value.override_policy_documents, var.defaults.override_policy_documents, []) policy_statements = try(each.value.policy_statements, var.defaults.policy_statements, {}) From 76b2d917d5463221e4f2e2f2f2b02ec5196135e5 Mon Sep 17 00:00:00 2001 From: magreenbaum Date: Sat, 1 Feb 2025 11:07:35 -0500 Subject: [PATCH 3/8] update READMEs --- README.md | 1 + examples/directory-bucket/README.md | 57 +++++++++++++++++++++++++++++ modules/directory-bucket/README.md | 57 +++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+) create mode 100644 examples/directory-bucket/README.md create mode 100644 modules/directory-bucket/README.md diff --git a/README.md b/README.md index 0e4e2ef8..c2561f45 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,7 @@ Users of Terragrunt can achieve similar results by using modules provided in the - [S3 Analytics](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/tree/master/examples/s3-analytics) - S3 bucket Analytics Configurations. - [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. ## Requirements diff --git a/examples/directory-bucket/README.md b/examples/directory-bucket/README.md new file mode 100644 index 00000000..e2720505 --- /dev/null +++ b/examples/directory-bucket/README.md @@ -0,0 +1,57 @@ +# S3 directory bucket + +Configuration in this directory creates S3 directory bucket and related resources. + +## 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 | +|------|--------|---------| +| [directory\_bucket](#module\_directory\_bucket) | ../../modules/directory-bucket | n/a | + +## Resources + +| Name | Type | +|------|------| +| [aws_kms_key.objects](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource | +| [random_pet.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet) | resource | +| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [directory\_bucket\_arn](#output\_directory\_bucket\_arn) | ARN of the directory bucket. | +| [directory\_bucket\_name](#output\_directory\_bucket\_name) | Name of the directory bucket. | + diff --git a/modules/directory-bucket/README.md b/modules/directory-bucket/README.md new file mode 100644 index 00000000..7ad5b3a9 --- /dev/null +++ b/modules/directory-bucket/README.md @@ -0,0 +1,57 @@ +# S3 directory bucket + +Creates S3 directory bucket and 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_s3_bucket_lifecycle_configuration.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_lifecycle_configuration) | resource | +| [aws_s3_bucket_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource | +| [aws_s3_bucket_server_side_encryption_configuration.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource | +| [aws_s3_directory_bucket.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_directory_bucket) | resource | +| [aws_iam_policy_document.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [availability\_zone\_id](#input\_availability\_zone\_id) | Availability Zone ID or Local Zone ID | `string` | `null` | no | +| [bucket\_name\_prefix](#input\_bucket\_name\_prefix) | Bucket name prefix | `string` | `null` | no | +| [create](#input\_create) | Whether to create directory bucket resources | `bool` | `true` | no | +| [create\_bucket\_policy](#input\_create\_bucket\_policy) | Whether to create a directory bucket policy. | `bool` | `false` | no | +| [data\_redundancy](#input\_data\_redundancy) | Data redundancy. Valid values: `SingleAvailabilityZone` | `string` | `null` | no | +| [force\_destroy](#input\_force\_destroy) | Boolean that indicates all objects should be deleted from the bucket when the bucket is destroyed so that the bucket can be destroyed without error. These objects are not recoverable | `bool` | `null` | no | +| [lifecycle\_rules](#input\_lifecycle\_rules) | List of maps containing configuration of object lifecycle management. | `any` | `{}` | no | +| [location\_type](#input\_location\_type) | Location type. Valid values: `AvailabilityZone` or `LocalZone` | `string` | `null` | no | +| [override\_policy\_documents](#input\_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 | +| [policy\_statements](#input\_policy\_statements) | A map of IAM policy statements for custom permission usage | `any` | `{}` | no | +| [server\_side\_encryption\_configuration](#input\_server\_side\_encryption\_configuration) | Map containing server-side encryption configuration. | `any` | `{}` | no | +| [source\_policy\_documents](#input\_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 | +| [type](#input\_type) | Bucket type. Valid values: `Directory` | `string` | `"Directory"` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [directory\_bucket\_arn](#output\_directory\_bucket\_arn) | ARN of the directory bucket. | +| [directory\_bucket\_name](#output\_directory\_bucket\_name) | Name of the directory bucket. | + From 249fc87789a69904860e46d3534ea3cb4cf793cb Mon Sep 17 00:00:00 2001 From: magreenbaum Date: Sat, 1 Feb 2025 11:38:33 -0500 Subject: [PATCH 4/8] remove comment about tags since tag filters are not supported in S3 Express lifecycle configurations --- modules/directory-bucket/main.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/directory-bucket/main.tf b/modules/directory-bucket/main.tf index f8f4db14..a328f8a9 100644 --- a/modules/directory-bucket/main.tf +++ b/modules/directory-bucket/main.tf @@ -61,7 +61,7 @@ resource "aws_s3_bucket_lifecycle_configuration" "this" { } } - # Max 1 block - filter - with one key argument or a single tag + # Max 1 block - filter - with one key argument dynamic "filter" { for_each = [for v in try(flatten([rule.value.filter]), []) : v if max(length(keys(v))) == 1] @@ -72,7 +72,7 @@ resource "aws_s3_bucket_lifecycle_configuration" "this" { } } - # Max 1 block - filter - with more than one key arguments or multiple tags + # Max 1 block - filter - with more than one key arguments dynamic "filter" { for_each = [for v in try(flatten([rule.value.filter]), []) : v if max(length(keys(v))) > 1] From 2cf917e62d4884d2019851dd9bc4d0f0f8ca68e4 Mon Sep 17 00:00:00 2001 From: magreenbaum Date: Sun, 9 Feb 2025 09:14:10 -0500 Subject: [PATCH 5/8] move resource for directory bucket into main module --- README.md | 14 ++- examples/complete/README.md | 4 +- examples/complete/versions.tf | 2 +- examples/directory-bucket/README.md | 9 +- examples/directory-bucket/main.tf | 128 ++++++++++++----------- examples/directory-bucket/outputs.tf | 18 ++-- examples/notification/README.md | 4 +- examples/notification/versions.tf | 2 +- examples/object/README.md | 4 +- examples/object/versions.tf | 2 +- examples/s3-analytics/README.md | 4 +- examples/s3-analytics/versions.tf | 2 +- examples/s3-inventory/README.md | 4 +- examples/s3-inventory/versions.tf | 2 +- examples/s3-replication/README.md | 6 +- examples/s3-replication/versions.tf | 2 +- main.tf | 76 +++++++++----- modules/directory-bucket/README.md | 57 ---------- modules/directory-bucket/main.tf | 144 -------------------------- modules/directory-bucket/outputs.tf | 9 -- modules/directory-bucket/variables.tf | 77 -------------- modules/directory-bucket/versions.tf | 10 -- outputs.tf | 10 ++ variables.tf | 31 ++++++ versions.tf | 2 +- wrappers/main.tf | 5 + wrappers/versions.tf | 2 +- 27 files changed, 209 insertions(+), 421 deletions(-) delete mode 100644 modules/directory-bucket/README.md delete mode 100644 modules/directory-bucket/main.tf delete mode 100644 modules/directory-bucket/outputs.tf delete mode 100644 modules/directory-bucket/variables.tf delete mode 100644 modules/directory-bucket/versions.tf diff --git a/README.md b/README.md index c2561f45..a0db17d3 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ These features of S3 bucket configurations are supported: - ELB log delivery bucket policy - ALB/NLB log delivery bucket policy - Account-level Public Access Block +- S3 Directory Bucket ## Usage @@ -131,13 +132,13 @@ Users of Terragrunt can achieve similar results by using modules provided in the | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | >= 5.70 | +| [aws](#requirement\_aws) | >= 5.83 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 5.70 | +| [aws](#provider\_aws) | >= 5.83 | ## Modules @@ -166,6 +167,8 @@ No modules. | [aws_s3_bucket_server_side_encryption_configuration.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource | | [aws_s3_bucket_versioning.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_versioning) | resource | | [aws_s3_bucket_website_configuration.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_website_configuration) | resource | +| [aws_s3_directory_bucket.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_directory_bucket) | resource | +| [aws_availability_zone.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zone) | data source | | [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | | [aws_canonical_user_id.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/canonical_user_id) | data source | | [aws_iam_policy_document.access_log_delivery](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | @@ -208,6 +211,7 @@ No modules. | [attach\_policy](#input\_attach\_policy) | Controls if S3 bucket should have bucket policy attached (set to `true` to use value of `policy` as bucket policy) | `bool` | `false` | no | | [attach\_public\_policy](#input\_attach\_public\_policy) | Controls if a user defined public bucket policy will be attached (set to `false` to allow upstream to apply defaults to the bucket) | `bool` | `true` | no | | [attach\_require\_latest\_tls\_policy](#input\_attach\_require\_latest\_tls\_policy) | Controls if S3 bucket should require the latest version of TLS | `bool` | `false` | no | +| [availability\_zone](#input\_availability\_zone) | Availability Zone ID or Local Zone ID | `string` | `null` | no | | [block\_public\_acls](#input\_block\_public\_acls) | Whether Amazon S3 should block public ACLs for this bucket. | `bool` | `true` | no | | [block\_public\_policy](#input\_block\_public\_policy) | Whether Amazon S3 should block public bucket policies for this bucket. | `bool` | `true` | no | | [bucket](#input\_bucket) | (Optional, Forces new resource) The name of the bucket. If omitted, Terraform will assign a random, unique name. | `string` | `null` | no | @@ -215,6 +219,7 @@ No modules. | [control\_object\_ownership](#input\_control\_object\_ownership) | Whether to manage S3 Bucket Ownership Controls on this bucket. | `bool` | `false` | no | | [cors\_rule](#input\_cors\_rule) | List of maps containing rules for Cross-Origin Resource Sharing. | `any` | `[]` | no | | [create\_bucket](#input\_create\_bucket) | Controls if S3 bucket should be created | `bool` | `true` | no | +| [data\_redundancy](#input\_data\_redundancy) | Data redundancy. Valid values: `SingleAvailabilityZone` | `string` | `null` | no | | [expected\_bucket\_owner](#input\_expected\_bucket\_owner) | The account ID of the expected bucket owner | `string` | `null` | no | | [force\_destroy](#input\_force\_destroy) | (Optional, Default:false ) A boolean that indicates all objects should be deleted from the bucket so that the bucket can be destroyed without error. These objects are not recoverable. | `bool` | `false` | no | | [grant](#input\_grant) | An ACL policy grant. Conflicts with `acl` | `any` | `[]` | no | @@ -224,7 +229,9 @@ No modules. | [inventory\_self\_source\_destination](#input\_inventory\_self\_source\_destination) | Whether or not the inventory source bucket is also the destination bucket. | `bool` | `false` | no | | [inventory\_source\_account\_id](#input\_inventory\_source\_account\_id) | The inventory source account id. | `string` | `null` | no | | [inventory\_source\_bucket\_arn](#input\_inventory\_source\_bucket\_arn) | The inventory source bucket ARN. | `string` | `null` | no | +| [is\_directory\_bucket](#input\_is\_directory\_bucket) | If the s3 bucket created is a directory bucket | `bool` | `false` | no | | [lifecycle\_rule](#input\_lifecycle\_rule) | List of maps containing configuration of object lifecycle management. | `any` | `[]` | no | +| [location\_type](#input\_location\_type) | Location type. Valid values: `AvailabilityZone` or `LocalZone` | `string` | `null` | no | | [logging](#input\_logging) | Map containing access bucket logging configuration. | `any` | `{}` | no | | [metric\_configuration](#input\_metric\_configuration) | Map containing bucket metric configuration. | `any` | `[]` | no | | [object\_lock\_configuration](#input\_object\_lock\_configuration) | Map containing S3 object locking configuration. | `any` | `{}` | no | @@ -239,6 +246,7 @@ No modules. | [server\_side\_encryption\_configuration](#input\_server\_side\_encryption\_configuration) | Map containing server-side encryption configuration. | `any` | `{}` | no | | [tags](#input\_tags) | (Optional) A mapping of tags to assign to the bucket. | `map(string)` | `{}` | no | | [transition\_default\_minimum\_object\_size](#input\_transition\_default\_minimum\_object\_size) | The default minimum object size behavior applied to the lifecycle configuration. Valid values: all\_storage\_classes\_128K (default), varies\_by\_storage\_class | `string` | `null` | no | +| [type](#input\_type) | Bucket type. Valid values: `Directory` | `string` | `"Directory"` | no | | [versioning](#input\_versioning) | Map containing versioning configuration. | `map(string)` | `{}` | no | | [website](#input\_website) | Map containing static web-site hosting or redirect configuration. | `any` | `{}` | no | @@ -256,6 +264,8 @@ No modules. | [s3\_bucket\_region](#output\_s3\_bucket\_region) | The AWS region this bucket resides in. | | [s3\_bucket\_website\_domain](#output\_s3\_bucket\_website\_domain) | The domain of the website endpoint, if the bucket is configured with a website. If not, this will be an empty string. This is used to create Route 53 alias records. | | [s3\_bucket\_website\_endpoint](#output\_s3\_bucket\_website\_endpoint) | The website endpoint, if the bucket is configured with a website. If not, this will be an empty string. | +| [s3\_directory\_bucket\_arn](#output\_s3\_directory\_bucket\_arn) | ARN of the directory bucket. | +| [s3\_directory\_bucket\_name](#output\_s3\_directory\_bucket\_name) | Name of the directory bucket. | ## Authors diff --git a/examples/complete/README.md b/examples/complete/README.md index b0783ff9..079f346f 100644 --- a/examples/complete/README.md +++ b/examples/complete/README.md @@ -30,14 +30,14 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | >= 5.70 | +| [aws](#requirement\_aws) | >= 5.83 | | [random](#requirement\_random) | >= 2.0 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 5.70 | +| [aws](#provider\_aws) | >= 5.83 | | [random](#provider\_random) | >= 2.0 | ## Modules diff --git a/examples/complete/versions.tf b/examples/complete/versions.tf index 6d9488f3..9ef2d5d8 100644 --- a/examples/complete/versions.tf +++ b/examples/complete/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 5.70" + version = ">= 5.83" } random = { source = "hashicorp/random" diff --git a/examples/directory-bucket/README.md b/examples/directory-bucket/README.md index e2720505..ab1688e8 100644 --- a/examples/directory-bucket/README.md +++ b/examples/directory-bucket/README.md @@ -34,7 +34,8 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Source | Version | |------|--------|---------| -| [directory\_bucket](#module\_directory\_bucket) | ../../modules/directory-bucket | n/a | +| [complete](#module\_complete) | ../../ | n/a | +| [simple](#module\_simple) | ../../ | n/a | ## Resources @@ -43,6 +44,7 @@ Note that this example may create resources which cost money. Run `terraform des | [aws_kms_key.objects](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource | | [random_pet.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet) | resource | | [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | +| [aws_iam_policy_document.bucket_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | ## Inputs @@ -50,8 +52,5 @@ No inputs. ## Outputs -| Name | Description | -|------|-------------| -| [directory\_bucket\_arn](#output\_directory\_bucket\_arn) | ARN of the directory bucket. | -| [directory\_bucket\_name](#output\_directory\_bucket\_name) | Name of the directory bucket. | +No outputs. diff --git a/examples/directory-bucket/main.tf b/examples/directory-bucket/main.tf index d7956486..96f039b0 100644 --- a/examples/directory-bucket/main.tf +++ b/examples/directory-bucket/main.tf @@ -1,6 +1,5 @@ locals { - region = "eu-west-1" - zone_id = "euw1-az1" + region = "eu-west-1" } provider "aws" { @@ -14,29 +13,39 @@ provider "aws" { data "aws_caller_identity" "current" {} -module "directory_bucket" { - source = "../../modules/directory-bucket" +module "simple" { + source = "../../" - bucket_name_prefix = random_pet.this.id - availability_zone_id = local.zone_id + is_directory_bucket = true + bucket = random_pet.this.id + availability_zone = "${local.region}b" +} + +module "complete" { + source = "../../" + is_directory_bucket = true + bucket = "${random_pet.this.id}-complete" + availability_zone = "${local.region}b" server_side_encryption_configuration = { - sse_algorithm = "aws:kms" - kms_master_key_id = aws_kms_key.objects.id + rule = { + bucket_key_enabled = true # required for directory buckets + apply_server_side_encryption_by_default = { + kms_master_key_id = aws_kms_key.objects.arn + sse_algorithm = "aws:kms" + } + } } - - lifecycle_rules = { - all = { + lifecycle_rule = [ + { id = "test" status = "Enabled" - abort_incomplete_multipart_upload = { - days_after_initiation = 7 - } expiration = { days = 7 } }, - logs = { + { + id = "logs" status = "Enabled" expiration = { days = 5 @@ -46,7 +55,7 @@ module "directory_bucket" { object_size_less_than = 10 } }, - other = { + { id = "other" status = "Enabled" expiration = { @@ -56,49 +65,9 @@ module "directory_bucket" { prefix = "other/" } } - } - - create_bucket_policy = true - policy_statements = { - write = { - sid = "ReadWriteAccess" - effect = "Allow" - - actions = [ - "s3express:CreateSession", - ] - - principals = [ - { - type = "AWS" - identifiers = [data.aws_caller_identity.current.account_id] - } - ] - } - readonly = { - sid = "ReadOnlyAccess" - effect = "Allow" - - actions = [ - "s3express:CreateSession", - ] - - principals = [ - { - type = "AWS" - identifiers = [data.aws_caller_identity.current.account_id] - } - ] - - conditions = [ - { - test = "StringEquals" - values = ["ReadOnly"] - variable = "s3express:SessionMode" - } - ] - } - } + ] + attach_policy = true + policy = data.aws_iam_policy_document.bucket_policy.json } resource "random_pet" "this" { @@ -109,3 +78,44 @@ resource "aws_kms_key" "objects" { description = "KMS key is used to encrypt bucket objects" deletion_window_in_days = 7 } + +data "aws_iam_policy_document" "bucket_policy" { + + statement { + sid = "ReadWriteAccess" + effect = "Allow" + + actions = [ + "s3express:CreateSession", + ] + + resources = [module.complete.s3_directory_bucket_arn] + + principals { + identifiers = [data.aws_caller_identity.current.account_id] + type = "AWS" + } + } + + statement { + sid = "ReadOnlyAccess" + effect = "Allow" + + actions = [ + "s3express:CreateSession", + ] + + resources = [module.complete.s3_directory_bucket_arn] + + principals { + identifiers = [data.aws_caller_identity.current.account_id] + type = "AWS" + } + + condition { + test = "StringEquals" + values = ["ReadOnly"] + variable = "s3express:SessionMode" + } + } +} diff --git a/examples/directory-bucket/outputs.tf b/examples/directory-bucket/outputs.tf index 6ccf6a99..4785c215 100644 --- a/examples/directory-bucket/outputs.tf +++ b/examples/directory-bucket/outputs.tf @@ -1,9 +1,9 @@ -output "directory_bucket_name" { - description = "Name of the directory bucket." - value = module.directory_bucket.directory_bucket_name -} - -output "directory_bucket_arn" { - description = "ARN of the directory bucket." - value = module.directory_bucket.directory_bucket_arn -} +#output "directory_bucket_name" { +# description = "Name of the directory bucket." +# value = module.directory_bucket.directory_bucket_name +#} +# +#output "directory_bucket_arn" { +# description = "ARN of the directory bucket." +# value = module.directory_bucket.directory_bucket_arn +#} diff --git a/examples/notification/README.md b/examples/notification/README.md index 63f52d98..8c331142 100644 --- a/examples/notification/README.md +++ b/examples/notification/README.md @@ -20,7 +20,7 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | >= 5.70 | +| [aws](#requirement\_aws) | >= 5.83 | | [null](#requirement\_null) | >= 2.0 | | [random](#requirement\_random) | >= 2.0 | @@ -28,7 +28,7 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 5.70 | +| [aws](#provider\_aws) | >= 5.83 | | [null](#provider\_null) | >= 2.0 | | [random](#provider\_random) | >= 2.0 | diff --git a/examples/notification/versions.tf b/examples/notification/versions.tf index 7fd34151..bd90e261 100644 --- a/examples/notification/versions.tf +++ b/examples/notification/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 5.70" + version = ">= 5.83" } random = { source = "hashicorp/random" diff --git a/examples/object/README.md b/examples/object/README.md index 3a968001..f694e65d 100644 --- a/examples/object/README.md +++ b/examples/object/README.md @@ -20,14 +20,14 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | >= 5.70 | +| [aws](#requirement\_aws) | >= 5.83 | | [random](#requirement\_random) | >= 2.0 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 5.70 | +| [aws](#provider\_aws) | >= 5.83 | | [random](#provider\_random) | >= 2.0 | ## Modules diff --git a/examples/object/versions.tf b/examples/object/versions.tf index 6d9488f3..9ef2d5d8 100644 --- a/examples/object/versions.tf +++ b/examples/object/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 5.70" + version = ">= 5.83" } random = { source = "hashicorp/random" diff --git a/examples/s3-analytics/README.md b/examples/s3-analytics/README.md index a64874e7..c2148d9e 100644 --- a/examples/s3-analytics/README.md +++ b/examples/s3-analytics/README.md @@ -10,14 +10,14 @@ Please check [complete example](https://github.com/terraform-aws-modules/terrafo | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | >= 5.70 | +| [aws](#requirement\_aws) | >= 5.83 | | [random](#requirement\_random) | >= 2.0 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 5.70 | +| [aws](#provider\_aws) | >= 5.83 | | [random](#provider\_random) | >= 2.0 | ## Modules diff --git a/examples/s3-analytics/versions.tf b/examples/s3-analytics/versions.tf index 6d9488f3..9ef2d5d8 100644 --- a/examples/s3-analytics/versions.tf +++ b/examples/s3-analytics/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 5.70" + version = ">= 5.83" } random = { source = "hashicorp/random" diff --git a/examples/s3-inventory/README.md b/examples/s3-inventory/README.md index e3a72de5..fb67fb8d 100644 --- a/examples/s3-inventory/README.md +++ b/examples/s3-inventory/README.md @@ -10,14 +10,14 @@ Please check [complete example](https://github.com/terraform-aws-modules/terrafo | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | >= 5.70 | +| [aws](#requirement\_aws) | >= 5.83 | | [random](#requirement\_random) | >= 2.0 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 5.70 | +| [aws](#provider\_aws) | >= 5.83 | | [random](#provider\_random) | >= 2.0 | ## Modules diff --git a/examples/s3-inventory/versions.tf b/examples/s3-inventory/versions.tf index 6d9488f3..9ef2d5d8 100644 --- a/examples/s3-inventory/versions.tf +++ b/examples/s3-inventory/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 5.70" + version = ">= 5.83" } random = { source = "hashicorp/random" diff --git a/examples/s3-replication/README.md b/examples/s3-replication/README.md index d256ca94..e85b5c03 100644 --- a/examples/s3-replication/README.md +++ b/examples/s3-replication/README.md @@ -22,15 +22,15 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | >= 5.70 | +| [aws](#requirement\_aws) | >= 5.83 | | [random](#requirement\_random) | >= 2.0 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 5.70 | -| [aws.replica](#provider\_aws.replica) | >= 5.70 | +| [aws](#provider\_aws) | >= 5.83 | +| [aws.replica](#provider\_aws.replica) | >= 5.83 | | [random](#provider\_random) | >= 2.0 | ## Modules diff --git a/examples/s3-replication/versions.tf b/examples/s3-replication/versions.tf index 6d9488f3..9ef2d5d8 100644 --- a/examples/s3-replication/versions.tf +++ b/examples/s3-replication/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 5.70" + version = ">= 5.83" } random = { source = "hashicorp/random" diff --git a/main.tf b/main.tf index ce49ab09..cea1caf6 100644 --- a/main.tf +++ b/main.tf @@ -6,6 +6,12 @@ data "aws_canonical_user_id" "this" { data "aws_caller_identity" "current" {} +data "aws_availability_zone" "current" { + count = local.create_bucket && var.is_directory_bucket ? 1 : 0 + + name = var.availability_zone +} + data "aws_partition" "current" {} locals { create_bucket = var.create_bucket && var.putin_khuylo @@ -23,7 +29,7 @@ locals { } resource "aws_s3_bucket" "this" { - count = local.create_bucket ? 1 : 0 + count = local.create_bucket && !var.is_directory_bucket ? 1 : 0 bucket = var.bucket bucket_prefix = var.bucket_prefix @@ -33,8 +39,22 @@ resource "aws_s3_bucket" "this" { tags = var.tags } +resource "aws_s3_directory_bucket" "this" { + count = local.create_bucket && var.is_directory_bucket ? 1 : 0 + + bucket = "${var.bucket}--${data.aws_availability_zone.current[0].zone_id}--x-s3" + data_redundancy = var.data_redundancy + force_destroy = var.force_destroy + type = var.type + + location { + name = data.aws_availability_zone.current[0].zone_id + type = var.location_type + } +} + resource "aws_s3_bucket_logging" "this" { - count = local.create_bucket && length(keys(var.logging)) > 0 ? 1 : 0 + count = local.create_bucket && length(keys(var.logging)) > 0 && !var.is_directory_bucket ? 1 : 0 bucket = aws_s3_bucket.this[0].id @@ -63,7 +83,7 @@ resource "aws_s3_bucket_logging" "this" { } resource "aws_s3_bucket_acl" "this" { - count = local.create_bucket && local.create_bucket_acl ? 1 : 0 + count = local.create_bucket && local.create_bucket_acl && !var.is_directory_bucket ? 1 : 0 bucket = aws_s3_bucket.this[0].id expected_bucket_owner = var.expected_bucket_owner @@ -102,7 +122,7 @@ resource "aws_s3_bucket_acl" "this" { } resource "aws_s3_bucket_website_configuration" "this" { - count = local.create_bucket && length(keys(var.website)) > 0 ? 1 : 0 + count = local.create_bucket && length(keys(var.website)) > 0 && !var.is_directory_bucket ? 1 : 0 bucket = aws_s3_bucket.this[0].id expected_bucket_owner = var.expected_bucket_owner @@ -157,7 +177,7 @@ resource "aws_s3_bucket_website_configuration" "this" { } resource "aws_s3_bucket_versioning" "this" { - count = local.create_bucket && length(keys(var.versioning)) > 0 ? 1 : 0 + count = local.create_bucket && length(keys(var.versioning)) > 0 && !var.is_directory_bucket ? 1 : 0 bucket = aws_s3_bucket.this[0].id expected_bucket_owner = var.expected_bucket_owner @@ -175,7 +195,7 @@ resource "aws_s3_bucket_versioning" "this" { resource "aws_s3_bucket_server_side_encryption_configuration" "this" { count = local.create_bucket && length(keys(var.server_side_encryption_configuration)) > 0 ? 1 : 0 - bucket = aws_s3_bucket.this[0].id + bucket = var.is_directory_bucket ? aws_s3_directory_bucket.this[0].bucket : aws_s3_bucket.this[0].id expected_bucket_owner = var.expected_bucket_owner dynamic "rule" { @@ -197,7 +217,7 @@ resource "aws_s3_bucket_server_side_encryption_configuration" "this" { } resource "aws_s3_bucket_accelerate_configuration" "this" { - count = local.create_bucket && var.acceleration_status != null ? 1 : 0 + count = local.create_bucket && var.acceleration_status != null && !var.is_directory_bucket ? 1 : 0 bucket = aws_s3_bucket.this[0].id expected_bucket_owner = var.expected_bucket_owner @@ -207,7 +227,7 @@ resource "aws_s3_bucket_accelerate_configuration" "this" { } resource "aws_s3_bucket_request_payment_configuration" "this" { - count = local.create_bucket && var.request_payer != null ? 1 : 0 + count = local.create_bucket && var.request_payer != null && !var.is_directory_bucket ? 1 : 0 bucket = aws_s3_bucket.this[0].id expected_bucket_owner = var.expected_bucket_owner @@ -217,7 +237,7 @@ resource "aws_s3_bucket_request_payment_configuration" "this" { } resource "aws_s3_bucket_cors_configuration" "this" { - count = local.create_bucket && length(local.cors_rules) > 0 ? 1 : 0 + count = local.create_bucket && length(local.cors_rules) > 0 && !var.is_directory_bucket ? 1 : 0 bucket = aws_s3_bucket.this[0].id expected_bucket_owner = var.expected_bucket_owner @@ -239,7 +259,7 @@ resource "aws_s3_bucket_cors_configuration" "this" { resource "aws_s3_bucket_lifecycle_configuration" "this" { count = local.create_bucket && length(local.lifecycle_rules) > 0 ? 1 : 0 - bucket = aws_s3_bucket.this[0].id + bucket = var.is_directory_bucket ? aws_s3_directory_bucket.this[0].bucket : aws_s3_bucket.this[0].id expected_bucket_owner = var.expected_bucket_owner transition_default_minimum_object_size = var.transition_default_minimum_object_size @@ -369,7 +389,7 @@ resource "aws_s3_bucket_object_lock_configuration" "this" { } resource "aws_s3_bucket_replication_configuration" "this" { - count = local.create_bucket && length(keys(var.replication_configuration)) > 0 ? 1 : 0 + count = local.create_bucket && length(keys(var.replication_configuration)) > 0 && !var.is_directory_bucket ? 1 : 0 bucket = aws_s3_bucket.this[0].id role = var.replication_configuration["role"] @@ -540,7 +560,7 @@ resource "aws_s3_bucket_policy" "this" { # to prevent "A conflicting conditional operation is currently in progress against this resource." # Ref: https://github.com/hashicorp/terraform-provider-aws/issues/7628 - bucket = aws_s3_bucket.this[0].id + bucket = var.is_directory_bucket ? aws_s3_directory_bucket.this[0].bucket : aws_s3_bucket.this[0].id policy = data.aws_iam_policy_document.combined[0].json depends_on = [ @@ -600,7 +620,7 @@ locals { } data "aws_iam_policy_document" "elb_log_delivery" { - count = local.create_bucket && var.attach_elb_log_delivery_policy ? 1 : 0 + count = local.create_bucket && var.attach_elb_log_delivery_policy && !var.is_directory_bucket ? 1 : 0 # Policy for AWS Regions created before August 2022 (e.g. US East (N. Virginia), Asia Pacific (Singapore), Asia Pacific (Sydney), Asia Pacific (Tokyo), Europe (Ireland)) dynamic "statement" { @@ -649,7 +669,7 @@ data "aws_iam_policy_document" "elb_log_delivery" { # ALB/NLB data "aws_iam_policy_document" "lb_log_delivery" { - count = local.create_bucket && var.attach_lb_log_delivery_policy ? 1 : 0 + count = local.create_bucket && var.attach_lb_log_delivery_policy && !var.is_directory_bucket ? 1 : 0 statement { sid = "AWSLogDeliveryWrite" @@ -702,7 +722,7 @@ data "aws_iam_policy_document" "lb_log_delivery" { # https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-ownership-migrating-acls-prerequisites.html#object-ownership-server-access-logs # https://docs.aws.amazon.com/AmazonS3/latest/userguide/enable-server-access-logging.html#grant-log-delivery-permissions-general data "aws_iam_policy_document" "access_log_delivery" { - count = local.create_bucket && var.attach_access_log_delivery_policy ? 1 : 0 + count = local.create_bucket && var.attach_access_log_delivery_policy && !var.is_directory_bucket ? 1 : 0 statement { sid = "AWSAccessLogDeliveryWrite" @@ -764,7 +784,7 @@ data "aws_iam_policy_document" "access_log_delivery" { } data "aws_iam_policy_document" "deny_insecure_transport" { - count = local.create_bucket && var.attach_deny_insecure_transport_policy ? 1 : 0 + count = local.create_bucket && var.attach_deny_insecure_transport_policy && !var.is_directory_bucket ? 1 : 0 statement { sid = "denyInsecureTransport" @@ -795,7 +815,7 @@ data "aws_iam_policy_document" "deny_insecure_transport" { } data "aws_iam_policy_document" "require_latest_tls" { - count = local.create_bucket && var.attach_require_latest_tls_policy ? 1 : 0 + count = local.create_bucket && var.attach_require_latest_tls_policy && !var.is_directory_bucket ? 1 : 0 statement { sid = "denyOutdatedTLS" @@ -826,7 +846,7 @@ data "aws_iam_policy_document" "require_latest_tls" { } data "aws_iam_policy_document" "deny_incorrect_encryption_headers" { - count = local.create_bucket && var.attach_deny_incorrect_encryption_headers ? 1 : 0 + count = local.create_bucket && var.attach_deny_incorrect_encryption_headers && !var.is_directory_bucket ? 1 : 0 statement { sid = "denyIncorrectEncryptionHeaders" @@ -854,7 +874,7 @@ data "aws_iam_policy_document" "deny_incorrect_encryption_headers" { } data "aws_iam_policy_document" "deny_incorrect_kms_key_sse" { - count = local.create_bucket && var.attach_deny_incorrect_kms_key_sse ? 1 : 0 + count = local.create_bucket && var.attach_deny_incorrect_kms_key_sse && !var.is_directory_bucket ? 1 : 0 statement { sid = "denyIncorrectKmsKeySse" @@ -882,7 +902,7 @@ data "aws_iam_policy_document" "deny_incorrect_kms_key_sse" { } data "aws_iam_policy_document" "deny_unencrypted_object_uploads" { - count = local.create_bucket && var.attach_deny_unencrypted_object_uploads ? 1 : 0 + count = local.create_bucket && var.attach_deny_unencrypted_object_uploads && !var.is_directory_bucket ? 1 : 0 statement { sid = "denyUnencryptedObjectUploads" @@ -910,7 +930,7 @@ data "aws_iam_policy_document" "deny_unencrypted_object_uploads" { } data "aws_iam_policy_document" "deny_ssec_encrypted_object_uploads" { - count = local.create_bucket && var.attach_deny_ssec_encrypted_object_uploads ? 1 : 0 + count = local.create_bucket && var.attach_deny_ssec_encrypted_object_uploads && !var.is_directory_bucket ? 1 : 0 statement { sid = "denySSECEncryptedObjectUploads" @@ -938,7 +958,7 @@ data "aws_iam_policy_document" "deny_ssec_encrypted_object_uploads" { } resource "aws_s3_bucket_public_access_block" "this" { - count = local.create_bucket && var.attach_public_policy ? 1 : 0 + count = local.create_bucket && var.attach_public_policy && !var.is_directory_bucket ? 1 : 0 bucket = aws_s3_bucket.this[0].id @@ -949,7 +969,7 @@ resource "aws_s3_bucket_public_access_block" "this" { } resource "aws_s3_bucket_ownership_controls" "this" { - count = local.create_bucket && var.control_object_ownership ? 1 : 0 + count = local.create_bucket && var.control_object_ownership && !var.is_directory_bucket ? 1 : 0 bucket = local.attach_policy ? aws_s3_bucket_policy.this[0].id : aws_s3_bucket.this[0].id @@ -966,7 +986,7 @@ resource "aws_s3_bucket_ownership_controls" "this" { } resource "aws_s3_bucket_intelligent_tiering_configuration" "this" { - for_each = { for k, v in local.intelligent_tiering : k => v if local.create_bucket } + for_each = { for k, v in local.intelligent_tiering : k => v if local.create_bucket && !var.is_directory_bucket } name = each.key bucket = aws_s3_bucket.this[0].id @@ -994,7 +1014,7 @@ resource "aws_s3_bucket_intelligent_tiering_configuration" "this" { } resource "aws_s3_bucket_metric" "this" { - for_each = { for k, v in local.metric_configuration : k => v if local.create_bucket } + for_each = { for k, v in local.metric_configuration : k => v if local.create_bucket && !var.is_directory_bucket } name = each.value.name bucket = aws_s3_bucket.this[0].id @@ -1009,7 +1029,7 @@ resource "aws_s3_bucket_metric" "this" { } resource "aws_s3_bucket_inventory" "this" { - for_each = { for k, v in var.inventory_configuration : k => v if local.create_bucket } + for_each = { for k, v in var.inventory_configuration : k => v if local.create_bucket && !var.is_directory_bucket } name = each.key bucket = try(each.value.bucket, aws_s3_bucket.this[0].id) @@ -1064,7 +1084,7 @@ resource "aws_s3_bucket_inventory" "this" { # Inventory and analytics destination bucket requires a bucket policy to allow source to PutObjects # https://docs.aws.amazon.com/AmazonS3/latest/userguide/example-bucket-policies.html#example-bucket-policies-use-case-9 data "aws_iam_policy_document" "inventory_and_analytics_destination_policy" { - count = local.create_bucket && var.attach_inventory_destination_policy || var.attach_analytics_destination_policy ? 1 : 0 + count = local.create_bucket && !var.is_directory_bucket && var.attach_inventory_destination_policy || var.attach_analytics_destination_policy ? 1 : 0 statement { sid = "destinationInventoryAndAnalyticsPolicy" @@ -1110,7 +1130,7 @@ data "aws_iam_policy_document" "inventory_and_analytics_destination_policy" { } resource "aws_s3_bucket_analytics_configuration" "this" { - for_each = { for k, v in var.analytics_configuration : k => v if local.create_bucket } + for_each = { for k, v in var.analytics_configuration : k => v if local.create_bucket && !var.is_directory_bucket } bucket = aws_s3_bucket.this[0].id name = each.key diff --git a/modules/directory-bucket/README.md b/modules/directory-bucket/README.md deleted file mode 100644 index 7ad5b3a9..00000000 --- a/modules/directory-bucket/README.md +++ /dev/null @@ -1,57 +0,0 @@ -# S3 directory bucket - -Creates S3 directory bucket and 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_s3_bucket_lifecycle_configuration.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_lifecycle_configuration) | resource | -| [aws_s3_bucket_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource | -| [aws_s3_bucket_server_side_encryption_configuration.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource | -| [aws_s3_directory_bucket.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_directory_bucket) | resource | -| [aws_iam_policy_document.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | - -## Inputs - -| Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| -| [availability\_zone\_id](#input\_availability\_zone\_id) | Availability Zone ID or Local Zone ID | `string` | `null` | no | -| [bucket\_name\_prefix](#input\_bucket\_name\_prefix) | Bucket name prefix | `string` | `null` | no | -| [create](#input\_create) | Whether to create directory bucket resources | `bool` | `true` | no | -| [create\_bucket\_policy](#input\_create\_bucket\_policy) | Whether to create a directory bucket policy. | `bool` | `false` | no | -| [data\_redundancy](#input\_data\_redundancy) | Data redundancy. Valid values: `SingleAvailabilityZone` | `string` | `null` | no | -| [force\_destroy](#input\_force\_destroy) | Boolean that indicates all objects should be deleted from the bucket when the bucket is destroyed so that the bucket can be destroyed without error. These objects are not recoverable | `bool` | `null` | no | -| [lifecycle\_rules](#input\_lifecycle\_rules) | List of maps containing configuration of object lifecycle management. | `any` | `{}` | no | -| [location\_type](#input\_location\_type) | Location type. Valid values: `AvailabilityZone` or `LocalZone` | `string` | `null` | no | -| [override\_policy\_documents](#input\_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 | -| [policy\_statements](#input\_policy\_statements) | A map of IAM policy statements for custom permission usage | `any` | `{}` | no | -| [server\_side\_encryption\_configuration](#input\_server\_side\_encryption\_configuration) | Map containing server-side encryption configuration. | `any` | `{}` | no | -| [source\_policy\_documents](#input\_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 | -| [type](#input\_type) | Bucket type. Valid values: `Directory` | `string` | `"Directory"` | no | - -## Outputs - -| Name | Description | -|------|-------------| -| [directory\_bucket\_arn](#output\_directory\_bucket\_arn) | ARN of the directory bucket. | -| [directory\_bucket\_name](#output\_directory\_bucket\_name) | Name of the directory bucket. | - diff --git a/modules/directory-bucket/main.tf b/modules/directory-bucket/main.tf deleted file mode 100644 index a328f8a9..00000000 --- a/modules/directory-bucket/main.tf +++ /dev/null @@ -1,144 +0,0 @@ -locals { - bucket_name = "${var.bucket_name_prefix}--${var.availability_zone_id}--x-s3" -} - -resource "aws_s3_directory_bucket" "this" { - count = var.create ? 1 : 0 - - bucket = local.bucket_name - data_redundancy = var.data_redundancy - force_destroy = var.force_destroy - type = var.type - - location { - name = var.availability_zone_id - type = var.location_type - } -} - -resource "aws_s3_bucket_server_side_encryption_configuration" "this" { - count = var.create && length(var.server_side_encryption_configuration) > 0 ? 1 : 0 - - bucket = aws_s3_directory_bucket.this[0].bucket - - rule { - bucket_key_enabled = true - - apply_server_side_encryption_by_default { - sse_algorithm = var.server_side_encryption_configuration.sse_algorithm - kms_master_key_id = try(var.server_side_encryption_configuration.kms_master_key_id, null) - } - } -} - -resource "aws_s3_bucket_lifecycle_configuration" "this" { - count = var.create && length(var.lifecycle_rules) > 0 ? 1 : 0 - - bucket = aws_s3_directory_bucket.this[0].bucket - - dynamic "rule" { - for_each = var.lifecycle_rules - - content { - id = try(rule.value.id, rule.key) - status = try(rule.value.enabled ? "Enabled" : "Disabled", tobool(rule.value.status) ? "Enabled" : "Disabled", title(lower(rule.value.status))) - - # Max 1 block - abort_incomplete_multipart_upload - dynamic "abort_incomplete_multipart_upload" { - for_each = try([rule.value.abort_incomplete_multipart_upload_days], []) - - content { - days_after_initiation = try(rule.value.abort_incomplete_multipart_upload_days, null) - } - } - - # Max 1 block - expiration - dynamic "expiration" { - for_each = try(flatten([rule.value.expiration]), []) - - content { - days = try(expiration.value.days, null) - } - } - - # Max 1 block - filter - with one key argument - dynamic "filter" { - for_each = [for v in try(flatten([rule.value.filter]), []) : v if max(length(keys(v))) == 1] - - content { - object_size_greater_than = try(filter.value.object_size_greater_than, null) - object_size_less_than = try(filter.value.object_size_less_than, null) - prefix = try(filter.value.prefix, null) - } - } - - # Max 1 block - filter - with more than one key arguments - dynamic "filter" { - for_each = [for v in try(flatten([rule.value.filter]), []) : v if max(length(keys(v))) > 1] - - content { - and { - object_size_greater_than = try(filter.value.object_size_greater_than, null) - object_size_less_than = try(filter.value.object_size_less_than, null) - prefix = try(filter.value.prefix, null) - } - } - } - } - } -} - -resource "aws_s3_bucket_policy" "this" { - count = var.create && var.create_bucket_policy ? 1 : 0 - - bucket = aws_s3_directory_bucket.this[0].bucket - policy = data.aws_iam_policy_document.this[0].json -} - -data "aws_iam_policy_document" "this" { - count = var.create && var.create_bucket_policy ? 1 : 0 - - source_policy_documents = var.source_policy_documents - override_policy_documents = var.override_policy_documents - - dynamic "statement" { - for_each = var.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, [aws_s3_directory_bucket.this[0].arn]) - 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 - } - } - } - } -} diff --git a/modules/directory-bucket/outputs.tf b/modules/directory-bucket/outputs.tf deleted file mode 100644 index 8e768d59..00000000 --- a/modules/directory-bucket/outputs.tf +++ /dev/null @@ -1,9 +0,0 @@ -output "directory_bucket_name" { - description = "Name of the directory bucket." - value = try(aws_s3_directory_bucket.this[0].bucket, null) -} - -output "directory_bucket_arn" { - description = "ARN of the directory bucket." - value = try(aws_s3_directory_bucket.this[0].arn, null) -} diff --git a/modules/directory-bucket/variables.tf b/modules/directory-bucket/variables.tf deleted file mode 100644 index 54a25555..00000000 --- a/modules/directory-bucket/variables.tf +++ /dev/null @@ -1,77 +0,0 @@ -variable "create" { - description = "Whether to create directory bucket resources" - type = bool - default = true -} - -variable "bucket_name_prefix" { - description = "Bucket name prefix" - type = string - default = null -} - -variable "data_redundancy" { - description = "Data redundancy. Valid values: `SingleAvailabilityZone`" - type = string - default = null -} - -variable "force_destroy" { - description = "Boolean that indicates all objects should be deleted from the bucket when the bucket is destroyed so that the bucket can be destroyed without error. These objects are not recoverable" - type = bool - default = null -} - -variable "type" { - description = "Bucket type. Valid values: `Directory`" - type = string - default = "Directory" -} - -variable "availability_zone_id" { - description = "Availability Zone ID or Local Zone ID" - type = string - default = null -} - -variable "location_type" { - description = "Location type. Valid values: `AvailabilityZone` or `LocalZone`" - type = string - default = null -} - -variable "server_side_encryption_configuration" { - description = "Map containing server-side encryption configuration." - type = any - default = {} -} - -variable "lifecycle_rules" { - description = "List of maps containing configuration of object lifecycle management." - type = any - default = {} -} - -variable "create_bucket_policy" { - description = "Whether to create a directory bucket policy." - type = bool - default = false -} - -variable "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 "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 "policy_statements" { - description = "A map of IAM policy statements for custom permission usage" - type = any - default = {} -} diff --git a/modules/directory-bucket/versions.tf b/modules/directory-bucket/versions.tf deleted file mode 100644 index e0d68841..00000000 --- a/modules/directory-bucket/versions.tf +++ /dev/null @@ -1,10 +0,0 @@ -terraform { - required_version = ">= 1.0" - - required_providers { - aws = { - source = "hashicorp/aws" - version = ">= 5.83" - } - } -} diff --git a/outputs.tf b/outputs.tf index e46a9a2f..58a2d0ab 100644 --- a/outputs.tf +++ b/outputs.tf @@ -47,3 +47,13 @@ output "s3_bucket_website_domain" { description = "The domain of the website endpoint, if the bucket is configured with a website. If not, this will be an empty string. This is used to create Route 53 alias records." value = try(aws_s3_bucket_website_configuration.this[0].website_domain, "") } + +output "s3_directory_bucket_name" { + description = "Name of the directory bucket." + value = try(aws_s3_directory_bucket.this[0].bucket, null) +} + +output "s3_directory_bucket_arn" { + description = "ARN of the directory bucket." + value = try(aws_s3_directory_bucket.this[0].arn, null) +} diff --git a/variables.tf b/variables.tf index 4d3aa95c..8852408b 100644 --- a/variables.tf +++ b/variables.tf @@ -322,6 +322,37 @@ variable "object_ownership" { default = "BucketOwnerEnforced" } +# Directory Bucket +variable "is_directory_bucket" { + description = "If the s3 bucket created is a directory bucket" + type = bool + default = false +} + +variable "data_redundancy" { + description = "Data redundancy. Valid values: `SingleAvailabilityZone`" + type = string + default = null +} + +variable "type" { + description = "Bucket type. Valid values: `Directory`" + type = string + default = "Directory" +} + +variable "availability_zone" { + description = "Availability Zone ID or Local Zone ID" + type = string + default = null +} + +variable "location_type" { + description = "Location type. Valid values: `AvailabilityZone` or `LocalZone`" + type = string + default = null +} + variable "putin_khuylo" { description = "Do you agree that Putin doesn't respect Ukrainian sovereignty and territorial integrity? More info: https://en.wikipedia.org/wiki/Putin_khuylo!" type = bool diff --git a/versions.tf b/versions.tf index b5d776ce..e0d68841 100644 --- a/versions.tf +++ b/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 5.70" + version = ">= 5.83" } } } diff --git a/wrappers/main.tf b/wrappers/main.tf index d207d50e..09566e0b 100644 --- a/wrappers/main.tf +++ b/wrappers/main.tf @@ -25,6 +25,7 @@ module "wrapper" { attach_policy = try(each.value.attach_policy, var.defaults.attach_policy, false) attach_public_policy = try(each.value.attach_public_policy, var.defaults.attach_public_policy, true) attach_require_latest_tls_policy = try(each.value.attach_require_latest_tls_policy, var.defaults.attach_require_latest_tls_policy, false) + availability_zone = try(each.value.availability_zone, var.defaults.availability_zone, null) block_public_acls = try(each.value.block_public_acls, var.defaults.block_public_acls, true) block_public_policy = try(each.value.block_public_policy, var.defaults.block_public_policy, true) bucket = try(each.value.bucket, var.defaults.bucket, null) @@ -32,6 +33,7 @@ module "wrapper" { control_object_ownership = try(each.value.control_object_ownership, var.defaults.control_object_ownership, false) cors_rule = try(each.value.cors_rule, var.defaults.cors_rule, []) create_bucket = try(each.value.create_bucket, var.defaults.create_bucket, true) + data_redundancy = try(each.value.data_redundancy, var.defaults.data_redundancy, null) expected_bucket_owner = try(each.value.expected_bucket_owner, var.defaults.expected_bucket_owner, null) force_destroy = try(each.value.force_destroy, var.defaults.force_destroy, false) grant = try(each.value.grant, var.defaults.grant, []) @@ -41,7 +43,9 @@ module "wrapper" { inventory_self_source_destination = try(each.value.inventory_self_source_destination, var.defaults.inventory_self_source_destination, false) inventory_source_account_id = try(each.value.inventory_source_account_id, var.defaults.inventory_source_account_id, null) inventory_source_bucket_arn = try(each.value.inventory_source_bucket_arn, var.defaults.inventory_source_bucket_arn, null) + is_directory_bucket = try(each.value.is_directory_bucket, var.defaults.is_directory_bucket, false) lifecycle_rule = try(each.value.lifecycle_rule, var.defaults.lifecycle_rule, []) + location_type = try(each.value.location_type, var.defaults.location_type, null) logging = try(each.value.logging, var.defaults.logging, {}) metric_configuration = try(each.value.metric_configuration, var.defaults.metric_configuration, []) object_lock_configuration = try(each.value.object_lock_configuration, var.defaults.object_lock_configuration, {}) @@ -56,6 +60,7 @@ module "wrapper" { server_side_encryption_configuration = try(each.value.server_side_encryption_configuration, var.defaults.server_side_encryption_configuration, {}) tags = try(each.value.tags, var.defaults.tags, {}) transition_default_minimum_object_size = try(each.value.transition_default_minimum_object_size, var.defaults.transition_default_minimum_object_size, null) + type = try(each.value.type, var.defaults.type, "Directory") versioning = try(each.value.versioning, var.defaults.versioning, {}) website = try(each.value.website, var.defaults.website, {}) } diff --git a/wrappers/versions.tf b/wrappers/versions.tf index b5d776ce..e0d68841 100644 --- a/wrappers/versions.tf +++ b/wrappers/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 5.70" + version = ">= 5.83" } } } From 73075398d97fd59e78a0fb4a74d70470666e6434 Mon Sep 17 00:00:00 2001 From: magreenbaum Date: Sun, 9 Feb 2025 18:09:16 -0500 Subject: [PATCH 6/8] update outputs --- examples/directory-bucket/README.md | 5 ++++- examples/directory-bucket/outputs.tf | 18 +++++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/examples/directory-bucket/README.md b/examples/directory-bucket/README.md index ab1688e8..7181fd3b 100644 --- a/examples/directory-bucket/README.md +++ b/examples/directory-bucket/README.md @@ -52,5 +52,8 @@ No inputs. ## Outputs -No outputs. +| Name | Description | +|------|-------------| +| [directory\_bucket\_arn](#output\_directory\_bucket\_arn) | ARN of the directory bucket. | +| [directory\_bucket\_name](#output\_directory\_bucket\_name) | Name of the directory bucket. | diff --git a/examples/directory-bucket/outputs.tf b/examples/directory-bucket/outputs.tf index 4785c215..eae34c94 100644 --- a/examples/directory-bucket/outputs.tf +++ b/examples/directory-bucket/outputs.tf @@ -1,9 +1,9 @@ -#output "directory_bucket_name" { -# description = "Name of the directory bucket." -# value = module.directory_bucket.directory_bucket_name -#} -# -#output "directory_bucket_arn" { -# description = "ARN of the directory bucket." -# value = module.directory_bucket.directory_bucket_arn -#} +output "directory_bucket_name" { + description = "Name of the directory bucket." + value = module.complete.s3_directory_bucket_name +} + +output "directory_bucket_arn" { + description = "ARN of the directory bucket." + value = module.complete.s3_directory_bucket_arn +} From f3dfc6a72a37a30792838d6dcbfe33518da0f78f Mon Sep 17 00:00:00 2001 From: magreenbaum Date: Sun, 9 Feb 2025 18:19:20 -0500 Subject: [PATCH 7/8] remove directory-bucket wrapper --- wrappers/directory-bucket/README.md | 100 ------------------------- wrappers/directory-bucket/main.tf | 19 ----- wrappers/directory-bucket/outputs.tf | 5 -- wrappers/directory-bucket/variables.tf | 11 --- wrappers/directory-bucket/versions.tf | 10 --- 5 files changed, 145 deletions(-) delete mode 100644 wrappers/directory-bucket/README.md delete mode 100644 wrappers/directory-bucket/main.tf delete mode 100644 wrappers/directory-bucket/outputs.tf delete mode 100644 wrappers/directory-bucket/variables.tf delete mode 100644 wrappers/directory-bucket/versions.tf diff --git a/wrappers/directory-bucket/README.md b/wrappers/directory-bucket/README.md deleted file mode 100644 index 1efcca77..00000000 --- a/wrappers/directory-bucket/README.md +++ /dev/null @@ -1,100 +0,0 @@ -# Wrapper for module: `modules/directory-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/directory-bucket" - # Alternative source: - # source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git//wrappers/directory-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/directory-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/directory-bucket/main.tf b/wrappers/directory-bucket/main.tf deleted file mode 100644 index 80ab6c88..00000000 --- a/wrappers/directory-bucket/main.tf +++ /dev/null @@ -1,19 +0,0 @@ -module "wrapper" { - source = "../../modules/directory-bucket" - - for_each = var.items - - availability_zone_id = try(each.value.availability_zone_id, var.defaults.availability_zone_id, null) - bucket_name_prefix = try(each.value.bucket_name_prefix, var.defaults.bucket_name_prefix, null) - create = try(each.value.create, var.defaults.create, true) - create_bucket_policy = try(each.value.create_bucket_policy, var.defaults.create_bucket_policy, false) - data_redundancy = try(each.value.data_redundancy, var.defaults.data_redundancy, null) - force_destroy = try(each.value.force_destroy, var.defaults.force_destroy, null) - lifecycle_rules = try(each.value.lifecycle_rules, var.defaults.lifecycle_rules, {}) - location_type = try(each.value.location_type, var.defaults.location_type, null) - override_policy_documents = try(each.value.override_policy_documents, var.defaults.override_policy_documents, []) - policy_statements = try(each.value.policy_statements, var.defaults.policy_statements, {}) - server_side_encryption_configuration = try(each.value.server_side_encryption_configuration, var.defaults.server_side_encryption_configuration, {}) - source_policy_documents = try(each.value.source_policy_documents, var.defaults.source_policy_documents, []) - type = try(each.value.type, var.defaults.type, "Directory") -} diff --git a/wrappers/directory-bucket/outputs.tf b/wrappers/directory-bucket/outputs.tf deleted file mode 100644 index ec6da5f4..00000000 --- a/wrappers/directory-bucket/outputs.tf +++ /dev/null @@ -1,5 +0,0 @@ -output "wrapper" { - description = "Map of outputs of a wrapper." - value = module.wrapper - # sensitive = false # No sensitive module output found -} diff --git a/wrappers/directory-bucket/variables.tf b/wrappers/directory-bucket/variables.tf deleted file mode 100644 index a6ea0962..00000000 --- a/wrappers/directory-bucket/variables.tf +++ /dev/null @@ -1,11 +0,0 @@ -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/directory-bucket/versions.tf b/wrappers/directory-bucket/versions.tf deleted file mode 100644 index e0d68841..00000000 --- a/wrappers/directory-bucket/versions.tf +++ /dev/null @@ -1,10 +0,0 @@ -terraform { - required_version = ">= 1.0" - - required_providers { - aws = { - source = "hashicorp/aws" - version = ">= 5.83" - } - } -} From b97142a9c7f7e08daa9b4ee4714fb85ac1bbac3f Mon Sep 17 00:00:00 2001 From: magreenbaum Date: Tue, 11 Feb 2025 19:32:58 -0500 Subject: [PATCH 8/8] feedback changes --- README.md | 3 +-- examples/directory-bucket/README.md | 1 + examples/directory-bucket/main.tf | 10 ++++++++-- main.tf | 10 ++-------- variables.tf | 2 +- wrappers/main.tf | 2 +- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index a0db17d3..95c66643 100644 --- a/README.md +++ b/README.md @@ -168,7 +168,6 @@ No modules. | [aws_s3_bucket_versioning.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_versioning) | resource | | [aws_s3_bucket_website_configuration.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_website_configuration) | resource | | [aws_s3_directory_bucket.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_directory_bucket) | resource | -| [aws_availability_zone.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zone) | data source | | [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | | [aws_canonical_user_id.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/canonical_user_id) | data source | | [aws_iam_policy_document.access_log_delivery](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | @@ -211,7 +210,7 @@ No modules. | [attach\_policy](#input\_attach\_policy) | Controls if S3 bucket should have bucket policy attached (set to `true` to use value of `policy` as bucket policy) | `bool` | `false` | no | | [attach\_public\_policy](#input\_attach\_public\_policy) | Controls if a user defined public bucket policy will be attached (set to `false` to allow upstream to apply defaults to the bucket) | `bool` | `true` | no | | [attach\_require\_latest\_tls\_policy](#input\_attach\_require\_latest\_tls\_policy) | Controls if S3 bucket should require the latest version of TLS | `bool` | `false` | no | -| [availability\_zone](#input\_availability\_zone) | Availability Zone ID or Local Zone ID | `string` | `null` | no | +| [availability\_zone\_id](#input\_availability\_zone\_id) | Availability Zone ID or Local Zone ID | `string` | `null` | no | | [block\_public\_acls](#input\_block\_public\_acls) | Whether Amazon S3 should block public ACLs for this bucket. | `bool` | `true` | no | | [block\_public\_policy](#input\_block\_public\_policy) | Whether Amazon S3 should block public bucket policies for this bucket. | `bool` | `true` | no | | [bucket](#input\_bucket) | (Optional, Forces new resource) The name of the bucket. If omitted, Terraform will assign a random, unique name. | `string` | `null` | no | diff --git a/examples/directory-bucket/README.md b/examples/directory-bucket/README.md index 7181fd3b..1ce36553 100644 --- a/examples/directory-bucket/README.md +++ b/examples/directory-bucket/README.md @@ -43,6 +43,7 @@ Note that this example may create resources which cost money. Run `terraform des |------|------| | [aws_kms_key.objects](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource | | [random_pet.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet) | resource | +| [aws_availability_zones.available](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source | | [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | | [aws_iam_policy_document.bucket_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | diff --git a/examples/directory-bucket/main.tf b/examples/directory-bucket/main.tf index 96f039b0..75c6e756 100644 --- a/examples/directory-bucket/main.tf +++ b/examples/directory-bucket/main.tf @@ -13,12 +13,17 @@ provider "aws" { data "aws_caller_identity" "current" {} +data "aws_availability_zones" "available" { + state = "available" +} + module "simple" { source = "../../" is_directory_bucket = true bucket = random_pet.this.id - availability_zone = "${local.region}b" + # https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-express-Endpoints.html + availability_zone_id = data.aws_availability_zones.available.zone_ids[1] } module "complete" { @@ -26,7 +31,8 @@ module "complete" { is_directory_bucket = true bucket = "${random_pet.this.id}-complete" - availability_zone = "${local.region}b" + # https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-express-Endpoints.html + availability_zone_id = data.aws_availability_zones.available.zone_ids[1] server_side_encryption_configuration = { rule = { bucket_key_enabled = true # required for directory buckets diff --git a/main.tf b/main.tf index cea1caf6..667c0b21 100644 --- a/main.tf +++ b/main.tf @@ -6,12 +6,6 @@ data "aws_canonical_user_id" "this" { data "aws_caller_identity" "current" {} -data "aws_availability_zone" "current" { - count = local.create_bucket && var.is_directory_bucket ? 1 : 0 - - name = var.availability_zone -} - data "aws_partition" "current" {} locals { create_bucket = var.create_bucket && var.putin_khuylo @@ -42,13 +36,13 @@ resource "aws_s3_bucket" "this" { resource "aws_s3_directory_bucket" "this" { count = local.create_bucket && var.is_directory_bucket ? 1 : 0 - bucket = "${var.bucket}--${data.aws_availability_zone.current[0].zone_id}--x-s3" + bucket = "${var.bucket}--${var.availability_zone_id}--x-s3" data_redundancy = var.data_redundancy force_destroy = var.force_destroy type = var.type location { - name = data.aws_availability_zone.current[0].zone_id + name = var.availability_zone_id type = var.location_type } } diff --git a/variables.tf b/variables.tf index 8852408b..912ae4ba 100644 --- a/variables.tf +++ b/variables.tf @@ -341,7 +341,7 @@ variable "type" { default = "Directory" } -variable "availability_zone" { +variable "availability_zone_id" { description = "Availability Zone ID or Local Zone ID" type = string default = null diff --git a/wrappers/main.tf b/wrappers/main.tf index 09566e0b..881a23f9 100644 --- a/wrappers/main.tf +++ b/wrappers/main.tf @@ -25,7 +25,7 @@ module "wrapper" { attach_policy = try(each.value.attach_policy, var.defaults.attach_policy, false) attach_public_policy = try(each.value.attach_public_policy, var.defaults.attach_public_policy, true) attach_require_latest_tls_policy = try(each.value.attach_require_latest_tls_policy, var.defaults.attach_require_latest_tls_policy, false) - availability_zone = try(each.value.availability_zone, var.defaults.availability_zone, null) + availability_zone_id = try(each.value.availability_zone_id, var.defaults.availability_zone_id, null) block_public_acls = try(each.value.block_public_acls, var.defaults.block_public_acls, true) block_public_policy = try(each.value.block_public_policy, var.defaults.block_public_policy, true) bucket = try(each.value.bucket, var.defaults.bucket, null)