Skip to content

fix(provisioning): enrich AWS::RDS::DBInstance Endpoint attributes on the Cloud Control path - #844

Merged
go-to-k merged 4 commits into
mainfrom
test/rds-full-stack
Jun 13, 2026
Merged

fix(provisioning): enrich AWS::RDS::DBInstance Endpoint attributes on the Cloud Control path#844
go-to-k merged 4 commits into
mainfrom
test/rds-full-stack

Conversation

@go-to-k

@go-to-k go-to-k commented Jun 13, 2026

Copy link
Copy Markdown
Owner

Summary

Enriches an AWS::RDS::DBInstance's computed endpoint attributes on the Cloud Control provisioning path so a downstream Fn::GetAtt(<db>, 'Endpoint.Address') resolves to the real endpoint hostname instead of the raw physical id.

Root cause

CloudControlProvider.enrichResourceAttributes (src/provisioning/cloud-control-provider.ts) had a per-type case for AWS::RDS::DBCluster (added per #381) but none for AWS::RDS::DBInstance.

When a DBInstance template sets even one #614 silent-drop top-level property (e.g. BackupRetentionPeriod / CopyTagsToSnapshot / MultiAZ / PubliclyAccessible / StorageType), the #614 routing rule sends the entire resource through the Cloud Control API and bypasses RDSProvider.create. The SDK provider would normally populate the flat-key Endpoint.Address / Endpoint.Port attributes after create; on the CC-API path those attributes were never set, so a downstream consumer doing Fn::GetAtt(<DBInstance>, 'Endpoint.Address') fell through the resolver's constructAttribute branch to the physicalId (the DB identifier, not the endpoint hostname).

Fix

Add a parallel AWS::RDS::DBInstance case to enrichResourceAttributes that issues DescribeDBInstances after create and flattens the result into the SDK provider's flat-key attribute shape:

  • Endpoint.Address
  • Endpoint.Port
  • Endpoint.HostedZoneId
  • Arn

Shape note: unlike the DBCluster case (whose Endpoint / ReaderEndpoint are flat scalars), DescribeDBInstances returns Endpoint as a NESTED object { Address, Port, HostedZoneId }, so the case flattens it. The lookup is best-effort: a failed Describe leaves the CC-API attribute shape unchanged and never fails the deploy (the resolver's nested-path walk is the second line of defence), exactly matching the DBCluster branch.

Tests

  • Unit tests in tests/unit/provisioning/cloud-control-provider.test.ts cover the new DBInstance case (endpoint/port/hosted-zone/ARN flattening from the nested Endpoint object, plus the best-effort Describe-failure path).
  • New rds-full-stack failure-seeking integ (tests/integration/rds-full-stack/**): a VPC (no NAT) + explicit rds.SubnetGroup + explicit rds.ParameterGroup (Postgres, non-default application_name) + SecurityGroup + a small rds.DatabaseInstance (db.t3.micro, 20 GiB, CDK-managed Secrets Manager credentials, no final snapshot) + an ssm.StringParameter whose value is Fn::GetAtt(<Database>, Endpoint.Address). The integ asserts the SSM parameter value equals the LIVE DescribeDBInstances endpoint address (proving the computed Fn::GetAtt resolved post-create), then destroys and asserts the instance, subnet group, parameter group, SSM parameter, and state file are all gone with 0 orphans. New scenario tag rds-full-stack; coverage matrices regenerated.

Validation

Validated GREEN end-to-end against real AWS (/run-integ rds-full-stack): deploy clean, the computed-endpoint Fn::GetAtt resolved to the live endpoint address, destroy reported 15 deleted / 0 errors, and the post-destroy orphan sweep was empty (0 orphans).

go-to-k added 4 commits June 13, 2026 23:40
…custom subnet/param groups

New `tests/integration/rds-full-stack/` fixture (`CdkdRdsFullStackExample`,
15 synthesized resources). Test-only; no `src/` change.

The two existing RDS fixtures use L1 (`CfnDBInstance` / `CfnDBCluster`) and
target #609 silent-drop closure + the `provisionedBy=sdk` routing guard.
Neither uses an explicit DBSubnetGroup + DBParameterGroup pair on an L2
`rds.DatabaseInstance`, and neither consumes a DBInstance's COMPUTED endpoint
via a downstream reference. This fixture covers exactly that angle.

Topology: VPC (natGateways:0, isolated subnets) + explicit rds.SubnetGroup +
explicit rds.ParameterGroup (Postgres 17.4, non-default application_name) +
explicit SecurityGroup + a small rds.DatabaseInstance (db.t3.micro, single-AZ,
20 GiB gp2, CDK-managed Secrets Manager credentials, deletionProtection false,
RemovalPolicy DESTROY, no final snapshot) + an ssm.StringParameter whose value
is Fn::GetAtt(<Database>, Endpoint.Address).

Stresses cdkd's event-driven DAG + intrinsic resolution under a slow-create
resource: cdkd must create the sub-groups + SG before the instance (Ref
edges), wait ~5-10 min for the instance to become available, read its computed
Endpoint.Address attribute back, then create the SSM Parameter with the
resolved value.

verify.sh (BSD/macOS-portable, real-rc capture, explicit [verify] PASS):
- deploys (dumps state on failure for triage)
- asserts the instance uses the custom subnet group + parameter group, and the
  group carries the non-default application_name
- asserts the SSM parameter value equals the LIVE DescribeDBInstances endpoint
  address (proves the computed Fn::GetAtt resolved post-create)
- destroys and asserts the instance, subnet group, parameter group, SSM
  parameter, and state file are all gone with 0 orphans
- cleanup trap deletes in RDS-safe order (instance first + wait, then groups,
  then SG / VPC)

Slow by RDS nature (~10-20 min end-to-end) — acceptable + expected.

New scenario tag `rds-full-stack` in KNOWN_SCENARIOS; coverage matrices
regenerated; changelog + README added.

NOTE: not yet run against real AWS — needs /run-integ rds-full-stack before
merge.
The real-AWS run failed at DBInstance create with:

  Cannot find version 17.4 for postgres (Service: Rds, Status Code: 400)

PostgreSQL 17.4 is not offered for new DBInstances in us-east-1. Pin the
fixture to 16.9, which is listed by:

  aws rds describe-db-engine-versions --engine postgres --region us-east-1

Both call sites are updated so the engine version and parameter-group
family stay consistent:

- rds.DatabaseInstance engine -> PostgresEngineVersion.VER_16_9
- rds.ParameterGroup engine   -> PostgresEngineVersion.VER_16_9
  (CDK derives the family from the major version, so the synthesized
  DBParameterGroup Family is now postgres16, matching the major)

Synth confirms EngineVersion "16.9" + Family "postgres16" in the template.
README updated to reflect 16.9 / postgres16.

Informational note (not addressed here, separate #609 concern): the deploy
log shows the RDS DBInstance routed via the Cloud Control API because
cdkd's SDK provider does not yet wire BackupRetentionPeriod /
CopyTagsToSnapshot / DBParameterGroupName / DeleteAutomatedBackups /
MultiAZ / StorageType (silent-drop -> #614 routing). That is expected and
acceptable: the CC-API path forwards the full property map, and the
GetAtt(Endpoint.Address) -> SSM-parameter assertion validates on both the
SDK and the CC-API path. No backfill is attempted here.

Everything else is unchanged: the DBSubnetGroup + DBParameterGroup + SG,
the SSM-parameter-from-GetAtt(Endpoint.Address) assertion, the RDS-safe
destroy ordering, removalPolicy DESTROY, no final snapshot, and
deletionProtection false (clean + cheap teardown).
…arsing

The application_name assertion read the parameter value via
`describe-db-parameters ... --query '...ParameterValue | [0]' --output
text`, which under `--output text` could render a trailing "None"
(a null/second field) on its own line, yielding
"cdkd-rds-full-stack\nNone" and failing the equality check against the
expected "cdkd-rds-full-stack".

Switch the readback to `--output json` piped through `jq -r '. // "null"'`
so the JMESPath `[0]` scalar is unwrapped to exactly the parameter value
(or the literal "null" when absent), eliminating the multi-line
text-mode contamination. No engine-version or other changes.
Add an AWS::RDS::DBInstance case to CloudControlProvider's
enrichResourceAttributes, mirroring the existing AWS::RDS::DBCluster case
(issue #381). When a DBInstance template sets a silent-drop top-level
property (BackupRetentionPeriod / CopyTagsToSnapshot / MultiAZ /
PubliclyAccessible / StorageType etc.), the #614 routing rule sends the
whole resource through Cloud Control API, bypassing RDSProvider.create.
RDSProvider.create is the only path that populated the flat-key
attributes['Endpoint.Address'] / ['Endpoint.Port'], so a CC-routed
DBInstance had no endpoint attributes and Fn::GetAtt(<DBInstance>,
'Endpoint.Address') / 'Endpoint.Port' fell through the resolver's
constructAttribute branch to the DB identifier (physicalId) instead of
the real endpoint hostname / port.

The new case calls DescribeDBInstances once after create and overlays the
flat-key Endpoint.Address / Endpoint.Port (coerced to string) /
Endpoint.HostedZoneId / Arn attributes. SHAPE DIFFERENCE vs DBCluster:
DescribeDBInstances returns Endpoint as a nested object { Address, Port,
HostedZoneId }, not a flat string, so the values are read off the nested
object. Best-effort: a failed Describe logs a debug line and leaves the
CC-API attribute shape unchanged so it never fails the deploy.

This is the CC-API DBInstance endpoint-enrichment parity fix for the
GetAtt Endpoint.Address bug found by the rds-full-stack failure-seeking
integ.

Adds two unit tests (happy-path overlay + best-effort Describe failure).
@go-to-k

go-to-k commented Jun 13, 2026

Copy link
Copy Markdown
Owner Author

Independent review complete: pr-code-reviewer + pr-test-reviewer ran (3-axis; spec-reviewer N/A for a bug fix without a design doc). No blockers. One minor nit accepted as known cost: the rds-full-stack verify.sh post-destroy DBInstance assertion accepts deleting as well as gone while the adjacent comment notes cdkd waits for terminal NotFound. The actual real-AWS run reported status: gone (15 deleted / 0 errors / 0 orphans) and the EXIT-trap cleanup is the backstop, so tightening it would only add flakiness risk if AWS is briefly slow. Setting pr-review marker bound to 4659e90.

@go-to-k
go-to-k merged commit bc4e44d into main Jun 13, 2026
5 checks passed
@go-to-k
go-to-k deleted the test/rds-full-stack branch June 13, 2026 14:57
github-actions Bot pushed a commit that referenced this pull request Jun 13, 2026
## [0.221.2](v0.221.1...v0.221.2) (2026-06-13)

### Bug Fixes

* **provisioning:** enrich AWS::RDS::DBInstance Endpoint attributes on the Cloud Control path ([#844](#844)) ([bc4e44d](bc4e44d))
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 0.221.2 🎉

The release is available on:

Your semantic-release bot 📦🚀

go-to-k added a commit that referenced this pull request Jun 15, 2026
Add a codegen + CI critic that makes the CC-API enrichment-gap bug class
(#844 / #864 / #865 / #866) non-regressing. enrichResourceAttributes in
cloud-control-provider.ts is a hand-maintained switch; previously nothing
prevented a new CC-routed type with a computed readOnly Fn::GetAtt
attribute from silently falling through the intrinsic resolver to the
physicalId.

scripts/gen-enrichment-coverage.ts (modeled on gen-property-coverage /
gen-unsupported-types) parses the enrichResourceAttributes switch via the
TypeScript Compiler API, cross-references each type's readOnlyProperties
from the cached CFn schema fixtures, and classifies every CC-routable
type into: enriched / no-computed-attr / sdk-fallback-gap (gap only on
the #614 CC-fallback path, informational) / unenriched-computed (gap on a
pure-CC type with no SDK provider, the real bug class). The
audit:enrichment-coverage:check critic hard-fails ONLY on
unenriched-computed. A readOnly prop that is the type's primaryIdentifier
is auto-classified not-a-gap; refresh-cfn-schemas.mjs now captures
primaryIdentifier into the fixtures so this is data-driven going forward.
Seed ENRICHMENT_ALLOW_LIST carves out MSK::Cluster (Arn ==
primaryIdentifier) and Elasticsearch::Domain (Tier-3 non-provisionable).

Current offline classification: 114 CC-routable types, 0 pure-CC latent
gaps (every cached schema today is an SDK-backed Tier 1 type, so the
critic passes by construction and becomes load-bearing the moment a
pure-CC type's schema is cached). 98 SDK-fallback gaps reported
informationally for a future #614-hardening pass; 8 enrichment cases lack
a cached schema and are listed as needing a fixture refresh.

- Emit docs/_generated/enrichment-coverage.{json,md}
- vp run gen:enrichment-coverage + vp run audit:enrichment-coverage:check
- CI staleness guard + critic step in ci.yml
- 20 unit tests (switch parser, classifier buckets, buildReport)

No AWS integ (pure static analysis / codegen); diff touches no integ-gate
scope.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant