Repository navigation
feat(providers/aws): rich self-describing reservation names matching the Azure #686 format #687
Description
Background
PR #686 adds a rich descriptive displayName for 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's ReservationId column:
ri-cxm-trading-rds-aurora-mysql-ap-northeast-1-db-t4g-medium-1x-80pct-20260521-002019-227b6194
The AWS reservation purchase paths in providers/aws/services/*/client.go currently construct flat, low-signal IDs — e.g.:
// providers/aws/services/opensearch/client.go:161
reservationName = common.SanitizeReservationID(
fmt.Sprintf("opensearch-%s-%d", rec.ResourceType, time.Now().Unix()),
"opensearch-reserved-",
)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>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:
{svc}-{region}-{sku}-{count}x-{term}-{paymt}-{ts}-{rand}
Where:
- svc (short code matching the AWS resource:
rds,cache,memdb,opensearch,redshift) - 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 viaSanitizeReservationIDwhich already exists - count:
{rec.Count}x - term:
1yr/3yr - paymt:
allup/noup/partup - ts:
YYYYMMDDhhmmssUTC 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:
type ReservationNameFields struct {
Service string // "rds", "cache", "memdb", "opensearch", "redshift"
Region string
ResourceType string
Count int
Term string
Payment string
Now 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).
func BuildReservationName(f ReservationNameFields) string { ... }Then update each of the 5 service fallback paths to use it.
Tests
- New unit tests for
BuildReservationNamecovering: happy path, worst-case length truncation, payment normalization, deterministic output given fixedNow+ 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
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). - Tags on Redshift (and any other tagging path added by 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) include the rich descriptors.
Why this matters
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.
Related
- bug(providers/azure): displayName contains spaces — every Azure approve fails with DisplayNameInvalid #685 / fix(azure): sanitize Azure DisplayName to [A-Za-z0-9_-] for calculatePrice (closes #685) #686 (Azure rich displayName — the model)
- 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 (AWS idempotency — sets the existing fallback-ID code path that this issue extends)
Activity
feat(common): add BuildReservationName for rich AWS reservation IDs
feat(aws): rich self-describing reservation names matching Azure (closes
feat(aws): rich self-describing reservation names matching Azure #686 (…
feat(providers/aws): rich self-describing RI Name tags (closes #687) (#…