RDS Aurora: known Terraform limitations
Some things people ask the terraform-aws-modules/rds-aurora/aws module for
cannot be implemented by any module, in any registry. They are limits of
Terraform and OpenTofu themselves. This page collects the recurring ones for this
module, says plainly what causes each, gives the native workaround in full, and -
where one exists - shows the Operational Rule that removes the need for a fork.
Read the native option first
Every problem below names the workaround you can apply today without compliance.tf. Several of them are the right answer on their own. The rule is an alternative to maintaining a fork, not a replacement for a control AWS already offers you. Where a rule exists, the source line at the end of this page applies it. To get started, register a free compliance.tf account and configure an access token. The Rules Playground shows the diff with no account.
terraform destroy deleted an Aurora cluster
An Aurora cluster is destroyed by the same paths as any other resource, and the cluster carries the data rather than the instances in it. prevent_destroy cannot be passed into this module either.
Why Terraform cannot fix this
lifecycle is a meta-argument block, and Terraform evaluates it before it evaluates the rest of the configuration. Its arguments therefore cannot reference a variable, a local, or anything else that is computed. That is the whole reason no module can expose prevent_destroy, ignore_changes, or create_before_destroy as an input: there is no expression a module author could put there that Terraform would accept.
This is a property of Terraform, not of this module. The request was first filed in 2015 (#3116, closed in 2023 without a change). The module-specific asks, #18367 and #22544, have been open since 2018 and 2019, and OpenTofu carries the same request. Until one of them ships a way to set a meta-argument from outside the resource block, every module in every registry has the same limitation.
| Repository | Issue | Title |
|---|---|---|
| hashicorp/terraform | #22544 | "Variables may not be used here" for prevent_destroy |
| hashicorp/terraform | #18367 | Feature request: support prevent_destroy for modules |
| hashicorp/terraform | #24746 | Ability to use locals or wildcards in ignore_changes lifecycle block |
| hashicorp/terraform | #24188 | Support for dynamic blocks and meta-arguments |
| hashicorp/terraform | #27360 | A method to override configuration and meta arguments within a module |
| opentofu/opentofu | #1329 | Support variables in lifecycle blocks |
The native workaround
Set deletion_protection = true on the cluster, which this module exposes and AWS enforces, and keep automated backups and a final snapshot configured so a deletion is recoverable rather than final. For a hard stop at plan time, the lifecycle block has to be on the cluster resource. Fork this module and add the lifecycle block to the resource yourself. That works, and it is the honest answer - it is what module maintainers do when they need it. The cost is ongoing rather than one-off: the fork has to be re-synced with every upstream release, and each sync needs a review to confirm the block still lands on the resource it was meant for. A wrapper module does not avoid this, because the lifecycle block still has to sit inside the resource block, which is inside the module you did not write.
The rule that answers it
lifecycle_prevent_destroy_data adds the block for you, server-side, while
the module is being downloaded. What arrives in .terraform/modules/ is ordinary
HCL with this change already in it:
resource "aws_rds_cluster" "this" {
# ... the module's own configuration, unchanged ...
+
+ lifecycle {
+ prevent_destroy = true
+ }
}Nothing else about the module changes: same inputs, same outputs, same version. The full definition is published at lifecycle_prevent_destroy_data.
Using the maintained alternative
compliance.tf serves this module from a registry that applies the rules above
during terraform init. The change is the source line plus the rules you
want; the inputs and outputs are the upstream module's. The version goes
into the URL as ?version= (an exact release; without it the registry serves
the latest, so pin it); delete the version argument, which Terraform accepts only on
registry-protocol sources.
Several rules go in ?add_rules= separated by commas.
module "rds_aurora" {
source = "https://registry.compliance.tf/terraform-aws-modules/rds-aurora/aws?version=x.y.z&add_rules=lifecycle_prevent_destroy_data"
# ... the same inputs you pass today; the version is in the URL above ...
}To get started, register a free compliance.tf account and configure an
access token; the token never goes in the source
line or in git.
On registry.compliance.tf, or on a framework host, you name the rules you want in
?add_rules=, on top of whatever
Baseline your organization has Enforced; on an organization host that
organization's configuration decides. A module compliance.tf builds for you
carries a compliancetf-manifest.json naming every rule the build received, each
with an outcome recording what it actually did, so the change is auditable
rather than implicit.
Before changing a source line, open RDS Aurora in the
Rules Playground with these rules
selected: it shows the diff at the version it pins for RDS Aurora,
which need not be the one you put in ?version=, with no account.
The preview reads the module root, and only the root. A rule whose resource
lives in a submodule shows no diff there even though the download applies it,
and a module that publishes nothing but submodules cannot be previewed at all -
the Playground says so rather than showing an empty result. Neither changes what
the source line below does; both change what the Playground can show you first.
The Playground takes its list as ?rules= because it previews rules on their
own rather than against your Baseline; your source line above keeps
?add_rules=, which adds to that Baseline instead of replacing it.
Related
- Operational Rules - what rules are, and how they differ from compliance controls
- Operational Rule Definitions - the HCL of every selectable rule
- lifecycle_prevent_destroy_data - the rule page, with limits and failure modes
- Stop paying the Terraform fork tax - why forking a module to add a
lifecycleblock costs more than it looks - RDS Aurora module reference - inputs, outputs, and the controls applied to it