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