aws / aws/aws-cdk

rds: RemovalPolicy.RETAIN_ON_UPDATE_OR_DELETE not propagated to child resources in RDS constructs

Open
#37,780 1 comment 0 reactions 0 assignees View on GitHub
@aws-cdk/aws-rds bug effort/medium good first issue p2
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.