diff --git a/modules/aws-s3/README.md b/modules/aws-s3/README.md index bc91fb083..954021494 100644 --- a/modules/aws-s3/README.md +++ b/modules/aws-s3/README.md @@ -24,13 +24,34 @@ It is flexible, production-ready, and easy to integrate into existing infrastruc - **IAM role** for replication - **Support for existing buckets** +## ⚠️ Important: Replication Setup Order + +When setting up S3 replication between buckets, follow this order to avoid failures: + +1. **Create the destination bucket first** (without any replication source configuration) + - Deploy the destination bucket without `s3_replication_source` parameter + +2. **Apply replication source configuration** + - Deploy the source bucket with `s3_replication_destination` configured + - This generates the IAM role and policies needed for replication + +3. **Configure destination to accept replication** + - Update the destination bucket Terraform with the `s3_replication_source` parameter + - Use the role ARN generated from step 2 + - Apply the full Terraform configuration + +This three-stage approach ensures that: +- IAM roles and policies are properly propagated across accounts +- The destination bucket exists before the source tries to replicate to it +- Cross-account permissions are correctly established before replication starts + ## Basic Usage ### Minimal Example (S3 bucket) ```hcl module "s3" { - source = "github.com/prefapp/tfm/modules/aws-s3" + source = "git::https://github.com/prefapp/tfm.git//modules/aws-s3?ref=aws-s3-v0.1.1" bucket = "my-bucket" } ``` @@ -39,12 +60,12 @@ module "s3" { ```hcl module "s3" { - source = "github.com/prefapp/tfm/modules/aws-s3" + source = "git::https://github.com/prefapp/tfm.git//modules/aws-s3?ref=aws-s3-v0.1.1" bucket = "my-bucket-origin" region = "eu-west-1" s3_bucket_versioning = "Enabled" s3_replication_destination = { - account = "1122334455" + account = "112233445566" bucket_arn = "arn:aws:s3:::my-bucket-destination" storage_class = "STANDARD" } @@ -55,14 +76,14 @@ module "s3" { ```hcl module "s3" { - source = "github.com/prefapp/tfm/modules/aws-s3" + source = "git::https://github.com/prefapp/tfm.git//modules/aws-s3?ref=aws-s3-v0.1.1" bucket = "my-bucket-origin" region = "eu-west-1" s3_bucket_versioning = "Enabled" s3_replication_source = { - account = "5544332211" - role_arn = "arn:aws:iam::5544332211:role/my-bucket-origin-replication" + account = "665544332211" + role_arn = "arn:aws:iam::665544332211:role/my-bucket-origin-replication" } } ``` @@ -107,7 +128,7 @@ The module is organized with the following directory and file structure: | Name | Version | |------|---------| -| [aws](#provider\_aws) | ~> 6.2 | +| [aws](#provider\_aws) | 6.30.0 | ## Modules diff --git a/modules/aws-s3/_examples/minimal_destination_source_and_destination/main.tf b/modules/aws-s3/_examples/minimal_destination_source_and_destination/main.tf index 90924bec9..8ed53b942 100644 --- a/modules/aws-s3/_examples/minimal_destination_source_and_destination/main.tf +++ b/modules/aws-s3/_examples/minimal_destination_source_and_destination/main.tf @@ -25,9 +25,9 @@ module "s3" { role_arn = "arn:aws:iam::1122334455:role/o-a-d-replication" } s3_replication_destination = { - account = "1122334455" + account = "112233445566" bucket_arn = "arn:aws:s3:::o-a-d" storage_class = "STANDARD" } -} \ No newline at end of file +} diff --git a/modules/aws-s3/_examples/minimal_replication/main.tf b/modules/aws-s3/_examples/minimal_replication/main.tf index 89f9e4384..a99168d44 100644 --- a/modules/aws-s3/_examples/minimal_replication/main.tf +++ b/modules/aws-s3/_examples/minimal_replication/main.tf @@ -22,7 +22,7 @@ module "s3" { s3_bucket_versioning = "Enabled" s3_replication_destination = { - account = "1112222333" + account = "112233445566" bucket_arn = "arn:aws:s3:::my-destination" storage_class = "STANDARD" } diff --git a/modules/aws-s3/_examples/minimal_source/main.tf b/modules/aws-s3/_examples/minimal_source/main.tf index 359841ea6..49d037b32 100644 --- a/modules/aws-s3/_examples/minimal_source/main.tf +++ b/modules/aws-s3/_examples/minimal_source/main.tf @@ -23,9 +23,9 @@ module "s3" { s3_bucket_versioning = "Enabled" s3_replication_destination = { - account = "1122334455" + account = "112233445566" bucket_arn = "arn:aws:s3:::destination-bucket" storage_class = "STANDARD" } -} \ No newline at end of file +} diff --git a/modules/aws-s3/docs/header.md b/modules/aws-s3/docs/header.md index 693e360e6..903dfd516 100644 --- a/modules/aws-s3/docs/header.md +++ b/modules/aws-s3/docs/header.md @@ -23,13 +23,34 @@ It is flexible, production-ready, and easy to integrate into existing infrastruc - **IAM role** for replication - **Support for existing buckets** +## ⚠️ Important: Replication Setup Order + +When setting up S3 replication between buckets, follow this order to avoid failures: + +1. **Create the destination bucket first** (without any replication source configuration) + - Deploy the destination bucket without `s3_replication_source` parameter + +2. **Apply replication source configuration** + - Deploy the source bucket with `s3_replication_destination` configured + - This generates the IAM role and policies needed for replication + +3. **Configure destination to accept replication** + - Update the destination bucket Terraform with the `s3_replication_source` parameter + - Use the role ARN generated from step 2 + - Apply the full Terraform configuration + +This three-stage approach ensures that: +- IAM roles and policies are properly propagated across accounts +- The destination bucket exists before the source tries to replicate to it +- Cross-account permissions are correctly established before replication starts + ## Basic Usage ### Minimal Example (S3 bucket) ```hcl module "s3" { - source = "github.com/prefapp/tfm/modules/aws-s3" + source = "git::https://github.com/prefapp/tfm.git//modules/aws-s3?ref=aws-s3-v0.1.1" bucket = "my-bucket" } ``` @@ -38,12 +59,12 @@ module "s3" { ```hcl module "s3" { - source = "github.com/prefapp/tfm/modules/aws-s3" + source = "git::https://github.com/prefapp/tfm.git//modules/aws-s3?ref=aws-s3-v0.1.1" bucket = "my-bucket-origin" region = "eu-west-1" s3_bucket_versioning = "Enabled" s3_replication_destination = { - account = "1122334455" + account = "112233445566" bucket_arn = "arn:aws:s3:::my-bucket-destination" storage_class = "STANDARD" } @@ -54,14 +75,14 @@ module "s3" { ```hcl module "s3" { - source = "github.com/prefapp/tfm/modules/aws-s3" + source = "git::https://github.com/prefapp/tfm.git//modules/aws-s3?ref=aws-s3-v0.1.1" bucket = "my-bucket-origin" region = "eu-west-1" s3_bucket_versioning = "Enabled" s3_replication_source = { - account = "5544332211" - role_arn = "arn:aws:iam::5544332211:role/my-bucket-origin-replication" + account = "665544332211" + role_arn = "arn:aws:iam::665544332211:role/my-bucket-origin-replication" } } ``` diff --git a/modules/aws-s3/local.tf b/modules/aws-s3/local.tf index 23df10e7a..ab28b5ac1 100644 --- a/modules/aws-s3/local.tf +++ b/modules/aws-s3/local.tf @@ -1,3 +1,12 @@ locals { - lifecycle_rules = (var.s3_replication_destination != null || var.s3_bucket_versioning == "Enabled") ? concat(var.lifecycle_rules, var.default_lifecycle_rules) : var.lifecycle_rules -} \ No newline at end of file + lifecycle_rules = (var.s3_replication_destination != null || var.s3_replication_source != null || var.s3_bucket_versioning == "Enabled") ? concat(var.lifecycle_rules, var.default_lifecycle_rules) : var.lifecycle_rules +} + +check "replication_requires_enabled_versioning" { + assert { + condition = ( + var.s3_replication_destination == null && var.s3_replication_source == null + ) || var.s3_bucket_versioning == "Enabled" + error_message = "s3_bucket_versioning must be set to \"Enabled\" when s3_replication_destination or s3_replication_source is configured." + } +} diff --git a/modules/aws-s3/main.tf b/modules/aws-s3/main.tf index ecadc3b8f..6b3695cb1 100644 --- a/modules/aws-s3/main.tf +++ b/modules/aws-s3/main.tf @@ -106,8 +106,8 @@ data "aws_iam_policy_document" "source_replication_s3_policy_with_https_policy" ## Bucket Versioning resource "aws_s3_bucket_versioning" "this" { - count = var.create_bucket ? 1 : 0 - bucket = aws_s3_bucket.this[0].id + count = (var.create_bucket || var.s3_replication_destination != null || var.s3_replication_source != null) ? 1 : 0 + bucket = var.create_bucket ? aws_s3_bucket.this[0].id : data.aws_s3_bucket.this[0].id region = var.region versioning_configuration { status = var.s3_bucket_versioning diff --git a/modules/aws-s3/replication.tf b/modules/aws-s3/replication.tf index 11befb3b4..95e8e95f0 100644 --- a/modules/aws-s3/replication.tf +++ b/modules/aws-s3/replication.tf @@ -1,10 +1,13 @@ resource "aws_s3_bucket_replication_configuration" "origin_to_destination" { - count = var.s3_replication_destination != null ? 1 : 0 - depends_on = [aws_s3_bucket_versioning.this] - region = var.region - role = aws_iam_role.replication[0].arn - bucket = var.create_bucket ? aws_s3_bucket.this[0].id : data.aws_s3_bucket.this[0].id + count = var.s3_replication_destination != null ? 1 : 0 + depends_on = [ + aws_s3_bucket_versioning.this, + aws_iam_role_policy_attachment.replication, + ] + region = var.region + role = aws_iam_role.replication[0].arn + bucket = var.create_bucket ? aws_s3_bucket.this[0].id : data.aws_s3_bucket.this[0].id rule { id = "origin-to-destination" diff --git a/modules/aws-s3/variables.tf b/modules/aws-s3/variables.tf index 505665044..ddccb8fe0 100644 --- a/modules/aws-s3/variables.tf +++ b/modules/aws-s3/variables.tf @@ -182,11 +182,6 @@ variable "s3_replication_destination" { })) }) default = null - - validation { - condition = var.s3_replication_destination == null || var.s3_bucket_versioning == "Enabled" - error_message = "When configuring s3_replication_destination, s3_bucket_versioning must be set to \"Enabled\"." - } } ## Replication variables