From e33a3bbe20e602c7a398c0c122e671d11e95b08c Mon Sep 17 00:00:00 2001 From: Cristian Magherusan-Stanciu Date: Thu, 28 May 2026 21:55:39 +0200 Subject: [PATCH 1/2] refactor(azure): consolidate AzureRetailPrice into shared pricing package (closes #594) Add pricing.RetailPriceItem (union of all per-service item fields) to providers/azure/internal/pricing/types.go and alias AzureRetailPrice to pricing.Page[pricing.RetailPriceItem] in all six service clients (compute, cache, database, cosmosdb, search, savingsplans) plus managedredis. Replace the three manual pagination loops in search, savingsplans, and managedredis with pricing.FetchAll calls. Remove the seven per-service struct definitions and their duplicated field lists. --- providers/azure/internal/pricing/types.go | 26 ++++++++ providers/azure/services/cache/client.go | 26 ++------ providers/azure/services/compute/client.go | 26 +++----- providers/azure/services/cosmosdb/client.go | 29 ++------- providers/azure/services/database/client.go | 32 ++-------- .../azure/services/managedredis/client.go | 21 ++---- .../azure/services/savingsplans/client.go | 19 +----- providers/azure/services/search/client.go | 64 +++---------------- .../azure/services/search/client_test.go | 14 +--- 9 files changed, 72 insertions(+), 185 deletions(-) create mode 100644 providers/azure/internal/pricing/types.go diff --git a/providers/azure/internal/pricing/types.go b/providers/azure/internal/pricing/types.go new file mode 100644 index 000000000..f554ad814 --- /dev/null +++ b/providers/azure/internal/pricing/types.go @@ -0,0 +1,26 @@ +package pricing + +// RetailPriceItem is the canonical item shape for the Azure Retail Prices API +// (https://prices.azure.com/api/retail/prices). It is the union of all +// fields used by the service clients in this repository; services that only +// need a subset can safely ignore the extra fields — they will decode to their +// zero value. +// +// Keeping a single named type here means every service uses the same +// pricing.FetchAll[pricing.RetailPriceItem] call and the same item accessors, +// so a field rename or addition only requires a single edit. +type RetailPriceItem struct { + CurrencyCode string `json:"currencyCode"` + RetailPrice float64 `json:"retailPrice"` + UnitPrice float64 `json:"unitPrice"` + ArmRegionName string `json:"armRegionName"` + Location string `json:"location"` + ProductName string `json:"productName"` + ServiceName string `json:"serviceName"` + ArmSKUName string `json:"armSkuName"` + SKUName string `json:"skuName"` + MeterName string `json:"meterName"` + UnitOfMeasure string `json:"unitOfMeasure"` + ReservationTerm string `json:"reservationTerm"` + Type string `json:"type"` +} diff --git a/providers/azure/services/cache/client.go b/providers/azure/services/cache/client.go index 5145fb5e8..3ba9d6030 100644 --- a/providers/azure/services/cache/client.go +++ b/providers/azure/services/cache/client.go @@ -134,26 +134,12 @@ func (c *CacheClient) GetRegion() string { return c.region } -// CacheRetailPriceItem is the Azure Retail Prices API item shape for -// Redis Cache. Lifted from the previous inline anonymous struct so it -// can serve as the type parameter to pricing.FetchAll. -type CacheRetailPriceItem struct { - CurrencyCode string `json:"currencyCode"` - RetailPrice float64 `json:"retailPrice"` - UnitPrice float64 `json:"unitPrice"` - ArmRegionName string `json:"armRegionName"` - ProductName string `json:"productName"` - ServiceName string `json:"serviceName"` - ArmSKUName string `json:"armSkuName"` - MeterName string `json:"meterName"` - ReservationTerm string `json:"reservationTerm"` - Type string `json:"type"` -} - -// AzureRetailPrice is the service-local envelope consumers still reference. -type AzureRetailPrice struct { - Items []CacheRetailPriceItem `json:"Items"` -} +// CacheRetailPriceItem is a type alias for pricing.RetailPriceItem kept for +// backward compatibility within this package. +type CacheRetailPriceItem = pricing.RetailPriceItem + +// AzureRetailPrice is the response envelope for the Azure Retail Prices API. +type AzureRetailPrice = pricing.Page[pricing.RetailPriceItem] // GetRecommendations gets Redis Cache reservation recommendations from Azure Consumption API func (c *CacheClient) GetRecommendations(ctx context.Context, params common.RecommendationParams) ([]common.Recommendation, error) { diff --git a/providers/azure/services/compute/client.go b/providers/azure/services/compute/client.go index 6ef720dcd..70a9a6d91 100644 --- a/providers/azure/services/compute/client.go +++ b/providers/azure/services/compute/client.go @@ -157,23 +157,15 @@ func (c *ComputeClient) GetRegion() string { return c.region } -// AzureRetailPrice represents pricing from Azure Retail Prices API -type AzureRetailPriceItem struct { - CurrencyCode string `json:"currencyCode"` - RetailPrice float64 `json:"retailPrice"` - UnitPrice float64 `json:"unitPrice"` - ArmRegionName string `json:"armRegionName"` - ProductName string `json:"productName"` - ServiceName string `json:"serviceName"` - ArmSKUName string `json:"armSkuName"` - ReservationTerm string `json:"reservationTerm"` - Type string `json:"type"` -} - -type AzureRetailPrice struct { - Items []AzureRetailPriceItem `json:"Items"` - NextPageLink string `json:"NextPageLink"` -} +// AzureRetailPriceItem is a type alias for pricing.RetailPriceItem kept for +// backward compatibility within this package. New code should use +// pricing.RetailPriceItem directly. +type AzureRetailPriceItem = pricing.RetailPriceItem + +// AzureRetailPrice is the response envelope for the Azure Retail Prices API. +// It wraps pricing.Page[pricing.RetailPriceItem] under a package-local name +// so existing call sites do not need to be updated. +type AzureRetailPrice = pricing.Page[pricing.RetailPriceItem] // GetRecommendations gets VM RI recommendations from Azure Consumption API func (c *ComputeClient) GetRecommendations(ctx context.Context, params common.RecommendationParams) ([]common.Recommendation, error) { diff --git a/providers/azure/services/cosmosdb/client.go b/providers/azure/services/cosmosdb/client.go index 5c8cad242..c20792d74 100644 --- a/providers/azure/services/cosmosdb/client.go +++ b/providers/azure/services/cosmosdb/client.go @@ -125,29 +125,12 @@ func (c *CosmosDBClient) GetRegion() string { return c.region } -// CosmosRetailPriceItem is the Azure Retail Prices API item shape for -// Cosmos DB. Lifted from the previous inline anonymous struct so it can -// serve as the type parameter to pricing.FetchAll. -type CosmosRetailPriceItem struct { - CurrencyCode string `json:"currencyCode"` - RetailPrice float64 `json:"retailPrice"` - UnitPrice float64 `json:"unitPrice"` - ArmRegionName string `json:"armRegionName"` - Location string `json:"location"` - MeterName string `json:"meterName"` - SKUName string `json:"skuName"` - ProductName string `json:"productName"` - ServiceName string `json:"serviceName"` - UnitOfMeasure string `json:"unitOfMeasure"` - Type string `json:"type"` - ArmSKUName string `json:"armSkuName"` - ReservationTerm string `json:"reservationTerm"` -} - -// AzureRetailPrice is the service-local envelope consumers still reference. -type AzureRetailPrice struct { - Items []CosmosRetailPriceItem `json:"Items"` -} +// CosmosRetailPriceItem is a type alias for pricing.RetailPriceItem kept for +// backward compatibility within this package. +type CosmosRetailPriceItem = pricing.RetailPriceItem + +// AzureRetailPrice is the response envelope for the Azure Retail Prices API. +type AzureRetailPrice = pricing.Page[pricing.RetailPriceItem] // GetRecommendations gets Cosmos DB reservation recommendations from Azure Consumption API func (c *CosmosDBClient) GetRecommendations(ctx context.Context, params common.RecommendationParams) ([]common.Recommendation, error) { diff --git a/providers/azure/services/database/client.go b/providers/azure/services/database/client.go index 28842f4c1..a6642ed40 100644 --- a/providers/azure/services/database/client.go +++ b/providers/azure/services/database/client.go @@ -127,32 +127,12 @@ func (c *DatabaseClient) GetRegion() string { return c.region } -// DatabaseRetailPriceItem is the Azure Retail Prices API item shape for -// SQL Database products. Lifted from the previous inline anonymous -// struct so it can serve as the type parameter to pricing.FetchAll. -type DatabaseRetailPriceItem struct { - CurrencyCode string `json:"currencyCode"` - RetailPrice float64 `json:"retailPrice"` - UnitPrice float64 `json:"unitPrice"` - ArmRegionName string `json:"armRegionName"` - Location string `json:"location"` - MeterName string `json:"meterName"` - SKUName string `json:"skuName"` - ProductName string `json:"productName"` - ServiceName string `json:"serviceName"` - UnitOfMeasure string `json:"unitOfMeasure"` - Type string `json:"type"` - ArmSKUName string `json:"armSkuName"` - ReservationTerm string `json:"reservationTerm"` -} - -// AzureRetailPrice is the service-local envelope consumers still reference. -// The shared pricing walker produces the items slice; this wrapper lets -// the rest of the package keep its `*AzureRetailPrice` idiom without a -// wider rename. -type AzureRetailPrice struct { - Items []DatabaseRetailPriceItem `json:"Items"` -} +// DatabaseRetailPriceItem is a type alias for pricing.RetailPriceItem kept +// for backward compatibility within this package. +type DatabaseRetailPriceItem = pricing.RetailPriceItem + +// AzureRetailPrice is the response envelope for the Azure Retail Prices API. +type AzureRetailPrice = pricing.Page[pricing.RetailPriceItem] // GetRecommendations gets SQL Database reservation recommendations from Azure Consumption API func (c *DatabaseClient) GetRecommendations(ctx context.Context, params common.RecommendationParams) ([]common.Recommendation, error) { diff --git a/providers/azure/services/managedredis/client.go b/providers/azure/services/managedredis/client.go index 809cdbf15..3554f3ae9 100644 --- a/providers/azure/services/managedredis/client.go +++ b/providers/azure/services/managedredis/client.go @@ -111,21 +111,8 @@ func (c *ManagedRedisClient) GetRegion() string { return c.region } -// redisPriceItem is a single record from the Azure Retail Prices API used by -// pricing.FetchAll as the type parameter T. Named so it can satisfy the -// constraint; the anonymous struct in AzureRetailPrice was the blocker. -type redisPriceItem struct { - CurrencyCode string `json:"currencyCode"` - RetailPrice float64 `json:"retailPrice"` - UnitPrice float64 `json:"unitPrice"` - ArmRegionName string `json:"armRegionName"` - ProductName string `json:"productName"` - ServiceName string `json:"serviceName"` - ArmSKUName string `json:"armSkuName"` - MeterName string `json:"meterName"` - ReservationTerm string `json:"reservationTerm"` - Type string `json:"type"` -} +// AzureRetailPrice is the response envelope for the Azure Retail Prices API. +type AzureRetailPrice = pricing.Page[pricing.RetailPriceItem] // GetRecommendations gets Redis Cache reservation recommendations from the Azure Consumption API. func (c *ManagedRedisClient) GetRecommendations(ctx context.Context, params common.RecommendationParams) ([]common.Recommendation, error) { @@ -458,7 +445,7 @@ func (c *ManagedRedisClient) getRedisPricing(ctx context.Context, sku, region st initialURL := "https://prices.azure.com/api/retail/prices?" + params.Encode() - items, err := pricing.FetchAll[redisPriceItem](ctx, c.httpClient, initialURL, pricing.DefaultPageTimeout, pricing.DefaultMaxPages) + items, err := pricing.FetchAll[pricing.RetailPriceItem](ctx, c.httpClient, initialURL, pricing.DefaultPageTimeout, pricing.DefaultMaxPages) if err != nil { return nil, fmt.Errorf("failed to fetch Redis Cache pricing: %w", err) } @@ -501,7 +488,7 @@ func azureTermString(termYears int) string { // parsePriceItems extracts on-demand price, reservation price, and currency // from the flat list returned by the Azure Retail Prices API. -func parsePriceItems(items []redisPriceItem, termYears int) (onDemand, reservation float64, currency string) { +func parsePriceItems(items []pricing.RetailPriceItem, termYears int) (onDemand, reservation float64, currency string) { currency = "USD" termStr := azureTermString(termYears) for _, item := range items { diff --git a/providers/azure/services/savingsplans/client.go b/providers/azure/services/savingsplans/client.go index 4b1b28a66..c40cdc50d 100644 --- a/providers/azure/services/savingsplans/client.go +++ b/providers/azure/services/savingsplans/client.go @@ -388,21 +388,8 @@ func checkValidateResponse(resp armbillingbenefits.RPClientValidatePurchaseRespo return nil } -// savingsPlanPriceItem is a single record from the Azure Retail Prices API used -// by pricing.FetchAll as the type parameter T (requires a named type, not an -// anonymous struct). NOTE: this struct mirrors the one in services/compute and -// services/search; consolidating into internal/pricing is tracked as a follow-up. -type savingsPlanPriceItem struct { - CurrencyCode string `json:"currencyCode"` - RetailPrice float64 `json:"retailPrice"` - UnitPrice float64 `json:"unitPrice"` - ArmRegionName string `json:"armRegionName"` - ProductName string `json:"productName"` - ServiceName string `json:"serviceName"` - ArmSKUName string `json:"armSkuName"` - ReservationTerm string `json:"reservationTerm"` - Type string `json:"type"` -} +// AzureRetailPrice is the response envelope for the Azure Retail Prices API. +type AzureRetailPrice = pricing.Page[pricing.RetailPriceItem] // GetOfferingDetails retrieves pricing details for an Azure Savings Plan offering. func (c *Client) GetOfferingDetails(ctx context.Context, rec common.Recommendation) (*common.OfferingDetails, error) { @@ -475,7 +462,7 @@ func (c *Client) fetchOnDemandRate(ctx context.Context, planType string) (float6 params.Add("api-version", "2023-01-01-preview") initialURL := "https://prices.azure.com/api/retail/prices?" + params.Encode() - items, err := pricing.FetchAll[savingsPlanPriceItem](ctx, c.httpClient, initialURL, pricing.DefaultPageTimeout, pricing.DefaultMaxPages) + items, err := pricing.FetchAll[pricing.RetailPriceItem](ctx, c.httpClient, initialURL, pricing.DefaultPageTimeout, pricing.DefaultMaxPages) if err != nil { return 0, fmt.Errorf("failed to fetch on-demand rate for plan type %s: %w", planType, err) } diff --git a/providers/azure/services/search/client.go b/providers/azure/services/search/client.go index 07dce3006..a1be559ec 100644 --- a/providers/azure/services/search/client.go +++ b/providers/azure/services/search/client.go @@ -5,7 +5,6 @@ import ( "context" "encoding/json" "fmt" - "io" "log" "net/http" "net/url" @@ -19,6 +18,7 @@ import ( "github.com/LeanerCloud/CUDly/pkg/common" "github.com/LeanerCloud/CUDly/providers/azure/internal/httpclient" + "github.com/LeanerCloud/CUDly/providers/azure/internal/pricing" azrecs "github.com/LeanerCloud/CUDly/providers/azure/internal/recommendations" "github.com/LeanerCloud/CUDly/providers/azure/services/internal/reservations" ) @@ -111,23 +111,8 @@ func (c *SearchClient) GetRegion() string { return c.region } -// AzureRetailPrice represents pricing information from Azure Retail Prices API -type AzureRetailPrice struct { - Items []struct { - CurrencyCode string `json:"currencyCode"` - RetailPrice float64 `json:"retailPrice"` - UnitPrice float64 `json:"unitPrice"` - ArmRegionName string `json:"armRegionName"` - ProductName string `json:"productName"` - ServiceName string `json:"serviceName"` - ArmSKUName string `json:"armSkuName"` - MeterName string `json:"meterName"` - ReservationTerm string `json:"reservationTerm"` - Type string `json:"type"` - } `json:"Items"` - NextPageLink string `json:"NextPageLink"` - Count int `json:"Count"` -} +// AzureRetailPrice is the response envelope for the Azure Retail Prices API. +type AzureRetailPrice = pricing.Page[pricing.RetailPriceItem] // GetRecommendations gets Azure Search reservation recommendations from Azure Consumption API func (c *SearchClient) GetRecommendations(ctx context.Context, params common.RecommendationParams) ([]common.Recommendation, error) { @@ -520,37 +505,19 @@ func (c *SearchClient) getSearchPricing(ctx context.Context, sku, region string, }, nil } -// fetchAzurePricing fetches pricing data from Azure Retail Prices API +// fetchAzurePricing fetches all pages of pricing data from the Azure Retail +// Prices API for the given OData filter expression. func (c *SearchClient) fetchAzurePricing(ctx context.Context, filter string) (*AzureRetailPrice, error) { - baseURL := "https://prices.azure.com/api/retail/prices" params := url.Values{} params.Add("$filter", filter) params.Add("api-version", "2023-01-01-preview") + initialURL := "https://prices.azure.com/api/retail/prices?" + params.Encode() - fullURL := baseURL + "?" + params.Encode() - - req, err := http.NewRequestWithContext(ctx, "GET", fullURL, nil) - if err != nil { - return nil, fmt.Errorf("failed to create request: %w", err) - } - - resp, err := c.httpClient.Do(req) + items, err := pricing.FetchAll[pricing.RetailPriceItem](ctx, c.httpClient, initialURL, pricing.DefaultPageTimeout, pricing.DefaultMaxPages) if err != nil { - return nil, fmt.Errorf("failed to call pricing API: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - body, _ := io.ReadAll(resp.Body) - return nil, fmt.Errorf("pricing API returned status %d: %s", resp.StatusCode, string(body)) - } - - var priceData AzureRetailPrice - if err := json.NewDecoder(resp.Body).Decode(&priceData); err != nil { - return nil, fmt.Errorf("failed to decode pricing response: %w", err) + return nil, err } - - return &priceData, nil + return &AzureRetailPrice{Items: items}, nil } // azureTermString returns the Retail Prices API ReservationTerm string for the @@ -564,18 +531,7 @@ func azureTermString(termYears int) string { } // extractSearchPricing extracts on-demand and reservation pricing from price items -func extractSearchPricing(items []struct { - CurrencyCode string `json:"currencyCode"` - RetailPrice float64 `json:"retailPrice"` - UnitPrice float64 `json:"unitPrice"` - ArmRegionName string `json:"armRegionName"` - ProductName string `json:"productName"` - ServiceName string `json:"serviceName"` - ArmSKUName string `json:"armSkuName"` - MeterName string `json:"meterName"` - ReservationTerm string `json:"reservationTerm"` - Type string `json:"type"` -}, termYears int) (onDemand, reservation float64, currency string) { +func extractSearchPricing(items []pricing.RetailPriceItem, termYears int) (onDemand, reservation float64, currency string) { currency = "USD" termStr := azureTermString(termYears) diff --git a/providers/azure/services/search/client_test.go b/providers/azure/services/search/client_test.go index 766570204..ab32c741c 100644 --- a/providers/azure/services/search/client_test.go +++ b/providers/azure/services/search/client_test.go @@ -19,6 +19,7 @@ import ( "github.com/stretchr/testify/require" "github.com/LeanerCloud/CUDly/pkg/common" + "github.com/LeanerCloud/CUDly/providers/azure/internal/pricing" "github.com/LeanerCloud/CUDly/providers/azure/mocks" ) @@ -374,18 +375,7 @@ func TestSearchClient_GetExistingCommitments_Empty(t *testing.T) { func TestAzureRetailPriceStructure(t *testing.T) { price := AzureRetailPrice{ Count: 2, - Items: []struct { - CurrencyCode string `json:"currencyCode"` - RetailPrice float64 `json:"retailPrice"` - UnitPrice float64 `json:"unitPrice"` - ArmRegionName string `json:"armRegionName"` - ProductName string `json:"productName"` - ServiceName string `json:"serviceName"` - ArmSKUName string `json:"armSkuName"` - MeterName string `json:"meterName"` - ReservationTerm string `json:"reservationTerm"` - Type string `json:"type"` - }{ + Items: []pricing.RetailPriceItem{ { CurrencyCode: "USD", RetailPrice: 500.0, From d2689f349294378b4912fc2af2dcacb9226ba5ff Mon Sep 17 00:00:00 2001 From: Cristian Magherusan-Stanciu Date: Fri, 19 Jun 2026 23:32:03 +0200 Subject: [PATCH 2/2] fix(azure/pricing): remove intermediate aliases, fix em-dash and fieldalignment - Remove CacheRetailPriceItem and DatabaseRetailPriceItem type aliases that triggered revive var-naming; replace all call sites with pricing.RetailPriceItem directly and add azpricing import alias to test files that shadow "pricing" as a local variable name - Fix em-dash and double-space in RetailPriceItem doc comment (types.go) - Reorder struct fields for optimal alignment per betteralign (RetailPriceItem: 184->168 bytes, Page[T]: 32->24 pointer bytes) --- providers/azure/internal/pricing/retail_prices.go | 2 +- providers/azure/internal/pricing/types.go | 8 ++++---- providers/azure/services/cache/client.go | 8 ++------ providers/azure/services/cache/client_test.go | 7 ++++--- providers/azure/services/database/client.go | 8 ++------ providers/azure/services/database/client_test.go | 3 ++- 6 files changed, 15 insertions(+), 21 deletions(-) diff --git a/providers/azure/internal/pricing/retail_prices.go b/providers/azure/internal/pricing/retail_prices.go index 5ee265f67..e71acdb59 100644 --- a/providers/azure/internal/pricing/retail_prices.go +++ b/providers/azure/internal/pricing/retail_prices.go @@ -32,8 +32,8 @@ type HTTPClient interface { // type in their own package so the JSON decode produces values the caller // already knows how to read. type Page[T any] struct { - Items []T `json:"Items"` NextPageLink string `json:"NextPageLink"` + Items []T `json:"Items"` Count int `json:"Count"` } diff --git a/providers/azure/internal/pricing/types.go b/providers/azure/internal/pricing/types.go index f554ad814..0959f36c7 100644 --- a/providers/azure/internal/pricing/types.go +++ b/providers/azure/internal/pricing/types.go @@ -1,9 +1,9 @@ package pricing // RetailPriceItem is the canonical item shape for the Azure Retail Prices API -// (https://prices.azure.com/api/retail/prices). It is the union of all +// (https://prices.azure.com/api/retail/prices). It is the union of all // fields used by the service clients in this repository; services that only -// need a subset can safely ignore the extra fields — they will decode to their +// need a subset can safely ignore the extra fields; they will decode to their // zero value. // // Keeping a single named type here means every service uses the same @@ -11,8 +11,6 @@ package pricing // so a field rename or addition only requires a single edit. type RetailPriceItem struct { CurrencyCode string `json:"currencyCode"` - RetailPrice float64 `json:"retailPrice"` - UnitPrice float64 `json:"unitPrice"` ArmRegionName string `json:"armRegionName"` Location string `json:"location"` ProductName string `json:"productName"` @@ -23,4 +21,6 @@ type RetailPriceItem struct { UnitOfMeasure string `json:"unitOfMeasure"` ReservationTerm string `json:"reservationTerm"` Type string `json:"type"` + RetailPrice float64 `json:"retailPrice"` + UnitPrice float64 `json:"unitPrice"` } diff --git a/providers/azure/services/cache/client.go b/providers/azure/services/cache/client.go index 3ba9d6030..1e5cdb111 100644 --- a/providers/azure/services/cache/client.go +++ b/providers/azure/services/cache/client.go @@ -134,10 +134,6 @@ func (c *CacheClient) GetRegion() string { return c.region } -// CacheRetailPriceItem is a type alias for pricing.RetailPriceItem kept for -// backward compatibility within this package. -type CacheRetailPriceItem = pricing.RetailPriceItem - // AzureRetailPrice is the response envelope for the Azure Retail Prices API. type AzureRetailPrice = pricing.Page[pricing.RetailPriceItem] @@ -576,7 +572,7 @@ func (c *CacheClient) fetchAzurePricing(ctx context.Context, serviceName, sku, r params.Add("api-version", "2023-01-01-preview") initialURL := "https://prices.azure.com/api/retail/prices?" + params.Encode() - items, err := pricing.FetchAll[CacheRetailPriceItem](ctx, c.httpClient, initialURL, pricing.DefaultPageTimeout, pricing.DefaultMaxPages) + items, err := pricing.FetchAll[pricing.RetailPriceItem](ctx, c.httpClient, initialURL, pricing.DefaultPageTimeout, pricing.DefaultMaxPages) if err != nil { return nil, err } @@ -594,7 +590,7 @@ func azureTermString(termYears int) string { } // extractRedisPricing extracts on-demand and reservation pricing from price items -func extractRedisPricing(items []CacheRetailPriceItem, termYears int) (onDemand, reservation float64, currency string) { +func extractRedisPricing(items []pricing.RetailPriceItem, termYears int) (onDemand, reservation float64, currency string) { currency = "USD" termStr := azureTermString(termYears) diff --git a/providers/azure/services/cache/client_test.go b/providers/azure/services/cache/client_test.go index 14397c747..f75cbe5c8 100644 --- a/providers/azure/services/cache/client_test.go +++ b/providers/azure/services/cache/client_test.go @@ -20,6 +20,7 @@ import ( "github.com/stretchr/testify/require" "github.com/LeanerCloud/CUDly/pkg/common" + azpricing "github.com/LeanerCloud/CUDly/providers/azure/internal/pricing" "github.com/LeanerCloud/CUDly/providers/azure/mocks" ) @@ -223,7 +224,7 @@ func TestCacheClient_ValidateOffering_InvalidSKU(t *testing.T) { func TestAzureRetailPriceStructure(t *testing.T) { price := AzureRetailPrice{ - Items: []CacheRetailPriceItem{ + Items: []azpricing.RetailPriceItem{ { CurrencyCode: "USD", RetailPrice: 100.0, @@ -1293,7 +1294,7 @@ func TestCacheClient_GetValidResourceTypes_PageCapFires(t *testing.T) { // skipped and reservationPrice remained 0, causing a false "no reservation // pricing found" error even when the pricing row was present. func TestExtractRedisPricing_SingularOneYear(t *testing.T) { - items := []CacheRetailPriceItem{ + items := []azpricing.RetailPriceItem{ { CurrencyCode: "USD", RetailPrice: 0.68, @@ -1318,7 +1319,7 @@ func TestExtractRedisPricing_SingularOneYear(t *testing.T) { // TestExtractRedisPricing_PluralThreeYears verifies that the plural form // "3 Years" continues to work for multi-year terms. func TestExtractRedisPricing_PluralThreeYears(t *testing.T) { - items := []CacheRetailPriceItem{ + items := []azpricing.RetailPriceItem{ {CurrencyCode: "USD", RetailPrice: 0.68, UnitPrice: 0.68, Type: "Consumption"}, {CurrencyCode: "USD", RetailPrice: 11000.0, ReservationTerm: "3 Years", Type: "Reservation"}, } diff --git a/providers/azure/services/database/client.go b/providers/azure/services/database/client.go index a6642ed40..dd2f9630e 100644 --- a/providers/azure/services/database/client.go +++ b/providers/azure/services/database/client.go @@ -127,10 +127,6 @@ func (c *DatabaseClient) GetRegion() string { return c.region } -// DatabaseRetailPriceItem is a type alias for pricing.RetailPriceItem kept -// for backward compatibility within this package. -type DatabaseRetailPriceItem = pricing.RetailPriceItem - // AzureRetailPrice is the response envelope for the Azure Retail Prices API. type AzureRetailPrice = pricing.Page[pricing.RetailPriceItem] @@ -555,7 +551,7 @@ func (c *DatabaseClient) fetchAzurePricing(ctx context.Context, filter string) ( params.Add("api-version", "2023-01-01-preview") initialURL := "https://prices.azure.com/api/retail/prices?" + params.Encode() - items, err := pricing.FetchAll[DatabaseRetailPriceItem](ctx, c.httpClient, initialURL, pricing.DefaultPageTimeout, pricing.DefaultMaxPages) + items, err := pricing.FetchAll[pricing.RetailPriceItem](ctx, c.httpClient, initialURL, pricing.DefaultPageTimeout, pricing.DefaultMaxPages) if err != nil { return nil, err } @@ -573,7 +569,7 @@ func azureTermString(termYears int) string { } // extractSQLPricing extracts on-demand and reservation pricing from price items -func extractSQLPricing(items []DatabaseRetailPriceItem, termYears int) (onDemand, reservation float64, currency string) { +func extractSQLPricing(items []pricing.RetailPriceItem, termYears int) (onDemand, reservation float64, currency string) { currency = "USD" termStr := azureTermString(termYears) diff --git a/providers/azure/services/database/client_test.go b/providers/azure/services/database/client_test.go index a2d9f2a84..b66915fbf 100644 --- a/providers/azure/services/database/client_test.go +++ b/providers/azure/services/database/client_test.go @@ -20,6 +20,7 @@ import ( "github.com/stretchr/testify/require" "github.com/LeanerCloud/CUDly/pkg/common" + azpricing "github.com/LeanerCloud/CUDly/providers/azure/internal/pricing" "github.com/LeanerCloud/CUDly/providers/azure/mocks" ) @@ -213,7 +214,7 @@ func TestSQLPricingStructure(t *testing.T) { func TestAzureRetailPriceStructure(t *testing.T) { price := AzureRetailPrice{ - Items: []DatabaseRetailPriceItem{ + Items: []azpricing.RetailPriceItem{ { CurrencyCode: "USD", RetailPrice: 500.0,