Skip to content

Comments

feat(azure): migrate to Track 2 SDK enabling 11+ new services#710

Merged
ehsandeep merged 11 commits intodevfrom
feat-azure-integrations
Oct 14, 2025
Merged

feat(azure): migrate to Track 2 SDK enabling 11+ new services#710
ehsandeep merged 11 commits intodevfrom
feat-azure-integrations

Conversation

@tarunKoyalwar
Copy link
Member

@tarunKoyalwar tarunKoyalwar commented Oct 14, 2025

Summary

Migrates Azure provider from deprecated Track 1 SDK to modern Track 2 SDK, enabling implementation of 11+ additional Azure services while maintaining 100% backward compatibility.

Fixes #711

What changed?

  • ⚠️ Deprecated Track 1 SDK, since September 2023 - no security patches
  • 📦 Added new services: 11+ services
    • Storage Accounts
    • Azure Kubernetes Service
    • CDN
    • DNS
    • Application Gateway/Load Balancer
    • Container Instances
    • App Service/Web Apps
    • Azure Functions + API Management
    • Front Door
    • Container Apps (Track 2 only)
    • Static Web Apps (Track 2 only)

Track 2 Migration:

  • ✅ Actively maintained with security updates
  • ✅ Foundation for 11+ new services (Storage, AKS, CDN, DNS, App Gateway, Container Instances, App Service, Functions, Front Door, Container Apps, Static Web Apps)
  • ✅ 7 authentication methods (covers 99% of real-world use cases)
  • ✅ Modern API patterns (pagers, better error handling)

Authentication Enhancements

Supported Methods (all backward compatible)

  1. Azure CLI (use_cli_auth: true) - local dev, Track 1 compatible
  2. Client Secret (tenant/client ID + secret) - CI/CD, Track 1 compatible
  3. Client Certificate (certificate_path) - enterprise security
  4. Managed Identity (use_managed_identity) - Azure VMs, AKS, Container Apps
  5. Workload Identity (use_workload_identity) - Kubernetes, GitHub Actions OIDC
  6. Environment Variables - via DefaultAzureCredential
  7. DefaultAzureCredential - smart auto-detection fallback

Smart Default Behavior

# Existing configs - work exactly as before
- provider: azure
  use_cli_auth: true  # Explicit: ONLY tries Azure CLI

- provider: azure
  client_id: xxx
  client_secret: yyy
  tenant_id: zzz      # Explicit: ONLY tries client secret

# New: minimal config with auto-detection
- provider: azure
  subscription_id: xxx  # No explicit auth → tries env, workload, managed identity, CLI

Implementation Details

Core Changes:

  • New auth.go - comprehensive authentication system
  • azure.go - replaced autorest.Authorizer with azcore.TokenCredential
  • Service files - updated to Track 2 client patterns (pagers vs iterators)
  • Dependencies - added Track 2 SDKs

Testing:

✅ Builds successfully
✅ CLI auth verified (az login)
✅ Subscription discovery working
✅ Resource enumeration tested (3 IPs + 1 Traffic Manager)

Migration Impact

For Existing Users:

  • ✅ No config changes required
  • ✅ All current authentication methods work identically
  • ✅ No breaking changes

For New Services:

  • Ready to implement 11+ additional services in future PRs

🤖 Generated with Claude Code

Co-Authored-By: Claude [email protected]

Summary by CodeRabbit

  • New Features
    • Added discovery for many Azure services (AKS, API Management, Application Gateway, App Service, CDN, Container Apps, Container Instances, DNS zones/records, Front Door, Functions, Load Balancer, Static Web Apps, Storage, Traffic Manager, Public IPs) emitting DNS/IP resources with optional enriched metadata.
  • Refactor
    • Migrated Azure integration to the newer credential-based SDK and pager patterns for more reliable authentication and resource listing.
  • Documentation / Chores
    • Added contributor guidance (CLAUDE.md) and updated ignore rules.

Migrates Azure provider from deprecated Track 1 SDK to modern Track 2 SDK
while maintaining 100% backward compatibility with existing configurations.

Key Changes:
- Replaced autorest.Authorizer with azcore.TokenCredential
- Implemented comprehensive authentication system supporting 7 methods
- Updated all service implementations (VM, Public IPs, Traffic Manager)
- Subscription discovery now uses Track 2 pager pattern

Authentication Methods (all backward compatible):
1. Azure CLI (use_cli_auth: true) - explicit, Track 1 compatible
2. Client Secret (tenant_id/client_id/client_secret) - Track 1 compatible
3. Client Certificate (certificate_path) - new, enterprise security
4. Managed Identity (use_managed_identity: true) - new, Azure-hosted apps
5. Workload Identity (use_workload_identity: true) - new, K8s/GitHub Actions
6. DefaultAzureCredential - new, auto-detection fallback when no explicit auth

Smart Default Behavior:
- Explicit auth methods (use_cli_auth, credentials) work exactly as before
- New: No explicit auth → DefaultAzureCredential auto-detects in order:
  Environment vars → Workload Identity → Managed Identity → Azure CLI

Benefits:
✅ 100% backward compatible - existing configs unchanged
✅ Non-breaking changes - all authentication methods preserved
✅ Modern SDK - actively maintained, receives security updates
✅ Foundation ready - prepared for new services (Container Apps, etc.)
✅ Smart defaults - minimal config for new users

🤖 Generated with Claude Code (https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 14, 2025

Walkthrough

Replaces Azure Track 1 SDK with Track 2 across the Azure provider: adds a Track 2 credential resolver, changes Provider to hold azcore.TokenCredential, migrates clients to arm/* packages and pager patterns, and adds many new Track 2 service providers and metadata extraction. go.mod updated for Track 2 and golang.org/x bumps.

Changes

Cohort / File(s) Summary
Dependencies: Track 2 & golang/x bumps
go.mod
Remove legacy autorest and some indirects; add Track 2 modules (azcore, azidentity, many resourcemanager/arm*), bump multiple golang.org/x/* modules, upgrade jwt v4→v5 and add several indirect utilities.
Auth: Track 2 credential resolver
pkg/providers/azure/auth.go
New: createCredential chooses among CLI, client certificate (with optional password), client secret, or DefaultAzureCredential and returns azcore.TokenCredential, with option validation and error wrapping.
Core provider migration
pkg/providers/azure/azure.go
Provider now stores Credential azcore.TokenCredential; New calls createCredential; subscription discovery and Verify use armsubscriptions pagers; services wired to Track 2 clients and pager patterns.
Network & compute migrations
pkg/providers/azure/publicips.go, pkg/providers/azure/trafficmanager.go, pkg/providers/azure/vm.go
Migrate from autorest→Track 2 (armnetwork, armtrafficmanager, armcompute); replace Authorizer→Credential; adopt pagers; update to Properties-based models, nil-safety, parseAzureResourceID utility, and adjust signatures.
Load balancer & application gateway
pkg/providers/azure/loadbalancer.go, pkg/providers/azure/applicationgateway.go
New/rewritten providers using Track 2; concurrent processing for load balancers; frontend IP resolution (public/private), DNS emission, and comprehensive metadata builders.
App platform & compute services
pkg/providers/azure/appservice.go, pkg/providers/azure/functions.go, pkg/providers/azure/aks.go, pkg/providers/azure/containerinstances.go, pkg/providers/azure/containerapps.go
New Track 2 providers for App Service, Functions, AKS, Container Instances, Container Apps; pager-based listing, hostname/IP extraction, optional extended metadata and per-resource metadata builders.
Edge & networking services
pkg/providers/azure/cdn.go, pkg/providers/azure/frontdoor.go, pkg/providers/azure/trafficmanager.go, pkg/providers/azure/dns.go, pkg/providers/azure/staticwebapps.go
New Track 2 providers for CDN, Front Door, Traffic Manager, DNS, Static Web Apps; endpoint/record discovery, hostname normalization, metadata extraction, and pager usage.
Storage provider
pkg/providers/azure/storage.go
New storage provider: lists storage accounts, extracts primary endpoints (blob/file/table/queue/dfs/web), resolves private endpoint IPs, and assembles extended metadata.
Public IPs & helper plumbing
pkg/providers/azure/publicips.go, pkg/providers/azure/vm.go, pkg/providers/azure/* (various)
Many provider structs and helpers updated to carry Credential azcore.TokenCredential; new parseAzureResourceID and ID utilities; signatures updated to use Track 2 pointer types and pager patterns.
Docs & infra
.gitignore, CLAUDE.md
.gitignore updated with root-anchored ignores (/cloudlist, /terraform/) and azure-readonly-config.yaml; adds CLAUDE.md with contributor/provider guidance.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Provider as Azure Provider
  participant Auth as Auth Resolver
  participant ARM as Azure ARM (Track 2)

  User->>Provider: New(options)
  Provider->>Auth: createCredential(options)
  alt explicit credential chosen
    Auth-->>Provider: azcore.TokenCredential (CLI / Cert / Secret)
  else fallback
    Auth-->>Provider: DefaultAzureCredential
  end
  Provider->>ARM: armsubscriptions.NewListPager(Credential)
  loop pages
    ARM-->>Provider: subscriptions page
    Provider->>Provider: parse IDs / init arm/* clients (New*Client)
  end
  Provider-->>User: Initialized (credential verified)
Loading
sequenceDiagram
  autonumber
  participant Provider as Azure Provider
  participant ARM as Azure ARM (Track 2)

  rect rgb(245,250,255)
  note over Provider,ARM: Service discovery uses Track 2 clients & pagers
  Provider->>ARM: New*Client(Credential)
  Provider->>ARM: NewList*Pager(...)
  loop For each page
    ARM-->>Provider: items (Properties pointers)
    Provider->>Provider: nil-checks, parse IDs, build resources (DNS/IP + metadata)
  end
  end
  Provider-->>User: Aggregated resources
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Poem

In my burrow I code, twitchy and spry,
Track Two hops in while old autorest says bye.
Pagers flip pages, credentials gleam,
DNS and IPs gather in a stream.
I nibble a bug — now clouds dance and beam. 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning The pull request introduces unrelated items such as the CLAUDE.md documentation, updates to Google API and JWT dependencies, and .gitignore changes for Terraform and azure-readonly-config.yaml that fall outside the scope of migrating the Azure provider to Track 2. Please separate out or move the CLAUDE.md file, go.mod updates unrelated to Azure Track 2 migration, and .gitignore modifications into dedicated pull requests to keep the Azure migration focused.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title succinctly describes the main change of migrating the Azure provider to the Track 2 SDK and enabling over eleven new services, directly reflecting the core objective of this PR without extraneous details or noise.
Linked Issues Check ✅ Passed The changes fully address issue #711 by replacing the Track 1 SDK with Track 2 dependencies, implementing discovery for all eleven listed Azure services, extending authentication to the seven specified methods, and preserving backward compatibility with existing configurations.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat-azure-integrations

Comment @coderabbitai help to get the list of available commands and usage tips.

@tarunKoyalwar tarunKoyalwar changed the title refactor(azure): migrate to Track 2 SDK with enhanced authentication feat(azure): migrate to Track 2 SDK enabling 11+ new services Oct 14, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (10)
pkg/providers/azure/auth.go (1)

146-155: Clarify DefaultAzureCredential chain in logs

DefaultAzureCredential tries more sources than the message suggests (includes SharedTokenCache and, on some platforms, other dev credentials). Update the log to avoid misleading users.

- gologger.Info().Msg("DefaultAzureCredential will try: Environment → Workload Identity → Managed Identity → Azure CLI")
+ gologger.Info().Msg("DefaultAzureCredential chain: Environment → Workload Identity → Managed Identity → SharedTokenCache → Azure CLI")
pkg/providers/azure/azure.go (1)

54-60: Trim services when parsing CSV

Split entries may contain spaces (e.g., "vm, publicip"). Trim before lookup to avoid silently dropping valid services.

- for _, s := range strings.Split(ss, ",") {
-   if _, ok := supportedServicesMap[s]; ok {
+ for _, s := range strings.Split(ss, ",") {
+   s = strings.TrimSpace(s)
+   if _, ok := supportedServicesMap[s]; ok {
      services[s] = struct{}{}
    }
  }
pkg/providers/azure/vm.go (3)

127-146: Handle private IPv6 addresses

PrivateIPAddress can be IPv4 or IPv6. Currently it always populates PrivateIpv4. Use PrivateIPAddressVersion to route to the correct field.

- // Add private IP if available
- if ipConfig.Properties.PrivateIPAddress != nil {
-   resource.PrivateIpv4 = *ipConfig.Properties.PrivateIPAddress
- }
+ // Add private IP if available (respect IP version)
+ if ipConfig.Properties.PrivateIPAddress != nil {
+   if ipConfig.Properties.PrivateIPAddressVersion != nil &&
+     *ipConfig.Properties.PrivateIPAddressVersion == armnetwork.IPVersionIPv6 {
+     resource.PrivateIpv6 = *ipConfig.Properties.PrivateIPAddress
+   } else {
+     resource.PrivateIpv4 = *ipConfig.Properties.PrivateIPAddress
+   }
+ }

172-186: Resource ID parser is minimal; confirm scope

This parser assumes the resource name is the last segment and only extracts resource group by scanning for "resourceGroups". It works for NIC/PIP but may fail for nested child resources. If you reuse it elsewhere, consider a more robust parser or constrain usage to top-level resources.


37-38: Typo: fetchResouceGroups → fetchResourceGroups

Rename for clarity.

- groups, err := fetchResouceGroups(ctx, d.SubscriptionID, d.Credential)
+ groups, err := fetchResourceGroups(ctx, d.SubscriptionID, d.Credential)
-func fetchResouceGroups(ctx context.Context, subscriptionID string, credential azcore.TokenCredential) (resGrpList []string, err error) {
+func fetchResourceGroups(ctx context.Context, subscriptionID string, credential azcore.TokenCredential) (resGrpList []string, err error) {

Also applies to: 188-210

pkg/providers/azure/trafficmanager.go (1)

87-96: Reuse existing resource ID parsing

You already have parseAzureResourceID in this package. Consider reusing it to extract resource_group to avoid duplicated parsing logic and keep behavior consistent.

pkg/providers/azure/publicips.go (4)

61-75: Mark DNS resource as public

DNS names derived from Public IPs are public-facing. Set Public=true for consistency with IP resources.

 		if pip.extendedMetadata && ip.Properties.DNSSettings != nil && ip.Properties.DNSSettings.Fqdn != nil {
 			dnsResource := &schema.Resource{
 				Provider: providerName,
 				ID:       pip.id,
-				DNSName:  *ip.Properties.DNSSettings.Fqdn,
+				DNSName:  *ip.Properties.DNSSettings.Fqdn,
+				Public:   true,
 				Service:  pip.name(),
 			}

84-101: Improve error context and consider heavy ListAll behavior

Include subscription ID in errors for triage; ListAll across a large subscription can be heavy.

-	ipClient, err := armnetwork.NewPublicIPAddressesClient(pip.SubscriptionID, pip.Credential, nil)
+	ipClient, err := armnetwork.NewPublicIPAddressesClient(pip.SubscriptionID, pip.Credential, nil)
 	if err != nil {
-		return nil, fmt.Errorf("failed to create public IP client: %w", err)
+		return nil, fmt.Errorf("subscription %s: failed to create public IP client: %w", pip.SubscriptionID, err)
 	}
@@
-		page, err := pager.NextPage(ctx)
+		page, err := pager.NextPage(ctx)
 		if err != nil {
-			return nil, fmt.Errorf("failed to list public IPs: %w", err)
+			return nil, fmt.Errorf("subscription %s: failed to list public IPs: %w", pip.SubscriptionID, err)
 		}

Operational note:

  • For very large tenants, consider processing per page directly in GetResource (streaming) instead of materializing all IPs first, or using per‑RG List where possible. This reduces peak memory.

112-121: Use arm.ParseResourceID for robust resource group extraction

Manual splitting is brittle. The SDK provides a parser.

-	// Track 2: Parse resource group from ID manually (no ParseResourceID in Track 2)
-	if ip.ID != nil {
-		// Azure resource ID format: /subscriptions/{subId}/resourceGroups/{rgName}/...
-		parts := strings.Split(*ip.ID, "/")
-		for i, part := range parts {
-			if strings.EqualFold(part, "resourceGroups") && i+1 < len(parts) {
-				metadata["resource_group"] = parts[i+1]
-				break
-			}
-		}
-	}
+	if ip.ID != nil {
+		if rid, err := arm.ParseResourceID(*ip.ID); err == nil && rid.ResourceGroupName != "" {
+			metadata["resource_group"] = rid.ResourceGroupName
+		}
+	}

Add import:

 import (
@@
-	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
+	arm "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
 )

34-37: Optional: emit DNS resource even when IP is not yet assigned

Currently, continue skips DNS-only records. If you want DNS assets regardless of IP assignment, move DNS resource creation before the early continue or guard IP resource creation with a hasIP check.

Example approach (conceptual):

  • Compute hasIP := ip.Properties != nil && ip.Properties.IPAddress != nil
  • If pip.extendedMetadata && DNSSettings present, append dnsResource first
  • Only build the IP Resource when hasIP

Also applies to: 61-75

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2e4fc52 and 146f618.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (6)
  • go.mod (5 hunks)
  • pkg/providers/azure/auth.go (1 hunks)
  • pkg/providers/azure/azure.go (8 hunks)
  • pkg/providers/azure/publicips.go (9 hunks)
  • pkg/providers/azure/trafficmanager.go (9 hunks)
  • pkg/providers/azure/vm.go (7 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
pkg/providers/azure/azure.go (1)
pkg/schema/schema.go (3)
  • ServiceMap (254-254)
  • OptionBlock (195-195)
  • Provider (18-28)
pkg/providers/azure/vm.go (2)
pkg/schema/schema.go (3)
  • Resource (142-163)
  • Provider (18-28)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/providers/azure/auth.go (1)
pkg/schema/schema.go (2)
  • OptionBlock (195-195)
  • ErrNoSuchKey (166-168)
pkg/providers/azure/trafficmanager.go (2)
pkg/schema/schema.go (2)
  • Resource (142-163)
  • Provider (18-28)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/providers/azure/publicips.go (2)
pkg/schema/schema.go (3)
  • Resource (142-163)
  • Provider (18-28)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Test Builds (1.22.x, ubuntu-latest)
  • GitHub Check: Test Builds (1.22.x, macOS-latest)
  • GitHub Check: Test Builds (1.22.x, windows-latest)
  • GitHub Check: Analyze (go)
  • GitHub Check: release-test
  • GitHub Check: Lint Test
🔇 Additional comments (2)
go.mod (1)

167-176: Azure SDK module versions are up-to-date All Track 2 modules exist at the specified versions with no newer releases available; go.mod’s Go directive is 1.24.0.

pkg/providers/azure/publicips.go (1)

8-10: No update needed for armnetwork import The go.mod pins armnetwork at v1.1.0, so the import path without a /v1 suffix is correct.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (1)
pkg/providers/azure/azure.go (1)

312-321: Verify() can pass without confirming any subscriptions were returned

You check pager.More() then fetch a single page without asserting non-empty results.

Suggested approach: iterate pages until you see at least one item; error otherwise (see prior review’s diff in this file’s history).

🧹 Nitpick comments (24)
pkg/providers/azure/functions.go (1)

41-46: Prefer DefaultHostName over formatting to avoid wrong host for sovereign clouds/slots

Use app.Properties.DefaultHostName when available; fallback to formatted name only if missing.

-		// Default hostname: <function-name>.azurewebsites.net
-		var defaultHostname string
-		if app.Name != nil {
-			defaultHostname = fmt.Sprintf("%s.azurewebsites.net", *app.Name)
-		}
+		// Default hostname: prefer DefaultHostName to support sovereign clouds/slots
+		var defaultHostname string
+		if app.Properties != nil && app.Properties.DefaultHostName != nil {
+			defaultHostname = *app.Properties.DefaultHostName
+		} else if app.Name != nil {
+			defaultHostname = fmt.Sprintf("%s.azurewebsites.net", *app.Name)
+		}

Also applies to: 52-61

pkg/providers/azure/containerapps.go (2)

155-164: Use existing parseAzureResourceID for resource group extraction

Simpler and consistent across providers.

-	// Parse resource group from ID manually
-	if app.ID != nil {
-		parts := strings.Split(*app.ID, "/")
-		for i, part := range parts {
-			if strings.EqualFold(part, "resourceGroups") && i+1 < len(parts) {
-				metadata["resource_group"] = parts[i+1]
-				break
-			}
-		}
-	}
+	// Parse resource group from ID
+	if app.ID != nil {
+		_, rg := parseAzureResourceID(*app.ID)
+		if rg != "" {
+			metadata["resource_group"] = rg
+		}
+	}

67-74: DRY: replace manual metadata copying with copyMetadata helper

Reduces repetition and avoids subtle aliasing mistakes.

-					if metadata != nil {
-						customResource.Metadata = make(map[string]string)
-						for k, v := range metadata {
-							customResource.Metadata[k] = v
-						}
-						customResource.Metadata["is_custom_domain"] = "true"
-					}
+					if metadata != nil {
+						customResource.Metadata = copyMetadata(metadata)
+						customResource.Metadata["is_custom_domain"] = "true"
+					}
-					if metadata != nil {
-						ipResource.Metadata = make(map[string]string)
-						for k, v := range metadata {
-							ipResource.Metadata[k] = v
-						}
-						ipResource.Metadata["ip_type"] = "outbound"
-					}
+					if metadata != nil {
+						ipResource.Metadata = copyMetadata(metadata)
+						ipResource.Metadata["ip_type"] = "outbound"
+					}
-			if metadata != nil {
-				revisionResource.Metadata = make(map[string]string)
-				for k, v := range metadata {
-					revisionResource.Metadata[k] = v
-				}
-				revisionResource.Metadata["is_revision_fqdn"] = "true"
-			}
+			if metadata != nil {
+				revisionResource.Metadata = copyMetadata(metadata)
+				revisionResource.Metadata["is_revision_fqdn"] = "true"
+			}

Also applies to: 90-97, 110-117

pkg/providers/azure/storage.go (2)

218-224: Hoist network clients out of the loop to avoid N^2 client creation

Creating clients per-PE and per-NIC is costly. Create them once.

 func (sp *storageProvider) extractPrivateEndpointIPs(ctx context.Context, account *armstorage.Account) []string {
 	var privateIPs []string
 
 	if account.Properties == nil || account.Properties.PrivateEndpointConnections == nil {
 		return privateIPs
 	}
+
+	// Create network clients once per call
+	peClient, err := armnetwork.NewPrivateEndpointsClient(sp.SubscriptionID, sp.Credential, nil)
+	if err != nil {
+		gologger.Warning().Msgf("failed to create private endpoints client: %s", err)
+		return privateIPs
+	}
+	nicClient, err := armnetwork.NewInterfacesClient(sp.SubscriptionID, sp.Credential, nil)
+	if err != nil {
+		gologger.Warning().Msgf("failed to create network interfaces client: %s", err)
+		return privateIPs
+	}
 
 	for _, peConn := range account.Properties.PrivateEndpointConnections {
 		if peConn.Properties == nil || peConn.Properties.PrivateEndpoint == nil || peConn.Properties.PrivateEndpoint.ID == nil {
 			continue
 		}
 
 		// Parse Private Endpoint resource ID
 		peName, peRG := parseAzureResourceID(*peConn.Properties.PrivateEndpoint.ID)
 		if peName == "" || peRG == "" {
 			continue
 		}
 
-		// Fetch Private Endpoint details
-		peClient, err := armnetwork.NewPrivateEndpointsClient(sp.SubscriptionID, sp.Credential, nil)
-		if err != nil {
-			gologger.Warning().Msgf("failed to create private endpoints client: %s", err)
-			continue
-		}
-
 		pe, err := peClient.Get(ctx, peRG, peName, nil)
 		if err != nil {
 			gologger.Warning().Msgf("failed to get private endpoint %s: %s", peName, err)
 			continue
 		}
 
 		// Extract private IPs from network interfaces
 		if pe.Properties != nil && pe.Properties.NetworkInterfaces != nil {
 			for _, nic := range pe.Properties.NetworkInterfaces {
 				if nic.ID == nil {
 					continue
 				}
 
 				nicName, nicRG := parseAzureResourceID(*nic.ID)
 				if nicName == "" || nicRG == "" {
 					continue
 				}
 
-				nicClient, err := armnetwork.NewInterfacesClient(sp.SubscriptionID, sp.Credential, nil)
-				if err != nil {
-					gologger.Warning().Msgf("failed to create network interfaces client: %s", err)
-					continue
-				}
-
 				nicRes, err := nicClient.Get(ctx, nicRG, nicName, nil)
 				if err != nil {
 					gologger.Warning().Msgf("failed to get network interface %s: %s", nicName, err)
 					continue
 				}

Also applies to: 236-246, 261-269


481-486: Avoid shadowing built-in name 'copy'

Minor readability nit.

-	copy := make(map[string]string)
-	for k, v := range metadata {
-		copy[k] = v
-	}
-	return copy
+	out := make(map[string]string)
+	for k, v := range metadata {
+		out[k] = v
+	}
+	return out
pkg/providers/azure/azure.go (1)

55-59: Trim service names when parsing options

Prevents mismatches from whitespace.

-	if ss, ok := options.GetMetadata("services"); ok {
-		for _, s := range strings.Split(ss, ",") {
-			if _, ok := supportedServicesMap[s]; ok {
-				services[s] = struct{}{}
-			}
-		}
-	}
+	if ss, ok := options.GetMetadata("services"); ok {
+		for _, raw := range strings.Split(ss, ",") {
+			s := strings.TrimSpace(raw)
+			if s == "" {
+				continue
+			}
+			if _, ok := supportedServicesMap[s]; ok {
+				services[s] = struct{}{}
+			}
+		}
+	}
pkg/providers/azure/staticwebapps.go (1)

145-149: Avoid metadata key collision with top-level Provider field.

Using metadata["provider"] for repo provider (GitHub/GitLab) is ambiguous. Prefer a distinct key.

Apply this diff:

-        // Provider (GitHub, GitLab, etc.)
-        if props.Provider != nil {
-            metadata["provider"] = *props.Provider
-        }
+        // Repository provider (GitHub, GitLab, etc.)
+        if props.Provider != nil {
+            metadata["repo_provider"] = *props.Provider
+        }
pkg/providers/azure/loadbalancer.go (2)

39-41: Verify pond v2 API usage (NewPool vs New).

pond/v2 typically uses New(maxWorkers, maxCapacity). Ensure NewPool exists in your pinned version, or switch to New to avoid build errors.

If needed, apply:

-    pool := pond.NewPool(10)
+    // maxWorkers=10, unbounded queue (0) or set a sensible capacity
+    pool := pond.New(10, 0)

140-144: Prune dead condition: resource.DNSName is never set on this resource.

This branch always evaluates that part to false; keep the check to IPs only.

Apply this diff:

-                if resource.PublicIPv4 != "" || resource.PublicIPv6 != "" || resource.PrivateIpv4 != "" || resource.DNSName != "" {
+                if resource.PublicIPv4 != "" || resource.PublicIPv6 != "" || resource.PrivateIpv4 != "" {
                     resources = append(resources, resource)
                 }
pkg/providers/azure/applicationgateway.go (2)

67-78: Mark public IP resources as Public.

Set Public=true when emitting a resource tied to a Public IP.

Apply this diff:

                             // Determine IP version
                             if publicIP.Properties.PublicIPAddressVersion != nil && *publicIP.Properties.PublicIPAddressVersion == armnetwork.IPVersionIPv4 {
                                 resource.PublicIPv4 = *publicIP.Properties.IPAddress
                             } else if publicIP.Properties.PublicIPAddressVersion != nil {
                                 resource.PublicIPv6 = *publicIP.Properties.IPAddress
                             } else {
                                 // Default to IPv4 if not specified
                                 resource.PublicIPv4 = *publicIP.Properties.IPAddress
                             }
+                            resource.Public = true
 
                             list.Append(resource)

196-205: Use existing helper to parse resource group from ID.

Prefer parseAzureResourceID for consistency and correctness.

Apply this diff:

-    // Parse resource group from ID
-    if gateway.ID != nil {
-        parts := strings.Split(*gateway.ID, "/")
-        for i, part := range parts {
-            if strings.EqualFold(part, "resourceGroups") && i+1 < len(parts) {
-                metadata["resource_group"] = parts[i+1]
-                break
-            }
-        }
-    }
+    // Parse resource group from ID
+    if gateway.ID != nil {
+        _, rg := parseAzureResourceID(*gateway.ID)
+        if rg != "" {
+            metadata["resource_group"] = rg
+        }
+    }
pkg/providers/azure/appservice.go (3)

91-94: Comment says “log warning” but no log is emitted.

Log the slot listing error and continue.

Apply this diff:

-        if err != nil {
-            // Don't fail the entire operation, just log warning
-            continue
-        }
+        if err != nil {
+            // Don't fail the entire operation, just log warning
+            gologger.Warning().Msgf("error listing deployment slots for app %s: %v", func() string { if app.Name != nil { return *app.Name }; return "(unknown)" }(), err)
+            continue
+        }

Add import if not present:

import "github.com/projectdiscovery/gologger"

184-193: Use helper to parse resource group from ID.

Align with other providers for consistency.

Apply this diff:

-    // Parse resource group from ID
-    if app.ID != nil {
-        parts := strings.Split(*app.ID, "/")
-        for i, part := range parts {
-            if strings.EqualFold(part, "resourceGroups") && i+1 < len(parts) {
-                metadata["resource_group"] = parts[i+1]
-                break
-            }
-        }
-    }
+    // Parse resource group from ID
+    if app.ID != nil {
+        _, rg := parseAzureResourceID(*app.ID)
+        if rg != "" {
+            metadata["resource_group"] = rg
+        }
+    }

214-218: Unify resource group metadata key.

Use “resource_group” (used elsewhere) instead of “resource_group_name” to avoid duplicate/confusing keys.

Apply this diff:

-        schema.AddMetadata(metadata, "resource_group_name", props.ResourceGroup)
+        schema.AddMetadata(metadata, "resource_group", props.ResourceGroup)
pkg/providers/azure/frontdoor.go (2)

101-110: Use shared resource ID parser instead of manual split

Prefer parseAzureResourceID for consistency and fewer edge cases.

-	// Track 2: Parse resource group from ID manually
-	if frontDoor.ID != nil {
-		parts := strings.Split(*frontDoor.ID, "/")
-		for i, part := range parts {
-			if strings.EqualFold(part, "resourceGroups") && i+1 < len(parts) {
-				metadata["resource_group"] = parts[i+1]
-				break
-			}
-		}
-	}
+	// Parse resource group from ID
+	if frontDoor.ID != nil {
+		_, rg := parseAzureResourceID(*frontDoor.ID)
+		if rg != "" {
+			metadata["resource_group"] = rg
+		}
+	}

47-51: Avoid recomputing base metadata per endpoint

getFrontDoorMetadata recomputes many Front Door-level fields for every endpoint. Precompute base metadata once per Front Door and merge endpoint-specific fields in-loop. Low-priority, but improves efficiency on tenants with many endpoints.

pkg/providers/azure/aks.go (1)

40-55: Build metadata once per cluster

You recompute getAKSMetadata twice. Compute once and reuse.

-		// Extract cluster FQDN as primary DNS resource
-		if cluster.Properties.Fqdn != nil && *cluster.Properties.Fqdn != "" {
-			var metadata map[string]string
-			if ap.extendedMetadata {
-				metadata = ap.getAKSMetadata(cluster)
-			}
+		// Precompute metadata once
+		var md map[string]string
+		if ap.extendedMetadata {
+			md = ap.getAKSMetadata(cluster)
+		}
+
+		// Extract cluster FQDN as primary DNS resource
+		if cluster.Properties.Fqdn != nil && *cluster.Properties.Fqdn != "" {
 			resource := &schema.Resource{
 				Provider: providerName,
 				ID:       ap.id,
 				DNSName:  *cluster.Properties.Fqdn,
 				Service:  ap.name(),
-				Metadata: metadata,
+				Metadata: md,
 			}
 			list.Append(resource)
 		}
 
 		// Extract private FQDN if available
 		if cluster.Properties.PrivateFQDN != nil && *cluster.Properties.PrivateFQDN != "" {
-			var metadata map[string]string
-			if ap.extendedMetadata {
-				metadata = ap.getAKSMetadata(cluster)
-			}
-
 			resource := &schema.Resource{
 				Provider: providerName,
 				ID:       ap.id,
 				DNSName:  *cluster.Properties.PrivateFQDN,
 				Service:  ap.name(),
-				Metadata: metadata,
+				Metadata: md,
 			}
 			list.Append(resource)
 		}

Also applies to: 57-72

pkg/providers/azure/containerinstances.go (3)

3-11: Import net to detect IPv4 vs IPv6

Required for proper IP family handling below.

 import (
 	"context"
 	"fmt"
+	"net"
 	"strings"
 
 	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
 	"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerinstance/armcontainerinstance"
 	"github.com/projectdiscovery/cloudlist/pkg/schema"
 )

45-58: Set PublicIPv4 vs PublicIPv6 correctly

Don’t stuff IPv6 into PublicIPv4.

-		// Extract public IP and FQDN from IPAddress
+		// Extract public IP and FQDN from IPAddress
 		if cg.Properties.IPAddress != nil {
 			// Public IP resource
-			if cg.Properties.IPAddress.IP != nil {
-				resource := &schema.Resource{
-					Provider:    providerName,
-					ID:          cip.id,
-					Public:      true,
-					Service:     cip.name(),
-					PublicIPv4:  *cg.Properties.IPAddress.IP,
-					Metadata:    metadata,
-				}
-				list.Append(resource)
-			}
+			if cg.Properties.IPAddress.IP != nil && *cg.Properties.IPAddress.IP != "" {
+				if ip := net.ParseIP(*cg.Properties.IPAddress.IP); ip != nil {
+					res := &schema.Resource{
+						Provider: providerName,
+						ID:       cip.id,
+						Public:   true,
+						Service:  cip.name(),
+						Metadata: metadata,
+					}
+					if ip.To4() != nil {
+						res.PublicIPv4 = *cg.Properties.IPAddress.IP
+					} else {
+						res.PublicIPv6 = *cg.Properties.IPAddress.IP
+					}
+					list.Append(res)
+				}
+			}

78-96: Set PrivateIpv4 vs PrivateIpv6 correctly

Also handle IPv6 private addresses.

-		// Extract private IPs from subnet IDs (VNet integration)
+		// Extract private IPs from subnet IDs (VNet integration)
 		if cg.Properties.SubnetIDs != nil && len(cg.Properties.SubnetIDs) > 0 {
 			// Container groups with VNet integration have private IPs
 			// The private IP is available in IPAddress even for VNet-integrated groups
 			if cg.Properties.IPAddress != nil && cg.Properties.IPAddress.IP != nil {
 				// Check if this is a private IP (VNet-integrated)
 				if cg.Properties.IPAddress.Type != nil && *cg.Properties.IPAddress.Type == armcontainerinstance.ContainerGroupIPAddressTypePrivate {
-					resource := &schema.Resource{
-						Provider:     providerName,
-						ID:           cip.id,
-						Public:       false,
-						Service:      cip.name(),
-						PrivateIpv4:  *cg.Properties.IPAddress.IP,
-						Metadata:     metadata,
-					}
-					list.Append(resource)
+					ipStr := *cg.Properties.IPAddress.IP
+					if ip := net.ParseIP(ipStr); ip != nil {
+						res := &schema.Resource{
+							Provider: providerName,
+							ID:       cip.id,
+							Public:   false,
+							Service:  cip.name(),
+							Metadata: metadata,
+						}
+						if ip.To4() != nil {
+							res.PrivateIpv4 = ipStr
+						} else {
+							res.PrivateIpv6 = ipStr
+						}
+						list.Append(res)
+					}
 				}
 			}
 		}
pkg/providers/azure/apimanagement.go (4)

199-208: Use shared resource ID parser instead of manual split

Align with other providers and reduce edge cases.

-	// Parse resource group from ID
-	if service.ID != nil {
-		parts := strings.Split(*service.ID, "/")
-		for i, part := range parts {
-			if strings.EqualFold(part, "resourceGroups") && i+1 < len(parts) {
-				metadata["resource_group"] = parts[i+1]
-				break
-			}
-		}
-	}
+	// Parse resource group from ID
+	if service.ID != nil {
+		_, rg := parseAzureResourceID(*service.ID)
+		if rg != "" {
+			metadata["resource_group"] = rg
+		}
+	}

121-140: Populate PublicIPv4 vs PublicIPv6 correctly

IPs may be IPv6; set the right field.

-		// Extract public IP addresses
+		// Extract public IP addresses
 		if service.Properties.PublicIPAddresses != nil {
 			for _, ip := range service.Properties.PublicIPAddresses {
 				if ip != nil {
-					resource := &schema.Resource{
-						Provider:   providerName,
-						ID:         amp.id,
-						PublicIPv4: *ip,
-						Service:    amp.name(),
-					}
-					if metadata != nil {
-						resource.Metadata = make(map[string]string)
-						for k, v := range metadata {
-							resource.Metadata[k] = v
-						}
-					}
-					list.Append(resource)
+					ipStr := *ip
+					res := &schema.Resource{
+						Provider: providerName,
+						ID:       amp.id,
+						Service:  amp.name(),
+						Public:   true,
+					}
+					if metadata != nil {
+						res.Metadata = make(map[string]string)
+						for k, v := range metadata {
+							res.Metadata[k] = v
+						}
+					}
+					if parsed := net.ParseIP(ipStr); parsed != nil {
+						if parsed.To4() != nil {
+							res.PublicIPv4 = ipStr
+						} else {
+							res.PublicIPv6 = ipStr
+						}
+					}
+					list.Append(res)
 				}
 			}
 		}

143-161: Populate PrivateIpv4 vs PrivateIpv6 correctly

Mirror IP family handling for private addresses.

-		// Extract private IP addresses (VNet integration)
+		// Extract private IP addresses (VNet integration)
 		if service.Properties.PrivateIPAddresses != nil {
 			for _, ip := range service.Properties.PrivateIPAddresses {
 				if ip != nil {
-					resource := &schema.Resource{
-						Provider:    providerName,
-						ID:          amp.id,
-						PrivateIpv4: *ip,
-						Service:     amp.name(),
-					}
-					if metadata != nil {
-						resource.Metadata = make(map[string]string)
-						for k, v := range metadata {
-							resource.Metadata[k] = v
-						}
-					}
-					list.Append(resource)
+					ipStr := *ip
+					res := &schema.Resource{
+						Provider: providerName,
+						ID:       amp.id,
+						Service:  amp.name(),
+						Public:   false,
+					}
+					if metadata != nil {
+						res.Metadata = make(map[string]string)
+						for k, v := range metadata {
+							res.Metadata[k] = v
+						}
+					}
+					if parsed := net.ParseIP(ipStr); parsed != nil {
+						if parsed.To4() != nil {
+							res.PrivateIpv4 = ipStr
+						} else {
+							res.PrivateIpv6 = ipStr
+						}
+					}
+					list.Append(res)
 				}
 			}
 		}

97-120: Set hostname_type even when extended metadata is off

Currently gated by metadata != nil. Always populate hostname_type on these resources.

-					resource := &schema.Resource{
+					resource := &schema.Resource{
 						Provider: providerName,
 						ID:       amp.id,
 						DNSName:  *hostnameConfig.HostName,
 						Service:  amp.name(),
 					}
-					if metadata != nil {
-						resource.Metadata = make(map[string]string)
-						for k, v := range metadata {
-							resource.Metadata[k] = v
-						}
-						if hostnameConfig.Type != nil {
-							resource.Metadata["hostname_type"] = string(*hostnameConfig.Type)
-						}
-					}
+					md := map[string]string{}
+					if metadata != nil {
+						for k, v := range metadata {
+							md[k] = v
+						}
+					}
+					if hostnameConfig.Type != nil {
+						md["hostname_type"] = string(*hostnameConfig.Type)
+					}
+					if len(md) > 0 {
+						resource.Metadata = md
+					}
 					list.Append(resource)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 146f618 and f28b6ff.

📒 Files selected for processing (14)
  • pkg/providers/azure/aks.go (1 hunks)
  • pkg/providers/azure/apimanagement.go (1 hunks)
  • pkg/providers/azure/applicationgateway.go (1 hunks)
  • pkg/providers/azure/appservice.go (1 hunks)
  • pkg/providers/azure/azure.go (7 hunks)
  • pkg/providers/azure/cdn.go (1 hunks)
  • pkg/providers/azure/containerapps.go (1 hunks)
  • pkg/providers/azure/containerinstances.go (1 hunks)
  • pkg/providers/azure/dns.go (1 hunks)
  • pkg/providers/azure/frontdoor.go (1 hunks)
  • pkg/providers/azure/functions.go (1 hunks)
  • pkg/providers/azure/loadbalancer.go (1 hunks)
  • pkg/providers/azure/staticwebapps.go (1 hunks)
  • pkg/providers/azure/storage.go (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (14)
pkg/providers/azure/frontdoor.go (2)
pkg/schema/schema.go (4)
  • Resources (39-42)
  • NewResources (45-50)
  • Resource (142-163)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/providers/azure/functions.go (2)
pkg/schema/schema.go (4)
  • Resources (39-42)
  • NewResources (45-50)
  • Resource (142-163)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/providers/azure/apimanagement.go (2)
pkg/schema/schema.go (4)
  • Resources (39-42)
  • NewResources (45-50)
  • Resource (142-163)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/providers/azure/azure.go (1)
pkg/schema/schema.go (3)
  • ServiceMap (254-254)
  • OptionBlock (195-195)
  • Provider (18-28)
pkg/providers/azure/appservice.go (2)
pkg/schema/schema.go (4)
  • Resources (39-42)
  • NewResources (45-50)
  • Resource (142-163)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/providers/azure/containerapps.go (2)
pkg/schema/schema.go (4)
  • Resources (39-42)
  • NewResources (45-50)
  • Resource (142-163)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/providers/azure/applicationgateway.go (1)
pkg/schema/schema.go (4)
  • Resources (39-42)
  • NewResources (45-50)
  • Resource (142-163)
  • AddMetadata (329-333)
pkg/providers/azure/containerinstances.go (2)
pkg/schema/schema.go (4)
  • Resources (39-42)
  • NewResources (45-50)
  • Resource (142-163)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/providers/azure/aks.go (2)
pkg/schema/schema.go (4)
  • Resources (39-42)
  • NewResources (45-50)
  • Resource (142-163)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/providers/azure/storage.go (3)
pkg/schema/schema.go (4)
  • Resources (39-42)
  • NewResources (45-50)
  • Resource (142-163)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/schema/validate/validate.go (1)
  • DNSName (72-72)
pkg/providers/azure/dns.go (2)
pkg/schema/schema.go (4)
  • Resources (39-42)
  • NewResources (45-50)
  • Resource (142-163)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/providers/azure/cdn.go (2)
pkg/schema/schema.go (4)
  • Resources (39-42)
  • NewResources (45-50)
  • Resource (142-163)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/providers/azure/staticwebapps.go (2)
pkg/schema/schema.go (4)
  • Resources (39-42)
  • NewResources (45-50)
  • Resource (142-163)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/providers/azure/loadbalancer.go (2)
pkg/schema/schema.go (4)
  • Resources (39-42)
  • NewResources (45-50)
  • Resource (142-163)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
🪛 GitHub Actions: 🔨 Build Test
pkg/providers/azure/apimanagement.go

[error] 9-9: Command 'go build .' failed: no required module provides package github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/apimanagement/armapimanagement. Add the dependency or adjust imports (go mod tidy).

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: release-test
  • GitHub Check: Analyze (go)
  • GitHub Check: Lint Test
🔇 Additional comments (5)
pkg/providers/azure/dns.go (1)

27-146: DNS provider implementation looks solid.

Pager usage, FQDN handling (incl. “@”), record-type branching, and metadata extraction are correct and consistent with other providers.

Also applies to: 148-188, 190-297

pkg/providers/azure/aks.go (1)

118-138: Pager migration looks good

Client creation + NewListPager loop is correct with contextual errors.

pkg/providers/azure/containerinstances.go (1)

102-122: Pager migration looks good

Client creation + NewListPager loop is correct.

pkg/providers/azure/apimanagement.go (2)

349-366: DNS extraction helper looks solid

Simple, robust stripping of scheme, path, and port.


8-10: Resolve: Azure API Management SDK module added The armapimanagement module v1.1.1 is now in go.mod/go.sum and build succeeds.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
go.mod (2)

3-3: Fix go directive version format.

The go directive should not include a patch version. Use go 1.24, not go 1.24.0.

Apply this diff:

-go 1.24.0
+go 1.24

167-189: Align CI workflows with Go 1.24 toolchain

  • In .github/workflows (build-test.yml, lint-test.yml, release-binary.yml, release-test.yml): update all go-version settings from 1.22.x to 1.24.x under actions/setup-go@v4.
  • Run go mod tidy to clean up the module graph.
♻️ Duplicate comments (1)
pkg/providers/azure/azure.go (1)

312-325: Verify() may pass with zero subscriptions (use page iteration)

Same concern as previously flagged; switch to iterating pages and ensure at least one item exists.

 func (p *Provider) Verify(ctx context.Context) error {
-  // Simple verification: try to create a subscriptions client and list one subscription
+  // Simple verification: ensure at least one subscription exists
   subsClient, err := armsubscriptions.NewClient(p.Credential, nil)
   if err != nil {
     return fmt.Errorf("failed to create subscriptions client: %w", err)
   }
-  // Try to list at least one subscription
-  pager := subsClient.NewListPager(nil)
-  if !pager.More() {
-    return fmt.Errorf("no subscriptions found with provided credentials")
-  }
-  _, err = pager.NextPage(ctx)
-  if err != nil {
-    return fmt.Errorf("failed to verify Azure credentials: %w", err)
-  }
+  pager := subsClient.NewListPager(nil)
+  found := false
+  for pager.More() {
+    page, err := pager.NextPage(ctx)
+    if err != nil {
+      return fmt.Errorf("failed to verify Azure credentials: %w", err)
+    }
+    if len(page.Value) > 0 {
+      found = true
+      break
+    }
+  }
+  if !found {
+    return fmt.Errorf("no subscriptions found with provided credentials")
+  }
   gologger.Info().Msg("Azure credentials verified successfully")
   return nil
 }
🧹 Nitpick comments (15)
pkg/providers/azure/appservice.go (2)

77-82: Consider extracting metadata copy logic to a helper function.

The manual map copy pattern (lines 77-82) is repeated in the codebase. While correct, extracting this into a helper like copyMetadata(src map[string]string) map[string]string would reduce duplication and cognitive load.

Example helper that could be added to a shared utils file or the azure package:

func copyMetadata(src map[string]string) map[string]string {
	if src == nil {
		return nil
	}
	dst := make(map[string]string, len(src))
	for k, v := range src {
		dst[k] = v
	}
	return dst
}

Then use it as:

-				if metadata != nil {
-					customResource.Metadata = make(map[string]string)
-					for k, v := range metadata {
-						customResource.Metadata[k] = v
-					}
+				if metadata != nil {
+					customResource.Metadata = copyMetadata(metadata)
 					customResource.Metadata["is_custom_domain"] = "true"
 				}

163-165: Clarify the error handling comment.

The comment "might not have slots" is slightly misleading, as errors here could also indicate permission issues, network failures, or API errors—not just the absence of slots. Consider rephrasing for accuracy.

-			// Don't fail if slots listing fails (might not have slots)
+			// Don't fail if slots listing fails (may have no slots or encounter API errors)
 			return nil, nil
pkg/providers/azure/cdn.go (1)

156-165: Optional: use the shared resource ID parser instead of manual split.

You already depend on parseAzureResourceID. Prefer reusing it for resource_group extraction to reduce duplication.

pkg/providers/azure/apimanagement.go (1)

349-366: Harden URL parsing using net/url.

Manual string ops miss cases (upper-case schemes, no scheme, ports). Use net/url and add a fallback for schemeless inputs.

Apply this diff:

-func extractDNSFromURL(url string) string {
-    // Remove protocol prefix (https://, http://)
-    url = strings.TrimPrefix(url, "https://")
-    url = strings.TrimPrefix(url, "http://")
-
-    // Remove path and query string
-    if idx := strings.Index(url, "/"); idx > 0 {
-        url = url[:idx]
-    }
-
-    // Remove port
-    if idx := strings.Index(url, ":"); idx > 0 {
-        url = url[:idx]
-    }
-
-    return url
-}
+func extractDNSFromURL(raw string) string {
+    u, err := url.Parse(raw)
+    if err != nil || u.Host == "" {
+        // Fallback for schemeless inputs
+        if !strings.Contains(raw, "://") {
+            u2, err2 := url.Parse("https://" + raw)
+            if err2 == nil && u2.Host != "" {
+                return u2.Hostname()
+            }
+        }
+        return ""
+    }
+    return u.Hostname()
+}

Add import:

import "net/url"
pkg/providers/azure/loadbalancer.go (2)

104-111: Be explicit when detecting IPv6.

Current else-if treats any non-IPv4 version as IPv6. Prefer explicit check for IPv6 to avoid misclassification on unexpected values.

Apply this diff:

-                            if publicIP.Properties.PublicIPAddressVersion != nil && *publicIP.Properties.PublicIPAddressVersion == armnetwork.IPVersionIPv4 {
-                                resource.PublicIPv4 = *publicIP.Properties.IPAddress
-                            } else if publicIP.Properties.PublicIPAddressVersion != nil {
-                                resource.PublicIPv6 = *publicIP.Properties.IPAddress
-                            } else {
+                            if publicIP.Properties.PublicIPAddressVersion != nil && *publicIP.Properties.PublicIPAddressVersion == armnetwork.IPVersionIPv4 {
+                                resource.PublicIPv4 = *publicIP.Properties.IPAddress
+                            } else if publicIP.Properties.PublicIPAddressVersion != nil && *publicIP.Properties.PublicIPAddressVersion == armnetwork.IPVersionIPv6 {
+                                resource.PublicIPv6 = *publicIP.Properties.IPAddress
+                            } else {
                                 // Default to IPv4 if not specified
                                 resource.PublicIPv4 = *publicIP.Properties.IPAddress
                             }

33-36: Rename fetchResouceGroups to fetchResourceGroups: the helper is defined and referenced with the missing ‘r’; updating the function name and all its calls will correct the spelling and improve clarity.

pkg/providers/azure/applicationgateway.go (1)

116-121: Optional: avoid recomputing metadata.

You already computed metadata per gateway above; reuse it here to avoid duplicate calls.

pkg/providers/azure/storage.go (3)

218-285: Avoid per-item client creation; instantiate network clients once

Creating armnetwork clients inside the loop is wasteful and increases API/token churn. Create them once per call and reuse.

 func (sp *storageProvider) extractPrivateEndpointIPs(ctx context.Context, account *armstorage.Account) []string {
   var privateIPs []string

   if account.Properties == nil || account.Properties.PrivateEndpointConnections == nil {
     return privateIPs
   }

+  // Create clients once
+  peClient, err := armnetwork.NewPrivateEndpointsClient(sp.SubscriptionID, sp.Credential, nil)
+  if err != nil {
+    gologger.Warning().Msgf("failed to create private endpoints client: %s", err)
+    return privateIPs
+  }
+  nicClient, err := armnetwork.NewInterfacesClient(sp.SubscriptionID, sp.Credential, nil)
+  if err != nil {
+    gologger.Warning().Msgf("failed to create network interfaces client: %s", err)
+    return privateIPs
+  }
+
   for _, peConn := range account.Properties.PrivateEndpointConnections {
     if peConn.Properties == nil || peConn.Properties.PrivateEndpoint == nil || peConn.Properties.PrivateEndpoint.ID == nil {
       continue
     }

     // Parse Private Endpoint resource ID
     peName, peRG := parseAzureResourceID(*peConn.Properties.PrivateEndpoint.ID)
     if peName == "" || peRG == "" {
       continue
     }

-    // Fetch Private Endpoint details
-    peClient, err := armnetwork.NewPrivateEndpointsClient(sp.SubscriptionID, sp.Credential, nil)
-    if err != nil {
-      gologger.Warning().Msgf("failed to create private endpoints client: %s", err)
-      continue
-    }
-
-    pe, err := peClient.Get(ctx, peRG, peName, nil)
+    // Fetch Private Endpoint details
+    pe, err := peClient.Get(ctx, peRG, peName, nil)
     if err != nil {
       gologger.Warning().Msgf("failed to get private endpoint %s: %s", peName, err)
       continue
     }

     // Extract private IPs from network interfaces
-    if pe.Properties != nil && pe.Properties.NetworkInterfaces != nil {
-      for _, nic := range pe.Properties.NetworkInterfaces {
+    if pe.Properties != nil && pe.Properties.NetworkInterfaces != nil {
+      for _, nic := range pe.Properties.NetworkInterfaces {
         if nic.ID == nil {
           continue
         }

         nicName, nicRG := parseAzureResourceID(*nic.ID)
         if nicName == "" || nicRG == "" {
           continue
         }

-        nicClient, err := armnetwork.NewInterfacesClient(sp.SubscriptionID, sp.Credential, nil)
-        if err != nil {
-          gologger.Warning().Msgf("failed to create network interfaces client: %s", err)
-          continue
-        }
-
-        nicRes, err := nicClient.Get(ctx, nicRG, nicName, nil)
+        nicRes, err := nicClient.Get(ctx, nicRG, nicName, nil)
         if err != nil {
           gologger.Warning().Msgf("failed to get network interface %s: %s", nicName, err)
           continue
         }

         if nicRes.Properties != nil && nicRes.Properties.IPConfigurations != nil {
           for _, ipConfig := range nicRes.Properties.IPConfigurations {
             if ipConfig.Properties != nil && ipConfig.Properties.PrivateIPAddress != nil {
               privateIPs = append(privateIPs, *ipConfig.Properties.PrivateIPAddress)
             }
           }
         }
       }
     }
   }
   return privateIPs
 }

297-306: Use existing resource ID parser for resource group extraction

You already have parseAzureResourceID elsewhere. Reuse it for consistency and less brittle parsing.

- // Parse resource group from ID
- if account.ID != nil {
-   parts := strings.Split(*account.ID, "/")
-   for i, part := range parts {
-     if strings.EqualFold(part, "resourceGroups") && i+1 < len(parts) {
-       metadata["resource_group"] = parts[i+1]
-       break
-     }
-   }
- }
+ // Parse resource group from ID
+ if account.ID != nil {
+   _, rg := parseAzureResourceID(*account.ID)
+   if rg != "" {
+     metadata["resource_group"] = rg
+   }
+ }

461-473: Prefer net/url for robust host extraction (optional)

Manual string trimming can miss edge cases (ports, credentials, queries). net/url.Parse is safer.

-// extractHostFromURL extracts the hostname from a URL (removes https:// and trailing /)
-func extractHostFromURL(urlStr string) string {
-  // Remove protocol
-  urlStr = strings.TrimPrefix(urlStr, "https://")
-  urlStr = strings.TrimPrefix(urlStr, "http://")
-
-  // Remove trailing slash and path
-  if idx := strings.Index(urlStr, "/"); idx != -1 {
-    urlStr = urlStr[:idx]
-  }
-  return strings.TrimSpace(urlStr)
-}
+// extractHostFromURL extracts the hostname from a URL
+func extractHostFromURL(u string) string {
+  if u == "" {
+    return ""
+  }
+  if !strings.HasPrefix(u, "http") {
+    u = "https://" + u
+  }
+  parsed, err := url.Parse(u)
+  if err != nil {
+    return strings.TrimSpace(u)
+  }
+  return parsed.Hostname()
+}
pkg/providers/azure/azure.go (1)

54-59: Normalize user-specified services (trim/lowercase) to avoid mismatches

Prevents subtle config issues like " VM " or "DNS".

 if ss, ok := options.GetMetadata("services"); ok {
   for _, s := range strings.Split(ss, ",") {
-    if _, ok := supportedServicesMap[s]; ok {
+    svc := strings.ToLower(strings.TrimSpace(s))
+    if _, ok := supportedServicesMap[svc]; ok {
-      services[s] = struct{}{}
+      services[svc] = struct{}{}
     }
   }
 }
pkg/providers/azure/containerapps.go (3)

67-75: Use package-level copyMetadata helper instead of manual loops

Simpler and consistent.

-          if metadata != nil {
-            customResource.Metadata = make(map[string]string)
-            for k, v := range metadata {
-              customResource.Metadata[k] = v
-            }
-            customResource.Metadata["is_custom_domain"] = "true"
-          }
+          if metadata != nil {
+            customResource.Metadata = copyMetadata(metadata)
+            customResource.Metadata["is_custom_domain"] = "true"
+          }

90-97: Use copyMetadata for IP resource metadata cloning

Avoid manual map copy.

-          if metadata != nil {
-            ipResource.Metadata = make(map[string]string)
-            for k, v := range metadata {
-              ipResource.Metadata[k] = v
-            }
-            ipResource.Metadata["ip_type"] = "outbound"
-          }
+          if metadata != nil {
+            ipResource.Metadata = copyMetadata(metadata)
+            ipResource.Metadata["ip_type"] = "outbound"
+          }

110-116: Use copyMetadata for revision resource metadata cloning

Consistent helper usage.

-      if metadata != nil {
-        revisionResource.Metadata = make(map[string]string)
-        for k, v := range metadata {
-          revisionResource.Metadata[k] = v
-        }
-        revisionResource.Metadata["is_revision_fqdn"] = "true"
-      }
+      if metadata != nil {
+        revisionResource.Metadata = copyMetadata(metadata)
+        revisionResource.Metadata["is_revision_fqdn"] = "true"
+      }
pkg/providers/azure/dns.go (1)

79-81: Prefer recordSet.Properties.Fqdn when available; fallback to composed FQDN

Azure returns the canonical FQDN; composing can be error-prone for edge cases.

-            // Build FQDN: <record-name>.<zone-name>
-            fqdn := buildFQDN(*recordSet.Name, *zone.Name)
+            // Prefer canonical FQDN if provided; fallback to composed
+            var fqdn string
+            if recordSet.Properties != nil && recordSet.Properties.Fqdn != nil && *recordSet.Properties.Fqdn != "" {
+              fqdn = *recordSet.Properties.Fqdn
+            } else {
+              fqdn = buildFQDN(*recordSet.Name, *zone.Name)
+            }
-            fqdn := buildFQDN(*recordSet.Name, *zone.Name)
+            var fqdn string
+            if recordSet.Properties != nil && recordSet.Properties.Fqdn != nil && *recordSet.Properties.Fqdn != "" {
+              fqdn = *recordSet.Properties.Fqdn
+            } else {
+              fqdn = buildFQDN(*recordSet.Name, *zone.Name)
+            }
-          fqdn := buildFQDN(*recordSet.Name, *zone.Name)
+          var fqdn string
+          if recordSet.Properties != nil && recordSet.Properties.Fqdn != nil && *recordSet.Properties.Fqdn != "" {
+            fqdn = *recordSet.Properties.Fqdn
+          } else {
+            fqdn = buildFQDN(*recordSet.Name, *zone.Name)
+          }

Also applies to: 107-109, 130-132

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 146f618 and 4e2b7c2.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (17)
  • .gitignore (1 hunks)
  • CLAUDE.md (1 hunks)
  • go.mod (5 hunks)
  • pkg/providers/azure/aks.go (1 hunks)
  • pkg/providers/azure/apimanagement.go (1 hunks)
  • pkg/providers/azure/applicationgateway.go (1 hunks)
  • pkg/providers/azure/appservice.go (1 hunks)
  • pkg/providers/azure/azure.go (7 hunks)
  • pkg/providers/azure/cdn.go (1 hunks)
  • pkg/providers/azure/containerapps.go (1 hunks)
  • pkg/providers/azure/containerinstances.go (1 hunks)
  • pkg/providers/azure/dns.go (1 hunks)
  • pkg/providers/azure/frontdoor.go (1 hunks)
  • pkg/providers/azure/functions.go (1 hunks)
  • pkg/providers/azure/loadbalancer.go (1 hunks)
  • pkg/providers/azure/staticwebapps.go (1 hunks)
  • pkg/providers/azure/storage.go (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • CLAUDE.md
🧰 Additional context used
🧬 Code graph analysis (14)
pkg/providers/azure/storage.go (2)
pkg/schema/schema.go (4)
  • Resources (39-42)
  • NewResources (45-50)
  • Resource (142-163)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/providers/azure/frontdoor.go (2)
pkg/schema/schema.go (4)
  • Resources (39-42)
  • NewResources (45-50)
  • Resource (142-163)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/providers/azure/azure.go (1)
pkg/schema/schema.go (3)
  • ServiceMap (254-254)
  • OptionBlock (195-195)
  • Provider (18-28)
pkg/providers/azure/staticwebapps.go (1)
pkg/schema/schema.go (4)
  • Resources (39-42)
  • NewResources (45-50)
  • Resource (142-163)
  • AddMetadata (329-333)
pkg/providers/azure/containerapps.go (2)
pkg/schema/schema.go (4)
  • Resources (39-42)
  • NewResources (45-50)
  • Resource (142-163)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/providers/azure/cdn.go (2)
pkg/schema/schema.go (4)
  • Resources (39-42)
  • NewResources (45-50)
  • Resource (142-163)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/providers/azure/appservice.go (2)
pkg/schema/schema.go (4)
  • Resources (39-42)
  • NewResources (45-50)
  • Resource (142-163)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/providers/azure/applicationgateway.go (2)
pkg/schema/schema.go (4)
  • Resources (39-42)
  • NewResources (45-50)
  • Resource (142-163)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/providers/azure/aks.go (2)
pkg/schema/schema.go (4)
  • Resources (39-42)
  • NewResources (45-50)
  • Resource (142-163)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/providers/azure/dns.go (2)
pkg/schema/schema.go (4)
  • Resources (39-42)
  • NewResources (45-50)
  • Resource (142-163)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/providers/azure/apimanagement.go (1)
pkg/schema/schema.go (4)
  • Resources (39-42)
  • NewResources (45-50)
  • Resource (142-163)
  • AddMetadata (329-333)
pkg/providers/azure/loadbalancer.go (2)
pkg/schema/schema.go (4)
  • Resources (39-42)
  • NewResources (45-50)
  • Resource (142-163)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/providers/azure/containerinstances.go (2)
pkg/schema/schema.go (4)
  • Resources (39-42)
  • NewResources (45-50)
  • Resource (142-163)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/providers/azure/functions.go (2)
pkg/schema/schema.go (4)
  • Resources (39-42)
  • NewResources (45-50)
  • Resource (142-163)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Test Builds (1.22.x, ubuntu-latest)
  • GitHub Check: Test Builds (1.22.x, windows-latest)
  • GitHub Check: Test Builds (1.22.x, macOS-latest)
  • GitHub Check: release-test
  • GitHub Check: Lint Test
  • GitHub Check: Analyze (go)
🔇 Additional comments (9)
pkg/providers/azure/appservice.go (6)

1-24: LGTM!

The type definition and imports are appropriate for Track 2 SDK usage. The struct fields correctly use azcore.TokenCredential consistent with the migration objectives.


118-138: Track 2 pager pattern correctly implemented.

The method properly uses the Track 2 SDK's NewListPager pattern and accumulates results. Error wrapping provides helpful context.

Note: The client is created on each fetchWebApps call. If performance profiling reveals this as a bottleneck, consider caching the client at the provider level. For most use cases, this should be fine.


173-306: Comprehensive metadata extraction implemented correctly.

The method extracts extensive metadata covering app configuration, networking, identity, and tags. Nil checks are thorough, and the use of schema.AddMetadata prevents issues with nil pointers.


308-362: Deployment slot metadata extraction is appropriate.

The method correctly extracts slot-specific metadata and links to the parent app. The hardcoded is_deployment_slot marker (line 317) is a good way to distinguish slot resources from app resources.

Note: The resource group parsing at lines 324-332 should also use parseAzureResourceID as mentioned in the earlier comment for lines 185-193.


300-300: Verified buildAzureTagString exists in pkg/providers/azure/vm.go:357; no further action needed.


146-146: No action needed: parseAzureResourceID defined with correct signature
The function in pkg/providers/azure/vm.go returns (resourceName, resourceGroup string), so discarding the first value and capturing resourceGroup is valid.

pkg/providers/azure/loadbalancer.go (1)

38-57: Concurrency pattern looks solid for Azure rate limits.

Bounded pool with mutex-guarded appends and StopAndWait is appropriate here.

Also applies to: 58-61

pkg/providers/azure/applicationgateway.go (1)

27-146: LGTM — correct Track 2 patterns and resource extraction.

Pager-based listing, PIP resolution, and metadata gating are implemented correctly.

pkg/providers/azure/aks.go (1)

140-373: AKS provider looks good overall

Track 2 usage, paging, and metadata extraction are sound. No blocking issues spotted.

Consider later enumerating LB service IPs via Kubernetes API if in-scope.

tarunKoyalwar and others added 2 commits October 14, 2025 18:10
- Fix IP version handling in publicips.go with fallback parsing
- Fix CDN custom domain to use Properties.HostName instead of Name
- Fix typo fetchResouceGroups → fetchResourceGroups
- Improve Verify() to ensure at least one subscription exists
- Remove extendedMetadata gates for DNS resources in applicationgateway and staticwebapps
- Ensure DNS resources are always emitted regardless of metadata flag

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Remove all authentication and subscription logging to maintain
backward compatibility with cloudlist 1.0.3 logging format.

Changes:
- Remove all authentication method logging from auth.go and azure.go
- Clean up unused imports (gologger in auth.go)
- Fix multiple lint issues (len() checks, unused variables)
- Fix SDK compatibility in publicips.go (DdosSettings fields)

Breaking changes: NONE
- No changes to JSON output structure
- No changes to core functionality
- Removed only newly added log lines
tarunKoyalwar

This comment was marked as spam.

@tarunKoyalwar
Copy link
Member Author

@coderabbitai resolve

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (5)
pkg/providers/azure/azure.go (1)

297-323: Verify() fix is correct

Now fetches pages and asserts at least one subscription exists before success. Good.

pkg/providers/azure/apimanagement.go (1)

46-55: Guard against empty DNS from GatewayURL

extractDNSFromURL can return "", which would append an invalid resource. Add the same guard you use for Portal/Management URLs.

Apply this diff:

-    if service.Properties.GatewayURL != nil {
-      resource := &schema.Resource{
-        Provider: providerName,
-        ID:       amp.id,
-        DNSName:  extractDNSFromURL(*service.Properties.GatewayURL),
-        Service:  amp.name(),
-        Metadata: metadata,
-      }
-      list.Append(resource)
-    }
+    if service.Properties.GatewayURL != nil {
+      gwDNS := extractDNSFromURL(*service.Properties.GatewayURL)
+      if gwDNS != "" {
+        resource := &schema.Resource{
+          Provider: providerName,
+          ID:       amp.id,
+          DNSName:  gwDNS,
+          Service:  amp.name(),
+          Metadata: metadata,
+        }
+        list.Append(resource)
+      }
+    }
pkg/providers/azure/appservice.go (3)

90-94: Log the deployment slot fetch error before continuing.

As noted in a previous review, the error from fetchDeploymentSlots is silently suppressed. While it's appropriate not to fail the entire operation, the error should be logged for observability.

Apply this diff to add error logging:

 		// Extract deployment slot hostnames
 		slots, err := asp.fetchDeploymentSlots(ctx, app)
 		if err != nil {
-			// Don't fail the entire operation, just log warning
+			gologger.Warning().Msgf("failed to fetch deployment slots for app %v: %v", ptrStringValue(app.Name), err)
 			continue
 		}

185-193: Use the parseAzureResourceID helper to extract resource group.

As noted in a previous review, this manual string parsing duplicates the logic of parseAzureResourceID used elsewhere in this file (line 146). Using the helper reduces code duplication and ensures consistent parsing.

Apply this diff:

 	// Parse resource group from ID
 	if app.ID != nil {
-		parts := strings.Split(*app.ID, "/")
-		for i, part := range parts {
-			if strings.EqualFold(part, "resourceGroups") && i+1 < len(parts) {
-				metadata["resource_group"] = parts[i+1]
-				break
-			}
-		}
+		_, resourceGroup := parseAzureResourceID(*app.ID)
+		if resourceGroup != "" {
+			metadata["resource_group"] = resourceGroup
+		}
 	}

324-332: Use the parseAzureResourceID helper to extract resource group.

As noted in a previous review, this manual string parsing duplicates the logic of parseAzureResourceID. This is the second occurrence of the same duplication (also at lines 185-193).

Apply this diff:

 	// Parse resource group from ID
 	if slot.ID != nil {
-		parts := strings.Split(*slot.ID, "/")
-		for i, part := range parts {
-			if strings.EqualFold(part, "resourceGroups") && i+1 < len(parts) {
-				metadata["resource_group"] = parts[i+1]
-				break
-			}
-		}
+		_, resourceGroup := parseAzureResourceID(*slot.ID)
+		if resourceGroup != "" {
+			metadata["resource_group"] = resourceGroup
+		}
 	}
🧹 Nitpick comments (9)
pkg/providers/azure/dns.go (3)

65-141: Consider logging skipped record types.

The implementation currently handles only A, AAAA, and CNAME records. Other DNS record types (MX, TXT, NS, SOA, SRV, etc.) are silently ignored. Consider adding a default case to log when record types are skipped to aid debugging and help users understand which records are enumerated.

Apply this diff to add logging for unhandled record types:

 			}
 		}
 	}
+	default:
+		// Log unsupported record types for debugging
+		gologger.Debug().Msgf("Skipping unsupported DNS record type: %s for record %s in zone %s", recordType, *recordSet.Name, *zone.Name)
 	}
 }

148-188: LGTM! Track 2 pager pattern correctly implemented.

Both fetchDNSZones and fetchRecordSets correctly use the Track 2 SDK pager pattern with proper error handling and context propagation.

For large DNS deployments, consider pre-allocating slices to reduce reallocations:

 func (dp *dnsProvider) fetchDNSZones(ctx context.Context) ([]*armdns.Zone, error) {
 	client, err := armdns.NewZonesClient(dp.SubscriptionID, dp.Credential, nil)
 	if err != nil {
 		return nil, fmt.Errorf("failed to create DNS zones client: %w", err)
 	}
 
-	var zones []*armdns.Zone
+	zones := make([]*armdns.Zone, 0, 32) // Pre-allocate with reasonable capacity
 	pager := client.NewListPager(nil)

Similarly for fetchRecordSets:

-	var recordSets []*armdns.RecordSet
+	recordSets := make([]*armdns.RecordSet, 0, 64)

201-209: Reduce code duplication in resource group parsing.

This manual resource group parsing duplicates the logic from lines 41-46 where parseAzureResourceID is called. Use the same helper function for consistency and maintainability.

Apply this diff to use the helper function:

 	// Parse resource group from zone ID
 	if zone.ID != nil {
-		parts := strings.Split(*zone.ID, "/")
-		for i, part := range parts {
-			if strings.EqualFold(part, "resourceGroups") && i+1 < len(parts) {
-				metadata["resource_group"] = parts[i+1]
-				break
-			}
-		}
+		_, resourceGroup := parseAzureResourceID(*zone.ID)
+		if resourceGroup != "" {
+			metadata["resource_group"] = resourceGroup
+		}
 	}
pkg/providers/azure/vm.go (3)

139-146: Consider explicit IPv6 check for clarity.

The else-if condition on line 141 assigns any non-IPv4 version to PublicIPv6 without explicitly verifying it's IPVersionIPv6. While Azure only supports IPv4/IPv6 in practice, an explicit check improves code clarity and guards against unexpected enum values.

Apply this diff to make the IPv6 check explicit:

-		} else if publicIP.Properties.PublicIPAddressVersion != nil {
-			resource.PublicIPv6 = *publicIP.Properties.IPAddress
+		} else if publicIP.Properties.PublicIPAddressVersion != nil && *publicIP.Properties.PublicIPAddressVersion == armnetwork.IPVersionIPv6 {
+			resource.PublicIPv6 = *publicIP.Properties.IPAddress
+		} else if publicIP.Properties.PublicIPAddressVersion != nil {
+			// Unexpected IP version, log and default to IPv4
+			gologger.Warning().Msgf("unexpected IP version: %v, defaulting to IPv4", *publicIP.Properties.PublicIPAddressVersion)
+			resource.PublicIPv4 = *publicIP.Properties.IPAddress
 		} else {

174-186: Document assumptions for resource ID parsing.

The parser assumes standard Azure resource ID format and uses case-insensitive matching for "resourceGroups". While this works for typical scenarios, documenting the expected format and limitations would help maintainability.

Consider adding a more detailed comment:

 // parseAzureResourceID parses an Azure resource ID and returns the resource name and resource group
 // Azure resource ID format: /subscriptions/{subId}/resourceGroups/{rgName}/providers/{provider}/{type}/{name}
+// Note: This parser handles standard ARM resource IDs. It may not work for non-standard formats
+// (e.g., classic resources, cross-tenant references, or malformed IDs).
+// Returns empty strings if resourceGroups segment or resource name cannot be extracted.
 func parseAzureResourceID(resourceID string) (resourceName, resourceGroup string) {

87-92: Use Azure SDK’s built-in parser or harden parseAzureResourceID for edge cases
Replace the custom split-and-fold logic with arm.ParseResourceID (from azcore/arm) or extend your manual parser to explicitly validate nested resource segments, trailing slashes, and malformed IDs.

pkg/providers/azure/azure.go (1)

52-58: Trim service names when parsing the CSV list

Currently, "vm, dns" (with spaces) won’t match; trim each token before lookup.

Apply this diff:

-    for _, s := range strings.Split(ss, ",") {
-      if _, ok := supportedServicesMap[s]; ok {
+    for _, raw := range strings.Split(ss, ",") {
+      s := strings.TrimSpace(raw)
+      if _, ok := supportedServicesMap[s]; ok {
         services[s] = struct{}{}
       }
     }
pkg/providers/azure/cdn.go (2)

156-165: Avoid duplicate Resource ID parsing logic

getCDNMetadata re-implements resource_group extraction; reuse parseAzureResourceID for consistency and single source of truth.


1-200: Centralize resource ID parsing in getCDNMetadata
In pkg/providers/azure/cdn.go:getCDNMetadata, replace the manual strings.Split logic for extracting "resource_group" with a call to parseAzureResourceID(*profile.ID) to reuse the existing helper and eliminate duplication.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4e2b7c2 and 3902739.

📒 Files selected for processing (14)
  • .gitignore (1 hunks)
  • pkg/providers/azure/aks.go (1 hunks)
  • pkg/providers/azure/apimanagement.go (1 hunks)
  • pkg/providers/azure/applicationgateway.go (1 hunks)
  • pkg/providers/azure/appservice.go (1 hunks)
  • pkg/providers/azure/auth.go (1 hunks)
  • pkg/providers/azure/azure.go (7 hunks)
  • pkg/providers/azure/cdn.go (1 hunks)
  • pkg/providers/azure/containerapps.go (1 hunks)
  • pkg/providers/azure/dns.go (1 hunks)
  • pkg/providers/azure/loadbalancer.go (1 hunks)
  • pkg/providers/azure/publicips.go (9 hunks)
  • pkg/providers/azure/staticwebapps.go (1 hunks)
  • pkg/providers/azure/vm.go (7 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • pkg/providers/azure/containerapps.go
  • pkg/providers/azure/staticwebapps.go
🧰 Additional context used
🧬 Code graph analysis (11)
pkg/providers/azure/publicips.go (2)
pkg/schema/schema.go (3)
  • Resource (142-163)
  • Provider (18-28)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/providers/azure/apimanagement.go (2)
pkg/schema/schema.go (4)
  • Resources (39-42)
  • NewResources (45-50)
  • Resource (142-163)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/providers/azure/cdn.go (2)
pkg/schema/schema.go (4)
  • Resources (39-42)
  • NewResources (45-50)
  • Resource (142-163)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/providers/azure/appservice.go (3)
pkg/schema/schema.go (4)
  • Resources (39-42)
  • NewResources (45-50)
  • Resource (142-163)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/schema/validate/validate.go (1)
  • DNSName (72-72)
pkg/providers/azure/dns.go (2)
pkg/schema/schema.go (4)
  • Resources (39-42)
  • NewResources (45-50)
  • Resource (142-163)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/providers/azure/auth.go (1)
pkg/schema/schema.go (2)
  • OptionBlock (195-195)
  • ErrNoSuchKey (166-168)
pkg/providers/azure/azure.go (1)
pkg/schema/schema.go (3)
  • ServiceMap (254-254)
  • OptionBlock (195-195)
  • Provider (18-28)
pkg/providers/azure/loadbalancer.go (2)
pkg/schema/schema.go (4)
  • Resources (39-42)
  • NewResources (45-50)
  • Resource (142-163)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/providers/azure/aks.go (2)
pkg/schema/schema.go (4)
  • Resources (39-42)
  • NewResources (45-50)
  • Resource (142-163)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/providers/azure/vm.go (2)
pkg/schema/schema.go (3)
  • Resource (142-163)
  • Provider (18-28)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/providers/azure/applicationgateway.go (2)
pkg/schema/schema.go (4)
  • Resources (39-42)
  • NewResources (45-50)
  • Resource (142-163)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Analyze (go)
  • GitHub Check: Lint Test
  • GitHub Check: Test Builds (1.22.x, ubuntu-latest)
  • GitHub Check: Test Builds (1.22.x, macOS-latest)
  • GitHub Check: Test Builds (1.22.x, windows-latest)
🔇 Additional comments (19)
pkg/providers/azure/dns.go (4)

1-20: LGTM! Clean Track 2 SDK structure.

The imports and dnsProvider struct correctly use Track 2 SDK types (azcore.TokenCredential, armdns package), consistent with the PR's migration objectives.


280-297: LGTM! Clean helper functions.

Both buildFQDN and extractRecordType are well-implemented:

  • buildFQDN correctly handles DNS apex records (@)
  • extractRecordType safely extracts the record type from Azure's resource type strings

271-275: Helper buildAzureTagString exists
Defined in pkg/providers/azure/vm.go at line 357.


41-46: Helper exists; no changes required. parseAzureResourceID is defined in pkg/providers/azure/vm.go.

Likely an incorrect or invalid review comment.

pkg/providers/azure/vm.go (4)

76-80: LGTM! Comprehensive nil guards for nested properties.

The defensive nil checking for vm.Properties, NetworkProfile, and NetworkInterfaces prevents potential NPEs when accessing deeply nested Track 2 SDK structures.


188-230: LGTM! Track 2 SDK patterns correctly implemented.

The migration to Track 2 SDK is well-executed:

  • Direct client creation with NewResourceGroupsClient and NewVirtualMachinesClient
  • Proper pager pattern usage with NewListPager(), pager.More(), and pager.NextPage()
  • Credential passed as azcore.TokenCredential
  • Appropriate error wrapping

232-264: LGTM! Client creation and resource fetching follow Track 2 conventions.

The functions correctly:

  • Create ARM clients with subscription ID and credential
  • Use Get() for single-resource retrieval (NICs and Public IPs)
  • Extract nested properties with nil guards
  • Return typed Track 2 SDK structs (armnetwork.InterfaceIPConfiguration, armnetwork.PublicIPAddress)

277-332: LGTM! Thorough nil checking throughout metadata extraction.

The metadata extraction handles Track 2 SDK's nested property structures with comprehensive nil guards at each level:

  • vm.Properties and subfields (HardwareProfile, OSProfile, StorageProfile, AvailabilitySet, etc.)
  • Array/slice bounds checking (Zones, etc.)
  • Proper type conversions for enums (VMSize, OSType, Identity.Type)

This defensive approach prevents NPEs when optional fields are missing.

pkg/providers/azure/auth.go (1)

35-144: Credential resolver looks solid and backward-compatible

Priority order and explicit key handling are clear; error wrapping is good. No blockers.

pkg/providers/azure/publicips.go (2)

52-66: IPv4/IPv6 assignment is robust now

Explicit enum checks with sane fallback by parsing address. Good.


98-110: Track 2 ListAll pager usage looks correct

NewPublicIPAddressesClient + NewListAllPager is the right pattern. Error wrapping is clear.

pkg/providers/azure/apimanagement.go (1)

175-180: No changes needed: NewListPager correctly lists services at subscription scope.

pkg/providers/azure/cdn.go (1)

109-113: Confirm pager method in armcdn v1.1.1
Unable to determine automatically which constructor is available; please check sdk/resourcemanager/cdn/armcdn/profiles_client.go for NewListPager versus NewListBySubscriptionPager and switch to client.NewListBySubscriptionPager(nil) if that’s the only option.

pkg/providers/azure/aks.go (2)

118-138: LGTM!

The Track 2 pager implementation correctly creates the client, iterates through pages, and wraps errors appropriately.


140-373: LGTM!

Comprehensive metadata extraction with consistent nil checks and proper use of the schema.AddMetadata helper. The enum conversions and list formatting are handled correctly throughout.

pkg/providers/azure/loadbalancer.go (3)

28-61: LGTM!

The concurrent resource group processing using a worker pool with mutex-protected aggregation is correctly implemented. Non-fatal errors are appropriately logged without failing the entire operation.


151-186: LGTM!

Both fetch methods correctly implement Track 2 SDK patterns with appropriate error handling.


188-336: LGTM!

Comprehensive metadata extraction with consistent nil checking and proper use of helpers. The enum conversions and list formatting are correctly implemented.

pkg/providers/azure/appservice.go (1)

118-138: LGTM!

Standard Track 2 pager implementation with appropriate error wrapping.

tarunKoyalwar and others added 2 commits October 14, 2025 18:24
Multiple improvements based on CodeRabbit AI review feedback:

**apimanagement.go:**
- Add guard against empty DNS when extracting gateway URL
- Ensures only non-empty hostnames are added as resources

**appservice.go:**
- Add error logging when deployment slot fetching fails
- Replace manual resource ID parsing with parseAzureResourceID helper in two locations
- Propagate error in fetchDeploymentSlots instead of silently returning nil
- Import gologger for proper error logging

**containerinstances.go:**
- Fix private IP misclassification as public IP
- Add type check before creating public IP resources
- Prevents private IPs from being incorrectly marked as public

These fixes improve error handling, code maintainability (DRY), and
resource classification accuracy.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@tarunKoyalwar

This comment was marked as resolved.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 14, 2025

✅ Actions performed

Comments resolved.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (3)
pkg/providers/azure/aks.go (1)

74-103: Remove dead scaffolding loop/comments

This loop does nothing (continues immediately) and the following comment block adds noise. Please delete.

-    // Extract LoadBalancer IPs from agent pools
-    if cluster.Properties.AgentPoolProfiles != nil {
-      for _, pool := range cluster.Properties.AgentPoolProfiles {
-        if pool.Name == nil {
-          continue
-        }
-
-        // Get public IPs from node resource group if available
-        // Note: LoadBalancer IPs are in the managed node resource group
-        // This requires additional API calls to the network resource provider
-        // We'll extract what's available from the cluster properties directly
-
-        // Extract node pool metadata if extended metadata is enabled
-        if ap.extendedMetadata {
-          // Node pools don't have direct IP exposure in the cluster object
-          // IPs are managed by Azure in the node resource group
-          continue
-        }
-      }
-    }
-
-    // Extract network profile information for potential IPs
-    // Note: LoadBalancer outbound IPs are in the managed infrastructure
-    // These are not directly exposed in the cluster API response
-    // To get LoadBalancer service IPs, we would need to:
-    // 1. Connect to the Kubernetes API using the cluster credentials
-    // 2. List services with type=LoadBalancer
-    // 3. Extract their external IPs
-    // This is beyond the scope of Azure Resource Manager API
pkg/providers/azure/storage.go (1)

236-279: Fix Track 2 Get() response handling and avoid per-iteration client creation

You’re treating Get() results as the resource and creating clients inside the loop. Track 2 returns response-wrapper types; also instantiate clients once per call.

Apply this consolidated diff:

@@
 func (sp *storageProvider) extractPrivateEndpointIPs(ctx context.Context, account *armstorage.Account) []string {
   var privateIPs []string
 
   if account.Properties == nil || account.Properties.PrivateEndpointConnections == nil {
     return privateIPs
   }
 
+  // Create clients once
+  peClient, err := armnetwork.NewPrivateEndpointsClient(sp.SubscriptionID, sp.Credential, nil)
+  if err != nil {
+    gologger.Warning().Msgf("failed to create private endpoints client: %s", err)
+    return privateIPs
+  }
+  nicClient, err := armnetwork.NewInterfacesClient(sp.SubscriptionID, sp.Credential, nil)
+  if err != nil {
+    gologger.Warning().Msgf("failed to create network interfaces client: %s", err)
+    return privateIPs
+  }
+
   for _, peConn := range account.Properties.PrivateEndpointConnections {
     if peConn.Properties == nil || peConn.Properties.PrivateEndpoint == nil || peConn.Properties.PrivateEndpoint.ID == nil {
       continue
     }
@@
-    // Fetch Private Endpoint details
-    peClient, err := armnetwork.NewPrivateEndpointsClient(sp.SubscriptionID, sp.Credential, nil)
-    if err != nil {
-      gologger.Warning().Msgf("failed to create private endpoints client: %s", err)
-      continue
-    }
-
-    pe, err := peClient.Get(ctx, peRG, peName, nil)
+    // Fetch Private Endpoint details
+    peResp, err := peClient.Get(ctx, peRG, peName, nil)
     if err != nil {
       gologger.Warning().Msgf("failed to get private endpoint %s: %s", peName, err)
       continue
     }
 
     // Extract private IPs from network interfaces
-    if pe.Properties != nil && pe.Properties.NetworkInterfaces != nil {
-      for _, nic := range pe.Properties.NetworkInterfaces {
+    if peResp.PrivateEndpoint != nil && peResp.PrivateEndpoint.Properties != nil && peResp.PrivateEndpoint.Properties.NetworkInterfaces != nil {
+      for _, nic := range peResp.PrivateEndpoint.Properties.NetworkInterfaces {
         if nic.ID == nil {
           continue
         }
@@
-        nicClient, err := armnetwork.NewInterfacesClient(sp.SubscriptionID, sp.Credential, nil)
-        if err != nil {
-          gologger.Warning().Msgf("failed to create network interfaces client: %s", err)
-          continue
-        }
-
-        nicRes, err := nicClient.Get(ctx, nicRG, nicName, nil)
+        nicResp, err := nicClient.Get(ctx, nicRG, nicName, nil)
         if err != nil {
           gologger.Warning().Msgf("failed to get network interface %s: %s", nicName, err)
           continue
         }
 
-        if nicRes.Properties != nil && nicRes.Properties.IPConfigurations != nil {
-          for _, ipConfig := range nicRes.Properties.IPConfigurations {
+        if nicResp.Interface != nil && nicResp.Interface.Properties != nil && nicResp.Interface.Properties.IPConfigurations != nil {
+          for _, ipConfig := range nicResp.Interface.Properties.IPConfigurations {
             if ipConfig.Properties != nil && ipConfig.Properties.PrivateIPAddress != nil {
               privateIPs = append(privateIPs, *ipConfig.Properties.PrivateIPAddress)
             }
           }
         }
       }
     }
   }
 
   return privateIPs
 }
pkg/providers/azure/loadbalancer.go (1)

90-93: Private IP assigned without version check.

The private IP address is unconditionally assigned to PrivateIpv4 without checking frontendIPConfig.Properties.PrivateIPAddressVersion. If the frontend uses IPv6, this will incorrectly populate the IPv4 field. The public IP handling at lines 104-111 correctly checks the version—apply the same pattern here.

Apply this diff to add IP version checking:

 				// Extract private IP (available for both public and internal LBs)
 				if frontendIPConfig.Properties.PrivateIPAddress != nil {
-					resource.PrivateIpv4 = *frontendIPConfig.Properties.PrivateIPAddress
+					if frontendIPConfig.Properties.PrivateIPAddressVersion != nil && *frontendIPConfig.Properties.PrivateIPAddressVersion == armnetwork.IPVersionIPv6 {
+						resource.PrivateIpv6 = *frontendIPConfig.Properties.PrivateIPAddress
+					} else {
+						// Default to IPv4 if not specified
+						resource.PrivateIpv4 = *frontendIPConfig.Properties.PrivateIPAddress
+					}
 				}
🧹 Nitpick comments (6)
pkg/providers/azure/storage.go (1)

461-473: Use url.Parse for robust host extraction

Safer and simpler with net/url; handles schemes, paths, and ports.

+import "net/url"
@@
 func extractHostFromURL(urlStr string) string {
-  // Remove protocol
-  urlStr = strings.TrimPrefix(urlStr, "https://")
-  urlStr = strings.TrimPrefix(urlStr, "http://")
-
-  // Remove trailing slash and path
-  if idx := strings.Index(urlStr, "/"); idx != -1 {
-    urlStr = urlStr[:idx]
-  }
-
-  return strings.TrimSpace(urlStr)
+  u, err := url.Parse(urlStr)
+  if err != nil {
+    return strings.TrimSpace(urlStr)
+  }
+  return strings.TrimSpace(u.Hostname())
 }
pkg/providers/azure/publicips.go (1)

70-84: Emit FQDN resources regardless of extendedMetadata

Currently DNS FQDN emission is gated by extendedMetadata, unlike other providers where hostnames are always emitted.

-    if pip.extendedMetadata && ip.Properties.DNSSettings != nil && ip.Properties.DNSSettings.Fqdn != nil {
+    if ip.Properties.DNSSettings != nil && ip.Properties.DNSSettings.Fqdn != nil && *ip.Properties.DNSSettings.Fqdn != "" {
       dnsResource := &schema.Resource{
         Provider: providerName,
         ID:       pip.id,
         DNSName:  *ip.Properties.DNSSettings.Fqdn,
         Service:  pip.name(),
       }
-      if metadata != nil {
+      if metadata != nil {
         dnsResource.Metadata = make(map[string]string)
         for k, v := range metadata {
           dnsResource.Metadata[k] = v
         }
       }
       list.Append(dnsResource)
     }

Would you like this applied to align with CDN/app gateway/staticwebapps behavior mentioned in the PR?

pkg/providers/azure/dns.go (2)

200-209: Prefer arm.ParseResourceID over manual string split

Use the SDK helper for correctness across ID shapes.

+import "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
@@
-  if zone.ID != nil {
-    parts := strings.Split(*zone.ID, "/")
-    for i, part := range parts {
-      if strings.EqualFold(part, "resourceGroups") && i+1 < len(parts) {
-        metadata["resource_group"] = parts[i+1]
-        break
-      }
-    }
-  }
+  if zone.ID != nil {
+    if rid, err := arm.ParseResourceID(*zone.ID); err == nil && rid.ResourceGroupName != "" {
+      metadata["resource_group"] = rid.ResourceGroupName
+    }
+  }

Also consider applying the same in GetResource (Lines 41-47).


65-141: Broaden record type coverage (TXT, MX, NS, SRV) later

Non-blocking: support additional types commonly used in zones.

pkg/providers/azure/azure.go (1)

297-322: Add success log after verification.

The verification logic is now correct and properly checks for at least one subscription. Consider adding a success log message after verification completes to confirm to users that their credentials are valid.

Apply this diff to add the log message:

 	if !found {
 		return fmt.Errorf("no subscriptions found with provided credentials")
 	}
 
+	gologger.Info().Msg("Azure credentials verified successfully")
 	return nil
 }
pkg/providers/azure/vm.go (1)

139-146: Tighten IP version check for IPv6.

Line 141 checks only for non-nil PublicIPAddressVersion without verifying it equals armnetwork.IPVersionIPv6. This means any future or unknown IP version would be treated as IPv6. Consider explicitly checking for IPv6 to make the logic more robust.

Apply this diff to tighten the check:

 				// Track 2: Check IP version
 				if publicIP.Properties.PublicIPAddressVersion != nil && *publicIP.Properties.PublicIPAddressVersion == armnetwork.IPVersionIPv4 {
 					resource.PublicIPv4 = *publicIP.Properties.IPAddress
-				} else if publicIP.Properties.PublicIPAddressVersion != nil {
+				} else if publicIP.Properties.PublicIPAddressVersion != nil && *publicIP.Properties.PublicIPAddressVersion == armnetwork.IPVersionIPv6 {
 					resource.PublicIPv6 = *publicIP.Properties.IPAddress
 				} else {
 					// Default to IPv4 if not specified
 					resource.PublicIPv4 = *publicIP.Properties.IPAddress
 				}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3902739 and 22bac2c.

📒 Files selected for processing (12)
  • pkg/providers/azure/aks.go (1 hunks)
  • pkg/providers/azure/azure.go (7 hunks)
  • pkg/providers/azure/containerapps.go (1 hunks)
  • pkg/providers/azure/containerinstances.go (1 hunks)
  • pkg/providers/azure/dns.go (1 hunks)
  • pkg/providers/azure/functions.go (1 hunks)
  • pkg/providers/azure/loadbalancer.go (1 hunks)
  • pkg/providers/azure/publicips.go (9 hunks)
  • pkg/providers/azure/staticwebapps.go (1 hunks)
  • pkg/providers/azure/storage.go (1 hunks)
  • pkg/providers/azure/trafficmanager.go (9 hunks)
  • pkg/providers/azure/vm.go (7 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • pkg/providers/azure/staticwebapps.go
  • pkg/providers/azure/functions.go
  • pkg/providers/azure/containerinstances.go
  • pkg/providers/azure/containerapps.go
🧰 Additional context used
🧬 Code graph analysis (8)
pkg/providers/azure/trafficmanager.go (2)
pkg/schema/schema.go (2)
  • Resource (142-163)
  • Provider (18-28)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/providers/azure/azure.go (1)
pkg/schema/schema.go (3)
  • ServiceMap (254-254)
  • OptionBlock (195-195)
  • Provider (18-28)
pkg/providers/azure/storage.go (2)
pkg/schema/schema.go (4)
  • Resources (39-42)
  • NewResources (45-50)
  • Resource (142-163)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/providers/azure/aks.go (2)
pkg/schema/schema.go (4)
  • Resources (39-42)
  • NewResources (45-50)
  • Resource (142-163)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/providers/azure/vm.go (2)
pkg/schema/schema.go (3)
  • Resource (142-163)
  • Provider (18-28)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/providers/azure/dns.go (2)
pkg/schema/schema.go (4)
  • Resources (39-42)
  • NewResources (45-50)
  • Resource (142-163)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/providers/azure/loadbalancer.go (2)
pkg/schema/schema.go (4)
  • Resources (39-42)
  • NewResources (45-50)
  • Resource (142-163)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/providers/azure/publicips.go (2)
pkg/schema/schema.go (3)
  • Resource (142-163)
  • Provider (18-28)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Lint Test
  • GitHub Check: Analyze (go)
  • GitHub Check: Test Builds (1.22.x, windows-latest)
  • GitHub Check: Test Builds (1.22.x, macOS-latest)
  • GitHub Check: Test Builds (1.22.x, ubuntu-latest)
🔇 Additional comments (2)
pkg/providers/azure/publicips.go (1)

52-66: IPv4/IPv6 detection with safe fallback looks good

Correctly handles nil version and infers from the address. Nice.

pkg/providers/azure/trafficmanager.go (1)

55-75: LGTM on Track 2 migration and pager usage

Client construction, ListBySubscription pager, and metadata extraction follow Track 2 patterns.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (3)
pkg/providers/azure/storage.go (1)

243-251: Fix Track 2 Get() response wrapper usage (compilation/logic bug).

armnetwork clients return response wrappers. Use resp.PrivateEndpoint and resp.Interface before accessing Properties; current code dereferences Properties on the wrapper itself.

Apply this diff:

-		pe, err := peClient.Get(ctx, peRG, peName, nil)
+		peResp, err := peClient.Get(ctx, peRG, peName, nil)
 		if err != nil {
 			gologger.Warning().Msgf("failed to get private endpoint %s: %s", peName, err)
 			continue
 		}

-		// Extract private IPs from network interfaces
-		if pe.Properties != nil && pe.Properties.NetworkInterfaces != nil {
-			for _, nic := range pe.Properties.NetworkInterfaces {
+		// Extract private IPs from network interfaces
+		if peResp.PrivateEndpoint != nil && peResp.PrivateEndpoint.Properties != nil && peResp.PrivateEndpoint.Properties.NetworkInterfaces != nil {
+			for _, nic := range peResp.PrivateEndpoint.Properties.NetworkInterfaces {
 				if nic.ID == nil {
 					continue
 				}
@@
-				nicRes, err := nicClient.Get(ctx, nicRG, nicName, nil)
+				nicResp, err := nicClient.Get(ctx, nicRG, nicName, nil)
 				if err != nil {
 					gologger.Warning().Msgf("failed to get network interface %s: %s", nicName, err)
 					continue
 				}
 
-				if nicRes.Properties != nil && nicRes.Properties.IPConfigurations != nil {
-					for _, ipConfig := range nicRes.Properties.IPConfigurations {
+				if nicResp.Interface != nil && nicResp.Interface.Properties != nil && nicResp.Interface.Properties.IPConfigurations != nil {
+					for _, ipConfig := range nicResp.Interface.Properties.IPConfigurations {
 						if ipConfig.Properties != nil && ipConfig.Properties.PrivateIPAddress != nil {
 							privateIPs = append(privateIPs, *ipConfig.Properties.PrivateIPAddress)
 						}
 					}
 				}

Also applies to: 267-279

pkg/providers/azure/aks.go (1)

74-103: Remove dead code scaffolding.

Lines 74-93 iterate over agent pools but only continue when extendedMetadata is true, performing no useful work. Lines 95-103 contain only explanatory comments about LoadBalancer IPs without any implementation. These scaffolding blocks add complexity without benefit and should be removed until the functionality is ready to implement.

pkg/providers/azure/loadbalancer.go (1)

90-93: Verify IP version before assigning to PrivateIpv4.

The private IP address is unconditionally assigned to PrivateIpv4 without checking frontendIPConfig.Properties.PrivateIPAddressVersion. If the frontend uses IPv6, this will incorrectly populate the IPv4 field. The public IP handling at lines 104-111 correctly checks the version—apply the same pattern here.

Apply this diff to add IP version checking:

 				// Extract private IP (available for both public and internal LBs)
 				if frontendIPConfig.Properties.PrivateIPAddress != nil {
-					resource.PrivateIpv4 = *frontendIPConfig.Properties.PrivateIPAddress
+					if frontendIPConfig.Properties.PrivateIPAddressVersion != nil && *frontendIPConfig.Properties.PrivateIPAddressVersion == armnetwork.IPVersionIPv6 {
+						resource.PrivateIpv6 = *frontendIPConfig.Properties.PrivateIPAddress
+					} else {
+						// Default to IPv4 if not specified
+						resource.PrivateIpv4 = *frontendIPConfig.Properties.PrivateIPAddress
+					}
 				}
🧹 Nitpick comments (9)
pkg/providers/azure/storage.go (2)

171-187: Set PrivateIpv4 vs PrivateIpv6 correctly for private endpoints.

Avoid assigning all private IPs to PrivateIpv4; classify by version.

-			for _, privateIP := range privateIPs {
+			for _, privateIP := range privateIPs {
 				var metadata map[string]string
 				if sp.extendedMetadata {
 					metadata = sp.getStorageMetadata(account)
 					if metadata != nil {
 						metadata["connection_type"] = "private_endpoint"
 					}
 				}
 
-				resource := &schema.Resource{
-					Provider:    providerName,
-					ID:          sp.id,
-					PrivateIpv4: privateIP,
-					Service:     sp.name(),
-					Metadata:    metadata,
-				}
+				resource := &schema.Resource{
+					Provider: providerName,
+					ID:       sp.id,
+					Service:  sp.name(),
+					Metadata: metadata,
+				}
+				if strings.Contains(privateIP, ":") {
+					resource.PrivateIpv6 = privateIP
+				} else {
+					resource.PrivateIpv4 = privateIP
+				}
 				list.Append(resource)
 			}

3-13: Use time.RFC3339 for creation_time (and add time import).

Consistent with vm.go and standard formats.

 import (
 	"context"
 	"fmt"
 	"strings"
+	"time"
@@
-		if props.CreationTime != nil {
-			metadata["creation_time"] = props.CreationTime.Format("2006-01-02T15:04:05Z")
-		}
+		if props.CreationTime != nil {
+			metadata["creation_time"] = props.CreationTime.Format(time.RFC3339)
+		}

Also applies to: 423-425

pkg/providers/azure/functions.go (2)

41-49: Prefer Properties.DefaultHostName over constructing from Name.

Use the actual default hostname when available; fall back to name-based only if missing.

-		// Default hostname: <function-name>.azurewebsites.net
-		var defaultHostname string
-		if app.Name != nil {
-			defaultHostname = fmt.Sprintf("%s.azurewebsites.net", *app.Name)
-		}
+		// Default hostname
+		var defaultHostname string
+		if app.Properties != nil && app.Properties.DefaultHostName != nil && *app.Properties.DefaultHostName != "" {
+			defaultHostname = *app.Properties.DefaultHostName
+		} else if app.Name != nil {
+			defaultHostname = fmt.Sprintf("%s.azurewebsites.net", *app.Name)
+		}

65-83: Avoid duplicating default hostname in custom domains (case-insensitive).

Compare case-insensitively to skip re-emitting the default hostname.

-			for _, hostname := range app.Properties.HostNames {
-				if hostname != nil && *hostname != defaultHostname {
+			for _, hostname := range app.Properties.HostNames {
+				if hostname != nil && !strings.EqualFold(*hostname, defaultHostname) {
pkg/providers/azure/publicips.go (1)

70-84: Always emit FQDN resources (don’t gate on extendedMetadata).

Hostnames are core assets; keep metadata optional but emit DNS unconditionally.

-		if pip.extendedMetadata && ip.Properties.DNSSettings != nil && ip.Properties.DNSSettings.Fqdn != nil {
+		if ip.Properties.DNSSettings != nil && ip.Properties.DNSSettings.Fqdn != nil {
 			dnsResource := &schema.Resource{
 				Provider: providerName,
 				ID:       pip.id,
 				DNSName:  *ip.Properties.DNSSettings.Fqdn,
 				Service:  pip.name(),
 			}
-			if metadata != nil {
+			if metadata != nil {
 				dnsResource.Metadata = make(map[string]string)
 				for k, v := range metadata {
 					dnsResource.Metadata[k] = v
 				}
 			}
 			list.Append(dnsResource)
 		}
pkg/providers/azure/azure.go (1)

3-12: Normalize service names and parse extended_metadata as bool.

Improves config robustness (handles spaces/case) and boolean parsing.

 import (
 	"context"
 	"fmt"
 	"strings"
+	"strconv"
@@
 	services := make(schema.ServiceMap)
 	if ss, ok := options.GetMetadata("services"); ok {
 		for _, s := range strings.Split(ss, ",") {
-			if _, ok := supportedServicesMap[s]; ok {
+			s = strings.ToLower(strings.TrimSpace(s))
+			if _, ok := supportedServicesMap[s]; ok {
 				services[s] = struct{}{}
 			}
 		}
 	}
@@
-	if extendedMetadata, ok := options.GetMetadata("extended_metadata"); ok {
-		provider.extendedMetadata = extendedMetadata == "true"
+	if extendedMetadata, ok := options.GetMetadata("extended_metadata"); ok {
+		if b, err := strconv.ParseBool(strings.TrimSpace(extendedMetadata)); err == nil {
+			provider.extendedMetadata = b
+		}
 	}

Also applies to: 52-58, 70-73

pkg/providers/azure/vm.go (2)

127-131: Classify private IP v4/v6 instead of always setting PrivateIpv4.

Avoid mislabeling IPv6 private addresses.

-				if ipConfig.Properties.PrivateIPAddress != nil {
-					resource.PrivateIpv4 = *ipConfig.Properties.PrivateIPAddress
-				}
+				if ipConfig.Properties.PrivateIPAddress != nil {
+					if strings.Contains(*ipConfig.Properties.PrivateIPAddress, ":") {
+						resource.PrivateIpv6 = *ipConfig.Properties.PrivateIPAddress
+					} else {
+						resource.PrivateIpv4 = *ipConfig.Properties.PrivateIPAddress
+					}
+				}

138-146: Infer public IP version when version is nil (consistent with publicips.go).

Defaulting to IPv4 can misclassify IPv6. Use address parsing fallback.

-				if publicIP.Properties.PublicIPAddressVersion != nil && *publicIP.Properties.PublicIPAddressVersion == armnetwork.IPVersionIPv4 {
-					resource.PublicIPv4 = *publicIP.Properties.IPAddress
-				} else if publicIP.Properties.PublicIPAddressVersion != nil {
-					resource.PublicIPv6 = *publicIP.Properties.IPAddress
-				} else {
-					// Default to IPv4 if not specified
-					resource.PublicIPv4 = *publicIP.Properties.IPAddress
-				}
+				if publicIP.Properties.PublicIPAddressVersion != nil && *publicIP.Properties.PublicIPAddressVersion == armnetwork.IPVersionIPv4 {
+					resource.PublicIPv4 = *publicIP.Properties.IPAddress
+				} else if publicIP.Properties.PublicIPAddressVersion != nil && *publicIP.Properties.PublicIPAddressVersion == armnetwork.IPVersionIPv6 {
+					resource.PublicIPv6 = *publicIP.Properties.IPAddress
+				} else {
+					if strings.Contains(*publicIP.Properties.IPAddress, ":") {
+						resource.PublicIPv6 = *publicIP.Properties.IPAddress
+					} else {
+						resource.PublicIPv4 = *publicIP.Properties.IPAddress
+					}
+				}
pkg/providers/azure/trafficmanager.go (1)

87-96: Consider using parseAzureResourceID helper for consistency.

The manual resource group extraction using strings.Split duplicates logic that exists in the parseAzureResourceID helper used elsewhere in the codebase (e.g., appservice.go line 191, aks.go line 142). Using the helper would improve consistency and reduce maintenance burden.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3902739 and 9742ef8.

📒 Files selected for processing (14)
  • pkg/providers/azure/aks.go (1 hunks)
  • pkg/providers/azure/apimanagement.go (1 hunks)
  • pkg/providers/azure/appservice.go (1 hunks)
  • pkg/providers/azure/azure.go (7 hunks)
  • pkg/providers/azure/containerapps.go (1 hunks)
  • pkg/providers/azure/containerinstances.go (1 hunks)
  • pkg/providers/azure/dns.go (1 hunks)
  • pkg/providers/azure/functions.go (1 hunks)
  • pkg/providers/azure/loadbalancer.go (1 hunks)
  • pkg/providers/azure/publicips.go (9 hunks)
  • pkg/providers/azure/staticwebapps.go (1 hunks)
  • pkg/providers/azure/storage.go (1 hunks)
  • pkg/providers/azure/trafficmanager.go (9 hunks)
  • pkg/providers/azure/vm.go (7 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • pkg/providers/azure/staticwebapps.go
  • pkg/providers/azure/containerapps.go
  • pkg/providers/azure/dns.go
🧰 Additional context used
🧬 Code graph analysis (10)
pkg/providers/azure/publicips.go (2)
pkg/schema/schema.go (3)
  • Resource (142-163)
  • Provider (18-28)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/providers/azure/vm.go (2)
pkg/schema/schema.go (3)
  • Resource (142-163)
  • Provider (18-28)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/providers/azure/trafficmanager.go (2)
pkg/schema/schema.go (2)
  • Resource (142-163)
  • Provider (18-28)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/providers/azure/storage.go (2)
pkg/schema/schema.go (4)
  • Resources (39-42)
  • NewResources (45-50)
  • Resource (142-163)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/providers/azure/appservice.go (2)
pkg/schema/schema.go (4)
  • Resources (39-42)
  • NewResources (45-50)
  • Resource (142-163)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/providers/azure/apimanagement.go (2)
pkg/schema/schema.go (4)
  • Resources (39-42)
  • NewResources (45-50)
  • Resource (142-163)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/providers/azure/containerinstances.go (2)
pkg/schema/schema.go (4)
  • Resources (39-42)
  • NewResources (45-50)
  • Resource (142-163)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/providers/azure/aks.go (2)
pkg/schema/schema.go (4)
  • Resources (39-42)
  • NewResources (45-50)
  • Resource (142-163)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/providers/azure/azure.go (1)
pkg/schema/schema.go (3)
  • ServiceMap (254-254)
  • OptionBlock (195-195)
  • Provider (18-28)
pkg/providers/azure/functions.go (2)
pkg/schema/schema.go (4)
  • Resources (39-42)
  • NewResources (45-50)
  • Resource (142-163)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Analyze (go)
  • GitHub Check: Lint Test
  • GitHub Check: Test Builds (1.22.x, macOS-latest)
  • GitHub Check: Test Builds (1.22.x, ubuntu-latest)
  • GitHub Check: Test Builds (1.22.x, windows-latest)
🔇 Additional comments (8)
pkg/providers/azure/apimanagement.go (4)

46-58: Past review concern addressed - empty DNS guard in place.

The empty DNS guard (if gatewayDNS != "") at line 48 correctly prevents creating invalid resources when extractDNSFromURL returns an empty string, resolving the previous review finding.


170-190: LGTM!

The Track 2 SDK pager pattern is correctly implemented with proper error handling and client creation.


192-369: LGTM!

The metadata extraction is comprehensive with proper nil checks throughout. The extractDNSFromURL helper correctly normalizes URLs by stripping protocol, path, and port components.


124-164: IPv6 detection not required for APIM IP addresses
Azure API Management does not support IPv6 for public or private endpoints, so the existing IPv4-only extraction logic is correct.

pkg/providers/azure/appservice.go (3)

91-100: LGTM! Error logging for deployment slots added.

The error from fetchDeploymentSlots is now properly logged with the app name and error message, addressing the previous review feedback. The operation continues without failing, which is appropriate for non-critical deployment slot retrieval.


190-195: LGTM! Using parseAzureResourceID helper.

The code now uses the parseAzureResourceID helper function instead of manual string parsing, eliminating code duplication and ensuring consistent parsing across the codebase.


167-170: LGTM! Error properly propagated.

The error from pagination is now correctly propagated instead of being suppressed with nil, enabling proper observability and debugging when deployment slot listing fails.

pkg/providers/azure/containerinstances.go (1)

45-100: LGTM! Private IP classification correctly implemented.

The code now properly gates the public IP resource emission by checking if IPAddress.Type == Private, preventing VNet-integrated container groups from being incorrectly reported as public. The private IP handling (lines 84-100) also correctly verifies the IP type before emitting private resources, avoiding duplication and misclassification.

Critical fixes based on CodeRabbit AI review:

**storage.go:**
- Fix private endpoint IP classification to detect IPv6 vs IPv4
- Use string-based detection (`:` in address) to properly classify IPs
- Prevents IPv6 addresses from being incorrectly stored as IPv4

**vm.go:**
- Add IP version checking for private IPs similar to public IPs
- Check PrivateIPAddressVersion field and default to IPv4 if unspecified
- Fix Track 2 SDK response wrapper usage in fetchIPConfigList
- Change from `nicRes.Properties` to `nicResp.Interface.Properties`
- Properly unwrap Interface from response wrapper with nil checks

These fixes ensure accurate IP version classification and correct SDK usage
patterns for Track 2 Azure SDK.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@tarunKoyalwar

This comment was marked as resolved.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
pkg/providers/azure/storage.go (1)

241-290: Track 2 response wrappers not handled correctly.

The Track 2 SDK Get methods return response wrappers, not the resources directly:

  • PrivateEndpointsClient.Get returns PrivateEndpointsClientGetResponse with a .PrivateEndpoint field
  • InterfacesClient.Get returns InterfacesClientGetResponse with an .Interface field

The current code incorrectly accesses properties as if the raw resource is returned.

Based on learnings

Apply this diff to fix the response wrapper handling:

-		pe, err := peClient.Get(ctx, peRG, peName, nil)
+		peResp, err := peClient.Get(ctx, peRG, peName, nil)
 		if err != nil {
 			gologger.Warning().Msgf("failed to get private endpoint %s: %s", peName, err)
 			continue
 		}
 
 		// Extract private IPs from network interfaces
-		if pe.Properties != nil && pe.Properties.NetworkInterfaces != nil {
-			for _, nic := range pe.Properties.NetworkInterfaces {
+		if peResp.PrivateEndpoint.Properties != nil && peResp.PrivateEndpoint.Properties.NetworkInterfaces != nil {
+			for _, nic := range peResp.PrivateEndpoint.Properties.NetworkInterfaces {
 				if nic.ID == nil {
 					continue
 				}
 
 				nicName, nicRG := parseAzureResourceID(*nic.ID)
 				if nicName == "" || nicRG == "" {
 					continue
 				}
 
 				nicClient, err := armnetwork.NewInterfacesClient(sp.SubscriptionID, sp.Credential, nil)
 				if err != nil {
 					gologger.Warning().Msgf("failed to create network interfaces client: %s", err)
 					continue
 				}
 
-				nicRes, err := nicClient.Get(ctx, nicRG, nicName, nil)
+				nicResp, err := nicClient.Get(ctx, nicRG, nicName, nil)
 				if err != nil {
 					gologger.Warning().Msgf("failed to get network interface %s: %s", nicName, err)
 					continue
 				}
 
-				if nicRes.Properties != nil && nicRes.Properties.IPConfigurations != nil {
-					for _, ipConfig := range nicRes.Properties.IPConfigurations {
+				if nicResp.Interface.Properties != nil && nicResp.Interface.Properties.IPConfigurations != nil {
+					for _, ipConfig := range nicResp.Interface.Properties.IPConfigurations {
 						if ipConfig.Properties != nil && ipConfig.Properties.PrivateIPAddress != nil {
 							privateIPs = append(privateIPs, *ipConfig.Properties.PrivateIPAddress)
 						}
 					}
 				}
 			}
 		}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9742ef8 and 01efdf8.

📒 Files selected for processing (2)
  • pkg/providers/azure/storage.go (1 hunks)
  • pkg/providers/azure/vm.go (7 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
pkg/providers/azure/vm.go (2)
pkg/schema/schema.go (3)
  • Resource (142-163)
  • Provider (18-28)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
pkg/providers/azure/storage.go (2)
pkg/schema/schema.go (4)
  • Resources (39-42)
  • NewResources (45-50)
  • Resource (142-163)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
🪛 GitHub Actions: 🔨 Release Test
pkg/providers/azure/vm.go

[error] 252-252: invalid operation: nicResp.Interface != nil (mismatched types armnetwork.Interface and untyped nil). goreleaser release failed: build for windows_amd64_v1 exited with status 1.

🪛 GitHub Check: release-test
pkg/providers/azure/vm.go

[failure] 252-252:
invalid operation: nicResp.Interface != nil (mismatched types armnetwork.Interface and untyped nil)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Test Builds (1.22.x, ubuntu-latest)
  • GitHub Check: Test Builds (1.22.x, macOS-latest)
  • GitHub Check: Test Builds (1.22.x, windows-latest)
  • GitHub Check: Analyze (go)
  • GitHub Check: Lint Test
🔇 Additional comments (7)
pkg/providers/azure/vm.go (1)

128-137: Private IP version checking implemented correctly.

The code now properly checks the IP version before assigning the private IP address, addressing the concern raised in the previous review. The implementation mirrors the public IP handling pattern (lines 145-153) and defaults to IPv4 when the version is not specified.

pkg/providers/azure/storage.go (6)

1-26: LGTM!

The provider structure follows the established pattern and correctly uses Track 2 SDK types.


28-198: LGTM!

The resource discovery logic is well-structured. IPv6 vs IPv4 detection (lines 186-191) is correctly implemented using strings.Contains(privateIP, ":"), which addresses the past review feedback.


200-221: LGTM!

The pager pattern is correctly implemented following Track 2 SDK best practices.


292-464: LGTM!

The metadata extraction is comprehensive and handles nil values properly throughout. Good use of the schema.AddMetadata helper for optional fields.


466-478: LGTM!

The URL parsing logic is straightforward and handles the expected Azure endpoint URL formats correctly.


480-491: LGTM!

The metadata deep copy implementation is correct and handles nil inputs appropriately.

Fixed type error in fetchIPConfigList where InterfacesClientGetResponse
embeds Interface directly rather than as a pointer field.

**Issue:**
- Lint error: "invalid operation: nicResp.Interface != nil
  (mismatched types armnetwork.Interface and untyped nil)"
- InterfacesClientGetResponse embeds Interface as a value type, not pointer

**Fix:**
- Changed from `nicResp.Interface.Properties` to `nicResp.Properties`
- InterfacesClientGetResponse embeds Interface, so Properties is accessible directly
- Removed unnecessary Interface nil check that was causing type error

This fixes all lint and build failures in CI/CD workflows.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@tarunKoyalwar

This comment was marked as resolved.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
pkg/providers/azure/vm.go (1)

246-257: Track 2 response handling is correct.

The code properly unwraps the NIC response by accessing nicResp.Properties directly, which works because the Track 2 response type embeds the Interface fields. The previous build failures have been resolved.

Optionally, clarify the comment to better reflect the actual access pattern:

-	// Track 2: Response embeds Interface directly, access Properties
+	// Track 2: Access Interface properties directly from response
 	if nicResp.Properties != nil && nicResp.Properties.IPConfigurations != nil {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 01efdf8 and 1cf53bc.

📒 Files selected for processing (1)
  • pkg/providers/azure/vm.go (7 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
pkg/providers/azure/vm.go (2)
pkg/schema/schema.go (3)
  • Resource (142-163)
  • Provider (18-28)
  • AddMetadata (329-333)
pkg/providers/azure/azure.go (1)
  • Provider (28-34)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Test Builds (1.22.x, macOS-latest)
  • GitHub Check: Test Builds (1.22.x, ubuntu-latest)
  • GitHub Check: Test Builds (1.22.x, windows-latest)
  • GitHub Check: Lint Test
  • GitHub Check: Analyze (go)
🔇 Additional comments (5)
pkg/providers/azure/vm.go (5)

128-137: Private IP version handling correctly implemented.

The IP version checking for private addresses now mirrors the public IP pattern (lines 145-153), properly assigning IPv6 addresses to PrivateIpv6 and defaulting to IPv4. This resolves the previous concern about unconditional IPv4 assignment.


179-193: Resource ID parsing handles expected cases well.

The parseAzureResourceID function correctly extracts resource names and groups from standard Azure resource ID formats. Callers appropriately handle parse failures (empty returns) by logging warnings and skipping the resource, providing good defensive handling.


195-272: Track 2 SDK migration executed correctly.

All resource fetching functions properly implement Track 2 patterns:

  • Credential-based authentication (azcore.TokenCredential)
  • Pager patterns for list operations (NewListPager, pager.More(), NextPage())
  • Direct client constructors (New*Client)
  • Proper response unwrapping

Error handling is appropriate with context-aware error wrapping.


274-363: Metadata extraction is comprehensive and safe.

The getVMMetadata function thoroughly extracts VM properties with proper nil checking throughout. Good use of the schema.AddMetadata helper and appropriate type conversions for Track 2 enum types.


1-373: Track 2 SDK migration successfully completed.

This file demonstrates a thorough and correct migration from Azure Track 1 to Track 2 SDK:

  • ✅ All authentication uses azcore.TokenCredential
  • ✅ Pager patterns replace legacy iterators
  • ✅ Response unwrapping follows Track 2 conventions
  • ✅ IP version handling for both public and private addresses
  • ✅ Comprehensive nil checking and error handling
  • ✅ Previous review issues resolved

The implementation maintains backward compatibility while enabling future service expansions as outlined in the PR objectives.

@tarunKoyalwar tarunKoyalwar marked this pull request as draft October 14, 2025 12:39
Removed explicit `use_workload_identity` and `use_managed_identity` flags
in favor of Azure SDK's industry-standard DefaultAzureCredential chain.

**What Changed:**
- Removed config options: `use_workload_identity`, `use_managed_identity`,
  `managed_identity_id`
- Simplified authentication to leverage DefaultAzureCredential's built-in chain
- Reduced configuration complexity while maintaining all functionality

**New Authentication Priority (follows Azure SDK best practices):**
1. Azure CLI (if `use_cli_auth: true`) - Explicit CLI-only (backward compatible)
2. Client Certificate (if `certificate_path` provided) - Explicit
3. Client Secret (if credentials provided) - Explicit (backward compatible)
4. DefaultAzureCredential - Auto-detection chain:
   a. Environment variables (AZURE_TENANT_ID, AZURE_CLIENT_ID, etc.)
   b. Workload Identity (Kubernetes/GitHub Actions OIDC)
   c. Managed Identity (Azure VMs, App Service, AKS)
   d. Azure CLI (az login)
   e. Azure PowerShell

**Benefits:**
- Simpler configuration (no explicit flags for workload/managed identity)
- Works seamlessly across environments (local, CI/CD, Azure)
- Follows Azure SDK recommended practices
- Reduces user error from misconfigured identity types
- Automatic detection means less configuration needed

**Backward Compatibility:**
- All existing configs continue to work (client secret, certificates, CLI)
- Only removes undocumented/new flags (use_workload_identity, use_managed_identity)

**Example - Managed Identity (BEFORE):**
```yaml
azure:
  - use_managed_identity: true
    azure_subscription_id: xxx
```

**Example - Managed Identity (AFTER - simpler!):**
```yaml
azure:
  - azure_subscription_id: xxx
    # DefaultAzureCredential automatically detects managed identity!
```

This aligns with how modern Azure tooling (Terraform azurerm provider,
Azure CLI, etc.) handles authentication.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@tarunKoyalwar tarunKoyalwar marked this pull request as ready for review October 14, 2025 12:41
@tarunKoyalwar
Copy link
Member Author

Simplified Azure Authentication (Industry Best Practices)

Refactored authentication to follow Azure SDK recommended patterns by removing explicit workload identity and managed identity flags.

What Changed

Removed Configuration Options:

  • use_workload_identity - No longer needed
  • use_managed_identity - No longer needed
  • managed_identity_id - No longer needed

Why Remove These?

These flags were unnecessary because Azure's DefaultAzureCredential already handles workload identity and managed identity detection automatically. Having explicit flags:

  1. Added configuration complexity
  2. Required users to know their deployment environment
  3. Duplicated functionality already in the SDK
  4. Went against Azure SDK best practices

New Authentication Chain (Industry Standard)

The simplified flow now follows Azure SDK recommendations:

# 1. Explicit credentials (when provided)
azure:
  - azure_client_id: xxx
    azure_client_secret: xxx
    azure_tenant_id: xxx
    azure_subscription_id: xxx

# 2. Or force CLI-only (backward compatible)
azure:
  - use_cli_auth: true
    azure_subscription_id: xxx

# 3. Or use DefaultAzureCredential (auto-detection)
azure:
  - azure_subscription_id: xxx
    # No credentials needed! Automatically tries:
    # - Environment variables
    # - Workload Identity (K8s/GitHub Actions)
    # - Managed Identity (Azure VMs/App Service)
    # - Azure CLI (az login)
    # - Azure PowerShell

Benefits

Simpler Configuration - Just provide subscription ID, let SDK figure out auth
Works Everywhere - Same config works locally, in CI/CD, and on Azure
Less User Error - No need to know if you're using workload identity vs managed identity
Industry Standard - Matches how Terraform azurerm, Azure CLI, and other tools work
Zero Config for Azure Resources - Running on Azure VM/App Service? Just works.

Real-World Examples

Local Development:

az login
cloudlist -pc config.yaml  # Uses your CLI session automatically

GitHub Actions (with OIDC):

- uses: azure/login@v1
  with:
    client-id: ${{ vars.AZURE_CLIENT_ID }}
    tenant-id: ${{ vars.AZURE_TENANT_ID }}
    subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
    # No secrets needed! Uses GitHub's OIDC token
    
- run: cloudlist -pc config.yaml  # Workload identity detected automatically

Azure VM/App Service:

# Enable managed identity on the VM/App Service
# Config file only needs subscription ID
azure:
  - azure_subscription_id: xxx

No code changes needed between environments!

Backward Compatibility

All existing authentication methods continue to work:

  • ✅ Client secret authentication (most common)
  • ✅ Certificate authentication
  • ✅ Azure CLI authentication (use_cli_auth: true)
  • ✅ Environment variables

Only the new (undocumented) use_workload_identity and use_managed_identity flags are removed, and their functionality is now automatic via DefaultAzureCredential.

Pushed in commit c6a0bfa.

🤖 Generated with Claude Code

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
go.mod (1)

142-150: Remove explicit version pins for indirect golang.org/x/ modules*
These x/* entries are only pulled in transitively; delete them from go.mod and run go mod tidy to let MVS pick appropriate versions. Keep pins only if you depend on a specific fix.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c6a0bfa and 84389d2.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (1)
  • go.mod (5 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Lint Test
  • GitHub Check: Test Builds (1.22.x, macOS-latest)
  • GitHub Check: Analyze (go)
  • GitHub Check: Test Builds (1.22.x, windows-latest)
  • GitHub Check: Test Builds (1.22.x, ubuntu-latest)
  • GitHub Check: release-test
🔇 Additional comments (4)
go.mod (4)

169-185: Version alignment across Azure SDK

Minor versions across arm*, azcore (v1.19.1), and sdk/internal (v1.11.2) look compatible. If you hit mixed-version issues, bump the few arm* packages still at v1.0.0. Otherwise, OK as-is.


200-201: Auth deps: only jwt/v5 present
Verified no github.com/golang-jwt/jwt/v4 imports remain; project uses github.com/golang-jwt/jwt/v5 v5.3.0.


3-5: go.mod: use go 1.24 and ensure CI Go versions

-go 1.24.0
+go 1.24

Ensure CI runners run Go ≥1.21 (to support toolchain) and align with go1.24.2.


169-185: No Track 1 remnants detected. Confirmed no go-autorest/adal/authorizer imports; all auth uses azcore.TokenCredential & azidentity.

@ehsandeep ehsandeep merged commit f089e44 into dev Oct 14, 2025
9 checks passed
@ehsandeep ehsandeep deleted the feat-azure-integrations branch October 14, 2025 18:33
visnetodev pushed a commit to visnetotest/cloudlist that referenced this pull request Dec 7, 2025
…tdiscovery#710)

* refactor(azure): migrate to Track 2 SDK with enhanced authentication

Migrates Azure provider from deprecated Track 1 SDK to modern Track 2 SDK
while maintaining 100% backward compatibility with existing configurations.

Key Changes:
- Replaced autorest.Authorizer with azcore.TokenCredential
- Implemented comprehensive authentication system supporting 7 methods
- Updated all service implementations (VM, Public IPs, Traffic Manager)
- Subscription discovery now uses Track 2 pager pattern

Authentication Methods (all backward compatible):
1. Azure CLI (use_cli_auth: true) - explicit, Track 1 compatible
2. Client Secret (tenant_id/client_id/client_secret) - Track 1 compatible
3. Client Certificate (certificate_path) - new, enterprise security
4. Managed Identity (use_managed_identity: true) - new, Azure-hosted apps
5. Workload Identity (use_workload_identity: true) - new, K8s/GitHub Actions
6. DefaultAzureCredential - new, auto-detection fallback when no explicit auth

Smart Default Behavior:
- Explicit auth methods (use_cli_auth, credentials) work exactly as before
- New: No explicit auth → DefaultAzureCredential auto-detects in order:
  Environment vars → Workload Identity → Managed Identity → Azure CLI

Benefits:
✅ 100% backward compatible - existing configs unchanged
✅ Non-breaking changes - all authentication methods preserved
✅ Modern SDK - actively maintained, receives security updates
✅ Foundation ready - prepared for new services (Container Apps, etc.)
✅ Smart defaults - minimal config for new users

🤖 Generated with Claude Code (https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>

* added missing services

* push changes

* fix(azure): address CodeRabbit review comments

- Fix IP version handling in publicips.go with fallback parsing
- Fix CDN custom domain to use Properties.HostName instead of Name
- Fix typo fetchResouceGroups → fetchResourceGroups
- Improve Verify() to ensure at least one subscription exists
- Remove extendedMetadata gates for DNS resources in applicationgateway and staticwebapps
- Ensure DNS resources are always emitted regardless of metadata flag

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>

* refactor(azure): remove extra logging for backward compatibility

Remove all authentication and subscription logging to maintain
backward compatibility with cloudlist 1.0.3 logging format.

Changes:
- Remove all authentication method logging from auth.go and azure.go
- Clean up unused imports (gologger in auth.go)
- Fix multiple lint issues (len() checks, unused variables)
- Fix SDK compatibility in publicips.go (DdosSettings fields)

Breaking changes: NONE
- No changes to JSON output structure
- No changes to core functionality
- Removed only newly added log lines

* resolve all lint S009 errors

* fix(azure): address remaining CodeRabbit review comments

Multiple improvements based on CodeRabbit AI review feedback:

**apimanagement.go:**
- Add guard against empty DNS when extracting gateway URL
- Ensures only non-empty hostnames are added as resources

**appservice.go:**
- Add error logging when deployment slot fetching fails
- Replace manual resource ID parsing with parseAzureResourceID helper in two locations
- Propagate error in fetchDeploymentSlots instead of silently returning nil
- Import gologger for proper error logging

**containerinstances.go:**
- Fix private IP misclassification as public IP
- Add type check before creating public IP resources
- Prevents private IPs from being incorrectly marked as public

These fixes improve error handling, code maintainability (DRY), and
resource classification accuracy.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>

* fix(azure): add IPv6 detection and fix Track 2 response handling

Critical fixes based on CodeRabbit AI review:

**storage.go:**
- Fix private endpoint IP classification to detect IPv6 vs IPv4
- Use string-based detection (`:` in address) to properly classify IPs
- Prevents IPv6 addresses from being incorrectly stored as IPv4

**vm.go:**
- Add IP version checking for private IPs similar to public IPs
- Check PrivateIPAddressVersion field and default to IPv4 if unspecified
- Fix Track 2 SDK response wrapper usage in fetchIPConfigList
- Change from `nicRes.Properties` to `nicResp.Interface.Properties`
- Properly unwrap Interface from response wrapper with nil checks

These fixes ensure accurate IP version classification and correct SDK usage
patterns for Track 2 Azure SDK.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>

* fix(azure): correct Track 2 SDK response type handling in vm.go

Fixed type error in fetchIPConfigList where InterfacesClientGetResponse
embeds Interface directly rather than as a pointer field.

**Issue:**
- Lint error: "invalid operation: nicResp.Interface != nil
  (mismatched types armnetwork.Interface and untyped nil)"
- InterfacesClientGetResponse embeds Interface as a value type, not pointer

**Fix:**
- Changed from `nicResp.Interface.Properties` to `nicResp.Properties`
- InterfacesClientGetResponse embeds Interface, so Properties is accessible directly
- Removed unnecessary Interface nil check that was causing type error

This fixes all lint and build failures in CI/CD workflows.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>

* refactor(azure): simplify auth to use DefaultAzureCredential chain

Removed explicit `use_workload_identity` and `use_managed_identity` flags
in favor of Azure SDK's industry-standard DefaultAzureCredential chain.

**What Changed:**
- Removed config options: `use_workload_identity`, `use_managed_identity`,
  `managed_identity_id`
- Simplified authentication to leverage DefaultAzureCredential's built-in chain
- Reduced configuration complexity while maintaining all functionality

**New Authentication Priority (follows Azure SDK best practices):**
1. Azure CLI (if `use_cli_auth: true`) - Explicit CLI-only (backward compatible)
2. Client Certificate (if `certificate_path` provided) - Explicit
3. Client Secret (if credentials provided) - Explicit (backward compatible)
4. DefaultAzureCredential - Auto-detection chain:
   a. Environment variables (AZURE_TENANT_ID, AZURE_CLIENT_ID, etc.)
   b. Workload Identity (Kubernetes/GitHub Actions OIDC)
   c. Managed Identity (Azure VMs, App Service, AKS)
   d. Azure CLI (az login)
   e. Azure PowerShell

**Benefits:**
- Simpler configuration (no explicit flags for workload/managed identity)
- Works seamlessly across environments (local, CI/CD, Azure)
- Follows Azure SDK recommended practices
- Reduces user error from misconfigured identity types
- Automatic detection means less configuration needed

**Backward Compatibility:**
- All existing configs continue to work (client secret, certificates, CLI)
- Only removes undocumented/new flags (use_workload_identity, use_managed_identity)

**Example - Managed Identity (BEFORE):**
```yaml
azure:
  - use_managed_identity: true
    azure_subscription_id: xxx
```

**Example - Managed Identity (AFTER - simpler!):**
```yaml
azure:
  - azure_subscription_id: xxx
    # DefaultAzureCredential automatically detects managed identity!
```

This aligns with how modern Azure tooling (Terraform azurerm provider,
Azure CLI, etc.) handles authentication.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>

---------

Co-authored-by: Claude <[email protected]>
Co-authored-by: Sandeep Singh <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Azure: Track 1 SDK deprecated, limiting service expansion

2 participants