rds: RemovalPolicy.RETAIN_ON_UPDATE_OR_DELETE not propagated to child resources in RDS constructs
- Dominant language
- TypeScript
- Stars
- 12.9k
- Forks
- 4.6k
- Avg merge
- 1d 19h
- Merged PRs (30d)
- 74
Description
### Describe the bug
When `RemovalPolicy.RETAIN_ON_UPDATE_OR_DELETE` is set on `DatabaseCluster`, `DatabaseInstance`, or
`ServerlessCluster`, the helper resources they create (subnet groups and instances) do not
inherit the policy. They are silently left with CloudFormation's default `Delete` behavior, meaning
they will attempt to be destroyed on stack deletion but usually fail because they are in use.
---
### Affected Constructs
- `aws-cdk-lib/aws-rds.DatabaseClusterNew`
- `aws-cdk-lib/aws-rds.DatabaseInstanceNew`
- `aws-cdk-lib/aws-rds.ServerlessClusterNew`
---
##Root Cause
Found three references in RDS that compare removal policies against RemovalPolicy.RETAIN only, missing
RETAIN_ON_UPDATE_OR_DELETE which was added later.
1. helperRemovalPolicy in aws-rds/lib/private/util.ts
This function determines the policy applied to helper resources (subnet groups, cluster instances). It
predates RETAIN_ON_UPDATE_OR_DELETE and only handles RETAIN:
https://github.com/aws/aws-cdk/blob/main/packages/aws-cdk-lib/aws-rds/lib/private/util.ts#L151
```ts
export function helperRemovalPolicy(basePolicy?: RemovalPolicy): RemovalPolicy {
return basePolicy === RemovalPolicy.RETAIN
? RemovalPolicy.RETAIN
: RemovalPolicy.DESTROY;
}
```
RETAIN_ON_UPDATE_OR_DELETE falls through to DESTROY, which is then suppressed by renderUnless,
resulting in undefined being passed to SubnetGroup — so no policy is applied at all.
2. defaultDeletionProtection in aws-rds/lib/private/util.ts
Deletion protection is not auto-enabled when removalPolicy is RETAIN_ON_UPDATE_OR_DELETE, even though
it is for RETAIN:
https://github.com/aws/aws-cdk/blob/main/packages/aws-cdk-lib/aws-rds/lib/private/util.ts#L83
```ts
return deletionProtection ?? (removalPolicy === RemovalPolicy.RETAIN ? true : undefined);
```
3. ServerlessClusterNew subnet group in aws-rds/lib/serverless-cluster.ts
An inline check independent of helperRemovalPolicy has the same problem:
https://github.com/aws/aws-cdk/blob/main/packages/aws-cdk-lib/aws-rds/lib/serverless-cluster.ts#L475
```ts
removalPolicy: props.removalPolicy === RemovalPolicy.RETAIN ? props.removalPolicy : undefined,
```
### Regression Issue
- [ ] Select this option if this issue appears to be a regression.
### Last Known Working CDK Library Version
RemovalPolicy.RETAIN_ON_UPDATE_OR_DELETE was introduced in aws/aws-cdk#26602.
### Expected Behavior
```json
"Postgres2V17ClusterSubnets001BC406": {
"Type": "AWS::RDS::DBSubnetGroup",
"Properties": {
"DBSubnetGroupDescription": "Subnets for Cluster database",
"SubnetIds": [
"subnet-0123",
"subnet-01234",
"subnet-012345"
],
},
"UpdateReplacePolicy": "Retain",
"DeletionPolicy": "RetainExceptOnCreate",
"Metadata": {
"aws:cdk:path": "Postgres2V17/Cluster/Subnets/Default"
}
},
```
### Current Behavior
```json
"Postgres2V17ClusterSubnets001BC406": {
"Type": "AWS::RDS::DBSubnetGroup",
"Properties": {
"DBSubnetGroupDescription": "Subnets for Cluster database",
"SubnetIds": [
"subnet-0123",
"subnet-01234",
"subnet-012345"
],
},
"Metadata": {
"aws:cdk:path": "Postgres2V17/Cluster/Subnets/Default"
}
},
```
### Reproduction Steps
```ts
const cluster = new rds.DatabaseCluster(stack, 'Cluster', {
engine: rds.DatabaseClusterEngine.auroraPostgres({
version: rds.AuroraPostgresEngineVersion.VER_17_2,
}),
vpc,
removalPolicy: cdk.RemovalPolicy.RETAIN_ON_UPDATE_OR_DELETE,
});
```
### Possible Solution
helperRemovalPolicy — pass RETAIN_ON_UPDATE_OR_DELETE through rather than collapsing it to RETAIN.
Helper resources are created before the primary resource, so if their creation fails and the stack
rolls back, RetainExceptOnCreate correctly allows them to be cleaned up rather than orphaned:
```ts
export function helperRemovalPolicy(basePolicy?: RemovalPolicy): RemovalPolicy {
return basePolicy === RemovalPolicy.RETAIN || basePolicy ===
RemovalPolicy.RETAIN_ON_UPDATE_OR_DELETE
? basePolicy
: RemovalPolicy.DESTROY;
}
```
defaultDeletionProtection — extend the condition:
```ts
return deletionProtection ?? (
removalPolicy === RemovalPolicy.RETAIN || removalPolicy === RemovalPolicy.RETAIN_ON_UPDATE_OR_DELETE
? true
: undefined
);
```
serverless-cluster.ts — replace the inline check with renderUnless and helperRemovalPolicyto stay
consistent with DatabaseCluster and DatabaseInstance:
```ts
removalPolicy: renderUnless(helperRemovalPolicy(props.removalPolicy), RemovalPolicy.DESTROY),
```
### Additional Information/Context
_No response_
### AWS CDK Library version (aws-cdk-lib)
2.240.0
### AWS CDK CLI version
2.1108.0 (build eace286)
### Node.js Version
v22.19.0
### OS
macOS Tahoe 26.4.1 (25E253)
### Language
TypeScript
### Language Version
_No response_
### Other information
_No response_
Contributor guide
Research direction
Start with aws-rds/lib/private/util.ts, reading helperRemovalPolicy and defaultDeletionProtection, then inspect the inline removal-policy check in aws-rds/lib/serverless-cluster.ts. Reproduce the DatabaseCluster case from the issue and compare synthesized helper-resource policies for RETAIN_ON_UPDATE_OR_DELETE; done means the affected RDS constructs consistently propagate the expected policies and deletion protection.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- aws, typescript
- Domain
- databases, infrastructure
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100