Summary
Azure reservation recommendations surface only the All-Upfront variant; the Monthly (no-upfront) payment option is missing across the UI even though Azure supports both billing plans for every reservation SKU.
Repro
- Add an Azure subscription with reservation-eligible workloads (VMs, SQL DBs, Cache for Redis, etc).
- Open the Recommendations page filtered to provider=Azure.
- Every row shows
PaymentOption = "upfront". There is no corresponding no-upfront / monthly row for the same SKU/term.
Root cause
Every Azure recommendation emission path hardcodes PaymentOption: "upfront" and emits a single variant per (account, region, SKU, term). Confirmed call sites:
providers/azure/recommendations.go:144 (Azure Advisor adapter)
providers/azure/services/compute/client.go:496 (convertAzureVMRecommendation)
providers/azure/services/database/client.go:511
providers/azure/services/cache/client.go:504
- (likely)
providers/azure/services/cosmosdb/client.go, providers/azure/services/search/client.go, providers/azure/services/synapse/client.go, providers/azure/services/managedredis/client.go, providers/azure/services/savingsplans/client.go
pkg/common/types.go:101 already declares the canonical option set as "all-upfront, partial, no-upfront, monthly", so the schema supports the missing variants. Azure itself supports two billing plans per reservation: Upfront (one payment) and Monthly (twelve / thirty-six equal payments, no partial-upfront). Currently the converter emits a single hardcoded "upfront" value and never fans out.
Fix shape
For each Azure recommendation source, emit two common.Recommendation rows per (account, region, SKU, term):
PaymentOption = "all-upfront" (current behaviour, but canonical string instead of plain "upfront")
PaymentOption = "no-upfront" (Azure Monthly billing plan)
The two rows must carry separate pricing fields. Azure typically charges the same total reservation price for both plans, but per-period cashflow differs — UpfrontCost, RecurringMonthlyCost, and EstimatedSavings need to be computed per plan. The pricing helper should accept paymentOption and return the correct triple, mirroring the AWS service-clients' three-variant fan-out (all-upfront / partial / no-upfront).
Tests: extend each service client's table-driven recommendation test to assert that both all-upfront and no-upfront rows are produced per source row, and that UpfrontCost == 0 on the no-upfront variant.
Scope
Backend-only. Frontend already supports filtering by PaymentOption per the existing all-upfront rows, so additional rows will surface automatically through the same UI.
Summary
Azure reservation recommendations surface only the All-Upfront variant; the Monthly (no-upfront) payment option is missing across the UI even though Azure supports both billing plans for every reservation SKU.
Repro
PaymentOption = "upfront". There is no correspondingno-upfront/monthlyrow for the same SKU/term.Root cause
Every Azure recommendation emission path hardcodes
PaymentOption: "upfront"and emits a single variant per (account, region, SKU, term). Confirmed call sites:providers/azure/recommendations.go:144(Azure Advisor adapter)providers/azure/services/compute/client.go:496(convertAzureVMRecommendation)providers/azure/services/database/client.go:511providers/azure/services/cache/client.go:504providers/azure/services/cosmosdb/client.go,providers/azure/services/search/client.go,providers/azure/services/synapse/client.go,providers/azure/services/managedredis/client.go,providers/azure/services/savingsplans/client.gopkg/common/types.go:101already declares the canonical option set as"all-upfront, partial, no-upfront, monthly", so the schema supports the missing variants. Azure itself supports two billing plans per reservation: Upfront (one payment) and Monthly (twelve / thirty-six equal payments, no partial-upfront). Currently the converter emits a single hardcoded"upfront"value and never fans out.Fix shape
For each Azure recommendation source, emit two
common.Recommendationrows per (account, region, SKU, term):PaymentOption = "all-upfront"(current behaviour, but canonical string instead of plain"upfront")PaymentOption = "no-upfront"(Azure Monthly billing plan)The two rows must carry separate pricing fields. Azure typically charges the same total reservation price for both plans, but per-period cashflow differs —
UpfrontCost,RecurringMonthlyCost, andEstimatedSavingsneed to be computed per plan. The pricing helper should acceptpaymentOptionand return the correct triple, mirroring the AWS service-clients' three-variant fan-out (all-upfront / partial / no-upfront).Tests: extend each service client's table-driven recommendation test to assert that both
all-upfrontandno-upfrontrows are produced per source row, and thatUpfrontCost == 0on the no-upfront variant.Scope
Backend-only. Frontend already supports filtering by
PaymentOptionper the existing all-upfront rows, so additional rows will surface automatically through the same UI.