Skip to content

feat(aws): storage.rds foundations — engines, groups, DatabaseSecret, canned metrics [stacked on #142] - #144

Open
so0k wants to merge 3 commits into
storage-prereqsfrom
rds-foundations
Open

feat(aws): storage.rds foundations — engines, groups, DatabaseSecret, canned metrics [stacked on #142]#144
so0k wants to merge 3 commits into
storage-prereqsfrom
rds-foundations

Conversation

@so0k

@so0k so0k commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Storage slice PR 2/N — storage.rds foundations (v2.263.0)

Stacked on #142. Lands the RDS foundation layer so the engine-table, instance, cluster, and proxy PRs can stack on top: engine.ts, engine-version.ts, database-insights-mode.ts, validate-database-insights.ts, endpoint.ts, perms.ts, props.ts, private/{util,ref-utils}.ts, subnet-group.ts, parameter-group.ts, option-group.ts, database-secret.ts, rds-canned-metrics.generated.ts + ported upstream test suites (60 tests, snapshots).

Design points

  • ParameterGroup: one L2, two Terraform resources. Upstream lazily creates CfnDBParameterGroup / CfnDBClusterParameterGroup on bindToInstance() / bindToCluster(); the port mirrors that exactly with aws_db_parameter_group / aws_rds_cluster_parameter_group. Both live-verified via SDK read-backs (DescribeDBParameters / DescribeDBClusterParameters filtered to Source=user). Note: get outputs() carries clusterName/clusterArn alongside the bare name/arn — a documented deviation from the bare-key convention because one construct can back two resources; flagging for review.
  • SubnetGroup is a fresh CDK-shaped port (vpc + vpcSubnets) onto aws_db_subnet_group — deliberately not reusing the deprecated network/subnet-group.ts.
  • rds-canned-metrics.generated.ts comes from aws-cdk's real generate phase (in-repo spec2cdk at v2.263.0 with @aws-cdk/aws-service-spec@0.1.193, validated to reproduce the published bundle) — not hand-written, not reconstructed from .js/.d.ts. rds-augmentations.generated.ts is deferred to the cluster PR (it declare-module-merges ./instance and ./cluster-ref).
  • Naming invariant: unnamed groups get gridUUID-scoped uniqueResourceName defaults (lowercased — RDS stores these names lowercase) instead of the provider's opaque terraform-<hash>.

Documented deviations to review

  • apply_method on parameters is left at the provider default (immediate) — Terraform-only field with no CFN analogue; static engine parameters will fail at apply until it's exposed (noted as pre-instance-PR follow-up in renderParameters()).
  • replaceOnPasswordCriteriaChanges is accepted but currently a no-op (Terraform locks resource addresses on first attribute read — structural, documented in depth on the prop) and now warns at synth via Annotations.
  • DatabaseSecret.recoveryWindow added (not upstream) — CFN deletes secrets immediately; recovery_window_in_days is the provider analogue and deterministic names need Duration.days(0) in fixtures.
  • OptionGroupProps.engine temporarily widened to IEngine until instance-engine.ts lands in the next PR — marked as a must-fix-before-release jsii-breaking tightening.

Live integ (integ/aws/storage, make rds.groups)

Fixture: isolated VPC + SubnetGroup + one ParameterGroup bound both ways + OptionGroup with a real MARIADB_AUDIT_PLUGIN option + DatabaseSecret. No instances — free/fast, validating the Terraform mapping round-trip with SDK read-backs and the post-apply drift oracle.

Three-run evidence story (receipts in thread):

  • Run 1: all read-backs passed; drift oracle caught a product defect in encryption.SecretgenerateSecretString uses the random-password data source, which regenerates every plan, so every apply would replace the live password. Fixed on feat(aws): storage slice prereqs — SecretRotation (SAR) unblock, storage.rds scaffolding, CaCertificate #142 (ignore_changes = [secret_string] for construct-generated values; CFN generates once at create — this is parity). Also exposed the terraform-<hash> naming-invariant miss.
  • Run 2: caught the AWS deletion-recovery-window trap on deterministic secret names → recoveryWindow pass-through.
  • Run 3: PASS 44.73s — apply 13, all validations, drift oracle clean, destroy 13/13.

so0k added 3 commits August 6, 2026 17:35
… canned metrics (v2.263.0)

RDS PR 2a: engine.ts, engine-version.ts, database-insights-mode.ts,
validate-database-insights.ts, endpoint.ts, perms.ts, props.ts,
private/{util,ref-utils}.ts, subnet-group.ts (CDK-shaped fresh port),
parameter-group.ts (ONE L2 lazily binding BOTH aws_db_parameter_group and
aws_rds_cluster_parameter_group, mirroring upstream bindToInstance/
bindToCluster), option-group.ts, database-secret.ts, and
rds-canned-metrics.generated.ts from the real spec2cdk generate phase
(rds-augmentations deferred to the cluster PR — needs ./instance and
./cluster-ref).

Deviations of note: apply_method left at provider default (documented;
static params fail at apply until exposed), replaceOnPasswordCriteriaChanges
is a synth-warned no-op (Terraform locks resource addresses on first
attribute read), unnamed groups get gridUUID-scoped uniqueResourceName
defaults (lowercased) per repo invariant instead of terraform-<hash>.

Live integ make rds.groups: SubnetGroup + both parameter-group binds +
OptionGroup (MARIADB_AUDIT_PLUGIN) + DatabaseSecret, SDK read-backs +
drift oracle. Run 1 caught the generated-password drift fixed on the
storage-prereqs branch (see PR 1).
…ture immediate deletion

Live run 2 failed re-creating the deterministic secret name inside AWS's
30-day deletion-recovery window from run 1's destroy. recoveryWindow is a
documented TERRACONSTRUCTS DEVIATION (CFN deletes immediately;
recovery_window_in_days is provider-level); fixture uses Duration.days(0).
Live run 3: PASS 44.73s, 13/13, drift oracle clean.
@so0k

so0k commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

Live apply/verify/destroy receipts — make rds.groups (aws-vault → tcons-vincent, us-east-1)

Run 3 (final) — PASS 44.73s:

TestRdsGroups 17:37:57 Apply complete! Resources: 13 added, 0 changed, 0 destroyed.
rds_groups_test.go:48: subnet group rdsgroupssubnetgroup407c2e69 spans 2 subnets
rds_groups_test.go:58: instance parameter group rdsgroupsinstanceparamsf87d52a5 has user log_connections   <-- aws_db_parameter_group
rds_groups_test.go:75: cluster parameter group rdsgroupsclusterparamsf182cc30 has user log_connections     <-- aws_rds_cluster_parameter_group (same L2)
rds_groups_test.go:90: option group rdsgroupsoptions76bc54f6 carries MARIADB_AUDIT_PLUGIN
rds_groups_test.go:98: database secret ...rdsgroupsSecretFF3D1A44-CNvU9N holds dbadmin credentials
TestRdsGroups 17:38:08 No changes. Your infrastructure matches the configuration.  <-- drift oracle
TestRdsGroups 17:38:21 Destroy complete! Resources: 13 destroyed.
--- PASS: TestRdsGroups (44.73s)

Run 1 — drift-oracle catches (why runs 2-3 exist):

  1. aws_secretsmanager_secret_version forced replacement: encryption.Secret.generateSecretString rides the aws_secretsmanager_random_password data source, which regenerates every plan — every apply would have REPLACED the live password of any generated secret (incl. DatabaseSecret). Fixed on feat(aws): storage slice prereqs — SecretRotation (SAR) unblock, storage.rds scaffolding, CaCertificate #142 (ignore_changes = [secret_string] for construct-generated values; CFN generates once server-side at create, so this is parity). ecs snapshot ripple refreshed there too.
  2. Groups deployed with provider-default terraform-<hash> names → gridUUID-scoped uniqueResourceName lowercased defaults (repo invariant; visible in run 3's receipts above).

Run 2 — AWS residue trap: re-creating the deterministic secret name failed inside the 30-day deletion-recovery window from run 1's destroy → DatabaseSecret.recoveryWindow pass-through (documented deviation), fixture pins Duration.days(0); stuck secret force-deleted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant