Commit e0de434f authored by Anton Babenko's avatar Anton Babenko Committed by GitHub

feat!: Update to support AWS provider v3.75 and newer (including v4.x) (#139)

BREAKING CHANGES:
- Yes, see file `UPGRADE-3.0.md`
- Requires AWS provider v3.75 or newer (including v4.x)
parent 5d0ed518
......@@ -110,13 +110,13 @@ inputs = {
| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 0.13.1 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | ~> 3.69 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 3.75 |
## Providers
| Name | Version |
|------|---------|
| <a name="provider_aws"></a> [aws](#provider\_aws) | ~> 3.69 |
| <a name="provider_aws"></a> [aws](#provider\_aws) | >= 3.75 |
## Modules
......@@ -127,9 +127,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 +154,7 @@ No modules.
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_acceleration_status"></a> [acceleration\_status](#input\_acceleration\_status) | (Optional) Sets the accelerate configuration of an existing bucket. Can be Enabled or Suspended. | `string` | `null` | no |
| <a name="input_acl"></a> [acl](#input\_acl) | (Optional) The canned ACL to apply. Defaults to 'private'. Conflicts with `grant` | `string` | `"private"` | no |
| <a name="input_acl"></a> [acl](#input\_acl) | (Optional) The canned ACL to apply. Conflicts with `grant` | `string` | `null` | no |
| <a name="input_attach_deny_insecure_transport_policy"></a> [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 |
| <a name="input_attach_elb_log_delivery_policy"></a> [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 |
| <a name="input_attach_lb_log_delivery_policy"></a> [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,6 +168,7 @@ No modules.
| <a name="input_control_object_ownership"></a> [control\_object\_ownership](#input\_control\_object\_ownership) | Whether to manage S3 Bucket Ownership Controls on this bucket. | `bool` | `false` | no |
| <a name="input_cors_rule"></a> [cors\_rule](#input\_cors\_rule) | List of maps containing rules for Cross-Origin Resource Sharing. | `any` | `[]` | no |
| <a name="input_create_bucket"></a> [create\_bucket](#input\_create\_bucket) | Controls if S3 bucket should be created | `bool` | `true` | no |
| <a name="input_expected_bucket_owner"></a> [expected\_bucket\_owner](#input\_expected\_bucket\_owner) | The account ID of the expected bucket owner | `string` | `null` | no |
| <a name="input_force_destroy"></a> [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 |
| <a name="input_grant"></a> [grant](#input\_grant) | An ACL policy grant. Conflicts with `acl` | `any` | `[]` | no |
| <a name="input_ignore_public_acls"></a> [ignore\_public\_acls](#input\_ignore\_public\_acls) | Whether Amazon S3 should ignore public ACLs for this bucket. | `bool` | `false` | no |
......@@ -163,6 +176,7 @@ No modules.
| <a name="input_logging"></a> [logging](#input\_logging) | Map containing access bucket logging configuration. | `map(string)` | `{}` | no |
| <a name="input_object_lock_configuration"></a> [object\_lock\_configuration](#input\_object\_lock\_configuration) | Map containing S3 object locking configuration. | `any` | `{}` | no |
| <a name="input_object_ownership"></a> [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 |
| <a name="input_owner"></a> [owner](#input\_owner) | Bucket owner's display name and ID. Conflicts with `acl` | `map(string)` | `{}` | no |
| <a name="input_policy"></a> [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 |
| <a name="input_putin_khuylo"></a> [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 |
| <a name="input_replication_configuration"></a> [replication\_configuration](#input\_replication\_configuration) | Map containing cross-region replication configuration. | `any` | `{}` | no |
......@@ -171,7 +185,7 @@ No modules.
| <a name="input_server_side_encryption_configuration"></a> [server\_side\_encryption\_configuration](#input\_server\_side\_encryption\_configuration) | Map containing server-side encryption configuration. | `any` | `{}` | no |
| <a name="input_tags"></a> [tags](#input\_tags) | (Optional) A mapping of tags to assign to the bucket. | `map(string)` | `{}` | no |
| <a name="input_versioning"></a> [versioning](#input\_versioning) | Map containing versioning configuration. | `map(string)` | `{}` | no |
| <a name="input_website"></a> [website](#input\_website) | Map containing static web-site hosting or redirect configuration. | `map(string)` | `{}` | no |
| <a name="input_website"></a> [website](#input\_website) | Map containing static web-site hosting or redirect configuration. | `any` | `{}` | no |
## Outputs
......
# 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 `v3.75.0` in order to have forward compatibility with Terraform AWS provider `v4.x`. Using the latest version of `v4` is highly recommended, if possible.
- 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) can help even when using AWS provider version 3.75 or newer.
- `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 = ">= 3.75" # or anything higher than 3.75.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.this[0]" <bucket-name>
terraform import "module.s3_bucket.aws_s3_bucket_acl.this[0]" <bucket-name>,<acl>
terraform import "module.s3_bucket.aws_s3_bucket_logging.this[0]" <bucket-name>
terraform import "module.s3_bucket.aws_s3_bucket_website_configuration.this[0]" <bucket-name>,<account-id>
terraform import "module.s3_bucket.aws_s3_bucket_versioning.this[0]" <bucket-name>,<account-id>
terraform import "module.s3_bucket.aws_s3_bucket_server_side_encryption_configuration.this[0]" <bucket-name>,<account-id>
terraform import "module.s3_bucket.aws_s3_bucket_request_payment_configuration.this[0]" <bucket-name>,<account-id>
terraform import "module.s3_bucket.aws_s3_bucket_accelerate_configuration.this[0]" <bucket-name>,<account-id>
terraform import "module.s3_bucket.aws_s3_bucket_policy.this[0]" <bucket-name>
terraform import "module.s3_bucket.aws_s3_bucket_ownership_controls.this[0]" <bucket-name>
terraform import "module.s3_bucket.aws_s3_bucket_cors_configuration.this[0]" <bucket-name>,<account-id>
terraform import "module.s3_bucket.aws_s3_bucket_object_lock_configuration.this[0]" <bucket-name>,<account-id>
terraform import "module.s3_bucket.aws_s3_bucket_public_access_block.this[0]" <bucket-name>
terraform import "module.s3_bucket.aws_s3_bucket_lifecycle_configuration.this[0]" <bucket-name>,<account-id>
terraform import "module.s3_bucket.aws_s3_bucket_replication_configuration.this[0]" <bucket-name>
```
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), `<account-id>` is your AWS account number (required only if `expected_bucket_owner` is set in the code).
# 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).
<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
## Requirements
| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 0.13.1 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | ~> 3.69.0 |
| <a name="requirement_random"></a> [random](#requirement\_random) | >= 2.0 |
## Providers
| Name | Version |
|------|---------|
| <a name="provider_aws"></a> [aws](#provider\_aws) | ~> 3.69.0 |
| <a name="provider_random"></a> [random](#provider\_random) | >= 2.0 |
## Modules
| Name | Source | Version |
|------|--------|---------|
| <a name="module_log_bucket"></a> [log\_bucket](#module\_log\_bucket) | terraform-aws-modules/s3-bucket/aws | ~> 2.0 |
| <a name="module_s3_bucket"></a> [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 |
|------|-------------|
| <a name="output_s3_bucket_arn"></a> [s3\_bucket\_arn](#output\_s3\_bucket\_arn) | The ARN of the bucket. Will be of format arn:aws:s3:::bucketname. |
| <a name="output_s3_bucket_bucket_domain_name"></a> [s3\_bucket\_bucket\_domain\_name](#output\_s3\_bucket\_bucket\_domain\_name) | The bucket domain name. Will be of format bucketname.s3.amazonaws.com. |
| <a name="output_s3_bucket_bucket_regional_domain_name"></a> [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. |
| <a name="output_s3_bucket_hosted_zone_id"></a> [s3\_bucket\_hosted\_zone\_id](#output\_s3\_bucket\_hosted\_zone\_id) | The Route 53 Hosted Zone ID for this bucket's region. |
| <a name="output_s3_bucket_id"></a> [s3\_bucket\_id](#output\_s3\_bucket\_id) | The name of the bucket. |
| <a name="output_s3_bucket_region"></a> [s3\_bucket\_region](#output\_s3\_bucket\_region) | The AWS region this bucket resides in. |
| <a name="output_s3_bucket_website_domain"></a> [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. |
| <a name="output_s3_bucket_website_endpoint"></a> [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. |
<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
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 = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
}
data "aws_iam_policy_document" "bucket_policy" {
statement {
principals {
type = "AWS"
identifiers = [aws_iam_role.this.arn]
}
actions = [
"s3:ListBucket",
]
resources = [
"arn:aws:s3:::${local.bucket_name}",
]
}
}
module "log_bucket" {
source = "terraform-aws-modules/s3-bucket/aws"
version = "~> 2.0"
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
attach_require_latest_tls_policy = true
}
# Sample which was used in this module with AWS provider before version 3.75 and 4.0
module "s3_bucket" {
source = "terraform-aws-modules/s3-bucket/aws"
version = "~> 2.0"
bucket = local.bucket_name
acl = "private"
force_destroy = true
acceleration_status = "Suspended"
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"
}
versioning = {
enabled = true
}
website = {
index_document = "index.html"
error_document = "error.html"
routing_rules = jsonencode([{
Condition : {
KeyPrefixEquals : "docs/"
},
Redirect : {
ReplaceKeyPrefixWith : "documents/"
}
}])
}
logging = {
target_bucket = module.log_bucket.s3_bucket_id
target_prefix = "log/"
}
cors_rule = [
{
allowed_methods = ["PUT", "POST"]
allowed_origins = ["https://modules.tf", "https://terraform-aws-modules.modules.tf"]
allowed_headers = ["*"]
expose_headers = ["ETag"]
max_age_seconds = 3000
}, {
allowed_methods = ["PUT"]
allowed_origins = ["https://example.com"]
allowed_headers = ["*"]
expose_headers = ["ETag"]
max_age_seconds = 3000
}
]
lifecycle_rule = [
{
id = "log"
enabled = true
prefix = "log/"
tags = {
rule = "log"
autoclean = "true"
}
transition = [
{
days = 30
storage_class = "ONEZONE_IA"
}, {
days = 60
storage_class = "GLACIER"
}
]
expiration = {
days = 90
}
noncurrent_version_expiration = {
days = 30
}
},
{
id = "log1"
enabled = true
prefix = "log1/"
abort_incomplete_multipart_upload_days = 7
noncurrent_version_transition = [
{
days = 30
storage_class = "STANDARD_IA"
},
{
days = 60
storage_class = "ONEZONE_IA"
},
{
days = 90
storage_class = "GLACIER"
},
]
noncurrent_version_expiration = {
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"
}
}
}
object_lock_configuration = {
object_lock_enabled = "Enabled"
rule = {
default_retention = {
mode = "GOVERNANCE"
days = 1
}
}
}
# 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
control_object_ownership = true
object_ownership = "BucketOwnerPreferred"
}
output "s3_bucket_id" {
description = "The name of the bucket."
value = module.s3_bucket.s3_bucket_id
}
output "s3_bucket_arn" {
description = "The ARN of the bucket. Will be of format arn:aws:s3:::bucketname."
value = module.s3_bucket.s3_bucket_arn
}
output "s3_bucket_bucket_domain_name" {
description = "The bucket domain name. Will be of format bucketname.s3.amazonaws.com."
value = module.s3_bucket.s3_bucket_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 = module.s3_bucket.s3_bucket_bucket_regional_domain_name
}
output "s3_bucket_hosted_zone_id" {
description = "The Route 53 Hosted Zone ID for this bucket's region."
value = module.s3_bucket.s3_bucket_hosted_zone_id
}
output "s3_bucket_region" {
description = "The AWS region this bucket resides in."
value = module.s3_bucket.s3_bucket_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 = module.s3_bucket.s3_bucket_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 = module.s3_bucket.s3_bucket_website_domain
}
terraform {
required_version = ">= 0.13.1"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 3.69.0"
}
random = {
source = "hashicorp/random"
version = ">= 2.0"
}
}
}
......@@ -30,14 +30,14 @@ Note that this example may create resources which cost money. Run `terraform des
| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 0.13.1 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | ~> 3.69 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 3.75 |
| <a name="requirement_random"></a> [random](#requirement\_random) | >= 2.0 |
## Providers
| Name | Version |
|------|---------|
| <a name="provider_aws"></a> [aws](#provider\_aws) | ~> 3.69 |
| <a name="provider_aws"></a> [aws](#provider\_aws) | >= 3.75 |
| <a name="provider_random"></a> [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 |
......
......@@ -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" {}
......@@ -68,6 +71,7 @@ module "log_bucket" {
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,17 +82,22 @@ 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"]
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
}]
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
}
......@@ -96,40 +105,94 @@ module "s3_bucket" {
source = "../../"
bucket = local.bucket_name
acl = "private"
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_configuration = {
object_lock_enabled = false
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/"
filter = {
tags = {
rule = "log"
autoclean = "true"
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
}
},
]
}
......@@ -4,7 +4,7 @@ terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 3.69"
version = ">= 3.75"
}
random = {
source = "hashicorp/random"
......
......@@ -20,7 +20,7 @@ Note that this example may create resources which cost money. Run `terraform des
| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 0.13.1 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | ~> 3.69 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 3.75 |
| <a name="requirement_null"></a> [null](#requirement\_null) | >= 2.0 |
| <a name="requirement_random"></a> [random](#requirement\_random) | >= 2.0 |
......@@ -28,7 +28,7 @@ Note that this example may create resources which cost money. Run `terraform des
| Name | Version |
|------|---------|
| <a name="provider_aws"></a> [aws](#provider\_aws) | ~> 3.69 |
| <a name="provider_aws"></a> [aws](#provider\_aws) | >= 3.75 |
| <a name="provider_null"></a> [null](#provider\_null) | >= 2.0 |
| <a name="provider_random"></a> [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 |
|------|--------|---------|
| <a name="module_all_notifications"></a> [all\_notifications](#module\_all\_notifications) | ../../modules/notification | n/a |
| <a name="module_lambda_function1"></a> [lambda\_function1](#module\_lambda\_function1) | terraform-aws-modules/lambda/aws | ~> 2.0 |
| <a name="module_lambda_function2"></a> [lambda\_function2](#module\_lambda\_function2) | terraform-aws-modules/lambda/aws | ~> 2.0 |
| <a name="module_lambda_function1"></a> [lambda\_function1](#module\_lambda\_function1) | terraform-aws-modules/lambda/aws | ~> 3.0 |
| <a name="module_lambda_function2"></a> [lambda\_function2](#module\_lambda\_function2) | terraform-aws-modules/lambda/aws | ~> 3.0 |
| <a name="module_s3_bucket"></a> [s3\_bucket](#module\_s3\_bucket) | ../../ | n/a |
| <a name="module_sns_topic1"></a> [sns\_topic1](#module\_sns\_topic1) | terraform-aws-modules/sns/aws | ~> 3.0 |
| <a name="module_sns_topic2"></a> [sns\_topic2](#module\_sns\_topic2) | terraform-aws-modules/sns/aws | ~> 3.0 |
......
......@@ -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 = {
......
......@@ -4,7 +4,7 @@ terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 3.69"
version = ">= 3.75"
}
random = {
source = "hashicorp/random"
......
......@@ -20,14 +20,14 @@ Note that this example may create resources which cost money. Run `terraform des
| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 0.13.1 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | ~> 3.69 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 3.75 |
| <a name="requirement_random"></a> [random](#requirement\_random) | >= 2.0 |
## Providers
| Name | Version |
|------|---------|
| <a name="provider_aws"></a> [aws](#provider\_aws) | ~> 3.69 |
| <a name="provider_aws"></a> [aws](#provider\_aws) | >= 3.75 |
| <a name="provider_random"></a> [random](#provider\_random) | >= 2.0 |
## Modules
......@@ -57,7 +57,7 @@ No inputs.
|------|-------------|
| <a name="output_s3_bucket_arn"></a> [s3\_bucket\_arn](#output\_s3\_bucket\_arn) | The ARN of the bucket. Will be of format arn:aws:s3:::bucketname. |
| <a name="output_s3_bucket_id"></a> [s3\_bucket\_id](#output\_s3\_bucket\_id) | The name of the bucket. |
| <a name="output_s3_bucket_object_etag"></a> [s3\_bucket\_object\_etag](#output\_s3\_bucket\_object\_etag) | The ETag generated for the object (an MD5 sum of the object content). |
| <a name="output_s3_bucket_object_id"></a> [s3\_bucket\_object\_id](#output\_s3\_bucket\_object\_id) | The key of S3 object |
| <a name="output_s3_bucket_object_version_id"></a> [s3\_bucket\_object\_version\_id](#output\_s3\_bucket\_object\_version\_id) | A unique version ID value for the object, if bucket versioning is enabled. |
| <a name="output_s3_object_etag"></a> [s3\_object\_etag](#output\_s3\_object\_etag) | The ETag generated for the object (an MD5 sum of the object content). |
| <a name="output_s3_object_id"></a> [s3\_object\_id](#output\_s3\_object\_id) | The key of S3 object |
| <a name="output_s3_object_version_id"></a> [s3\_object\_version\_id](#output\_s3\_object\_version\_id) | A unique version ID value for the object, if bucket versioning is enabled. |
<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
# 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
......
......@@ -4,7 +4,7 @@ terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 3.69"
version = ">= 3.75"
}
random = {
source = "hashicorp/random"
......
......@@ -22,15 +22,15 @@ Note that this example may create resources which cost money. Run `terraform des
| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 0.13.1 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | ~> 3.69 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 3.75 |
| <a name="requirement_random"></a> [random](#requirement\_random) | >= 2.0 |
## Providers
| Name | Version |
|------|---------|
| <a name="provider_aws"></a> [aws](#provider\_aws) | ~> 3.69 |
| <a name="provider_aws.replica"></a> [aws.replica](#provider\_aws.replica) | ~> 3.69 |
| <a name="provider_aws"></a> [aws](#provider\_aws) | >= 3.75 |
| <a name="provider_aws.replica"></a> [aws.replica](#provider\_aws.replica) | >= 3.75 |
| <a name="provider_random"></a> [random](#provider\_random) | >= 2.0 |
## Modules
......
......@@ -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
}
......@@ -92,15 +97,19 @@ module "s3_bucket" {
destination = {
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"
......
......@@ -4,7 +4,7 @@ terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 3.69"
version = ">= 3.75"
}
random = {
source = "hashicorp/random"
......
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,494 @@ resource "aws_s3_bucket" "this" {
bucket = var.bucket
bucket_prefix = var.bucket_prefix
# 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
tags = var.tags
force_destroy = var.force_destroy
acceleration_status = var.acceleration_status
request_payer = var.request_payer
dynamic "website" {
for_each = length(keys(var.website)) == 0 ? [] : [var.website]
# Max 1 block - object_lock_configuration
dynamic "object_lock_configuration" {
for_each = compact([try(var.object_lock_configuration["object_lock_enabled"] ? "Enabled" : null, tobool(var.object_lock_configuration["object_lock_enabled"]) ? "Enabled" : null, title(lower(var.object_lock_configuration["object_lock_enabled"])), null)])
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)
object_lock_enabled = "Enabled"
}
}
}
dynamic "cors_rule" {
for_each = try(jsondecode(var.cors_rule), var.cors_rule)
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" ? null : var.acl
dynamic "access_control_policy" {
for_each = length(local.grants) > 0 ? [true] : []
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)
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 "versioning" {
for_each = length(keys(var.versioning)) == 0 ? [] : [var.versioning]
dynamic "index_document" {
for_each = try([var.website["index_document"]], [])
content {
enabled = lookup(versioning.value, "enabled", null)
mfa_delete = lookup(versioning.value, "mfa_delete", null)
suffix = index_document.value
}
}
dynamic "logging" {
for_each = length(keys(var.logging)) == 0 ? [] : [var.logging]
dynamic "error_document" {
for_each = try([var.website["error_document"]], [])
content {
target_bucket = logging.value.target_bucket
target_prefix = lookup(logging.value, "target_prefix", null)
key = error_document.value
}
}
dynamic "grant" {
for_each = try(jsondecode(var.grant), var.grant)
dynamic "redirect_all_requests_to" {
for_each = try([var.website["redirect_all_requests_to"]], [])
content {
id = lookup(grant.value, "id", null)
type = grant.value.type
permissions = grant.value.permissions
uri = lookup(grant.value, "uri", null)
host_name = redirect_all_requests_to.value.host_name
protocol = try(redirect_all_requests_to.value.protocol, null)
}
}
dynamic "routing_rule" {
for_each = try(flatten([var.website["routing_rules"]]), [])
content {
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["hostname"], 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)
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 {
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 "lifecycle_rule" {
for_each = try(jsondecode(var.lifecycle_rule), var.lifecycle_rule)
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(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(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
bucket = aws_s3_bucket.this[0].id
expected_bucket_owner = var.expected_bucket_owner
dynamic "rule" {
for_each = local.lifecycle_rules
content {
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)
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 - replication_configuration
dynamic "replication_configuration" {
for_each = length(keys(var.replication_configuration)) == 0 ? [] : [var.replication_configuration]
# 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 {
role = replication_configuration.value.role
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 "rules" {
for_each = replication_configuration.value.rules
dynamic "tag" {
for_each = try(filter.value.tags, filter.value.tag, [])
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
key = tag.key
value = tag.value
}
}
}
}
dynamic "destination" {
for_each = length(keys(lookup(rules.value, "destination", {}))) == 0 ? [] : [lookup(rules.value, "destination", {})]
# 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 {
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)
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)
}
}
}
}
}
dynamic "access_control_translation" {
for_each = length(keys(lookup(destination.value, "access_control_translation", {}))) == 0 ? [] : [lookup(destination.value, "access_control_translation", {})]
# Must have bucket versioning enabled first
depends_on = [aws_s3_bucket_versioning.this]
}
content {
owner = access_control_translation.value.owner
resource "aws_s3_bucket_object_lock_configuration" "this" {
count = local.create_bucket && 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)
}
}
}
dynamic "replication_time" {
for_each = length(keys(lookup(destination.value, "replication_time", {}))) == 0 ? [] : [lookup(destination.value, "replication_time", {})]
resource "aws_s3_bucket_replication_configuration" "this" {
count = local.create_bucket && length(keys(var.replication_configuration)) > 0 ? 1 : 0
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 {
status = replication_time.value.status
minutes = replication_time.value.minutes
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 "delete_marker_replication" {
for_each = flatten(try([rule.value.delete_marker_replication_status], [rule.value.delete_marker_replication], []))
content {
# Valid values: "Enabled" or "Disabled"
status = try(tobool(delete_marker_replication.value) ? "Enabled" : "Disabled", title(lower(delete_marker_replication.value)))
}
}
dynamic "metrics" {
for_each = length(keys(lookup(destination.value, "metrics", {}))) == 0 ? [] : [lookup(destination.value, "metrics", {})]
# 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], []))
content {
status = metrics.value.status
minutes = metrics.value.minutes
# 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 {
owner = title(lower(access_control_translation.value.owner))
}
}
dynamic "source_selection_criteria" {
for_each = length(keys(lookup(rules.value, "source_selection_criteria", {}))) == 0 ? [] : [lookup(rules.value, "source_selection_criteria", {})]
dynamic "encryption_configuration" {
for_each = flatten([try(destination.value.encryption_configuration.replica_kms_key_id, destination.value.replica_kms_key_id, [])])
content {
replica_kms_key_id = encryption_configuration.value
}
}
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 "replication_time" {
for_each = try(flatten([destination.value.replication_time]), [])
content {
# Valid values: "Enabled" or "Disabled"
status = try(tobool(replication_time.value.status) ? "Enabled" : "Disabled", title(lower(replication_time.value.status)), "Disabled")
dynamic "time" {
for_each = try(flatten([replication_time.value.minutes]), [])
enabled = sse_kms_encrypted_objects.value.enabled
content {
minutes = replication_time.value.minutes
}
}
}
}
# Send empty map if `filter` is an empty map or absent entirely
dynamic "filter" {
for_each = length(keys(lookup(rules.value, "filter", {}))) == 0 ? [{}] : []
dynamic "metrics" {
for_each = try(flatten([destination.value.metrics]), [])
content {}
}
content {
# Valid values: "Enabled" or "Disabled"
status = try(tobool(metrics.value.status) ? "Enabled" : "Disabled", title(lower(metrics.value.status)), "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 "event_threshold" {
for_each = try(flatten([metrics.value.minutes]), [])
content {
prefix = lookup(filter.value, "prefix", null)
tags = lookup(filter.value, "tags", null)
minutes = metrics.value.minutes
}
}
}
}
}
}
# 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]
dynamic "source_selection_criteria" {
for_each = try(flatten([rule.value.source_selection_criteria]), [])
content {
dynamic "rule" {
for_each = length(keys(lookup(server_side_encryption_configuration.value, "rule", {}))) == 0 ? [] : [lookup(server_side_encryption_configuration.value, "rule", {})]
dynamic "replica_modifications" {
for_each = flatten([try(source_selection_criteria.value.replica_modifications.enabled, source_selection_criteria.value.replica_modifications.status, [])])
content {
bucket_key_enabled = lookup(rule.value, "bucket_key_enabled", null)
# Valid values: "Enabled" or "Disabled"
status = try(tobool(replica_modifications.value) ? "Enabled" : "Disabled", title(lower(replica_modifications.value)), "Disabled")
}
}
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 "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 {
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)
# 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 - filter - without any key arguments or tags
dynamic "filter" {
for_each = length(try(flatten([rule.value.filter]), [])) == 0 ? [true] : []
content {
}
}
# Max 1 block - object_lock_configuration
dynamic "object_lock_configuration" {
for_each = length(keys(var.object_lock_configuration)) == 0 ? [] : [var.object_lock_configuration]
# 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_lock_enabled = object_lock_configuration.value.object_lock_enabled
prefix = try(filter.value.prefix, null)
dynamic "rule" {
for_each = length(keys(lookup(object_lock_configuration.value, "rule", {}))) == 0 ? [] : [lookup(object_lock_configuration.value, "rule", {})]
dynamic "tag" {
for_each = try(filter.value.tags, filter.value.tag, [])
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)
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 {
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" {
......
......@@ -8,13 +8,13 @@ Creates S3 bucket notification resource with all supported types of deliveries:
| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 0.13.1 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | ~> 3.28 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 3.74 |
## Providers
| Name | Version |
|------|---------|
| <a name="provider_aws"></a> [aws](#provider\_aws) | ~> 3.28 |
| <a name="provider_aws"></a> [aws](#provider\_aws) | >= 3.74 |
## Modules
......@@ -41,6 +41,7 @@ No modules.
| <a name="input_create"></a> [create](#input\_create) | Whether to create this resource or not? | `bool` | `true` | no |
| <a name="input_create_sns_policy"></a> [create\_sns\_policy](#input\_create\_sns\_policy) | Whether to create a policy for SNS permissions or not? | `bool` | `true` | no |
| <a name="input_create_sqs_policy"></a> [create\_sqs\_policy](#input\_create\_sqs\_policy) | Whether to create a policy for SQS permissions or not? | `bool` | `true` | no |
| <a name="input_eventbridge"></a> [eventbridge](#input\_eventbridge) | Whether to enable Amazon EventBridge notifications | `bool` | `null` | no |
| <a name="input_lambda_notifications"></a> [lambda\_notifications](#input\_lambda\_notifications) | Map of S3 bucket notifications to Lambda function | `any` | `{}` | no |
| <a name="input_sns_notifications"></a> [sns\_notifications](#input\_sns\_notifications) | Map of S3 bucket notifications to SNS topic | `any` | `{}` | no |
| <a name="input_sqs_notifications"></a> [sqs\_notifications](#input\_sqs\_notifications) | Map of S3 bucket notifications to SQS queue | `any` | `{}` | no |
......
......@@ -3,7 +3,7 @@ locals {
# 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" {
......@@ -11,6 +11,8 @@ resource "aws_s3_bucket_notification" "this" {
bucket = var.bucket
eventbridge = var.eventbridge
dynamic "lambda_function" {
for_each = var.lambda_notifications
......@@ -18,8 +20,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 +32,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 +44,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 +63,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 +106,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
}
......
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, "")
}
......@@ -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
......
......@@ -4,7 +4,7 @@ terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 3.28"
version = ">= 3.74"
}
}
}
......@@ -8,13 +8,13 @@ Creates S3 bucket objects with different configurations.
| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 0.13.1 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | ~> 3.36 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 3.75 |
## Providers
| Name | Version |
|------|---------|
| <a name="provider_aws"></a> [aws](#provider\_aws) | ~> 3.36 |
| <a name="provider_aws"></a> [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 |
|------|-------------|
| <a name="output_s3_bucket_object_etag"></a> [s3\_bucket\_object\_etag](#output\_s3\_bucket\_object\_etag) | The ETag generated for the object (an MD5 sum of the object content). |
| <a name="output_s3_bucket_object_id"></a> [s3\_bucket\_object\_id](#output\_s3\_bucket\_object\_id) | The key of S3 object |
| <a name="output_s3_bucket_object_version_id"></a> [s3\_bucket\_object\_version\_id](#output\_s3\_bucket\_object\_version\_id) | A unique version ID value for the object, if bucket versioning is enabled. |
| <a name="output_s3_object_etag"></a> [s3\_object\_etag](#output\_s3\_object\_etag) | The ETag generated for the object (an MD5 sum of the object content). |
| <a name="output_s3_object_id"></a> [s3\_object\_id](#output\_s3\_object\_id) | The key of S3 object |
| <a name="output_s3_object_version_id"></a> [s3\_object\_version\_id](#output\_s3\_object\_version\_id) | A unique version ID value for the object, if bucket versioning is enabled. |
<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
resource "aws_s3_bucket_object" "this" {
resource "aws_s3_object" "this" {
count = var.create ? 1 : 0
bucket = var.bucket
......
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, "")
}
......@@ -4,7 +4,7 @@ terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 3.36"
version = ">= 3.75"
}
}
}
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, "")
}
......@@ -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
......
......@@ -4,7 +4,7 @@ terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 3.69"
version = ">= 3.75"
}
}
}
......@@ -3,33 +3,37 @@ 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, true)
attach_elb_log_delivery_policy = try(each.value.attach_elb_log_delivery_policy, false)
attach_lb_log_delivery_policy = try(each.value.attach_lb_log_delivery_policy, false)
attach_deny_insecure_transport_policy = try(each.value.attach_deny_insecure_transport_policy, false)
attach_require_latest_tls_policy = try(each.value.attach_require_latest_tls_policy, false)
attach_policy = try(each.value.attach_policy, false)
attach_public_policy = try(each.value.attach_public_policy, true)
bucket = try(each.value.bucket, null)
bucket_prefix = try(each.value.bucket_prefix, null)
acl = try(each.value.acl, null)
policy = try(each.value.policy, null)
tags = try(each.value.tags, {})
force_destroy = try(each.value.force_destroy, false)
acceleration_status = try(each.value.acceleration_status, null)
request_payer = try(each.value.request_payer, null)
website = try(each.value.website, {})
cors_rule = try(each.value.cors_rule, [])
versioning = try(each.value.versioning, {})
logging = try(each.value.logging, {})
grant = try(each.value.grant, [])
owner = try(each.value.owner, {})
expected_bucket_owner = try(each.value.expected_bucket_owner, null)
lifecycle_rule = try(each.value.lifecycle_rule, [])
replication_configuration = try(each.value.replication_configuration, {})
server_side_encryption_configuration = try(each.value.server_side_encryption_configuration, {})
object_lock_configuration = try(each.value.object_lock_configuration, {})
block_public_acls = try(each.value.block_public_acls, false)
block_public_policy = try(each.value.block_public_policy, false)
ignore_public_acls = try(each.value.ignore_public_acls, false)
restrict_public_buckets = try(each.value.restrict_public_buckets, false)
control_object_ownership = try(each.value.control_object_ownership, false)
object_ownership = try(each.value.object_ownership, "ObjectWriter")
putin_khuylo = try(each.value.putin_khuylo, true)
}
......@@ -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, true)
create_sns_policy = try(each.value.create_sns_policy, true)
create_sqs_policy = try(each.value.create_sqs_policy, true)
bucket = try(each.value.bucket, "")
bucket_arn = try(each.value.bucket_arn, null)
eventbridge = try(each.value.eventbridge, null)
lambda_notifications = try(each.value.lambda_notifications, {})
sqs_notifications = try(each.value.sqs_notifications, {})
sns_notifications = try(each.value.sns_notifications, {})
}
......@@ -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, true)
bucket = try(each.value.bucket, "")
key = try(each.value.key, "")
file_source = try(each.value.file_source, null)
content = try(each.value.content, null)
content_base64 = try(each.value.content_base64, null)
acl = try(each.value.acl, null)
cache_control = try(each.value.cache_control, null)
content_disposition = try(each.value.content_disposition, null)
content_encoding = try(each.value.content_encoding, null)
content_language = try(each.value.content_language, null)
content_type = try(each.value.content_type, null)
website_redirect = try(each.value.website_redirect, null)
storage_class = try(each.value.storage_class, null)
etag = try(each.value.etag, null)
server_side_encryption = try(each.value.server_side_encryption, null)
kms_key_id = try(each.value.kms_key_id, null)
bucket_key_enabled = try(each.value.bucket_key_enabled, null)
metadata = try(each.value.metadata, {})
tags = try(each.value.tags, {})
force_destroy = try(each.value.force_destroy, false)
object_lock_legal_hold_status = try(each.value.object_lock_legal_hold_status, null)
object_lock_mode = try(each.value.object_lock_mode, null)
object_lock_retain_until_date = try(each.value.object_lock_retain_until_date, null)
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment