Repository navigation
fix(aws/savingsplans): paginate GetExistingCommitments via NextToken loop - #1039
Conversation
…loop DescribeSavingsPlans paginates (NextToken on both input and output) but GetExistingCommitments was making a single un-paginated call, silently truncating the SP list to the first page. On accounts with more SPs than one page, CUDly under-reported existing commitments, causing duplicate- detection and expiry-coverage math to see lower coverage than reality and recommend or auto-purchase redundant SPs (financial overspend). Add the standard NextToken accumulator loop consistent with the MemoryDB and OpenSearch commitment fetches: ctx.Err() guard at the top of each iteration (cancellation is terminal, not accumulated), MaxResults=100 per page, and a maxCommitmentPages=100 page cap that logs a warning and returns partial results (data loss is worse than a hard error for the commitment listing path). Also add two regression tests: - two-page mock asserts all items are accumulated (fails pre-fix, passes post-fix) - cancellation test asserts ctx.Err() propagates verbatim on cancel Fix pre-existing build break: two TestLookupOfferingID_* tests called findOfferingID with 2 args after its signature grew to 3 args in the same branch; pass empty string as execID to match ValidateOffering. Closes #1019
|
Warning Review limit reached
More reviews will be available in 45 minutes and 11 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the 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 include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughThe PR fixes issue ChangesSavings Plans Pagination Fix
🎯 3 (Moderate) | ⏱️ ~25 minutes
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
@coderabbitai review |
✅ Action performedReview finished.
|
The pagination loop added in this branch pushed GetExistingCommitments to cyclomatic complexity 15 (budget 10), failing the pre-commit gocyclo hook in CI. Pull the per-SavingsPlan conversion (nil-ID guard, plan-type filter, and the two RFC3339 time-parse branches) into a toCommitment helper that returns (Commitment, ok). This is a behavior-preserving extraction: the loop now skips entries when ok is false, identical to the previous continue branches. GetExistingCommitments drops back under the limit; the existing two-page and cancellation regression tests still pass unchanged.
|
pr-iterate: the failing CI (the gocyclo pre-commit hook flagging |
… deterministic sort to findOfferingID DescribeSavingsPlansOfferings was called without currency or region constraints, so non-USD offerings and (for EC2Instance SPs) off-region offerings could appear in SearchResults. The first element was then returned without sorting, making the selected offering ID nondeterministic across API calls. - Set Currencies: [USD] on DescribeSavingsPlansOfferingsInput so non-USD currency variants are excluded server-side. - For EC2Instance SPs (the only region-scoped SP type), add a SavingsPlanOfferingFilterAttributeRegion filter element using the client's region. Compute, SageMaker, and Database SPs are global and receive no region filter. - In lookupOfferingID, accumulate all matching offering IDs across pages, sort lexicographically, then return the first so the result is stable regardless of AWS response ordering. Closes #1066. Finding 08-L1 (SP portion).
FOLD-1039: added finding 08-L1 (SP portion)Finding: Root cause: Commit: Changes:
Verification: All 731 aws provider tests pass; root Closes #1066. @coderabbitai review |
🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Read the updated savingsplans client.go to review the findOfferingID / lookupOfferingID changes
fd -e go -p 'savingsplans/client' | xargs catLength of output: 94144
One minor style note (not a blocking concern): The 5 regression tests are a solid safety net against regression. Triggering the review now. 🧠 Learnings used✅ Action performedReview finished.
|
…t default convertPaymentOption and convertTermToSeconds previously logged a warning and silently substituted All Upfront (largest cash outlay) or 1 year when given an unrecognized or empty value, risking a mis-purchase on any typo or new/renamed AWS enum value. Both functions now return an explicit error on unrecognized input so the purchase and offering-lookup paths fail loud instead of buying the wrong product. Mirrors the pattern already used by the RDS convertPaymentOption. Regression tests for H1 (unknown/empty payment option) and H2 (unknown/empty term) are added to TestClient_FindOfferingID_AllPaymentOptions and TestClient_FindOfferingID_TermVariations; the previously-passing "default term" case is updated to assert the error path. Fixes H1 and H2 from docs/code-review/19-hardcoded-fallbacks-aws.md (PR #1039).
Scope expanded: H1 + H2 silent-default fixes (commit bcc1f96)Added fixes for the two High-severity findings from the docs/code-review/19-hardcoded-fallbacks-aws.md audit assigned to this PR. H1: convertPaymentOption now returns (SavingsPlanPaymentOption, error) and errors on unknown/empty input instead of silently defaulting to All Upfront. Error threaded through findOfferingID. H2: convertTermToSeconds now returns (int64, error) and errors on unknown/empty term instead of silently defaulting to 1 year. Error threaded through findOfferingID. Regression tests in TestClient_FindOfferingID_AllPaymentOptions and TestClient_FindOfferingID_TermVariations assert the error path for unknown/empty values and confirm the AWS API is never called. go test -race ./services/savingsplans/...: 66 passed. @coderabbitai review |
🧩 Analysis chain🏁 Script executed: #!/bin/bash
fd -e go -p 'savingsplans/client' | xargs catLength of output: 94046
While the review runs, here's what I'll be verifying in the diff: H1 —
H2 —
Tests (
One pre-emptive flag: if either function accepts 🧠 Learnings used✅ Action performedReview finished.
|
…ilent defaults (#1085) * fix(aws/savingsplans): error on unknown payment-option/term, no silent default convertPaymentOption and convertTermToSeconds previously logged a warning and silently substituted All Upfront (largest cash outlay) or 1 year when given an unrecognized or empty value, risking a mis-purchase on any typo or new/renamed AWS enum value. Both functions now return an explicit error on unrecognized input so the purchase and offering-lookup paths fail loud instead of buying the wrong product. Mirrors the pattern already used by the RDS convertPaymentOption. Regression tests for H1 (unknown/empty payment option) and H2 (unknown/empty term) are added to TestClient_FindOfferingID_AllPaymentOptions and TestClient_FindOfferingID_TermVariations; the previously-passing "default term" case is updated to assert the error path. Fixes H1 and H2 from docs/code-review/19-hardcoded-fallbacks-aws.md (PR #1039). * fix(aws): fail loud on unknown payment-option/term/engine Fixes H3, H4, M1-M6, L1, L2 from the 19-hardcoded-fallbacks-aws.md audit plus H1/H2 from the now-merged #1039 branch (savingsplans). H3 (converters.go): add convertPaymentOptionE/convertTermInYearsE/ convertLookbackPeriodE that error on unrecognized input; wire into the SP recommendation path (parser_sp.go). The legacy non-erroring wrappers are retained for the RI path in client.go (owned by #865/#1075) with a deprecation comment and a follow-up reference. H4 (elasticache/client.go): convertPaymentOption now returns (string, error) mirroring the RDS sibling that already errors correctly. H1/H2 (savingsplans/client.go): convertPaymentOption and convertTermToSeconds now return (type, error); cherry-picked from the merged #1039 branch where the fix was staged. M4 (parser_services.go, rds/client.go): parseRDSDetails no longer silently defaults AZConfig to "single-az" when CE omits DeploymentOption; findOfferingID rejects empty AZConfig with an explicit error. M5 (parser_services.go): resolveEC2Tenancy now maps only the known CE values ("shared"/nil -> default, "dedicated" -> dedicated); any other value (e.g. "host" for Dedicated Hosts) returns an error rather than silently collapsing to default tenancy. M6 (rds/client.go): normalizeEngineName now returns (string, error) and errors for ambiguous Oracle ("oracle*"), SQL Server ("sqlserver*"/"sql-server*"), and bare Aurora inputs; unambiguous engines (mysql, postgresql, mariadb, aurora-mysql, aurora-postgresql) pass through unchanged. M1/M2/M3 (ec2/client.go): replace raw "convertible" literals with types.OfferingClassTypeConvertible; buildEC2OfferingQuery errors on empty Platform (purchase path); centralize "Linux/UNIX" literal into defaultEC2Platform constant (exchange-path helpers retain their safe fallback; the purchase path does not). Regression tests added for H3, H1/H2, H4, M2, M4, M5, M6, L1, L2; each fails pre-fix and passes post-fix. Closes #1084 * test(aws/rds): clarify ambiguous-engine regression test Simplify TestNormalizeEngineName_AmbiguousErrors: remove the confusing oracle-se2 case (strings.Contains("oracle-se2","oracle") is true, so it errors like all other oracle inputs -- no special case needed) and remove the misleading inline comment. All remaining cases assert that any Oracle, SQL Server, or bare Aurora input errors, which is the invariant under test. * refactor(aws): extract helpers to fix gocyclo violations in rds and savingsplans gocyclo -over 10 fails on two functions added by the fail-loud refactor: - savingsplans.(*Client).findOfferingID (complexity 12): extract resolveSPPlanType and buildSPOfferingsInput helpers - rds.(*Client).paginateRDSOfferings (complexity 11): extract fetchRDSOfferingPage to isolate the per-page API call and scan Also apply gofmt to rds/client.go (trailing whitespace / brace style). Behaviour and error paths are unchanged; all rds and savingsplans tests still pass. * fix(aws/rds): tighten AZ-config and engine-name validation (CR #1085) Three related fail-loud fixes in the AWS RDS purchase path: 1. parseRDSDetails: unknown DeploymentOption now errors instead of silently folding into "single-az". Only "Multi-AZ" and "Single-AZ" are accepted; any other non-nil value (e.g. "Multi-AZ-Readable-Standbys") returns an explicit error so the bad token surfaces rather than driving findOfferingID to the wrong RI class. 2. findOfferingID: AZConfig validation now uses a switch with an explicit default error case rather than only checking for empty string. A non-empty but invalid value (typo, future AWS enum addition) would previously fall through to multiAZ==false in paginateRDSOfferings, silently treating the bad input as single-AZ. 3. normalizeEngineName: Oracle and SQL Server checks changed from strings.Contains to == so that edition-qualified tokens (oracle-se2, oracle-ee, sqlserver-se, sqlserver-web, etc.) pass through as valid RDS ProductDescription values. The previous Contains check contradicted the error message itself, which told callers to supply those exact edition tokens. Regression tests added for all three: each new test was verified to fail against the pre-fix code and pass after.
Summary
GetExistingCommitmentsinservices/savingsplans/client.gowas callingDescribeSavingsPlansonce with noNextTokenloop, silently truncating to the first page of results.ctx.Err()terminal guard and amaxCommitmentPages=100page cap, consistent with the MemoryDB and OpenSearch commitment fetches and the ctx-cancel pattern from thefeedback_ctx_cancel_terminalmemory entry.TestLookupOfferingID_*tests calledfindOfferingIDwith 2 args after its signature grew to 3 args on this branch; updated to pass empty string asexecID.Closes #1019
Test plan
TestClient_GetExistingCommitments_Pagination: two-page mock asserts both items accumulated (would fail on pre-fix code returning only page 1)TestClient_GetExistingCommitments_CtxCancellation: cancel fires after page 1; assertscontext.Canceledpropagates verbatim (not swallowed)go test ./services/savingsplans/...)go test ./...fromproviders/aws)go build ./...passes; 2 pre-existingauthtest failures are unrelated to this change (confirmed by running without changes)Scope expanded
Added finding 08-L1 (SP portion) from the code-review remaining-findings plan (FOLD-1039 bucket):
findOfferingIDcalledDescribeSavingsPlansOfferingswithout narrowing bycurrency or region, then returned
SearchResults[0]without sorting. AWS doesnot guarantee response ordering, so when multiple offerings matched (e.g. USD
vs CNY, or global vs region-scoped) the selected offering ID was
nondeterministic.
Changes in commit
9d292dcf5:Currencies: [USD]toDescribeSavingsPlansOfferingsInputto exclude non-USD offerings server-side.SavingsPlanOfferingFilterAttributeRegionfilter element usingc.region. Compute, SageMaker, and Database SPs are global and receive no region filter.lookupOfferingIDto accumulate all offering IDs across pages, sort lexicographically, then return the first - stable regardless of AWS response ordering.Also closes #1066