Skip to content

feat(aws): rich self-describing reservation names matching Azure #686 (closes #687)#689

Merged
cristim merged 3 commits into
feat/multicloud-web-frontendfrom
feat/issue-687-aws-rich-names
May 22, 2026
Merged

feat(aws): rich self-describing reservation names matching Azure #686 (closes #687)#689
cristim merged 3 commits into
feat/multicloud-web-frontendfrom
feat/issue-687-aws-rich-names

Conversation

@cristim
Copy link
Copy Markdown
Member

@cristim cristim commented May 22, 2026

Summary

Per-service examples (no-token CLI path)

rds         rds-us-east-1-db-medium-1x-1yr-noup-20260521T002019-a1b2c3d4
cache       cache-us-east-1-cache-m6g-large-2x-1yr-allup-20260521T002019
memdb       memdb-us-east-1-db-r6gd-large-3x-1yr-allup-20260521T002019
opensearch  opensearch-us-east-1-m5-xlarge-search-2x-3yr-allup-20260521
redshift    (tagged, no customer-supplied ID — see Count/Term/PaymentOption tags)

Token-based path is unchanged (deterministic IDs via IdempotentReservationID).

Length-fit algorithm

When composed length exceeds 60 chars, optional tail segments drop in priority order:

  1. random suffix (8 hex chars)
  2. timestamp (YYYYMMDDThhmmss)
  3. payment option (allup / noup / partup)

Service code / region / SKU / count / term never drop. In the pathological worst case (an impossibly long SKU), the SKU itself is truncated rather than the joined string, so count and term still survive the final assembly.

Test plan

  • 16 new BuildReservationName unit tests in pkg/common/reservation_name_test.go — happy path, length-fit at each drop priority (rand → ts → payment), worst-case SKU truncation, payment normalization (3 canonical + unknown), term normalization, dot→hyphen SKU normalization, deterministic output with fixed sources, always-sanitized output, UTC normalization, per-service prefix coverage
  • 5 new per-service capture tests asserting ^{svc-code}- prefix, region presence, SKU presence (dots→hyphens), {count}x-{term} embedded, and 60-char cap
  • Existing 208 AWS service-client tests + 401 pkg tests still pass (no regressions)
  • Full repo go test ./... clean (4 744 tests across root + AWS submodule)
  • gofmt, go vet, go build ./... clean

After merge + deploy, AWS console will show rich self-describing IDs for new reservation purchases via the no-token CLI path. Existing reservations are untouched. The token-based path (web UI) keeps its deterministic IDs from #641.

Related

Summary by CodeRabbit

  • New Features

    • Reservation identifiers for AWS commitments (ElastiCache, MemoryDB, OpenSearch, RDS, Redshift) are now richer and self-describing (service, region, resource type/SKU, count, term), include timestamps/random suffixes when space allows, and always respect the 60-char limit with progressive trimming and sanitization.
    • Redshift reserved-node tags now include count, term, and payment option.
  • Tests

    • Extensive unit tests cover name composition, normalization, truncation order, deterministic outputs, and per-service prefixes.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 22, 2026

Warning

Review limit reached

@cristim, we couldn't start this review because you've used your available PR reviews for now.

Your plan currently allows 2 reviews/hour. Refill in 2 minutes and 48 seconds.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more review capacity refills, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than trial, open-source, and free plans. In all cases, review capacity refills continuously over time.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8d2ad4ad-371d-4d56-aba4-ea1f7e361a6d

📥 Commits

Reviewing files that changed from the base of the PR and between 9bf073c and 0ddd4e5.

📒 Files selected for processing (12)
  • pkg/common/reservation_name.go
  • pkg/common/reservation_name_test.go
  • providers/aws/services/elasticache/client.go
  • providers/aws/services/elasticache/client_test.go
  • providers/aws/services/memorydb/client.go
  • providers/aws/services/memorydb/client_test.go
  • providers/aws/services/opensearch/client.go
  • providers/aws/services/opensearch/client_test.go
  • providers/aws/services/rds/client.go
  • providers/aws/services/rds/client_test.go
  • providers/aws/services/redshift/client.go
  • providers/aws/services/redshift/client_test.go
📝 Walkthrough

Walkthrough

Adds a shared BuildReservationName utility that composes self-describing AWS reservation identifiers within a 60-character cap, with normalization, timestamp and random suffix, optional-tail dropping and SKU truncation. Rolls the utility out to ElastiCache, MemoryDB, OpenSearch, RDS, and Redshift (plus tests and expanded Redshift tags).

Changes

Reservation Name Builder and Service Integration

Layer / File(s) Summary
BuildReservationName core utility
pkg/common/reservation_name.go
ReservationNameFields struct with test hooks and WithRandSource. BuildReservationName normalizes service/region/SKU/term/payment/count, builds UTC timestamp and 8-hex random suffix, assembles required + optional tail segments, drops optional tails (random → timestamp → payment) to fit 60 chars, truncates SKU if needed, and final-sanitizes the result.
BuildReservationName test suite
pkg/common/reservation_name_test.go
Unit tests covering happy path, exact short-input output, deterministic outputs with fixed Now/randSource, length-regime behavior (drop order and SKU truncation), payment/term/SKU normalization, sanitization rules, UTC timestamp formatting, and per-service prefixes.
ElastiCache integration
providers/aws/services/elasticache/client.go, client_test.go
PurchaseCommitment now calls BuildReservationName for fallback reservationID when no idempotency token is present. Test asserts cache- prefix, region, hyphenated SKU, count/term encoding, and ≤60 chars.
MemoryDB integration
providers/aws/services/memorydb/client.go, client_test.go
PurchaseCommitment generates a memorydb-reserved- prefixed name via BuildReservationName on the no-token path; test validates service prefix, region, SKU, count/term, and length cap.
OpenSearch integration
providers/aws/services/opensearch/client.go, client_test.go
PurchaseCommitment uses BuildReservationName for non-token fallback instead of a timestamp-based sanitized string; test validates opensearch- prefix, region, dots→hyphens SKU, count/term, and ≤60 chars.
RDS integration
providers/aws/services/rds/client.go, client_test.go
deriveReservationID fallback uses BuildReservationName with rds-reserved- prefix when no idempotency token and no explicit ReservationID; test asserts rds- prefix, region, SKU, count/term, and 60-char limit.
Redshift reserved-node tagging
providers/aws/services/redshift/client.go, client_test.go
tagReservedNode expands base tags to include Count, Term, and PaymentOption; test verifies CreateTags receives these enriched descriptors matching the recommendation.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

  • LeanerCloud/CUDly#652: Related changes to AWS PurchaseCommitment identifier handling; this PR complements that work by replacing the no-token fallback with structured names.
  • LeanerCloud/CUDly#618: Related RDS reservation-ID handling changes; both PRs touch reservation identifier composition and sanitization.

Suggested labels

priority/p3, severity/low, urgency/this-quarter

Poem

🐰 I built a name both neat and tight,
Sixty chars to say things right,
Region, SKU, count, and year—
A hex-suffix hop to make it clear,
Safe, short, and snug tonight.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 39.39% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: implementing rich, self-describing AWS reservation names. It is specific, directly related to the primary objective, and appropriately references the associated issue numbers.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/issue-687-aws-rich-names

Comment @coderabbitai help to get the list of available commands and usage tips.

@cristim cristim added triaged Item has been triaged priority/p2 Backlog-worthy severity/medium Moderate harm urgency/eventually No deadline impact/many Affects most users effort/m Days type/feat New capability labels May 22, 2026
@cristim
Copy link
Copy Markdown
Member Author

cristim commented May 22, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 22, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/common/reservation_name_test.go`:
- Around line 100-120: The test
TestBuildReservationName_LengthFit_DropsPaymentLast needs an assertion that the
payment segment was actually dropped; update the test that calls
BuildReservationName(ReservationNameFields{... Payment: "all-upfront",
...}.WithRandSource(...), ...) to include assert.NotContains(t, got,
"all-upfront") (and optionally assert.NotContains(t, got, "allup") if shortened
forms are produced) so the test verifies payment is removed in this
length-regime alongside the existing timestamp and rand checks; keep the other
assertions (length, service/region, count, term) unchanged.

In `@pkg/common/reservation_name.go`:
- Line 98: The timestamp formatting currently uses f.Now directly which can be
the zero time and produce "00010101T000000"; update the code that builds the
timestamp (the line using f.Now.UTC().Format in the
ReservationNameFields/ReservationName helper) to check for a zero value and fall
back to time.Now() when f.Now.IsZero(), then use that non-zero time converted to
UTC and formatted; reference the ReservationNameFields.Now field and the
timestamp formatting expression to locate where to add the IsZero() check and
fallback.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e20c29a5-da08-4ecf-bed3-36151bac9cf6

📥 Commits

Reviewing files that changed from the base of the PR and between b727ecb and 9bf073c.

📒 Files selected for processing (12)
  • pkg/common/reservation_name.go
  • pkg/common/reservation_name_test.go
  • providers/aws/services/elasticache/client.go
  • providers/aws/services/elasticache/client_test.go
  • providers/aws/services/memorydb/client.go
  • providers/aws/services/memorydb/client_test.go
  • providers/aws/services/opensearch/client.go
  • providers/aws/services/opensearch/client_test.go
  • providers/aws/services/rds/client.go
  • providers/aws/services/rds/client_test.go
  • providers/aws/services/redshift/client.go
  • providers/aws/services/redshift/client_test.go

Comment thread pkg/common/reservation_name_test.go
Comment thread pkg/common/reservation_name.go Outdated
cristim added a commit that referenced this pull request May 22, 2026
…op in length test

Default f.Now to time.Now() when IsZero() so a missed caller field produces a
current timestamp rather than a year-0001 artifact. Deterministic tests are
unaffected because they set Now: testFixedNow explicitly.

Add assert.NotContains(t, got, "allup", ...) to
TestBuildReservationName_LengthFit_DropsPaymentLast so the test actually
verifies the payment segment is dropped in the tight-length regime, not just
that timestamp and random were removed.

Addresses CodeRabbit findings on PR #689.
@cristim
Copy link
Copy Markdown
Member Author

cristim commented May 22, 2026

Both CodeRabbit findings addressed in 55b571e:

Finding 1 (test, pkg/common/reservation_name_test.go): Added assert.NotContains(t, got, "allup", "payment should be the last optional segment to drop") to TestBuildReservationName_LengthFit_DropsPaymentLast. The payment token is allup - confirmed from normalizeReservationPayment switch where "all-upfront" maps to "allup". The test now actually catches regressions in drop-order logic.

Finding 2 (pkg/common/reservation_name.go, line 98): Added IsZero() guard before timestamp formatting. When f.Now is zero-valued, falls back to time.Now() instead of emitting a year-0001 artifact. The time package was already imported. All deterministic tests pass unchanged because they set Now: testFixedNow explicitly.

All 33 tests in pkg/common pass. go vet clean. gofmt no changes.

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 22, 2026

@cristim Thanks for the detailed follow-up! Both fixes look well-considered:

  • Finding 1: The assert.NotContains(t, got, "allup") assertion is the right way to make TestBuildReservationName_LengthFit_DropsPaymentLast meaningful — without it the test was passing vacuously regardless of drop order.
  • Finding 2: The IsZero() guard is a clean, minimal fix. Since all deterministic tests already supply an explicit Now, the fallback to time.Now() is purely a defensive measure for callers who omit the field, and it won't disturb any existing test behavior.

Triggering a fresh review of commit 55b571e.

(´• ω •`) 🐇

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

cristim added 3 commits May 23, 2026 00:14
Add a shared builder that composes self-describing identifiers for AWS
reservation purchases. The format mirrors the Azure DisplayName format
introduced in #686 (cross-cloud symmetry for ops dashboards):

  {svc}-{region}-{sku}-{count}x-{term}-{paymt}-{ts}-{rand}

Output is always sanitized via SanitizeReservationID for the AWS
reservation-name allowlist ([a-zA-Z0-9-], dots in SKUs become hyphens)
and capped at 60 characters — the tightest length cap across the five
AWS services that accept a customer-supplied reservation ID/name (RDS
ReservedDBInstanceId, ElastiCache ReservedCacheNodeId, MemoryDB
ReservationId, OpenSearch ReservationName).

When composed length exceeds the cap, optional tail segments drop in
priority order: random suffix first, then timestamp, then payment
option. The service code, region, SKU, count, and term are never
dropped. In the pathological worst case the builder truncates the SKU
itself so count and term still survive the final assembly. Time and
random sources are injectable for deterministic tests.

Refs #687

  pkg/common/reservation_name.go       | +201
  pkg/common/reservation_name_test.go  | +281
 #687)

Wire common.BuildReservationName into the five AWS reservation purchase
paths. On the no-token CLI fallback path (i.e. when opts.IdempotencyToken
is empty), each service now composes a self-describing identifier of
shape:

  {svc}-{region}-{sku}-{count}x-{term}-{paymt}-{ts}-{rand}

For example, an OpenSearch RI purchase that previously yielded the
opaque `opensearch-Standard_X-1779468234` now becomes
`opensearch-us-east-1-r6g-large-search-3x-1yr-allup-20260521T002019-a1b2c3d4`
(truncated to 60 chars per service allowlist; see #687).

Per service:

* rds: deriveReservationID's fmt.Sprintf fallback -> BuildReservationName
  ("rds-" prefix). opts.ReservationID still wins when set; idempotency
  token still wins above both.
* elasticache: ReservedCacheNodeId fallback -> BuildReservationName
  ("cache-" prefix).
* memorydb: ReservationId fallback -> BuildReservationName ("memdb-").
* opensearch: ReservationName fallback -> BuildReservationName
  ("opensearch-").
* redshift: PurchaseReservedNodeOffering doesn't accept a customer-
  supplied node ID, so the rich descriptors travel as tags instead.
  tagReservedNode now writes Count / Term / PaymentOption alongside
  the pre-existing NodeType / Region / PurchaseDate / Tool tags so
  the reserved node is identifiable from the AWS console alone.

The token-based path (issue #641) is unchanged: IdempotentReservationID
still derives the deterministic ID from the token so a re-drive sends
the identical name and AWS rejects the duplicate server-side. The rich
builder applies ONLY to the no-token fallback.

Tests: one new per-service capture test asserts ^{svc-code}- prefix,
region presence, SKU (dots->hyphens) presence, count+term embedded, and
the 60-char cap. Redshift's test asserts the new tag keys
(Count/Term/PaymentOption) appear in the CreateTags input.

Closes #687
…op in length test

Default f.Now to time.Now() when IsZero() so a missed caller field produces a
current timestamp rather than a year-0001 artifact. Deterministic tests are
unaffected because they set Now: testFixedNow explicitly.

Add assert.NotContains(t, got, "allup", ...) to
TestBuildReservationName_LengthFit_DropsPaymentLast so the test actually
verifies the payment segment is dropped in the tight-length regime, not just
that timestamp and random were removed.

Addresses CodeRabbit findings on PR #689.
@cristim cristim force-pushed the feat/issue-687-aws-rich-names branch from 55b571e to 0ddd4e5 Compare May 22, 2026 22:18
@cristim
Copy link
Copy Markdown
Member Author

cristim commented May 22, 2026

Rebased onto feat/multicloud-web-frontend@b581fe777 to resolve conflicts in providers/aws/services/rds/client_test.go and providers/aws/services/opensearch/client_test.go.

Both conflicts were additive: this PR's TestClient_PurchaseCommitment_NoToken_RichReservationName (issue #687) landed at the same spot as the base branch's TestFindOfferingID_PaginationCapFires / WrongVariantRejected / HappyPath (issue #688, via #690). Resolution keeps every test from both sides; no logic was dropped or reverted. The AWS payment terminology (all-upfront -> allup) stays AWS-correct and was not converted to Azure terms.

The two CR-fix commits from the prior head (55b571e) survived the rebase: the zero-Now -> time.Now() default guard in BuildReservationName and the assert.NotContains(t, got, "allup", ...) payment-drop assertion in TestBuildReservationName_LengthFit_DropsPaymentLast.

Tests re-run green: go test ./common/... (pkg, 172 passed), AWS service tests for rds/opensearch/elasticache/memorydb/redshift (211 passed), go build ./..., go vet, and gofmt all clean.

New HEAD: 0ddd4e5

@cristim
Copy link
Copy Markdown
Member Author

cristim commented May 22, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 22, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@cristim cristim merged commit 0635480 into feat/multicloud-web-frontend May 22, 2026
5 checks passed
@cristim
Copy link
Copy Markdown
Member Author

cristim commented May 22, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 22, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

Labels

effort/m Days impact/many Affects most users priority/p2 Backlog-worthy severity/medium Moderate harm triaged Item has been triaged type/feat New capability urgency/eventually No deadline

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant