Repository navigation
fix(providers/aws): fail loud on unparseable SP cost fields + RI utilization hours (COR-07 siblings to #1171) #18
Description
Problem
PR LeanerCloud/cloud-commitments-cli#1236 (closes LeanerCloud/cloud-commitments-cli#1171, COR-07) made AWS RI cost-field parsing fail loud in parseAWSCostDetails (providers/aws/recommendations/parser_ri.go). The same silent-swallow pattern still exists in two sibling code paths in the same package, on equally money-affecting fields:
1. Savings Plans parser (providers/aws/recommendations/parser_sp.go)
parseOptionalFloat (lines 182-192) silently returns 0 when strconv.ParseFloat fails, then is called for every money field on every SP recommendation:
func parseOptionalFloat(field string, s *string) float64 {
if s == nil { return 0 }
val, err := strconv.ParseFloat(*s, 64)
if err != nil {
log.Printf("WARNING: failed to parse %s: %v", field, err)
return 0 // <-- silent fallback to 0 on money path
}
return val
}Callers (parseSavingsPlanDetail, lines 202-221):
HourlyCommitmentToPurchase-> drivesRecurringMonthlyCost(× 730) andhourlyCommitmentfield for the recommendationEstimatedMonthlySavingsAmount->rec.EstimatedSavings(direct savings figure)EstimatedSavingsPercentage->rec.SavingsPercentageUpfrontCost->rec.CommitmentCostCurrentAverageHourlyOnDemandSpend->rec.OnDemandCost(× 730) — the denominator in effective-savings math (see issue fix(recommendations/aws): incorrect Effective Savings % — plumb on_demand_cost like Azure (#277) cloud-commitments-cli#303)EstimatedAverageUtilization-> utilization signal (non-money, "0 = no signal" semantics documented, so the silent fallback is intentional here)
Same shape as COR-07: an all-upfront SP with an unparseable UpfrontCost would surface as $0 upfront -> wrong money figure feeding effective-savings math and purchase decisions, with only a WARNING log.
2. RI utilization parser (providers/aws/recommendations/utilization.go)
parseFloat (lines 142-149) silently returns 0 on parse failure for Utilization.PurchasedHours, Utilization.TotalActualHours, Utilization.UnusedHours (lines 131-138). These accumulate into riAccumulator, then buildUtilizations divides:
pct = (a.totalActualHours / a.purchasedHours) * 100A parse failure on purchasedHours quietly understates utilization or yields 0%; a failure on totalActualHours understates the numerator. This corrupts the --target-coverage sizing input directly.
Why this is the same bug as COR-07
Memory feedback_no_silent_fallbacks.md ("no fallbacks/defaults/fabricated values on money paths; return an explicit error") covers this exact pattern. PR LeanerCloud/cloud-commitments-cli#1236 fixed RI cost fields; SP cost fields and RI utilization hours are equivalent money-path inputs in the same package.
Suggested fix
Mirror the parser_ri.go pattern: parseOptionalFloat and parseFloat return (float64, error). Callers either propagate to parseSavingsPlanDetail / GetRIUtilization (which then skip-with-warning, matching the existing RI skip in parseRecommendations for parseRecommendationDetail failures), or for utilization-signal-only fields (EstimatedAverageUtilization, AverageNumberOfInstancesUsedPerHour, AverageUtilization) keep the log-and-default semantics with a docstring noting "zero means no signal".
Acceptance
- Regression tests covering malformed
HourlyCommitmentToPurchase,EstimatedMonthlySavingsAmount,UpfrontCost,CurrentAverageHourlyOnDemandSpendfor SP; malformedPurchasedHours,TotalActualHoursfor utilization. Tests confirmed FAILING pre-fix, PASSING post-fix. - No
strconv.ParseFloat(..., 64); err == nil { rec.<money field> = v }patterns left inproviders/aws/recommendations/for money paths. - Consistent with COR-04: RI auto-exchange daily-cap check coerces unparseable/absent PaymentDue to $0 instead of failing loud cloud-commitments-cli#1166 (COR-04 PaymentDue) and COR-07: AWS RI parser silently zeroes unparseable upfront/on-demand/recurring cost fields cloud-commitments-cli#1171 (COR-07 RI cost fields).
Related
- COR-07: AWS RI parser silently zeroes unparseable upfront/on-demand/recurring cost fields cloud-commitments-cli#1171 (COR-07: RI cost fields) -- fixed by fix(providers/aws): fail loud on unparseable RI cost fields cloud-commitments-cli#1236
- COR-04: RI auto-exchange daily-cap check coerces unparseable/absent PaymentDue to $0 instead of failing loud cloud-commitments-cli#1166 (COR-04: PaymentDue silent zero) -- fixed by fix(exchange): fail closed on unparseable PaymentDue in daily-cap check cloud-commitments-cli#1229
- fix(recommendations/aws): incorrect Effective Savings % — plumb on_demand_cost like Azure (#277) cloud-commitments-cli#303 (effective savings % depends on OnDemandCost being correct)
- Memory:
feedback_no_silent_fallbacks.md,feedback_nullable_not_zero.md,feedback_strict_int_parse.md
Activity
cristim commentedon Jul 27, 2026
Verification sweep against
main(101f099fb) on 2026-07-27 finds this already resolved.parseOptionalFloat now returns an error and rejects unparseable, NaN, Inf and negative values.
Evidence: PRs LeanerCloud/cloud-commitments-cli#1455, LeanerCloud/cloud-commitments-cli#1461, LeanerCloud/cloud-commitments-cli#1473.
Recommending close.