Skip to content

Azure Advisor recommendations silently dropped due to wrong impactedField mapping #6

@rocketri

Description

@rocketri

Summary

Azure Advisor recommendations are never surfaced in CUDly for any Azure subscription. The account test passes and credentials are valid, but the recommendations page always shows 0 after a refresh.

Verified test environment:

  • Subscription: 4c45d0c3-42d9-439a-a0fe-333f6837e837
  • az advisor recommendation list --category Cost returns 11 recommendations (9 VM reservations, 2 Savings Plans)
  • CUDly account test: ✅ passes
  • CUDly recommendations after refresh: ❌ 0

Root Cause

extractServiceType() in providers/azure/recommendations.go (line 171) maps service type by inspecting ImpactedField:

switch {
case contains(impactedField, "Microsoft.Compute"):
case contains(impactedField, "Microsoft.Sql"):
case contains(impactedField, "Microsoft.Cache"):
default:
    return ""  // ← ALL subscription-scoped recommendations fall here
}

Azure Advisor reservation and savings plan recommendations set ImpactedField to "Microsoft.Subscriptions/subscriptions", not a resource-specific namespace. This means extractServiceType always returns "", convertAdvisorRecommendation returns nil (line 134), and every Azure Advisor recommendation is silently discarded.

All 11 recommendations in the test subscription have:

"impactedField": "Microsoft.Subscriptions/subscriptions"

Fix

The actual service type is available in ExtendedProperties:

"extendedProperties": {
  "reservedResourceType": "virtualmachines",
  "recommendationSubCategory": "Reservations",
  "sku": "Standard_D2as_v4"
}

extractServiceType should fall back to ExtendedProperties["reservedResourceType"] when ImpactedField doesn't match a known resource namespace:

if rec.Properties.ExtendedProperties != nil {
    if rrt, ok := rec.Properties.ExtendedProperties["reservedResourceType"]; ok && rrt != nil {
        switch strings.ToLower(*rrt) {
        case "virtualmachines":
            return string(common.ServiceCompute)
        case "sqldatabases":
            return string(common.ServiceRelationalDB)
        case "rediscache":
            return string(common.ServiceCache)
        }
    }
    if subcat, ok := rec.Properties.ExtendedProperties["recommendationSubCategory"]; ok && subcat != nil {
        if strings.EqualFold(*subcat, "SavingsPlan") {
            return string(common.ServiceCompute) // or a dedicated ServiceSavingsPlan type
        }
    }
}

Additional Issues in the Same File

  1. annualSavingsAmount is never parsed (lines 158–161) — comment says "for now just note it's available". rec.EstimatedSavings is always 0 for all Azure Advisor recommendations even if the fix above is applied.
  2. Pagination errors silently break the loop (line 104–105) — bare break with no log or error propagation.
  3. Advisor errors silently suppressed (line 66) — if err == nil pattern means failures produce 0 recommendations with no signal to the caller or logs.

Expected Behavior

After the fix to extractServiceType, the 11 recommendations currently visible in Azure Advisor should appear in CUDly after a refresh.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions