diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index b8f1b8a5..adea23e0 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -71,6 +71,14 @@ jobs: id: minMax uses: clowdhaus/terraform-min-max@v1.0.3 + - name: Install hcledit (for terraform_wrapper_module_for_each hook) + shell: bash + run: | + curl -L "$(curl -s https://api.github.com/repos/minamijoyo/hcledit/releases/latest | grep -o -E -m 1 "https://.+?_linux_amd64.tar.gz")" > hcledit.tgz + sudo tar -xzf hcledit.tgz -C /usr/bin/ hcledit + rm -f hcledit.tgz 2> /dev/null + hcledit version + - name: Pre-commit Terraform ${{ steps.minMax.outputs.maxVersion }} uses: clowdhaus/terraform-composite-actions/pre-commit@v1.3.0 with: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8a010fdd..954c5373 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,8 +1,9 @@ repos: - repo: https://github.com/antonbabenko/pre-commit-terraform - rev: v1.64.0 + rev: v1.71.0 hooks: - id: terraform_fmt + - id: terraform_wrapper_module_for_each - id: terraform_validate - id: terraform_docs args: @@ -23,7 +24,7 @@ repos: - '--args=--only=terraform_standard_module_structure' - '--args=--only=terraform_workspace_remote' - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.1.0 + rev: v4.2.0 hooks: - id: check-merge-conflict - id: end-of-file-fixer diff --git a/CHANGELOG.md b/CHANGELOG.md index a429f893..178d4240 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,73 @@ All notable changes to this project will be documented in this file. +### [3.2.4](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/compare/v3.2.3...v3.2.4) (2022-06-14) + + +### Bug Fixes + +* Remove hardcoded aws partition in notifications sub-module ([#165](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/issues/165)) ([c51db21](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/commit/c51db21c7dd0f177dae890a108f625bffe4320f6)) + +### [3.2.3](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/compare/v3.2.2...v3.2.3) (2022-05-25) + + +### Bug Fixes + +* Revert change for grant in aws_s3_bucket_acl resource ([#164](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/issues/164)) ([ec88013](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/commit/ec88013616a049434aad08590bbf478f2e05c597)) + +### [3.2.2](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/compare/v3.2.1...v3.2.2) (2022-05-25) + + +### Bug Fixes + +* Fixed issue with multiple grants in aws_s3_bucket_acl ([#163](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/issues/163)) ([9ed6eaa](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/commit/9ed6eaae0c2d5786275ae4199bd5b02135125fcf)) + +### [3.2.1](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/compare/v3.2.0...v3.2.1) (2022-05-18) + + +### Bug Fixes + +* Allow setup eventbridge without notifications ([#160](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/issues/160)) ([31b8e9d](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/commit/31b8e9dcd1793e77b1cd0242a42d7b0abee66b4e)) + +## [3.2.0](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/compare/v3.1.1...v3.2.0) (2022-05-04) + + +### Features + +* Added wrappers automatically generated via hook ([#156](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/issues/156)) ([3634462](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/commit/363446280bfd0a4ed56d07c6538bf9e3e92c6c0e)) + +### [3.1.1](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/compare/v3.1.0...v3.1.1) (2022-04-26) + + +### Bug Fixes + +* Key `host_name` on website routing rules redirect ([#152](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/issues/152)) ([3ca8327](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/commit/3ca83273992c7083a91a2d5c74b4b91e5c9add79)) + +## [3.1.0](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/compare/v3.0.1...v3.1.0) (2022-04-15) + + +### Features + +* Upgraded AWS provider to 4.5, fixed object_lock_enabled ([#149](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/issues/149)) ([70d08fd](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/commit/70d08fd4e6d0c1977ffe423e2b9e675c8fb38235)) + +### [3.0.1](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/compare/v3.0.0...v3.0.1) (2022-04-02) + + +### Bug Fixes + +* Add lifecycle ignore changes on s3_bucket resource to prevent configuration loop ([#145](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/issues/145)) ([895cfa5](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/commit/895cfa529ed0162f4f12f1e99f2f2b14bb262072)) + +## [3.0.0](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/compare/v2.15.0...v3.0.0) (2022-03-30) + + +### ⚠ BREAKING CHANGES + +* Update to support AWS provider v3.75 and newer (including v4.x) (#139) + +### Features + +* Update to support AWS provider v3.75 and newer (including v4.x) ([#139](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/issues/139)) ([e0de434](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/commit/e0de434f2213518d6c2c9c710dd1bb3fd0eaf46d)) + ## [2.15.0](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/compare/v2.14.1...v2.15.0) (2022-03-12) diff --git a/README.md b/README.md index 94d2974c..4c05bcf1 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,14 @@ inputs = { } ``` + +## Module wrappers + +Users of this Terraform module can create multiple similar resources by using [`for_each` meta-argument within `module` block](https://www.terraform.io/language/meta-arguments/for_each) which became available in Terraform 0.13. + +Users of Terragrunt can achieve similar results by using modules provided in the [wrappers](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/tree/master/wrappers) directory, if they prefer to reduce amount of configuration files. + + ## Examples: - [Complete](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/tree/master/examples/complete) - Complete S3 bucket with most of supported features enabled @@ -110,13 +118,13 @@ inputs = { | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 0.13.1 | -| [aws](#requirement\_aws) | ~> 3.69 | +| [aws](#requirement\_aws) | >= 4.5 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | ~> 3.69 | +| [aws](#provider\_aws) | >= 4.5 | ## Modules @@ -127,9 +135,21 @@ No modules. | Name | Type | |------|------| | [aws_s3_bucket.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource | +| [aws_s3_bucket_accelerate_configuration.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_accelerate_configuration) | resource | +| [aws_s3_bucket_acl.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_acl) | resource | +| [aws_s3_bucket_cors_configuration.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_cors_configuration) | resource | +| [aws_s3_bucket_lifecycle_configuration.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_lifecycle_configuration) | resource | +| [aws_s3_bucket_logging.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_logging) | resource | +| [aws_s3_bucket_object_lock_configuration.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_object_lock_configuration) | resource | | [aws_s3_bucket_ownership_controls.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_ownership_controls) | resource | | [aws_s3_bucket_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource | | [aws_s3_bucket_public_access_block.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource | +| [aws_s3_bucket_replication_configuration.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_replication_configuration) | resource | +| [aws_s3_bucket_request_payment_configuration.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_request_payment_configuration) | 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_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_canonical_user_id.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/canonical_user_id) | data source | | [aws_elb_service_account.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/elb_service_account) | data source | | [aws_iam_policy_document.combined](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.deny_insecure_transport](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | @@ -142,7 +162,7 @@ No modules. | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| | [acceleration\_status](#input\_acceleration\_status) | (Optional) Sets the accelerate configuration of an existing bucket. Can be Enabled or Suspended. | `string` | `null` | no | -| [acl](#input\_acl) | (Optional) The canned ACL to apply. Defaults to 'private'. Conflicts with `grant` | `string` | `"private"` | no | +| [acl](#input\_acl) | (Optional) The canned ACL to apply. Conflicts with `grant` | `string` | `null` | no | | [attach\_deny\_insecure\_transport\_policy](#input\_attach\_deny\_insecure\_transport\_policy) | Controls if S3 bucket should have deny non-SSL transport policy attached | `bool` | `false` | no | | [attach\_elb\_log\_delivery\_policy](#input\_attach\_elb\_log\_delivery\_policy) | Controls if S3 bucket should have ELB log delivery policy attached | `bool` | `false` | no | | [attach\_lb\_log\_delivery\_policy](#input\_attach\_lb\_log\_delivery\_policy) | Controls if S3 bucket should have ALB/NLB log delivery policy attached | `bool` | `false` | no | @@ -156,13 +176,16 @@ 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 | +| [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 | | [ignore\_public\_acls](#input\_ignore\_public\_acls) | Whether Amazon S3 should ignore public ACLs for this bucket. | `bool` | `false` | no | | [lifecycle\_rule](#input\_lifecycle\_rule) | List of maps containing configuration of object lifecycle management. | `any` | `[]` | no | | [logging](#input\_logging) | Map containing access bucket logging configuration. | `map(string)` | `{}` | no | | [object\_lock\_configuration](#input\_object\_lock\_configuration) | Map containing S3 object locking configuration. | `any` | `{}` | no | +| [object\_lock\_enabled](#input\_object\_lock\_enabled) | Whether S3 bucket should have an Object Lock configuration enabled. | `bool` | `false` | no | | [object\_ownership](#input\_object\_ownership) | Object ownership. Valid values: BucketOwnerEnforced, BucketOwnerPreferred or ObjectWriter. 'BucketOwnerEnforced': ACLs are disabled, and the bucket owner automatically owns and has full control over every object in the bucket. 'BucketOwnerPreferred': Objects uploaded to the bucket change ownership to the bucket owner if the objects are uploaded with the bucket-owner-full-control canned ACL. 'ObjectWriter': The uploading account will own the object if the object is uploaded with the bucket-owner-full-control canned ACL. | `string` | `"ObjectWriter"` | no | +| [owner](#input\_owner) | Bucket owner's display name and ID. Conflicts with `acl` | `map(string)` | `{}` | no | | [policy](#input\_policy) | (Optional) A valid bucket policy JSON document. Note that if the policy document is not specific enough (but still valid), Terraform may view the policy as constantly changing in a terraform plan. In this case, please make sure you use the verbose/specific version of the policy. For more information about building AWS IAM policy documents with Terraform, see the AWS IAM Policy Document Guide. | `string` | `null` | no | | [putin\_khuylo](#input\_putin\_khuylo) | Do you agree that Putin doesn't respect Ukrainian sovereignty and territorial integrity? More info: https://en.wikipedia.org/wiki/Putin_khuylo! | `bool` | `true` | no | | [replication\_configuration](#input\_replication\_configuration) | Map containing cross-region replication configuration. | `any` | `{}` | no | @@ -171,7 +194,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 | | [versioning](#input\_versioning) | Map containing versioning configuration. | `map(string)` | `{}` | no | -| [website](#input\_website) | Map containing static web-site hosting or redirect configuration. | `map(string)` | `{}` | no | +| [website](#input\_website) | Map containing static web-site hosting or redirect configuration. | `any` | `{}` | no | ## Outputs diff --git a/UPGRADE-3.0.md b/UPGRADE-3.0.md new file mode 100644 index 00000000..ce5d603e --- /dev/null +++ b/UPGRADE-3.0.md @@ -0,0 +1,131 @@ +# Upgrade from v2.x to v3.x + +If you have any questions regarding this upgrade process, please consult the [`examples/`](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/tree/master/examples) projects: + +If you find a bug, please open an issue with supporting configuration to reproduce. + +## List of backwards incompatible changes + +- Terraform AWS provider minimum version is now `v4.5.0` in order to have forward compatibility with Terraform AWS provider `v4.x`. Using the latest version of `v4` is highly recommended, if possible. +- If you are using AWS provider `v3.75` the latest supported version of this module is `v3.0.1` +- Main group of changes is related to refactoring of `aws_s3_bucket` resource into several smaller resources. Read [`S3 bucket refactor` section in the official Terraform AWS Provider Version 4 Upgrade Guide](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/guides/version-4-upgrade#s3-bucket-refactor) and [discussion around these changes](https://github.com/hashicorp/terraform-provider-aws/issues/23106). +- `modules/object`: Changed resource type from `aws_bucket_s3_object` to `aws_s3_object`. After upgrade, on the next apply, Terraform will recreate the object. If you prefer to not have Terraform recreate the object, import the object using `aws_s3_object`. [Read more](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_object#import). + +## Additional changes + +### Added + +- None + +### Modified + +- `acl` variable is set to `null` by default +- In addition to pseudo-boolean values like "Enabled", "Disabled", "Suspended", it is now possible to specify `true` or `false` in all such arguments (e.g. `versioning = { enabled = true }`). + +### Variable and output changes + +1. Removed variables: + + - None + +2. Renamed variables: + + - None + +3. Added variables: + + - `owner` + - `expected_bucket_owner` + +4. Removed outputs: + + - None + +5. Renamed outputs: + +`modules/object`: + + - `s3_bucket_object_id` -> `s3_object_id` + - `s3_bucket_object_etag` -> `s3_object_etag` + - `s3_bucket_object_version_id` -> `s3_object_version_id` + +6. Added outputs: + + - None + +## Upgrade Migrations + +The following examples demonstrate some of the changes that users can elect to make to avoid any potential disruptions when upgrading. + +### Before 2.x Example + +See code in [`examples/complete-legacy`](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/tree/master/examples/complete-legacy). + +```hcl +module "s3_bucket" { + source = "terraform-aws-modules/s3-bucket/aws" + version = "~> 2.0" + + bucket = "my-awesome-bucket" + acl = "log-delivery-write" +} + +terraform { + required_providers { + aws = "~> 3.69.0" # or anything lower than 3.75.0 + } +} +``` + +### After 3.x Example + +See code in [`examples/complete`](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/tree/master/examples/complete). + +```hcl +module "s3_bucket" { + source = "terraform-aws-modules/s3-bucket/aws" + version = "~> 3.0" + + bucket = "my-awesome-bucket" + acl = "log-delivery-write" +} + +terraform { + required_providers { + aws = ">= 4.5" # or anything higher than 4.5.0 + } +} +``` + +After the code is updated, you need run `terraform init -upgrade` to download newer AWS provider, and then import S3 bucket ACL using such command: + +``` +terraform import "module.s3_bucket.aws_s3_bucket_acl.this[0]" my-awesome-bucket,log-delivery-write +``` + +Where `log-delivery-write` is the value of `acl` argument in the module block above. + +Read more about [import in the official documentation for `aws_s3_bucket_acl`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_acl#import). + +#### Import existing resources (required during the migration from v2.x of this module) + +During the migration to v3.x of this module, several S3 resources will be created by this module. In order to guarantee the best experience and prevent data loss, you will need to import them into terraform state using commands like these: + +```bash +terraform import "module.s3_bucket.aws_s3_bucket_acl.this[0]" , +terraform import "module.s3_bucket.aws_s3_bucket_logging.this[0]" +terraform import "module.s3_bucket.aws_s3_bucket_website_configuration.this[0]" , +terraform import "module.s3_bucket.aws_s3_bucket_versioning.this[0]" , +terraform import "module.s3_bucket.aws_s3_bucket_server_side_encryption_configuration.this[0]" , +terraform import "module.s3_bucket.aws_s3_bucket_request_payment_configuration.this[0]" , +terraform import "module.s3_bucket.aws_s3_bucket_accelerate_configuration.this[0]" , +terraform import "module.s3_bucket.aws_s3_bucket_policy.this[0]" +terraform import "module.s3_bucket.aws_s3_bucket_ownership_controls.this[0]" +terraform import "module.s3_bucket.aws_s3_bucket_cors_configuration.this[0]" , +terraform import "module.s3_bucket.aws_s3_bucket_object_lock_configuration.this[0]" , +terraform import "module.s3_bucket.aws_s3_bucket_public_access_block.this[0]" +terraform import "module.s3_bucket.aws_s3_bucket_lifecycle_configuration.this[0]" , +terraform import "module.s3_bucket.aws_s3_bucket_replication_configuration.this[0]" +``` + +Where `s3_bucket` is the name of your module definition, `bucket-name` is the name of the bucket, `acl` is the bucket ACL (e.g. `private`, `log-delivery-write`, etc), `` is your AWS account number (required only if `expected_bucket_owner` is set in the code). diff --git a/examples/complete-legacy/README.md b/examples/complete-legacy/README.md new file mode 100644 index 00000000..24e69dd6 --- /dev/null +++ b/examples/complete-legacy/README.md @@ -0,0 +1,59 @@ +# Legacy - Complete S3 bucket with most of supported features enabled + +Configuration in this directory creates S3 bucket using previous (2.x) version of this module to test upgrade process. + +This configuration is similar to the code in [examples/complete](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/tree/master/examples/complete) but not identical. + +## Usage + +Once this configuration is created, you need to use the newer version of this module (e.g. `~> 3.0`), review/update arguments (see code in [examples/complete](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/tree/master/examples/complete)) and import existing resources (see [UPGRADE-3.0.md](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket/blob/master/UPGRADE-3.0.md) for more precise commands). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.13.1 | +| [aws](#requirement\_aws) | ~> 3.69.0 | +| [random](#requirement\_random) | >= 2.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | ~> 3.69.0 | +| [random](#provider\_random) | >= 2.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [log\_bucket](#module\_log\_bucket) | terraform-aws-modules/s3-bucket/aws | ~> 2.0 | +| [s3\_bucket](#module\_s3\_bucket) | terraform-aws-modules/s3-bucket/aws | ~> 2.0 | + +## Resources + +| Name | Type | +|------|------| +| [aws_iam_role.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [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_iam_policy_document.bucket_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [s3\_bucket\_arn](#output\_s3\_bucket\_arn) | The ARN of the bucket. Will be of format arn:aws:s3:::bucketname. | +| [s3\_bucket\_bucket\_domain\_name](#output\_s3\_bucket\_bucket\_domain\_name) | The bucket domain name. Will be of format bucketname.s3.amazonaws.com. | +| [s3\_bucket\_bucket\_regional\_domain\_name](#output\_s3\_bucket\_bucket\_regional\_domain\_name) | The bucket region-specific domain name. The bucket domain name including the region name, please refer here for format. Note: The AWS CloudFront allows specifying S3 region-specific endpoint when creating S3 origin, it will prevent redirect issues from CloudFront to S3 Origin URL. | +| [s3\_bucket\_hosted\_zone\_id](#output\_s3\_bucket\_hosted\_zone\_id) | The Route 53 Hosted Zone ID for this bucket's region. | +| [s3\_bucket\_id](#output\_s3\_bucket\_id) | The name of the bucket. | +| [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. | + diff --git a/examples/complete-legacy/main.tf b/examples/complete-legacy/main.tf new file mode 100644 index 00000000..ca5c1ffd --- /dev/null +++ b/examples/complete-legacy/main.tf @@ -0,0 +1,218 @@ +provider "aws" { + region = local.region + + # Make it faster by skipping something + skip_get_ec2_platforms = true + skip_metadata_api_check = true + skip_region_validation = true + skip_credentials_validation = true + skip_requesting_account_id = true +} + +locals { + bucket_name = "s3-bucket-${random_pet.this.id}" + region = "eu-west-1" +} + +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 +} + +resource "aws_iam_role" "this" { + assume_role_policy = < [terraform](#requirement\_terraform) | >= 0.13.1 | -| [aws](#requirement\_aws) | ~> 3.69 | +| [aws](#requirement\_aws) | >= 4.5 | | [random](#requirement\_random) | >= 2.0 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | ~> 3.69 | +| [aws](#provider\_aws) | >= 4.5 | | [random](#provider\_random) | >= 2.0 | ## Modules @@ -55,6 +55,7 @@ Note that this example may create resources which cost money. Run `terraform des | [aws_iam_role.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | | [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_canonical_user_id.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/canonical_user_id) | data source | | [aws_cloudfront_log_delivery_canonical_user_id.cloudfront](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/cloudfront_log_delivery_canonical_user_id) | 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/complete/main.tf b/examples/complete/main.tf index f4b18fd7..2b3cebb8 100644 --- a/examples/complete/main.tf +++ b/examples/complete/main.tf @@ -14,6 +14,9 @@ locals { region = "eu-west-1" } + +data "aws_caller_identity" "current" {} + data "aws_canonical_user_id" "current" {} data "aws_cloudfront_log_delivery_canonical_user_id" "cloudfront" {} @@ -65,9 +68,10 @@ data "aws_iam_policy_document" "bucket_policy" { module "log_bucket" { source = "../../" - bucket = "logs-${random_pet.this.id}" - acl = "log-delivery-write" - force_destroy = true + bucket = "logs-${random_pet.this.id}" + acl = "log-delivery-write" + force_destroy = true + attach_elb_log_delivery_policy = true attach_lb_log_delivery_policy = true attach_deny_insecure_transport_policy = true @@ -78,58 +82,117 @@ module "cloudfront_log_bucket" { source = "../../" bucket = "cloudfront-logs-${random_pet.this.id}" - acl = null # conflicts with default of `acl = "private"` so set to null to use grants + grant = [{ - type = "CanonicalUser" - permissions = ["FULL_CONTROL"] - id = data.aws_canonical_user_id.current.id + type = "CanonicalUser" + permission = "FULL_CONTROL" + id = data.aws_canonical_user_id.current.id }, { - type = "CanonicalUser" - permissions = ["FULL_CONTROL"] - id = data.aws_cloudfront_log_delivery_canonical_user_id.cloudfront.id - # Ref. https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/AccessLogs.html - }] + type = "CanonicalUser" + permission = "FULL_CONTROL" + id = data.aws_cloudfront_log_delivery_canonical_user_id.cloudfront.id # Ref. https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/AccessLogs.html + } + ] + + owner = { + id = "457414f555e45c2e6fe1069d1a527a90d6337e1acb012ba99f3833859b23d338" + } + force_destroy = true } module "s3_bucket" { source = "../../" - bucket = local.bucket_name - acl = "private" - force_destroy = true + bucket = local.bucket_name - attach_policy = true - policy = data.aws_iam_policy_document.bucket_policy.json + force_destroy = true + acceleration_status = "Suspended" + request_payer = "BucketOwner" + tags = { + Owner = "Anton" + } + + # Note: Object Lock configuration can be enabled only on new buckets + # https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_object_lock_configuration + object_lock_enabled = true + object_lock_configuration = { + rule = { + default_retention = { + mode = "GOVERNANCE" + days = 1 + } + } + } + + # Bucket policies + attach_policy = true + policy = data.aws_iam_policy_document.bucket_policy.json attach_deny_insecure_transport_policy = true attach_require_latest_tls_policy = true - tags = { - Owner = "Anton" + # S3 bucket-level Public Access Block configuration + block_public_acls = true + block_public_policy = true + ignore_public_acls = true + restrict_public_buckets = true + + # S3 Bucket Ownership Controls + # https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_ownership_controls + control_object_ownership = true + object_ownership = "BucketOwnerPreferred" + + expected_bucket_owner = data.aws_caller_identity.current.account_id + + acl = "private" # "acl" conflicts with "grant" and "owner" + + logging = { + target_bucket = module.log_bucket.s3_bucket_id + target_prefix = "log/" } versioning = { - enabled = true + status = true + mfa_delete = false } website = { + # conflicts with "error_document" + # redirect_all_requests_to = { + # host_name = "https://modules.tf" + # } + index_document = "index.html" error_document = "error.html" - routing_rules = jsonencode([{ - Condition : { - KeyPrefixEquals : "docs/" + routing_rules = [{ + condition = { + key_prefix_equals = "docs/" }, - Redirect : { - ReplaceKeyPrefixWith : "documents/" + redirect = { + replace_key_prefix_with = "documents/" } - }]) - + }, { + condition = { + http_error_code_returned_equals = 404 + key_prefix_equals = "archive/" + }, + redirect = { + host_name = "archive.myhost.com" + http_redirect_code = 301 + protocol = "https" + replace_key_with = "not_found.html" + } + }] } - logging = { - target_bucket = module.log_bucket.s3_bucket_id - target_prefix = "log/" + server_side_encryption_configuration = { + rule = { + apply_server_side_encryption_by_default = { + kms_master_key_id = aws_kms_key.objects.arn + sse_algorithm = "aws:kms" + } + } } cors_rule = [ @@ -152,11 +215,12 @@ module "s3_bucket" { { id = "log" enabled = true - prefix = "log/" - tags = { - rule = "log" - autoclean = "true" + filter = { + tags = { + some = "value" + another = "value2" + } } transition = [ @@ -169,18 +233,19 @@ module "s3_bucket" { } ] - expiration = { - days = 90 - } + # expiration = { + # days = 90 + # expired_object_delete_marker = true + # } - noncurrent_version_expiration = { - days = 30 - } + # noncurrent_version_expiration = { + # newer_noncurrent_versions = 5 + # days = 30 + # } }, { id = "log1" enabled = true - prefix = "log1/" abort_incomplete_multipart_upload_days = 7 noncurrent_version_transition = [ @@ -202,34 +267,30 @@ module "s3_bucket" { days = 300 } }, - ] - - server_side_encryption_configuration = { - rule = { - apply_server_side_encryption_by_default = { - kms_master_key_id = aws_kms_key.objects.arn - sse_algorithm = "aws:kms" - } - } - } + { + id = "log2" + enabled = true - object_lock_configuration = { - object_lock_enabled = "Enabled" - rule = { - default_retention = { - mode = "GOVERNANCE" - days = 1 + filter = { + prefix = "log1/" + object_size_greater_than = 200000 + object_size_less_than = 500000 + tags = { + some = "value" + another = "value2" + } } - } - } - # S3 bucket-level Public Access Block configuration - block_public_acls = true - block_public_policy = true - ignore_public_acls = true - restrict_public_buckets = true + noncurrent_version_transition = [ + { + days = 30 + storage_class = "STANDARD_IA" + }, + ] - # S3 Bucket Ownership Controls - control_object_ownership = true - object_ownership = "BucketOwnerPreferred" + noncurrent_version_expiration = { + days = 300 + } + }, + ] } diff --git a/examples/complete/versions.tf b/examples/complete/versions.tf index 57a001e6..497f47be 100644 --- a/examples/complete/versions.tf +++ b/examples/complete/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = "~> 3.69" + version = ">= 4.5" } random = { source = "hashicorp/random" diff --git a/examples/notification/README.md b/examples/notification/README.md index 1819f4d2..534f8d2c 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) | >= 0.13.1 | -| [aws](#requirement\_aws) | ~> 3.69 | +| [aws](#requirement\_aws) | >= 4.5 | | [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) | ~> 3.69 | +| [aws](#provider\_aws) | >= 4.5 | | [null](#provider\_null) | >= 2.0 | | [random](#provider\_random) | >= 2.0 | @@ -37,8 +37,8 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Source | Version | |------|--------|---------| | [all\_notifications](#module\_all\_notifications) | ../../modules/notification | n/a | -| [lambda\_function1](#module\_lambda\_function1) | terraform-aws-modules/lambda/aws | ~> 2.0 | -| [lambda\_function2](#module\_lambda\_function2) | terraform-aws-modules/lambda/aws | ~> 2.0 | +| [lambda\_function1](#module\_lambda\_function1) | terraform-aws-modules/lambda/aws | ~> 3.0 | +| [lambda\_function2](#module\_lambda\_function2) | terraform-aws-modules/lambda/aws | ~> 3.0 | | [s3\_bucket](#module\_s3\_bucket) | ../../ | n/a | | [sns\_topic1](#module\_sns\_topic1) | terraform-aws-modules/sns/aws | ~> 3.0 | | [sns\_topic2](#module\_sns\_topic2) | terraform-aws-modules/sns/aws | ~> 3.0 | diff --git a/examples/notification/main.tf b/examples/notification/main.tf index 71788bf5..a208ae59 100644 --- a/examples/notification/main.tf +++ b/examples/notification/main.tf @@ -46,7 +46,7 @@ resource "null_resource" "download_package" { module "lambda_function1" { source = "terraform-aws-modules/lambda/aws" - version = "~> 2.0" + version = "~> 3.0" function_name = "${random_pet.this.id}-lambda1" handler = "index.lambda_handler" @@ -58,7 +58,7 @@ module "lambda_function1" { module "lambda_function2" { source = "terraform-aws-modules/lambda/aws" - version = "~> 2.0" + version = "~> 3.0" function_name = "${random_pet.this.id}-lambda2" handler = "index.lambda_handler" @@ -112,6 +112,8 @@ module "all_notifications" { bucket = module.s3_bucket.s3_bucket_id + eventbridge = true + # Common error - Error putting S3 notification configuration: InvalidArgument: Configuration is ambiguously defined. Cannot have overlapping suffixes in two rules if the prefixes are overlapping for the same event type. lambda_notifications = { @@ -139,11 +141,6 @@ module "all_notifications" { # queue_id = aws_sqs_queue.this[0].id // optional } - - sqs2 = { - queue_arn = aws_sqs_queue.this[1].arn - events = ["s3:ObjectCreated:Copy"] - } } sns_notifications = { diff --git a/examples/notification/versions.tf b/examples/notification/versions.tf index 4dee4042..6764b80c 100644 --- a/examples/notification/versions.tf +++ b/examples/notification/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = "~> 3.69" + version = ">= 4.5" } random = { source = "hashicorp/random" diff --git a/examples/object/README.md b/examples/object/README.md index c24cbc33..13a396a2 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) | >= 0.13.1 | -| [aws](#requirement\_aws) | ~> 3.69 | +| [aws](#requirement\_aws) | >= 4.5 | | [random](#requirement\_random) | >= 2.0 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | ~> 3.69 | +| [aws](#provider\_aws) | >= 4.5 | | [random](#provider\_random) | >= 2.0 | ## Modules @@ -57,7 +57,7 @@ No inputs. |------|-------------| | [s3\_bucket\_arn](#output\_s3\_bucket\_arn) | The ARN of the bucket. Will be of format arn:aws:s3:::bucketname. | | [s3\_bucket\_id](#output\_s3\_bucket\_id) | The name of the bucket. | -| [s3\_bucket\_object\_etag](#output\_s3\_bucket\_object\_etag) | The ETag generated for the object (an MD5 sum of the object content). | -| [s3\_bucket\_object\_id](#output\_s3\_bucket\_object\_id) | The key of S3 object | -| [s3\_bucket\_object\_version\_id](#output\_s3\_bucket\_object\_version\_id) | A unique version ID value for the object, if bucket versioning is enabled. | +| [s3\_object\_etag](#output\_s3\_object\_etag) | The ETag generated for the object (an MD5 sum of the object content). | +| [s3\_object\_id](#output\_s3\_object\_id) | The key of S3 object | +| [s3\_object\_version\_id](#output\_s3\_object\_version\_id) | A unique version ID value for the object, if bucket versioning is enabled. | diff --git a/examples/object/outputs.tf b/examples/object/outputs.tf index 223539bd..8302ff05 100644 --- a/examples/object/outputs.tf +++ b/examples/object/outputs.tf @@ -1,17 +1,17 @@ # S3 object -output "s3_bucket_object_id" { +output "s3_object_id" { description = "The key of S3 object" - value = module.object.s3_bucket_object_id + value = module.object.s3_object_id } -output "s3_bucket_object_etag" { +output "s3_object_etag" { description = "The ETag generated for the object (an MD5 sum of the object content)." - value = module.object.s3_bucket_object_etag + value = module.object.s3_object_etag } -output "s3_bucket_object_version_id" { +output "s3_object_version_id" { description = "A unique version ID value for the object, if bucket versioning is enabled." - value = module.object.s3_bucket_object_version_id + value = module.object.s3_object_version_id } # S3 bucket diff --git a/examples/object/versions.tf b/examples/object/versions.tf index 57a001e6..497f47be 100644 --- a/examples/object/versions.tf +++ b/examples/object/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = "~> 3.69" + version = ">= 4.5" } random = { source = "hashicorp/random" diff --git a/examples/s3-replication/README.md b/examples/s3-replication/README.md index f70b1ea2..efafd5ee 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) | >= 0.13.1 | -| [aws](#requirement\_aws) | ~> 3.69 | +| [aws](#requirement\_aws) | >= 4.5 | | [random](#requirement\_random) | >= 2.0 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | ~> 3.69 | -| [aws.replica](#provider\_aws.replica) | ~> 3.69 | +| [aws](#provider\_aws) | >= 4.5 | +| [aws.replica](#provider\_aws.replica) | >= 4.5 | | [random](#provider\_random) | >= 2.0 | ## Modules diff --git a/examples/s3-replication/main.tf b/examples/s3-replication/main.tf index b9e5b197..98897334 100644 --- a/examples/s3-replication/main.tf +++ b/examples/s3-replication/main.tf @@ -73,10 +73,15 @@ module "s3_bucket" { rules = [ { id = "something-with-kms-and-filter" - status = "Enabled" + status = true priority = 10 + delete_marker_replication = false + source_selection_criteria = { + replica_modifications = { + status = "Enabled" + } sse_kms_encrypted_objects = { enabled = true } @@ -90,17 +95,21 @@ module "s3_bucket" { } destination = { - bucket = "arn:aws:s3:::${local.destination_bucket_name}" - storage_class = "STANDARD" + bucket = "arn:aws:s3:::${local.destination_bucket_name}" + storage_class = "STANDARD" + replica_kms_key_id = aws_kms_key.replica.arn account_id = data.aws_caller_identity.current.account_id + access_control_translation = { owner = "Destination" } + replication_time = { status = "Enabled" minutes = 15 } + metrics = { status = "Enabled" minutes = 15 @@ -109,9 +118,10 @@ module "s3_bucket" { }, { id = "something-with-filter" - status = "Enabled" priority = 20 + delete_marker_replication = false + filter = { prefix = "two" tags = { @@ -129,6 +139,8 @@ module "s3_bucket" { status = "Enabled" priority = 30 + delete_marker_replication = true + filter = { prefix = "" } @@ -142,6 +154,8 @@ module "s3_bucket" { id = "everything-without-filters" status = "Enabled" + delete_marker_replication = true + destination = { bucket = "arn:aws:s3:::${local.destination_bucket_name}" storage_class = "STANDARD" diff --git a/examples/s3-replication/versions.tf b/examples/s3-replication/versions.tf index 57a001e6..497f47be 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 = "~> 3.69" + version = ">= 4.5" } random = { source = "hashicorp/random" diff --git a/main.tf b/main.tf index 47f855c9..cc76cea9 100644 --- a/main.tf +++ b/main.tf @@ -1,7 +1,14 @@ +data "aws_canonical_user_id" "this" {} + locals { create_bucket = var.create_bucket && var.putin_khuylo attach_policy = var.attach_require_latest_tls_policy || var.attach_elb_log_delivery_policy || var.attach_lb_log_delivery_policy || var.attach_deny_insecure_transport_policy || var.attach_policy + + # Variables with type `any` should be jsonencode()'d when value is coming from Terragrunt + grants = try(jsondecode(var.grant), var.grant) + cors_rules = try(jsondecode(var.cors_rule), var.cors_rule) + lifecycle_rules = try(jsondecode(var.lifecycle_rule), var.lifecycle_rule) } resource "aws_s3_bucket" "this" { @@ -10,258 +17,503 @@ resource "aws_s3_bucket" "this" { bucket = var.bucket bucket_prefix = var.bucket_prefix + force_destroy = var.force_destroy + object_lock_enabled = var.object_lock_enabled + tags = var.tags + + lifecycle { + ignore_changes = [ + acceleration_status, + acl, + grant, + cors_rule, + lifecycle_rule, + logging, + object_lock_configuration, + replication_configuration, + request_payer, + server_side_encryption_configuration, + versioning, + website + ] + } +} + +resource "aws_s3_bucket_logging" "this" { + count = local.create_bucket && length(keys(var.logging)) > 0 ? 1 : 0 + + bucket = aws_s3_bucket.this[0].id + + target_bucket = var.logging["target_bucket"] + target_prefix = try(var.logging["target_prefix"], null) +} + +resource "aws_s3_bucket_acl" "this" { + count = local.create_bucket && ((var.acl != null && var.acl != "null") || length(local.grants) > 0) ? 1 : 0 + + bucket = aws_s3_bucket.this[0].id + expected_bucket_owner = var.expected_bucket_owner + # hack when `null` value can't be used (eg, from terragrunt, https://github.com/gruntwork-io/terragrunt/pull/1367) - acl = var.acl != "null" ? var.acl : null + acl = var.acl == "null" ? null : var.acl - tags = var.tags - force_destroy = var.force_destroy - acceleration_status = var.acceleration_status - request_payer = var.request_payer + dynamic "access_control_policy" { + for_each = length(local.grants) > 0 ? [true] : [] - dynamic "website" { - for_each = length(keys(var.website)) == 0 ? [] : [var.website] + content { + dynamic "grant" { + for_each = local.grants + + content { + permission = grant.value.permission + + grantee { + type = grant.value.type + id = try(grant.value.id, null) + uri = try(grant.value.uri, null) + email_address = try(grant.value.email, null) + } + } + } + + owner { + id = try(var.owner["id"], data.aws_canonical_user_id.this.id) + display_name = try(var.owner["display_name"], null) + } + } + } +} + +resource "aws_s3_bucket_website_configuration" "this" { + count = local.create_bucket && length(keys(var.website)) > 0 ? 1 : 0 + + bucket = aws_s3_bucket.this[0].id + expected_bucket_owner = var.expected_bucket_owner + + dynamic "index_document" { + for_each = try([var.website["index_document"]], []) content { - index_document = lookup(website.value, "index_document", null) - error_document = lookup(website.value, "error_document", null) - redirect_all_requests_to = lookup(website.value, "redirect_all_requests_to", null) - routing_rules = lookup(website.value, "routing_rules", null) + suffix = index_document.value } } - dynamic "cors_rule" { - for_each = try(jsondecode(var.cors_rule), var.cors_rule) + dynamic "error_document" { + for_each = try([var.website["error_document"]], []) content { - allowed_methods = cors_rule.value.allowed_methods - allowed_origins = cors_rule.value.allowed_origins - allowed_headers = lookup(cors_rule.value, "allowed_headers", null) - expose_headers = lookup(cors_rule.value, "expose_headers", null) - max_age_seconds = lookup(cors_rule.value, "max_age_seconds", null) + key = error_document.value + } + } + + dynamic "redirect_all_requests_to" { + for_each = try([var.website["redirect_all_requests_to"]], []) + + content { + host_name = redirect_all_requests_to.value.host_name + protocol = try(redirect_all_requests_to.value.protocol, null) } } - dynamic "versioning" { - for_each = length(keys(var.versioning)) == 0 ? [] : [var.versioning] + dynamic "routing_rule" { + for_each = try(flatten([var.website["routing_rules"]]), []) content { - enabled = lookup(versioning.value, "enabled", null) - mfa_delete = lookup(versioning.value, "mfa_delete", null) + dynamic "condition" { + for_each = [try([routing_rule.value.condition], [])] + + content { + http_error_code_returned_equals = try(routing_rule.value.condition["http_error_code_returned_equals"], null) + key_prefix_equals = try(routing_rule.value.condition["key_prefix_equals"], null) + } + } + + redirect { + host_name = try(routing_rule.value.redirect["host_name"], null) + http_redirect_code = try(routing_rule.value.redirect["http_redirect_code"], null) + protocol = try(routing_rule.value.redirect["protocol"], null) + replace_key_prefix_with = try(routing_rule.value.redirect["replace_key_prefix_with"], null) + replace_key_with = try(routing_rule.value.redirect["replace_key_with"], null) + } } } +} + +resource "aws_s3_bucket_versioning" "this" { + count = local.create_bucket && length(keys(var.versioning)) > 0 ? 1 : 0 + + bucket = aws_s3_bucket.this[0].id + expected_bucket_owner = var.expected_bucket_owner + mfa = try(var.versioning["mfa"], null) - dynamic "logging" { - for_each = length(keys(var.logging)) == 0 ? [] : [var.logging] + versioning_configuration { + # Valid values: "Enabled" or "Suspended" + status = try(var.versioning["enabled"] ? "Enabled" : "Suspended", tobool(var.versioning["status"]) ? "Enabled" : "Suspended", title(lower(var.versioning["status"]))) + + # Valid values: "Enabled" or "Disabled" + mfa_delete = try(tobool(var.versioning["mfa_delete"]) ? "Enabled" : "Disabled", title(lower(var.versioning["mfa_delete"])), null) + } +} + +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 + expected_bucket_owner = var.expected_bucket_owner + + dynamic "rule" { + for_each = try(flatten([var.server_side_encryption_configuration["rule"]]), []) content { - target_bucket = logging.value.target_bucket - target_prefix = lookup(logging.value, "target_prefix", null) + bucket_key_enabled = try(rule.value.bucket_key_enabled, null) + + dynamic "apply_server_side_encryption_by_default" { + for_each = try([rule.value.apply_server_side_encryption_by_default], []) + + content { + sse_algorithm = apply_server_side_encryption_by_default.value.sse_algorithm + kms_master_key_id = try(apply_server_side_encryption_by_default.value.kms_master_key_id, null) + } + } } } +} - dynamic "grant" { - for_each = try(jsondecode(var.grant), var.grant) +resource "aws_s3_bucket_accelerate_configuration" "this" { + count = local.create_bucket && var.acceleration_status != null ? 1 : 0 + + bucket = aws_s3_bucket.this[0].id + expected_bucket_owner = var.expected_bucket_owner + + # Valid values: "Enabled" or "Suspended" + status = title(lower(var.acceleration_status)) +} + +resource "aws_s3_bucket_request_payment_configuration" "this" { + count = local.create_bucket && var.request_payer != null ? 1 : 0 + + bucket = aws_s3_bucket.this[0].id + expected_bucket_owner = var.expected_bucket_owner + + # Valid values: "BucketOwner" or "Requester" + payer = lower(var.request_payer) == "requester" ? "Requester" : "BucketOwner" +} + +resource "aws_s3_bucket_cors_configuration" "this" { + count = local.create_bucket && length(local.cors_rules) > 0 ? 1 : 0 + + bucket = aws_s3_bucket.this[0].id + expected_bucket_owner = var.expected_bucket_owner + + dynamic "cors_rule" { + for_each = local.cors_rules content { - id = lookup(grant.value, "id", null) - type = grant.value.type - permissions = grant.value.permissions - uri = lookup(grant.value, "uri", null) + id = try(cors_rule.value.id, null) + allowed_methods = cors_rule.value.allowed_methods + allowed_origins = cors_rule.value.allowed_origins + allowed_headers = try(cors_rule.value.allowed_headers, null) + expose_headers = try(cors_rule.value.expose_headers, null) + max_age_seconds = try(cors_rule.value.max_age_seconds, null) } } +} + +resource "aws_s3_bucket_lifecycle_configuration" "this" { + count = local.create_bucket && length(local.lifecycle_rules) > 0 ? 1 : 0 - dynamic "lifecycle_rule" { - for_each = try(jsondecode(var.lifecycle_rule), var.lifecycle_rule) + bucket = aws_s3_bucket.this[0].id + expected_bucket_owner = var.expected_bucket_owner + + dynamic "rule" { + for_each = local.lifecycle_rules content { - id = lookup(lifecycle_rule.value, "id", null) - prefix = lookup(lifecycle_rule.value, "prefix", null) - tags = lookup(lifecycle_rule.value, "tags", null) - abort_incomplete_multipart_upload_days = lookup(lifecycle_rule.value, "abort_incomplete_multipart_upload_days", null) - enabled = lifecycle_rule.value.enabled + id = try(rule.value.id, null) + 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 = length(keys(lookup(lifecycle_rule.value, "expiration", {}))) == 0 ? [] : [lookup(lifecycle_rule.value, "expiration", {})] + for_each = try(flatten([rule.value.expiration]), []) content { - date = lookup(expiration.value, "date", null) - days = lookup(expiration.value, "days", null) - expired_object_delete_marker = lookup(expiration.value, "expired_object_delete_marker", null) + date = try(expiration.value.date, null) + days = try(expiration.value.days, null) + expired_object_delete_marker = try(expiration.value.expired_object_delete_marker, null) } } # Several blocks - transition dynamic "transition" { - for_each = lookup(lifecycle_rule.value, "transition", []) + for_each = try(flatten([rule.value.transition]), []) content { - date = lookup(transition.value, "date", null) - days = lookup(transition.value, "days", null) + date = try(transition.value.date, null) + days = try(transition.value.days, null) storage_class = transition.value.storage_class } } # Max 1 block - noncurrent_version_expiration dynamic "noncurrent_version_expiration" { - for_each = length(keys(lookup(lifecycle_rule.value, "noncurrent_version_expiration", {}))) == 0 ? [] : [lookup(lifecycle_rule.value, "noncurrent_version_expiration", {})] + for_each = try(flatten([rule.value.noncurrent_version_expiration]), []) content { - days = lookup(noncurrent_version_expiration.value, "days", null) + newer_noncurrent_versions = try(noncurrent_version_expiration.value.newer_noncurrent_versions, null) + noncurrent_days = try(noncurrent_version_expiration.value.days, noncurrent_version_expiration.value.noncurrent_days, null) } } # Several blocks - noncurrent_version_transition dynamic "noncurrent_version_transition" { - for_each = lookup(lifecycle_rule.value, "noncurrent_version_transition", []) + for_each = try(flatten([rule.value.noncurrent_version_transition]), []) content { - days = lookup(noncurrent_version_transition.value, "days", null) - storage_class = noncurrent_version_transition.value.storage_class + newer_noncurrent_versions = try(noncurrent_version_transition.value.newer_noncurrent_versions, null) + noncurrent_days = try(noncurrent_version_transition.value.days, noncurrent_version_transition.value.noncurrent_days, null) + storage_class = noncurrent_version_transition.value.storage_class } } + + # Max 1 block - filter - without any key arguments or tags + dynamic "filter" { + for_each = length(try(flatten([rule.value.filter]), [])) == 0 ? [true] : [] + + content { + # prefix = "" + } + } + + # 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)), length(try(rule.value.filter.tags, rule.value.filter.tag, []))) == 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) + + dynamic "tag" { + for_each = try(filter.value.tags, filter.value.tag, []) + + content { + key = tag.key + value = tag.value + } + } + } + } + + # 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)), length(try(rule.value.filter.tags, rule.value.filter.tag, []))) > 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) + tags = try(filter.value.tags, filter.value.tag, null) + } + } + } + } + } + + # Must have bucket versioning enabled first + depends_on = [aws_s3_bucket_versioning.this] +} + +resource "aws_s3_bucket_object_lock_configuration" "this" { + count = local.create_bucket && var.object_lock_enabled && try(var.object_lock_configuration.rule.default_retention, null) != null ? 1 : 0 + + bucket = aws_s3_bucket.this[0].id + expected_bucket_owner = var.expected_bucket_owner + token = try(var.object_lock_configuration.token, null) + + rule { + default_retention { + mode = var.object_lock_configuration.rule.default_retention.mode + days = try(var.object_lock_configuration.rule.default_retention.days, null) + years = try(var.object_lock_configuration.rule.default_retention.years, null) } } +} + +resource "aws_s3_bucket_replication_configuration" "this" { + count = local.create_bucket && length(keys(var.replication_configuration)) > 0 ? 1 : 0 - # Max 1 block - replication_configuration - dynamic "replication_configuration" { - for_each = length(keys(var.replication_configuration)) == 0 ? [] : [var.replication_configuration] + bucket = aws_s3_bucket.this[0].id + role = var.replication_configuration["role"] + + dynamic "rule" { + for_each = flatten(try([var.replication_configuration["rule"]], [var.replication_configuration["rules"]], [])) content { - role = replication_configuration.value.role + id = try(rule.value.id, null) + priority = try(rule.value.priority, null) + prefix = try(rule.value.prefix, null) + status = try(tobool(rule.value.status) ? "Enabled" : "Disabled", title(lower(rule.value.status)), "Enabled") - dynamic "rules" { - for_each = replication_configuration.value.rules + dynamic "delete_marker_replication" { + for_each = flatten(try([rule.value.delete_marker_replication_status], [rule.value.delete_marker_replication], [])) content { - id = lookup(rules.value, "id", null) - priority = lookup(rules.value, "priority", null) - prefix = lookup(rules.value, "prefix", null) - delete_marker_replication_status = lookup(rules.value, "delete_marker_replication_status", null) - status = rules.value.status + # Valid values: "Enabled" or "Disabled" + status = try(tobool(delete_marker_replication.value) ? "Enabled" : "Disabled", title(lower(delete_marker_replication.value))) + } + } + + # Amazon S3 does not support this argument according to: + # https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_replication_configuration + # More infor about what does Amazon S3 replicate? + # https://docs.aws.amazon.com/AmazonS3/latest/userguide/replication-what-is-isnot-replicated.html + dynamic "existing_object_replication" { + for_each = flatten(try([rule.value.existing_object_replication_status], [rule.value.existing_object_replication], [])) - dynamic "destination" { - for_each = length(keys(lookup(rules.value, "destination", {}))) == 0 ? [] : [lookup(rules.value, "destination", {})] + content { + # Valid values: "Enabled" or "Disabled" + status = try(tobool(existing_object_replication.value) ? "Enabled" : "Disabled", title(lower(existing_object_replication.value))) + } + } + + dynamic "destination" { + for_each = try(flatten([rule.value.destination]), []) + + content { + bucket = destination.value.bucket + storage_class = try(destination.value.storage_class, null) + account = try(destination.value.account_id, destination.value.account, null) + + dynamic "access_control_translation" { + for_each = try(flatten([destination.value.access_control_translation]), []) content { - bucket = destination.value.bucket - storage_class = lookup(destination.value, "storage_class", null) - replica_kms_key_id = lookup(destination.value, "replica_kms_key_id", null) - account_id = lookup(destination.value, "account_id", null) + owner = title(lower(access_control_translation.value.owner)) + } + } - dynamic "access_control_translation" { - for_each = length(keys(lookup(destination.value, "access_control_translation", {}))) == 0 ? [] : [lookup(destination.value, "access_control_translation", {})] + dynamic "encryption_configuration" { + for_each = flatten([try(destination.value.encryption_configuration.replica_kms_key_id, destination.value.replica_kms_key_id, [])]) - content { - owner = access_control_translation.value.owner - } - } + content { + replica_kms_key_id = encryption_configuration.value + } + } - dynamic "replication_time" { - for_each = length(keys(lookup(destination.value, "replication_time", {}))) == 0 ? [] : [lookup(destination.value, "replication_time", {})] + dynamic "replication_time" { + for_each = try(flatten([destination.value.replication_time]), []) - content { - status = replication_time.value.status - minutes = replication_time.value.minutes - } - } + content { + # Valid values: "Enabled" or "Disabled" + status = try(tobool(replication_time.value.status) ? "Enabled" : "Disabled", title(lower(replication_time.value.status)), "Disabled") - dynamic "metrics" { - for_each = length(keys(lookup(destination.value, "metrics", {}))) == 0 ? [] : [lookup(destination.value, "metrics", {})] + dynamic "time" { + for_each = try(flatten([replication_time.value.minutes]), []) content { - status = metrics.value.status - minutes = metrics.value.minutes + minutes = replication_time.value.minutes } } } + } - dynamic "source_selection_criteria" { - for_each = length(keys(lookup(rules.value, "source_selection_criteria", {}))) == 0 ? [] : [lookup(rules.value, "source_selection_criteria", {})] + dynamic "metrics" { + for_each = try(flatten([destination.value.metrics]), []) content { + # Valid values: "Enabled" or "Disabled" + status = try(tobool(metrics.value.status) ? "Enabled" : "Disabled", title(lower(metrics.value.status)), "Disabled") - dynamic "sse_kms_encrypted_objects" { - for_each = length(keys(lookup(source_selection_criteria.value, "sse_kms_encrypted_objects", {}))) == 0 ? [] : [lookup(source_selection_criteria.value, "sse_kms_encrypted_objects", {})] + dynamic "event_threshold" { + for_each = try(flatten([metrics.value.minutes]), []) content { - - enabled = sse_kms_encrypted_objects.value.enabled + minutes = metrics.value.minutes } } } } + } + } + + dynamic "source_selection_criteria" { + for_each = try(flatten([rule.value.source_selection_criteria]), []) - # Send empty map if `filter` is an empty map or absent entirely - dynamic "filter" { - for_each = length(keys(lookup(rules.value, "filter", {}))) == 0 ? [{}] : [] + content { + dynamic "replica_modifications" { + for_each = flatten([try(source_selection_criteria.value.replica_modifications.enabled, source_selection_criteria.value.replica_modifications.status, [])]) - content {} + content { + # Valid values: "Enabled" or "Disabled" + status = try(tobool(replica_modifications.value) ? "Enabled" : "Disabled", title(lower(replica_modifications.value)), "Disabled") + } } - # Send `filter` if it is present and has at least one field - dynamic "filter" { - for_each = length(keys(lookup(rules.value, "filter", {}))) != 0 ? [lookup(rules.value, "filter", {})] : [] + dynamic "sse_kms_encrypted_objects" { + for_each = flatten([try(source_selection_criteria.value.sse_kms_encrypted_objects.enabled, source_selection_criteria.value.sse_kms_encrypted_objects.status, [])]) content { - prefix = lookup(filter.value, "prefix", null) - tags = lookup(filter.value, "tags", null) + # Valid values: "Enabled" or "Disabled" + status = try(tobool(sse_kms_encrypted_objects.value) ? "Enabled" : "Disabled", title(lower(sse_kms_encrypted_objects.value)), "Disabled") } } - } } - } - } - # Max 1 block - server_side_encryption_configuration - dynamic "server_side_encryption_configuration" { - for_each = length(keys(var.server_side_encryption_configuration)) == 0 ? [] : [var.server_side_encryption_configuration] + # Max 1 block - filter - without any key arguments or tags + dynamic "filter" { + for_each = length(try(flatten([rule.value.filter]), [])) == 0 ? [true] : [] - content { + content { + } + } - dynamic "rule" { - for_each = length(keys(lookup(server_side_encryption_configuration.value, "rule", {}))) == 0 ? [] : [lookup(server_side_encryption_configuration.value, "rule", {})] + # 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)), length(try(rule.value.filter.tags, rule.value.filter.tag, []))) == 1] content { - bucket_key_enabled = lookup(rule.value, "bucket_key_enabled", null) + prefix = try(filter.value.prefix, null) - dynamic "apply_server_side_encryption_by_default" { - for_each = length(keys(lookup(rule.value, "apply_server_side_encryption_by_default", {}))) == 0 ? [] : [ - lookup(rule.value, "apply_server_side_encryption_by_default", {})] + dynamic "tag" { + for_each = try(filter.value.tags, filter.value.tag, []) content { - sse_algorithm = apply_server_side_encryption_by_default.value.sse_algorithm - kms_master_key_id = lookup(apply_server_side_encryption_by_default.value, "kms_master_key_id", null) + key = tag.key + value = tag.value } } } } - } - } - - # Max 1 block - object_lock_configuration - dynamic "object_lock_configuration" { - for_each = length(keys(var.object_lock_configuration)) == 0 ? [] : [var.object_lock_configuration] - - content { - object_lock_enabled = object_lock_configuration.value.object_lock_enabled - dynamic "rule" { - for_each = length(keys(lookup(object_lock_configuration.value, "rule", {}))) == 0 ? [] : [lookup(object_lock_configuration.value, "rule", {})] + # 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)), length(try(rule.value.filter.tags, rule.value.filter.tag, []))) > 1] content { - default_retention { - mode = lookup(lookup(rule.value, "default_retention", {}), "mode") - days = lookup(lookup(rule.value, "default_retention", {}), "days", null) - years = lookup(lookup(rule.value, "default_retention", {}), "years", null) + and { + prefix = try(filter.value.prefix, null) + tags = try(filter.value.tags, filter.value.tag, null) } } } } } + # Must have bucket versioning enabled first + depends_on = [aws_s3_bucket_versioning.this] } resource "aws_s3_bucket_policy" "this" { diff --git a/modules/notification/README.md b/modules/notification/README.md index 824715c7..d93f19ac 100644 --- a/modules/notification/README.md +++ b/modules/notification/README.md @@ -8,13 +8,13 @@ Creates S3 bucket notification resource with all supported types of deliveries: | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 0.13.1 | -| [aws](#requirement\_aws) | ~> 3.28 | +| [aws](#requirement\_aws) | >= 3.74 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | ~> 3.28 | +| [aws](#provider\_aws) | >= 3.74 | ## Modules @@ -31,6 +31,7 @@ No modules. | [aws_arn.queue](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/arn) | data source | | [aws_iam_policy_document.sns](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.sqs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_partition.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | ## Inputs @@ -41,6 +42,7 @@ No modules. | [create](#input\_create) | Whether to create this resource or not? | `bool` | `true` | no | | [create\_sns\_policy](#input\_create\_sns\_policy) | Whether to create a policy for SNS permissions or not? | `bool` | `true` | no | | [create\_sqs\_policy](#input\_create\_sqs\_policy) | Whether to create a policy for SQS permissions or not? | `bool` | `true` | no | +| [eventbridge](#input\_eventbridge) | Whether to enable Amazon EventBridge notifications | `bool` | `null` | no | | [lambda\_notifications](#input\_lambda\_notifications) | Map of S3 bucket notifications to Lambda function | `any` | `{}` | no | | [sns\_notifications](#input\_sns\_notifications) | Map of S3 bucket notifications to SNS topic | `any` | `{}` | no | | [sqs\_notifications](#input\_sqs\_notifications) | Map of S3 bucket notifications to SQS queue | `any` | `{}` | no | diff --git a/modules/notification/main.tf b/modules/notification/main.tf index 8bd7405e..edde7e25 100644 --- a/modules/notification/main.tf +++ b/modules/notification/main.tf @@ -1,16 +1,20 @@ +data "aws_partition" "this" {} + locals { - bucket_arn = coalesce(var.bucket_arn, "arn:aws:s3:::${var.bucket}") + bucket_arn = coalesce(var.bucket_arn, "arn:${data.aws_partition.this.partition}:s3:::${var.bucket}") # Convert from "arn:aws:sqs:eu-west-1:835367859851:bold-starling-0" into "https://sqs.eu-west-1.amazonaws.com/835367859851/bold-starling-0" if queue_id was not specified # queue_url used in aws_sqs_queue_policy is not the same as arn which is used in all other places - queue_ids = { for k, v in var.sqs_notifications : k => format("https://%s.%s.amazonaws.com/%s/%s", data.aws_arn.queue[k].service, data.aws_arn.queue[k].region, data.aws_arn.queue[k].account, data.aws_arn.queue[k].resource) if lookup(v, "queue_id", "") == "" } + queue_ids = { for k, v in var.sqs_notifications : k => format("https://%s.%s.amazonaws.com/%s/%s", data.aws_arn.queue[k].service, data.aws_arn.queue[k].region, data.aws_arn.queue[k].account, data.aws_arn.queue[k].resource) if try(v.queue_id, "") == "" } } resource "aws_s3_bucket_notification" "this" { - count = var.create && (length(var.lambda_notifications) > 0 || length(var.sqs_notifications) > 0 || length(var.sns_notifications) > 0) ? 1 : 0 + count = var.create ? 1 : 0 bucket = var.bucket + eventbridge = var.eventbridge + dynamic "lambda_function" { for_each = var.lambda_notifications @@ -18,8 +22,8 @@ resource "aws_s3_bucket_notification" "this" { id = lambda_function.key lambda_function_arn = lambda_function.value.function_arn events = lambda_function.value.events - filter_prefix = lookup(lambda_function.value, "filter_prefix", null) - filter_suffix = lookup(lambda_function.value, "filter_suffix", null) + filter_prefix = try(lambda_function.value.filter_prefix, null) + filter_suffix = try(lambda_function.value.filter_suffix, null) } } @@ -30,8 +34,8 @@ resource "aws_s3_bucket_notification" "this" { id = queue.key queue_arn = queue.value.queue_arn events = queue.value.events - filter_prefix = lookup(queue.value, "filter_prefix", null) - filter_suffix = lookup(queue.value, "filter_suffix", null) + filter_prefix = try(queue.value.filter_prefix, null) + filter_suffix = try(queue.value.filter_suffix, null) } } @@ -42,8 +46,8 @@ resource "aws_s3_bucket_notification" "this" { id = topic.key topic_arn = topic.value.topic_arn events = topic.value.events - filter_prefix = lookup(topic.value, "filter_prefix", null) - filter_suffix = lookup(topic.value, "filter_suffix", null) + filter_prefix = try(topic.value.filter_prefix, null) + filter_suffix = try(topic.value.filter_suffix, null) } } @@ -61,10 +65,10 @@ resource "aws_lambda_permission" "allow" { statement_id_prefix = "AllowLambdaS3BucketNotification-" action = "lambda:InvokeFunction" function_name = each.value.function_name - qualifier = lookup(each.value, "qualifier", null) + qualifier = try(each.value.qualifier, null) principal = "s3.amazonaws.com" source_arn = local.bucket_arn - source_account = lookup(each.value, "source_account", null) + source_account = try(each.value.source_account, null) } # SQS Queue @@ -104,7 +108,7 @@ data "aws_iam_policy_document" "sqs" { resource "aws_sqs_queue_policy" "allow" { for_each = { for k, v in var.sqs_notifications : k => v if var.create_sqs_policy } - queue_url = lookup(each.value, "queue_id", lookup(local.queue_ids, each.key, null)) + queue_url = try(each.value.queue_id, local.queue_ids[each.key], null) policy = data.aws_iam_policy_document.sqs[each.key].json } diff --git a/modules/notification/outputs.tf b/modules/notification/outputs.tf index 6086a9e7..6c08c237 100644 --- a/modules/notification/outputs.tf +++ b/modules/notification/outputs.tf @@ -1,4 +1,4 @@ output "s3_bucket_notification_id" { description = "ID of S3 bucket" - value = element(concat(aws_s3_bucket_notification.this.*.id, [""]), 0) + value = try(aws_s3_bucket_notification.this[0].id, "") } diff --git a/modules/notification/variables.tf b/modules/notification/variables.tf index 2a78d8f3..16f81937 100644 --- a/modules/notification/variables.tf +++ b/modules/notification/variables.tf @@ -28,6 +28,12 @@ variable "bucket_arn" { default = null } +variable "eventbridge" { + description = "Whether to enable Amazon EventBridge notifications" + type = bool + default = null +} + variable "lambda_notifications" { description = "Map of S3 bucket notifications to Lambda function" type = any diff --git a/modules/notification/versions.tf b/modules/notification/versions.tf index 9324c89a..538b9152 100644 --- a/modules/notification/versions.tf +++ b/modules/notification/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = "~> 3.28" + version = ">= 3.74" } } } diff --git a/modules/object/README.md b/modules/object/README.md index 9656be3f..bea7970c 100644 --- a/modules/object/README.md +++ b/modules/object/README.md @@ -8,13 +8,13 @@ Creates S3 bucket objects with different configurations. | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 0.13.1 | -| [aws](#requirement\_aws) | ~> 3.36 | +| [aws](#requirement\_aws) | >= 3.75 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | ~> 3.36 | +| [aws](#provider\_aws) | >= 3.75 | ## Modules @@ -24,7 +24,7 @@ No modules. | Name | Type | |------|------| -| [aws_s3_bucket_object.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_object) | resource | +| [aws_s3_object.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_object) | resource | ## Inputs @@ -59,7 +59,7 @@ No modules. | Name | Description | |------|-------------| -| [s3\_bucket\_object\_etag](#output\_s3\_bucket\_object\_etag) | The ETag generated for the object (an MD5 sum of the object content). | -| [s3\_bucket\_object\_id](#output\_s3\_bucket\_object\_id) | The key of S3 object | -| [s3\_bucket\_object\_version\_id](#output\_s3\_bucket\_object\_version\_id) | A unique version ID value for the object, if bucket versioning is enabled. | +| [s3\_object\_etag](#output\_s3\_object\_etag) | The ETag generated for the object (an MD5 sum of the object content). | +| [s3\_object\_id](#output\_s3\_object\_id) | The key of S3 object | +| [s3\_object\_version\_id](#output\_s3\_object\_version\_id) | A unique version ID value for the object, if bucket versioning is enabled. | diff --git a/modules/object/main.tf b/modules/object/main.tf index a6f9501b..42346720 100644 --- a/modules/object/main.tf +++ b/modules/object/main.tf @@ -1,4 +1,4 @@ -resource "aws_s3_bucket_object" "this" { +resource "aws_s3_object" "this" { count = var.create ? 1 : 0 bucket = var.bucket diff --git a/modules/object/outputs.tf b/modules/object/outputs.tf index c5075ade..5cdb1db4 100644 --- a/modules/object/outputs.tf +++ b/modules/object/outputs.tf @@ -1,14 +1,14 @@ -output "s3_bucket_object_id" { +output "s3_object_id" { description = "The key of S3 object" - value = element(concat(aws_s3_bucket_object.this.*.id, [""]), 0) + value = try(aws_s3_object.this[0].id, "") } -output "s3_bucket_object_etag" { +output "s3_object_etag" { description = "The ETag generated for the object (an MD5 sum of the object content)." - value = element(concat(aws_s3_bucket_object.this.*.etag, [""]), 0) + value = try(aws_s3_object.this[0].etag, "") } -output "s3_bucket_object_version_id" { +output "s3_object_version_id" { description = "A unique version ID value for the object, if bucket versioning is enabled." - value = element(concat(aws_s3_bucket_object.this.*.version_id, [""]), 0) + value = try(aws_s3_object.this[0].version_id, "") } diff --git a/modules/object/versions.tf b/modules/object/versions.tf index fd876394..8a65df68 100644 --- a/modules/object/versions.tf +++ b/modules/object/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = "~> 3.36" + version = ">= 3.75" } } } diff --git a/outputs.tf b/outputs.tf index 6bb32f75..518cf437 100644 --- a/outputs.tf +++ b/outputs.tf @@ -1,39 +1,39 @@ output "s3_bucket_id" { description = "The name of the bucket." - value = element(concat(aws_s3_bucket_policy.this.*.id, aws_s3_bucket.this.*.id, [""]), 0) + value = try(aws_s3_bucket_policy.this[0].id, aws_s3_bucket.this[0].id, "") } output "s3_bucket_arn" { description = "The ARN of the bucket. Will be of format arn:aws:s3:::bucketname." - value = element(concat(aws_s3_bucket.this.*.arn, [""]), 0) + value = try(aws_s3_bucket.this[0].arn, "") } output "s3_bucket_bucket_domain_name" { description = "The bucket domain name. Will be of format bucketname.s3.amazonaws.com." - value = element(concat(aws_s3_bucket.this.*.bucket_domain_name, [""]), 0) + value = try(aws_s3_bucket.this[0].bucket_domain_name, "") } output "s3_bucket_bucket_regional_domain_name" { description = "The bucket region-specific domain name. The bucket domain name including the region name, please refer here for format. Note: The AWS CloudFront allows specifying S3 region-specific endpoint when creating S3 origin, it will prevent redirect issues from CloudFront to S3 Origin URL." - value = element(concat(aws_s3_bucket.this.*.bucket_regional_domain_name, [""]), 0) + value = try(aws_s3_bucket.this[0].bucket_regional_domain_name, "") } output "s3_bucket_hosted_zone_id" { description = "The Route 53 Hosted Zone ID for this bucket's region." - value = element(concat(aws_s3_bucket.this.*.hosted_zone_id, [""]), 0) + value = try(aws_s3_bucket.this[0].hosted_zone_id, "") } output "s3_bucket_region" { description = "The AWS region this bucket resides in." - value = element(concat(aws_s3_bucket.this.*.region, [""]), 0) + value = try(aws_s3_bucket.this[0].region, "") } output "s3_bucket_website_endpoint" { description = "The website endpoint, if the bucket is configured with a website. If not, this will be an empty string." - value = element(concat(aws_s3_bucket.this.*.website_endpoint, [""]), 0) + value = try(aws_s3_bucket_website_configuration.this[0].website_endpoint, "") } 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 = element(concat(aws_s3_bucket.this.*.website_domain, [""]), 0) + 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, "") } diff --git a/variables.tf b/variables.tf index 6460a296..447f0ffb 100644 --- a/variables.tf +++ b/variables.tf @@ -53,9 +53,9 @@ variable "bucket_prefix" { } variable "acl" { - description = "(Optional) The canned ACL to apply. Defaults to 'private'. Conflicts with `grant`" + description = "(Optional) The canned ACL to apply. Conflicts with `grant`" type = string - default = "private" + default = null } variable "policy" { @@ -90,7 +90,7 @@ variable "request_payer" { variable "website" { description = "Map containing static web-site hosting or redirect configuration." - type = map(string) + type = any # map(string) default = {} } @@ -118,6 +118,18 @@ variable "grant" { default = [] } +variable "owner" { + description = "Bucket owner's display name and ID. Conflicts with `acl`" + type = map(string) + default = {} +} + +variable "expected_bucket_owner" { + description = "The account ID of the expected bucket owner" + type = string + default = null +} + variable "lifecycle_rule" { description = "List of maps containing configuration of object lifecycle management." type = any @@ -142,6 +154,12 @@ variable "object_lock_configuration" { default = {} } +variable "object_lock_enabled" { + description = "Whether S3 bucket should have an Object Lock configuration enabled." + type = bool + default = false +} + variable "block_public_acls" { description = "Whether Amazon S3 should block public ACLs for this bucket." type = bool diff --git a/versions.tf b/versions.tf index 126d7041..5cd772c4 100644 --- a/versions.tf +++ b/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = "~> 3.69" + version = ">= 4.5" } } } diff --git a/wrappers/README.md b/wrappers/README.md index d0e1c4b2..9aa15fce 100644 --- a/wrappers/README.md +++ b/wrappers/README.md @@ -12,10 +12,20 @@ This wrapper does not implement any extra functionality. ```hcl terraform { - source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git?ref=master//wrappers" + source = "tfr:///terraform-aws-modules/s3-bucket/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git?ref=master//wrappers" } inputs = { + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + items = { my-item = { # omitted... can be any argument supported by the module @@ -34,6 +44,14 @@ inputs = { module "wrapper" { source = "terraform-aws-modules/s3-bucket/aws//wrappers" + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + items = { my-item = { # omitted... can be any argument supported by the module @@ -52,18 +70,30 @@ module "wrapper" { ```hcl terraform { - source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git?ref=master//wrappers" + source = "tfr:///terraform-aws-modules/s3-bucket/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git?ref=master//wrappers" } 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" - force_destroy = true + bucket = "my-random-bucket-1" } bucket2 = { - bucket = "my-random-bucket-2" - force_destroy = true + bucket = "my-random-bucket-2" + tags = { + Secure = "probably" + } } } } diff --git a/wrappers/main.tf b/wrappers/main.tf index 44d17e2e..ea8315c2 100644 --- a/wrappers/main.tf +++ b/wrappers/main.tf @@ -3,33 +3,38 @@ module "wrapper" { for_each = var.items - create_bucket = lookup(each.value, "create_bucket", true) - attach_elb_log_delivery_policy = lookup(each.value, "attach_elb_log_delivery_policy", false) - attach_lb_log_delivery_policy = lookup(each.value, "attach_lb_log_delivery_policy", false) - attach_deny_insecure_transport_policy = lookup(each.value, "attach_deny_insecure_transport_policy", false) - attach_policy = lookup(each.value, "attach_policy", false) - attach_public_policy = lookup(each.value, "attach_public_policy", true) - bucket = lookup(each.value, "bucket", null) - bucket_prefix = lookup(each.value, "bucket_prefix", null) - acl = lookup(each.value, "acl", "private") - policy = lookup(each.value, "policy", null) - tags = lookup(each.value, "tags", {}) - force_destroy = lookup(each.value, "force_destroy", false) - acceleration_status = lookup(each.value, "acceleration_status", null) - request_payer = lookup(each.value, "request_payer", null) - website = lookup(each.value, "website", {}) - cors_rule = lookup(each.value, "cors_rule", []) - versioning = lookup(each.value, "versioning", {}) - logging = lookup(each.value, "logging", {}) - grant = lookup(each.value, "grant", []) - lifecycle_rule = lookup(each.value, "lifecycle_rule", []) - replication_configuration = lookup(each.value, "replication_configuration", {}) - server_side_encryption_configuration = lookup(each.value, "server_side_encryption_configuration", {}) - object_lock_configuration = lookup(each.value, "object_lock_configuration", {}) - block_public_acls = lookup(each.value, "block_public_acls", false) - block_public_policy = lookup(each.value, "block_public_policy", false) - ignore_public_acls = lookup(each.value, "ignore_public_acls", false) - restrict_public_buckets = lookup(each.value, "restrict_public_buckets", false) - control_object_ownership = lookup(each.value, "control_object_ownership", false) - object_ownership = lookup(each.value, "object_ownership", "ObjectWriter") + create_bucket = try(each.value.create_bucket, var.defaults.create_bucket, true) + attach_elb_log_delivery_policy = try(each.value.attach_elb_log_delivery_policy, var.defaults.attach_elb_log_delivery_policy, false) + attach_lb_log_delivery_policy = try(each.value.attach_lb_log_delivery_policy, var.defaults.attach_lb_log_delivery_policy, false) + attach_deny_insecure_transport_policy = try(each.value.attach_deny_insecure_transport_policy, var.defaults.attach_deny_insecure_transport_policy, false) + attach_require_latest_tls_policy = try(each.value.attach_require_latest_tls_policy, var.defaults.attach_require_latest_tls_policy, false) + 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) + bucket = try(each.value.bucket, var.defaults.bucket, null) + bucket_prefix = try(each.value.bucket_prefix, var.defaults.bucket_prefix, null) + acl = try(each.value.acl, var.defaults.acl, null) + policy = try(each.value.policy, var.defaults.policy, null) + tags = try(each.value.tags, var.defaults.tags, {}) + force_destroy = try(each.value.force_destroy, var.defaults.force_destroy, false) + acceleration_status = try(each.value.acceleration_status, var.defaults.acceleration_status, null) + request_payer = try(each.value.request_payer, var.defaults.request_payer, null) + website = try(each.value.website, var.defaults.website, {}) + cors_rule = try(each.value.cors_rule, var.defaults.cors_rule, []) + versioning = try(each.value.versioning, var.defaults.versioning, {}) + logging = try(each.value.logging, var.defaults.logging, {}) + grant = try(each.value.grant, var.defaults.grant, []) + owner = try(each.value.owner, var.defaults.owner, {}) + expected_bucket_owner = try(each.value.expected_bucket_owner, var.defaults.expected_bucket_owner, null) + lifecycle_rule = try(each.value.lifecycle_rule, var.defaults.lifecycle_rule, []) + replication_configuration = try(each.value.replication_configuration, var.defaults.replication_configuration, {}) + server_side_encryption_configuration = try(each.value.server_side_encryption_configuration, var.defaults.server_side_encryption_configuration, {}) + object_lock_configuration = try(each.value.object_lock_configuration, var.defaults.object_lock_configuration, {}) + object_lock_enabled = try(each.value.object_lock_enabled, var.defaults.object_lock_enabled, false) + block_public_acls = try(each.value.block_public_acls, var.defaults.block_public_acls, false) + block_public_policy = try(each.value.block_public_policy, var.defaults.block_public_policy, false) + ignore_public_acls = try(each.value.ignore_public_acls, var.defaults.ignore_public_acls, false) + restrict_public_buckets = try(each.value.restrict_public_buckets, var.defaults.restrict_public_buckets, false) + control_object_ownership = try(each.value.control_object_ownership, var.defaults.control_object_ownership, false) + object_ownership = try(each.value.object_ownership, var.defaults.object_ownership, "ObjectWriter") + putin_khuylo = try(each.value.putin_khuylo, var.defaults.putin_khuylo, true) } diff --git a/wrappers/notification/README.md b/wrappers/notification/README.md index cf3e1cad..f9690b41 100644 --- a/wrappers/notification/README.md +++ b/wrappers/notification/README.md @@ -12,10 +12,20 @@ This wrapper does not implement any extra functionality. ```hcl terraform { - source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git?ref=master//wrappers/notification" + source = "tfr:///terraform-aws-modules/s3-bucket/aws//wrappers/notification" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git?ref=master//wrappers/notification" } inputs = { + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + items = { my-item = { # omitted... can be any argument supported by the module @@ -34,6 +44,14 @@ inputs = { module "wrapper" { source = "terraform-aws-modules/s3-bucket/aws//wrappers/notification" + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + items = { my-item = { # omitted... can be any argument supported by the module @@ -52,18 +70,30 @@ module "wrapper" { ```hcl terraform { - source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git?ref=master//wrappers" + source = "tfr:///terraform-aws-modules/s3-bucket/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git?ref=master//wrappers" } 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" - force_destroy = true + bucket = "my-random-bucket-1" } bucket2 = { - bucket = "my-random-bucket-2" - force_destroy = true + bucket = "my-random-bucket-2" + tags = { + Secure = "probably" + } } } } diff --git a/wrappers/notification/main.tf b/wrappers/notification/main.tf index a6b89b54..ddd2cbba 100644 --- a/wrappers/notification/main.tf +++ b/wrappers/notification/main.tf @@ -3,12 +3,13 @@ module "wrapper" { for_each = var.items - create = lookup(each.value, "create", true) - create_sns_policy = lookup(each.value, "create_sns_policy", true) - create_sqs_policy = lookup(each.value, "create_sqs_policy", true) - bucket = lookup(each.value, "bucket", "") - bucket_arn = lookup(each.value, "bucket_arn", null) - lambda_notifications = lookup(each.value, "lambda_notifications", {}) - sqs_notifications = lookup(each.value, "sqs_notifications", {}) - sns_notifications = lookup(each.value, "sns_notifications", {}) + create = try(each.value.create, var.defaults.create, true) + create_sns_policy = try(each.value.create_sns_policy, var.defaults.create_sns_policy, true) + create_sqs_policy = try(each.value.create_sqs_policy, var.defaults.create_sqs_policy, true) + bucket = try(each.value.bucket, var.defaults.bucket, "") + bucket_arn = try(each.value.bucket_arn, var.defaults.bucket_arn, null) + eventbridge = try(each.value.eventbridge, var.defaults.eventbridge, null) + lambda_notifications = try(each.value.lambda_notifications, var.defaults.lambda_notifications, {}) + sqs_notifications = try(each.value.sqs_notifications, var.defaults.sqs_notifications, {}) + sns_notifications = try(each.value.sns_notifications, var.defaults.sns_notifications, {}) } diff --git a/wrappers/notification/outputs.tf b/wrappers/notification/outputs.tf index d4602543..5da7c09b 100644 --- a/wrappers/notification/outputs.tf +++ b/wrappers/notification/outputs.tf @@ -1,4 +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/notification/variables.tf b/wrappers/notification/variables.tf index 9c7d9234..a6ea0962 100644 --- a/wrappers/notification/variables.tf +++ b/wrappers/notification/variables.tf @@ -1,3 +1,9 @@ +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 diff --git a/wrappers/object/README.md b/wrappers/object/README.md index b852ec15..f4e6f0e5 100644 --- a/wrappers/object/README.md +++ b/wrappers/object/README.md @@ -12,10 +12,20 @@ This wrapper does not implement any extra functionality. ```hcl terraform { - source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git?ref=master//wrappers/object" + source = "tfr:///terraform-aws-modules/s3-bucket/aws//wrappers/object" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git?ref=master//wrappers/object" } inputs = { + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + items = { my-item = { # omitted... can be any argument supported by the module @@ -34,6 +44,14 @@ inputs = { module "wrapper" { source = "terraform-aws-modules/s3-bucket/aws//wrappers/object" + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + items = { my-item = { # omitted... can be any argument supported by the module @@ -52,18 +70,30 @@ module "wrapper" { ```hcl terraform { - source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git?ref=master//wrappers" + source = "tfr:///terraform-aws-modules/s3-bucket/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git?ref=master//wrappers" } 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" - force_destroy = true + bucket = "my-random-bucket-1" } bucket2 = { - bucket = "my-random-bucket-2" - force_destroy = true + bucket = "my-random-bucket-2" + tags = { + Secure = "probably" + } } } } diff --git a/wrappers/object/main.tf b/wrappers/object/main.tf index f6eb2e84..b0e94a71 100644 --- a/wrappers/object/main.tf +++ b/wrappers/object/main.tf @@ -3,28 +3,28 @@ module "wrapper" { for_each = var.items - create = lookup(each.value, "create", true) - bucket = lookup(each.value, "bucket", "") - key = lookup(each.value, "key", "") - file_source = lookup(each.value, "file_source", null) - content = lookup(each.value, "content", null) - content_base64 = lookup(each.value, "content_base64", null) - acl = lookup(each.value, "acl", null) - cache_control = lookup(each.value, "cache_control", null) - content_disposition = lookup(each.value, "content_disposition", null) - content_encoding = lookup(each.value, "content_encoding", null) - content_language = lookup(each.value, "content_language", null) - content_type = lookup(each.value, "content_type", null) - website_redirect = lookup(each.value, "website_redirect", null) - storage_class = lookup(each.value, "storage_class", null) - etag = lookup(each.value, "etag", null) - server_side_encryption = lookup(each.value, "server_side_encryption", null) - kms_key_id = lookup(each.value, "kms_key_id", null) - bucket_key_enabled = lookup(each.value, "bucket_key_enabled", null) - metadata = lookup(each.value, "metadata", {}) - tags = lookup(each.value, "tags", {}) - force_destroy = lookup(each.value, "force_destroy", false) - object_lock_legal_hold_status = lookup(each.value, "object_lock_legal_hold_status", null) - object_lock_mode = lookup(each.value, "object_lock_mode", null) - object_lock_retain_until_date = lookup(each.value, "object_lock_retain_until_date", null) + create = try(each.value.create, var.defaults.create, true) + bucket = try(each.value.bucket, var.defaults.bucket, "") + key = try(each.value.key, var.defaults.key, "") + file_source = try(each.value.file_source, var.defaults.file_source, null) + content = try(each.value.content, var.defaults.content, null) + content_base64 = try(each.value.content_base64, var.defaults.content_base64, null) + acl = try(each.value.acl, var.defaults.acl, null) + cache_control = try(each.value.cache_control, var.defaults.cache_control, null) + content_disposition = try(each.value.content_disposition, var.defaults.content_disposition, null) + content_encoding = try(each.value.content_encoding, var.defaults.content_encoding, null) + content_language = try(each.value.content_language, var.defaults.content_language, null) + content_type = try(each.value.content_type, var.defaults.content_type, null) + website_redirect = try(each.value.website_redirect, var.defaults.website_redirect, null) + storage_class = try(each.value.storage_class, var.defaults.storage_class, null) + etag = try(each.value.etag, var.defaults.etag, null) + server_side_encryption = try(each.value.server_side_encryption, var.defaults.server_side_encryption, null) + kms_key_id = try(each.value.kms_key_id, var.defaults.kms_key_id, null) + bucket_key_enabled = try(each.value.bucket_key_enabled, var.defaults.bucket_key_enabled, null) + metadata = try(each.value.metadata, var.defaults.metadata, {}) + tags = try(each.value.tags, var.defaults.tags, {}) + force_destroy = try(each.value.force_destroy, var.defaults.force_destroy, false) + object_lock_legal_hold_status = try(each.value.object_lock_legal_hold_status, var.defaults.object_lock_legal_hold_status, null) + object_lock_mode = try(each.value.object_lock_mode, var.defaults.object_lock_mode, null) + object_lock_retain_until_date = try(each.value.object_lock_retain_until_date, var.defaults.object_lock_retain_until_date, null) } diff --git a/wrappers/object/outputs.tf b/wrappers/object/outputs.tf index d4602543..5da7c09b 100644 --- a/wrappers/object/outputs.tf +++ b/wrappers/object/outputs.tf @@ -1,4 +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/object/variables.tf b/wrappers/object/variables.tf index 9c7d9234..a6ea0962 100644 --- a/wrappers/object/variables.tf +++ b/wrappers/object/variables.tf @@ -1,3 +1,9 @@ +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 diff --git a/wrappers/outputs.tf b/wrappers/outputs.tf index d4602543..5da7c09b 100644 --- a/wrappers/outputs.tf +++ b/wrappers/outputs.tf @@ -1,4 +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/variables.tf b/wrappers/variables.tf index 9c7d9234..a6ea0962 100644 --- a/wrappers/variables.tf +++ b/wrappers/variables.tf @@ -1,3 +1,9 @@ +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