From 57aa870be86984dc105ff2dc0afdb4b67ad0efe4 Mon Sep 17 00:00:00 2001 From: Sebastien Tardif Date: Wed, 13 May 2026 14:38:56 -0700 Subject: [PATCH 1/2] cost-analysis-export: Fix resource leaks and apply timeout Apply the configured EXPORT_TIMEOUT via context.WithTimeout so the entire pipeline has a deadline. Replace http.DefaultClient with a client that has a 2-minute per-request timeout. Close and remove the temp file returned by JoinData in joinAndExportData. Introduced in #5393 (2025-10-24). Signed-off-by: Sebastien Tardif --- examples/cost-analysis-export/main.go | 40 ++++++++++++++++----------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/examples/cost-analysis-export/main.go b/examples/cost-analysis-export/main.go index 9194176bc..54c583ad8 100644 --- a/examples/cost-analysis-export/main.go +++ b/examples/cost-analysis-export/main.go @@ -124,6 +124,9 @@ func ConfigureAndProcess(ctx context.Context, operation string) error { return fmt.Errorf("invalid config: %w", err) } + ctx, cancel := context.WithTimeout(ctx, cfg.Timeout) + defer cancel() + app, err := NewApp(cfg) if err != nil { return err @@ -182,8 +185,9 @@ func (a *App) Export(ctx context.Context) error { return fmt.Errorf("creating request: %w", err) } - // Execute request - resp, err := http.DefaultClient.Do(req) + // Execute request with timeout + httpClient := &http.Client{Timeout: 2 * time.Minute} + resp, err := httpClient.Do(req) if err != nil { return fmt.Errorf("failed to reach cost analysis service: %w", err) } @@ -461,6 +465,10 @@ func (a *App) joinAndExportData(ctx context.Context) error { if err != nil { return fmt.Errorf("joining data: %w", err) } + defer func() { + _ = file.Close() + _ = os.Remove(file.Name()) + }() slog.Info("data joined successfully", "file", file.Name()) contentType := "text/csv" @@ -648,21 +656,21 @@ func stripBOMReader(r io.Reader) io.Reader { // - MCA: https://learn.microsoft.com/en-us/azure/cost-management-billing/dataset-schema/cost-usage-details-mca // - FOCUS: https://learn.microsoft.com/en-us/azure/cost-management-billing/dataset-schema/cost-usage-details-focus var columnsToMultiply = map[string]bool{ - "PreTaxCost": true, - "CostInBillingCurrency": true, - "costInBillingCurrency": true, - "BilledCost": true, - "EffectiveCost": true, - "ListCost": true, - "ContractedCost": true, - "costInUsd": true, + "PreTaxCost": true, + "CostInBillingCurrency": true, + "costInBillingCurrency": true, + "BilledCost": true, + "EffectiveCost": true, + "ListCost": true, + "ContractedCost": true, + "costInUsd": true, "paygCostInBillingCurrency": true, - "paygCostInUsd": true, - "UsageQuantity": true, - "Quantity": true, - "quantity": true, - "ConsumedQuantity": true, - "PricingQuantity": true, + "paygCostInUsd": true, + "UsageQuantity": true, + "Quantity": true, + "quantity": true, + "ConsumedQuantity": true, + "PricingQuantity": true, } // resourceIDColumns lists possible column names for resource ID. From 7e163175c57507291d62c618f43930cdf3dbb63d Mon Sep 17 00:00:00 2001 From: Sebastien Tardif Date: Thu, 14 May 2026 08:14:57 -0700 Subject: [PATCH 2/2] Remove redundant HTTP client timeout, rely on context The request already uses NewRequestWithContext(ctx) which carries cfg.Timeout. A separate http.Client{Timeout: 2m} creates a second undocumented timeout knob. Use DefaultClient and let the context deadline handle cancellation. Signed-off-by: Sebastien Tardif --- examples/cost-analysis-export/main.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/cost-analysis-export/main.go b/examples/cost-analysis-export/main.go index 54c583ad8..53876bddf 100644 --- a/examples/cost-analysis-export/main.go +++ b/examples/cost-analysis-export/main.go @@ -185,9 +185,8 @@ func (a *App) Export(ctx context.Context) error { return fmt.Errorf("creating request: %w", err) } - // Execute request with timeout - httpClient := &http.Client{Timeout: 2 * time.Minute} - resp, err := httpClient.Do(req) + // Execute request (timeout inherited from ctx) + resp, err := http.DefaultClient.Do(req) if err != nil { return fmt.Errorf("failed to reach cost analysis service: %w", err) }