-
-
Notifications
You must be signed in to change notification settings - Fork 839
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
S3 Replication Improvements #93
S3 Replication Improvements #93
Conversation
Allows easy management of buckets intended as cross-account S3 replication destinations.
This removes var.s3_replica_bucket_arn and lets you set it per-replication rule.
No longer required. Replication is implicitly enabled if there are any replication rules.
I tested this myself with cross-account replication and confirmed it works: source bucket: module "source" {
source = "github.com/alexjurkiewicz/terraform-aws-s3-bucket?ref=9cc2a02e2d2796110ba894d090b86356459c681b"
name = "ajtest-replication-source"
sse_algorithm = "AES256"
replication_rules = [
{
id = "destination"
priority = 0
status = "Enabled"
filter = {}
destination = {
bucket = "arn:aws:s3:::ajtest-replication-destination"
storage_class = "ONEZONE_IA"
}
},
]
} destination bucket: module "destination" {
source = "github.com/alexjurkiewicz/terraform-aws-s3-bucket?ref=9cc2a02e2d2796110ba894d090b86356459c681b"
name = "ajtest-replication-destination"
sse_algorithm = "AES256"
replication_source_roles = [
# From oneoff stack
"arn:aws:iam::XXX:role/ajtest-replication-source"
]
} |
/test all |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
variables.tf
Outdated
variable "s3_replication_enabled" { | ||
type = bool | ||
default = false | ||
description = "Set this to true and specify `s3_replica_bucket_arn` to enable replication. `versioning_enabled` must also be `true`." | ||
} | ||
|
||
variable "s3_replica_bucket_arn" { | ||
type = string | ||
default = "" | ||
description = "The ARN of the S3 replica bucket (destination)" | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will need to weigh in on @Nuru and @osterman to decide on whether removing these outright is the best upgrade path for our module's users.
In this particular case I feel like var.s3_replication_enabled
could be deprecated instead of outright removed, whereas var.s3_replica_bucket_arn
cannot be. But in that case, maybe it doesn't make sense to deprecate and keep one without the other.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let me know.
As someone who went through the change of lifecycle rules from individual variables to the lifecycle_rules
combined structure, my experience was that the transition was significantly harder because the old variables still existed but did nothing. I think it's easier for users to simply break compatibility and describe the migration path in release notes.
This Pull Request has been updated, so we're dismissing all reviews.
/test all |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After speaking with @Nuru , we are leaning towards deprecating the variables. Even if this PR isn't the best example of variables that are straight forward to deprecate, it's nonetheless an effort we are attempting to do throughout all of our cloudposse
modules.
I've requested the changes to make this happen.
After digging into these changes, I also realized that we do not have a proper test suite for replication. I think it's appropriate for this PR to add that. I've taken the time to build it out and test it:
examples/complete/main.tf
(this file was changed to support replication, but only when it's enabled):
locals {
replication_enabled = length(var.replication_rules) > 0
}
provider "aws" {
region = var.region
}
module "s3_bucket" {
source = "../../"
user_enabled = true
acl = var.acl
force_destroy = var.force_destroy
grants = var.grants
lifecycle_rules = var.lifecycle_rules
versioning_enabled = var.versioning_enabled
allow_encrypted_uploads_only = var.allow_encrypted_uploads_only
allowed_bucket_actions = var.allowed_bucket_actions
bucket_name = var.bucket_name
object_lock_configuration = var.object_lock_configuration
replication_rules = local.replication_enabled ? [for rule in var.replication_rules : merge(rule, {destination: {bucket: module.s3_bucket_replication_target[0].bucket_arn}})] : []
context = module.this.context
}
module "replication_target_label" {
count = local.replication_enabled ? 1 : 0
source = "cloudposse/label/null"
version = "0.24.1"
attributes = ["target"]
context = module.this.context
}
module "s3_bucket_replication_target" {
count = local.replication_enabled ? 1 : 0
source = "../../"
user_enabled = true
acl = "private"
force_destroy = true
versioning_enabled = true
bucket_name = module.replication_target_label[0].id
replication_source_roles = [module.s3_bucket.replication_role_arn]
context = module.replication_target_label[0].context
}
examples/complete/replication.us-west-1.tfvars
(new file):
enabled = true
region = "us-west-1"
namespace = "eg"
stage = "test"
name = "s3-replication-test"
acl = "private"
force_destroy = true
versioning_enabled = true
allow_encrypted_uploads_only = true
allowed_bucket_actions = ["s3:PutObject", "s3:PutObjectAcl", "s3:GetObject", "s3:DeleteObject", "s3:ListBucket", "s3:ListBucketMultipartUploads", "s3:GetBucketLocation", "s3:AbortMultipartUpload"]
# This variable is going to be manipulated to get the ID of the replication target bucket
replication_rules = [
{
id = "replication-test"
status = "Enabled"
prefix = "/"
}
]
examples/complete/variables.tf
(right before var.restrict_public_buckets
):
variable "replication_rules" {
default = []
description = "S3 replication rules"
}
At the bottom of test/src/examples_complete_test.go
:
func TestExamplesCompleteWithReplication(t *testing.T) {
rand.Seed(time.Now().UnixNano())
attributes := []string{strconv.Itoa(rand.Intn(100000))}
rootFolder := "../../"
terraformFolderRelativeToRoot := "examples/complete"
varFiles := []string{"replication.us-west-1.tfvars"}
tempTestFolder := test_structure.CopyTerraformFolderToTemp(t, rootFolder, terraformFolderRelativeToRoot)
terraformOptions := &terraform.Options{
// The path to where our Terraform code is located
TerraformDir: tempTestFolder,
Upgrade: true,
// Variables to pass to our Terraform code using -var-file options
VarFiles: varFiles,
Vars: map[string]interface{}{
"attributes": attributes,
},
}
// At the end of the test, run `terraform destroy` to clean up any resources that were created
defer terraform.Destroy(t, terraformOptions)
// This will run `terraform init` and `terraform apply` and fail the test if there are any errors
terraform.InitAndApply(t, terraformOptions)
// Run `terraform output` to get the value of an output variable
userName := terraform.Output(t, terraformOptions, "user_name")
expectedUserName := "eg-test-s3-replication-test-" + attributes[0]
// Verify we're getting back the outputs we expect
assert.Equal(t, expectedUserName, userName)
// Run `terraform output` to get the value of an output variable
s3BucketId := terraform.Output(t, terraformOptions, "bucket_id")
expectedS3BucketId := "eg-test-s3-replication-test-" + attributes[0]
// Verify we're getting back the outputs we expect
assert.Equal(t, expectedS3BucketId, s3BucketId)
}
main.tf
Outdated
@@ -1,5 +1,7 @@ | |||
locals { | |||
bucket_name = var.bucket_name != null && var.bucket_name != "" ? var.bucket_name : module.this.id | |||
bucket_name = var.bucket_name != null && var.bucket_name != "" ? var.bucket_name : module.this.id | |||
replication_enabled = length(var.replication_rules) > 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
replication_enabled = length(var.replication_rules) > 0 | |
# the following also takes into account the deprecated var.s3_replication_enabled variable (which is null by default) | |
replication_enabled = length(var.replication_rules) > 0 || var.s3_replication_enabled == true |
main.tf
Outdated
@@ -154,7 +156,7 @@ resource "aws_s3_bucket" "default" { | |||
status = try(rules.value.status, null) | |||
|
|||
destination { | |||
bucket = var.s3_replica_bucket_arn | |||
bucket = rules.value.destination.bucket |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bucket = rules.value.destination.bucket | |
# The following takes into account the deprecated var.s3_replica_bucket_arn variable | |
bucket = try(rules.value.destination.bucket, var.s3_replica_bucket_arn)``` |
variables.tf
Outdated
variable "s3_replication_enabled" { | ||
type = bool | ||
default = false | ||
description = "Set this to true and specify `s3_replica_bucket_arn` to enable replication. `versioning_enabled` must also be `true`." | ||
} | ||
|
||
variable "s3_replica_bucket_arn" { | ||
type = string | ||
default = "" | ||
description = "The ARN of the S3 replica bucket (destination)" | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add this to the bottom of variables.tf
# The variables below are DEPRECATED and should not be used anymore
variable "s3_replication_enabled" {
type = bool
default = null
description = "DEPRECATED. Use `replication_rules` instead."
}
variable "s3_replica_bucket_arn" {
type = string
default = null
description = "DEPRECATED. Use `replication_rules` instead."
}
@alexjurkiewicz I agree with @korenyoni but given how much additional work we are asking for, I am going to take over and finish this PR building on both of your work. |
This Pull Request has been updated, so we're dismissing all reviews.
/test all |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me. I think the level of precedence set fors3_replica_bucket_arn
is logical. I wish the names of var.replication_rules
and var.replication_source_roles
were more consistent with var.s3_repliction_enabled
and var.s3_replica_bucket_arn
, but all of these are old variables, so there's nothing we can really do.
/test all |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Nuru please see my comment on cloudposse/actions
, terratest
and AWS_SECRET_KEY
# Verify we have valid AWS credential | ||
if command -v aws >/dev/null; then \ | ||
(unset AWS_PROFILE; aws sts get-caller-identity >/dev/null); \ | ||
fi | ||
go test -v -timeout 10m -run TestExamplesComplete |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Nuru I'm not 100% sure entirely why this is, but since cloudposse/actions
sets AWS_SECRET_KEY
instead of the newer AWS_SECRET_ACCESS_KEY
(see https://github.com/cloudposse/actions/blob/tf-v1/.github/workflows/test-command.yml#L403) aws sts get-caller-identity
will fail, even though terratest
will not.
I think this is because the AWS golang SDK maintains backwards compatibility between AWS_SECRET_ACCESS_KEY
and AWS_SECRET_KEY
, but the cli does not.
So, aws sts get-caller-identity
before invoking terratest
will cause the run to fail
https://github.com/cloudposse/actions/runs/2925169210?check_suite_focus=true
Run make -C test/src
make -C test/src
shell: bash -e -o pipefail {0}
env:
MAKE_INCLUDES: Makefile
AWS_ACCESS_KEY_ID: ***
AWS_SECRET_KEY: ***
make: Entering directory '/__w/actions/actions/test/src'
# Verify we have valid AWS credential
if command -v aws >/dev/null; then \
(unset AWS_PROFILE; aws sts get-caller-identity >/dev/null); \
fi
Partial credentials found in env, missing: AWS_SECRET_ACCESS_KEY
make: *** [Makefile:18: test] Error 255
make: Leaving directory '/__w/actions/actions/test/src'
Error: Process completed with exit code 2.
/test terratest |
This Pull Request has been updated, so we're dismissing all reviews.
/test all |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, but requested a minor change to a variable description.
variables.tf
Outdated
@@ -169,21 +169,29 @@ variable "restrict_public_buckets" { | |||
variable "s3_replication_enabled" { | |||
type = bool | |||
default = false | |||
description = "Set this to true and specify `s3_replica_bucket_arn` to enable replication. `versioning_enabled` must also be `true`." | |||
description = "Set this to true and specify `replication_rules` to enable replication. `versioning_enabled` must also be `true`." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The description references a deprecated variable
description = "Set this to true and specify `replication_rules` to enable replication. `versioning_enabled` must also be `true`." | |
description = "Set this to true and specify `s3_replication_rules` to enable replication. `versioning_enabled` must also be `true`." |
examples/complete/variables.tf
Outdated
variable "replication_rules" { | ||
default = [] | ||
description = "S3 replication rules" | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: we could add the s3_
prefix to this variable in examples/complete
to be more consistent with the newly-added variables in root module.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bridgecrew has found 0 infrastructure configuration error in this PR ⬇️
This Pull Request has been updated, so we're dismissing all reviews.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bridgecrew has found 0 infrastructure configuration error in this PR ⬇️
/test all |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, minor typo though.
Co-authored-by: Yonatan Koren <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bridgecrew has found 0 infrastructure configuration error in this PR ⬇️
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bridgecrew has found 0 infrastructure configuration error in this PR ⬇️
This Pull Request has been updated, so we're dismissing all reviews.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bridgecrew has found 0 infrastructure configuration error in this PR ⬇️
/test all |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me.
thanks for all the merge help @korenyoni and @Nuru ! |
Terraform plan impact
In order to support multiple S3 Bucket replication destinations, we must use the
filter
in the replication rule, even if there is nothing to filter. The filter, even if empty, conflicts with theprefix
attribute of the rule (a v1 feature replaced in v2 with the filter). So we moved allprefix
settings into the filter. Therefore, you may see Terraform make a change like this:Click to show plan
Deprecation
To provide consistency in naming, the
replication_rules
input has been deprecated in favor ofs3_replication_rules
. Existing code will continue to work, but new users should uses3_replication_rules
and current users ofreplication_rules
should update their code to uses3_replication_rules
at their convenience.what
@alexjurkiewicz
@korenyoni
@Nuru
prefix
to v2filter
to support multiple replication destinationsreplication_rules
tos3_replication_rules
for consistencyus-east-2
region because that is where Cloud Posse prefers to do testingwhy
references