Skip to content

feat(providers/aws): rich self-describing reservation names matching the Azure #686 format #687

Description

@cristim

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:

  1. 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.
  2. 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 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:

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 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

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

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions