You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feat(providers/aws): rich self-describing reservation names matching the Azure #686 format #687
That yields opensearch-Standard_X-1779468234 — no region, no count, no term, no payment option, no human-readable timestamp. The same shape applies to the other AWS services that use IdempotentReservationID fallback or construct their own ID for Purchase* AWS SDK calls.
What to do
Extend the same rich-name pattern to AWS reservation purchases (5 services: rds, elasticache, memorydb, opensearch, redshift). Specifically:
Reservation-ID fallback strings in each AWS service client.go — when the idempotency token is empty / CLI-driven path. Currently:
rds, elasticache, memorydb, opensearch — fallback to <service>-<resourceType>-<unix> via SanitizeReservationID.
redshift — tag-guard, doesn't have a customer-supplied ID per se but writes a idempotency tag with similar metadata.
region: rec.Region (AWS regions are already conformant, e.g. us-east-1)
sku: rec.ResourceType (db.t4g.medium, m5.large, etc.) — note dots become hyphens per AWS reservation-name allowlist; do this via SanitizeReservationID which already exists
count: {rec.Count}x
term: 1yr/3yr
paymt: allup / noup / partup
ts: YYYYMMDDhhmmss UTC compact
rand: 8-hex from crypto/rand
Each AWS service has its own allowlist for reservation-name characters (RDS allows [a-zA-Z0-9-], ElastiCache similar, OpenSearch allows underscores, etc.) — defer to the existing common.SanitizeReservationID to enforce the union.
Implementation approach
Extract a shared builder in pkg/common/ (similar to where SanitizeReservationID lives), analogous to Azure's BuildDisplayName:
typeReservationNameFieldsstruct {
Servicestring// "rds", "cache", "memdb", "opensearch", "redshift"RegionstringResourceTypestringCountintTermstringPaymentstringNow time.Time// injectable for tests
}
// BuildReservationName composes a rich, parseable identifier for an AWS// reservation purchase. Matches the Azure DisplayName format from #686.// Format: {svc}-{region}-{sku}-{count}x-{term}-{paymt}-{ts}-{rand}//// Pass the result through SanitizeReservationID for per-service allowlist// compliance. Max length is constrained per the most-restrictive AWS API// limit observed across the 5 services (verify before shipping —// ReservationName for OpenSearch is up to 64 chars; ElastiCache's// ReservedCacheNodeId allows up to 60).funcBuildReservationName(fReservationNameFields) string { ... }
Then update each of the 5 service fallback paths to use it.
Tests
New unit tests for BuildReservationName covering: happy path, worst-case length truncation, payment normalization, deterministic output given fixed Now + stubbed random.
Per-service test extension: each service's existing reservation-name-construction test should assert the new shape (regex ^{svc-code}-) and that key fields (region, SKU, count) are present.
Acceptance criteria
All 5 AWS service reservation-name fallback paths use the rich format.
Operator opening the AWS console (or running aws rds describe-reserved-db-instances) sees an ID that encodes who/when/what without needing to query CUDly's DB.
No regression in idempotency — same input + same IdempotencyToken still produces the same name (the token-based path bypasses the rich builder; the rich builder is for the no-token CLI fallback path only).
Operators reading the AWS console see meaningless opensearch-Standard_X-1779468234 IDs today. After this lands, they see opensearch-us-east-1-r6gd_large_search-3x-1yr-allup-20260521T002019-a1b2c3d4 — instantly clear what was purchased, when, in which region, for how long. Symmetry with the Azure side (#686) means cross-cloud ops dashboards / runbooks can share parsing logic.
Background
PR #686 adds a rich descriptive
displayNamefor Azure reservation purchases (format{svc}-{region}-{sku}-{count}x-{term}-{paymt}-{ts}-{rand}), matching the operator-friendly shape of the existing AWS RDS purchase CSV'sReservationIdcolumn:The AWS reservation purchase paths in
providers/aws/services/*/client.gocurrently construct flat, low-signal IDs — e.g.:That yields
opensearch-Standard_X-1779468234— no region, no count, no term, no payment option, no human-readable timestamp. The same shape applies to the other AWS services that useIdempotentReservationIDfallback or construct their own ID forPurchase*AWS SDK calls.What to do
Extend the same rich-name pattern to AWS reservation purchases (5 services: rds, elasticache, memorydb, opensearch, redshift). Specifically:
Reservation-ID fallback strings in each AWS service
client.go— when the idempotency token is empty / CLI-driven path. Currently:rds,elasticache,memorydb,opensearch— fallback to<service>-<resourceType>-<unix>viaSanitizeReservationID.redshift— tag-guard, doesn't have a customer-supplied ID per se but writes aidempotencytag with similar metadata.Tagging — for services that tag the purchased reservation (per feat(purchases): idempotent commitment creation (ClientToken/dedupe) to enable auto-re-drive of stranded approvals #636/feat(purchases): idempotent commitment creation (ClientToken/dedupe) (closes #636) #638/feat(purchases): make AWS RDS/ElastiCache/MemoryDB/OpenSearch/Redshift idempotent (refs #641) #652 idempotency work), include the same rich descriptors as tag values so the AWS console / CLI output is self-describing without cross-referencing CUDly's DB.
Proposed format
Match Azure's #686 format closely so the two sides are operationally symmetric:
Where:
rds,cache,memdb,opensearch,redshift)rec.Region(AWS regions are already conformant, e.g.us-east-1)rec.ResourceType(db.t4g.medium,m5.large, etc.) — note dots become hyphens per AWS reservation-name allowlist; do this viaSanitizeReservationIDwhich already exists{rec.Count}x1yr/3yrallup/noup/partupYYYYMMDDhhmmssUTC compactcrypto/randEach AWS service has its own allowlist for reservation-name characters (RDS allows
[a-zA-Z0-9-], ElastiCache similar, OpenSearch allows underscores, etc.) — defer to the existingcommon.SanitizeReservationIDto enforce the union.Implementation approach
Extract a shared builder in
pkg/common/(similar to whereSanitizeReservationIDlives), analogous to Azure'sBuildDisplayName:Then update each of the 5 service fallback paths to use it.
Tests
BuildReservationNamecovering: happy path, worst-case length truncation, payment normalization, deterministic output given fixedNow+ stubbed random.^{svc-code}-) and that key fields (region, SKU, count) are present.Acceptance criteria
aws rds describe-reserved-db-instances) sees an ID that encodes who/when/what without needing to query CUDly's DB.IdempotencyTokenstill produces the same name (the token-based path bypasses the rich builder; the rich builder is for the no-token CLI fallback path only).Why this matters
Operators reading the AWS console see meaningless
opensearch-Standard_X-1779468234IDs today. After this lands, they seeopensearch-us-east-1-r6gd_large_search-3x-1yr-allup-20260521T002019-a1b2c3d4— instantly clear what was purchased, when, in which region, for how long. Symmetry with the Azure side (#686) means cross-cloud ops dashboards / runbooks can share parsing logic.Related