Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Jan 13, 2026

Users were selecting regions where project services aren't available, causing provision failures (e.g., Static Web Apps unavailable in France Central).

Implementation

Resource Type Extraction (pkg/azure/arm_template.go)

  • Parse compiled ARM templates to extract resource types from resources[] array
  • Returns deduplicated list like ["Microsoft.App/containerApps", "Microsoft.DBforPostgreSQL/flexibleServers"]

Provider Availability Check (pkg/account/subscriptions.go)

  • Query Azure Provider APIs per location to verify resource type registration and availability
  • Filter locations to those supporting ALL required resource types
  • Best-effort with error logging - API failures don't block user

Integration (pkg/infra/provisioning/bicep/)

  • Extract resource types during EnsureEnv() after Bicep compilation
  • Pass to location prompt via new PromptLocationWithResourceTypes() method
  • Works alongside existing allowedValues and quota filtering

Interface Extensions

  • Manager.GetLocationsWithFilter(resourceTypes []string) - filtered location retrieval
  • Prompter.PromptLocationWithResourceTypes(resourceTypes []string) - filtered prompting
  • EnsureSubscriptionAndLocationOptions.ResourceTypes - provisioning options

Example

Project using Container Apps + PostgreSQL:

// Bicep compiles to ARM template
resourceTypes := azure.ExtractResourceTypes(armTemplate)
// ["Microsoft.App/containerApps", "Microsoft.DBforPostgreSQL/flexibleServers"]

// Only ~30 compatible locations shown instead of 60+ total
locations := manager.GetLocationsWithFilter(ctx, subId, resourceTypes)

Notes

  • Backward compatible: when resourceTypes is nil/empty, no filtering occurs
  • Bicepparam mode skipped (requires subscription/location before compilation)
  • Invalid resource types logged and skipped
  • Provider API errors logged but don't fail operation

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • code.cloudfoundry.org
    • Triggering command: /update-job-proxy /update-job-proxy DROP bin/bash test -e cef98f5.0 /opt/hostedtoolc-e /bin/test rvice-availabili/usr/bin/git -buildtags /home/REDACTED/.do--global test -e oot_CA_2022.pem git est 1db302536c17e6f4/usr/sbin/iptables les ache/go/1.25.5/x-t test (dns block)
  • dario.cat
    • Triggering command: /update-job-proxy /update-job-proxy DROP bin/bash test -e cef98f5.0 /opt/hostedtoolc-e /bin/test rvice-availabili/usr/bin/git -buildtags /home/REDACTED/.do--global test -e oot_CA_2022.pem git est 1db302536c17e6f4/usr/sbin/iptables les ache/go/1.25.5/x-t test (dns block)
  • go.googlesource.com
    • Triggering command: /update-job-proxy /update-job-proxy DROP bin/bash test -e cef98f5.0 /opt/hostedtoolc-e /bin/test rvice-availabili/usr/bin/git -buildtags /home/REDACTED/.do--global test -e oot_CA_2022.pem git est 1db302536c17e6f4/usr/sbin/iptables les ache/go/1.25.5/x-t test (dns block)
  • go.opentelemetry.io
    • Triggering command: /update-job-proxy /update-job-proxy DROP bin/bash test -e cef98f5.0 /opt/hostedtoolc-e /bin/test rvice-availabili/usr/bin/git -buildtags /home/REDACTED/.do--global test -e oot_CA_2022.pem git est 1db302536c17e6f4/usr/sbin/iptables les ache/go/1.25.5/x-t test (dns block)
  • go.uber.org
    • Triggering command: /update-job-proxy /update-job-proxy DROP bin/bash test -e cef98f5.0 /opt/hostedtoolc-e /bin/test rvice-availabili/usr/bin/git -buildtags /home/REDACTED/.do--global test -e oot_CA_2022.pem git est 1db302536c17e6f4/usr/sbin/iptables les ache/go/1.25.5/x-t test (dns block)
  • google.golang.org
    • Triggering command: /update-job-proxy /update-job-proxy DROP bin/bash test -e cef98f5.0 /opt/hostedtoolc-e /bin/test rvice-availabili/usr/bin/git -buildtags /home/REDACTED/.do--global test -e oot_CA_2022.pem git est 1db302536c17e6f4/usr/sbin/iptables les ache/go/1.25.5/x-t test (dns block)
  • gopkg.in
    • Triggering command: /update-job-proxy /update-job-proxy DROP bin/bash test -e cef98f5.0 /opt/hostedtoolc-e /bin/test rvice-availabili/usr/bin/git -buildtags /home/REDACTED/.do--global test -e oot_CA_2022.pem git est 1db302536c17e6f4/usr/sbin/iptables les ache/go/1.25.5/x-t test (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Original prompt

This section details on the original issue you should resolve

<issue_title>Filter Region list by provider/service availability being used in project</issue_title>
<issue_description>Findings are showing that there are unsuccessful provision attempts due to services/providers not available in the region selected for a service that is in the project.

We are already filtering the available regions by subscription, we can add the services in the project as an additional filter to further reduce the list.

There are some of the templates that attempt to do this via the allowed parameter in the bicep files, but this is a manual list and has room for error.

Proposed solution -

Extend Existing Method with Optional Filtering
A backwards-compatible approach would be to add optional parameters:

// ...existing code...

// LocationFilterOptions provides filtering options for location queries
type LocationFilterOptions struct {
    ResourceProviders []string // Filter by resource provider availability
    ServiceTypes      []string // Filter by specific service types
    SkipQuotaCheck    bool     // Skip quota availability checks
}

// ListSubscriptionLocationsWithFilter lists physical locations with optional filtering
func (s *SubscriptionsService) ListSubscriptionLocationsWithFilter(
    ctx context.Context, 
    subscriptionId string, 
    tenantId string,
    options *LocationFilterOptions,
) ([]Location, error) {
    client, err := s.createSubscriptionsClient(ctx, tenantId)
    if err != nil {
        return nil, err
    }

    locations := []Location{}
    pager := client.NewListLocationsPager(subscriptionId, nil)

    for pager.More() {
        page, err := pager.NextPage(ctx)
        if err != nil {
            return nil, fmt.Errorf("failed getting next page of locations: %w", err)
        }

        for _, location := range page.LocationListResult.Value {
            // Only include physical locations
            if *location.Metadata.RegionType == "Physical" &&
                !compare.PtrValueEquals(location.Metadata.PhysicalLocation, "") {
                
                locationName := *location.Name
                
                // Apply filters if specified
                if options != nil {
                    if len(options.ResourceProviders) > 0 {
                        supported, err := s.checkProviderAvailability(ctx, subscriptionId, tenantId, locationName, options.ResourceProviders)
                        if err != nil || !supported {
                            continue
                        }
                    }
                    
                    if len(options.ServiceTypes) > 0 {
                        supported, err := s.checkServiceAvailability(ctx, subscriptionId, tenantId, locationName, options.ServiceTypes)
                        if err != nil || !supported {
                            continue
                        }
                    }
                }

                displayName := convert.ToValueWithDefault(location.DisplayName, locationName)
                regionalDisplayName := convert.ToValueWithDefault(location.RegionalDisplayName, displayName)

                locations = append(locations, Location{
                    Name:                locationName,
                    DisplayName:         displayName,
                    RegionalDisplayName: regionalDisplayName,
                })
            }
        }
    }

    sort.Slice(locations, func(i, j int) bool {
        return locations[i].RegionalDisplayName < locations[j].RegionalDisplayName
    })

    return locations, nil
}

Integration with Existing Prompt System
You would also need to update the prompt system to use the filtered locations. This would involve modifying pkg/azureutil/location.go

// Add new function that accepts service filters
func PromptLocationWithServiceFilter(
    ctx context.Context,
    subscriptionId string,
    message string,
    help string,
    console input.Console,
    accountManager account.Manager,
    shouldDisplay func(account.Location) bool,
    defaultSelectedLocation *string,
    serviceTypes []string, // New parameter for service filtering
) (string, error) {
    // Get filtered locations instead of all locations
    allLocations, err := accountManager.GetLocationsForServices(ctx, subscriptionId, serviceTypes)
    if err != nil {
        return "", fmt.Errorf("listing locations: %w", err)
    }

    // Rest of the function remains the same...
}

We would have to parse the bicep files for the services.</issue_description>

Comments on the Issue (you are @copilot in this section)

@weikanglim @spboyer Do you have a couple of a quick examples in mind?

I suspect what this ends up looking, in the case of a single location being used across multiple resources, could look like:

@metadata{
  azd:{
    // these resources use this same location variable
    resour...

</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes Azure/azure-dev#5424

<!-- START COPILOT CODING AGENT TIPS -->
---

💬 We'd love your input! Share your thoughts on Copilot coding agent in our [2 minute survey](https://gh.io/copilot-coding-agent-survey).

Copilot AI and others added 4 commits January 13, 2026 16:18
Co-authored-by: spboyer <7681382+spboyer@users.noreply.github.com>
Co-authored-by: spboyer <7681382+spboyer@users.noreply.github.com>
Co-authored-by: spboyer <7681382+spboyer@users.noreply.github.com>
Co-authored-by: spboyer <7681382+spboyer@users.noreply.github.com>
Copilot AI changed the title [WIP] Filter region list by service availability in project Filter location prompts by resource provider availability Jan 13, 2026
Copilot AI requested a review from spboyer January 13, 2026 16:33
@spboyer spboyer marked this pull request as ready for review January 14, 2026 17:03
Copilot AI review requested due to automatic review settings January 14, 2026 17:03
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR implements location filtering based on Azure resource provider availability to prevent provisioning failures when users select regions where required services are unavailable. The implementation extracts resource types from compiled ARM templates and filters location prompts to only show regions that support all required resource types.

Changes:

  • Adds ARM template parsing to extract resource types from compiled Bicep templates
  • Implements Azure Provider API integration to check resource type availability per location
  • Updates location prompting flow to filter by resource availability alongside existing quota filtering
  • Maintains backward compatibility by only applying filters when resource types are provided

Reviewed changes

Copilot reviewed 12 out of 13 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
cli/azd/pkg/azure/arm_template.go Adds ExtractResourceTypes function to parse ARM templates and extract unique resource types
cli/azd/pkg/azure/arm_template_test.go Comprehensive unit tests for ARM template resource type extraction with various scenarios
cli/azd/pkg/account/subscriptions.go Implements resource type availability checking via Azure Provider APIs with location filtering
cli/azd/pkg/account/subscriptions_manager.go Adds ListLocationsWithFilter method to manager layer
cli/azd/pkg/account/manager.go Extends Manager interface with GetLocationsWithFilter method
cli/azd/pkg/infra/provisioning/manager.go Adds ResourceTypes field to options and integrates filtered location prompting
cli/azd/pkg/infra/provisioning/bicep/bicep_provider.go Extracts resource types during EnsureEnv and stores for parameter prompting
cli/azd/pkg/infra/provisioning/bicep/prompt.go Updates location prompts to use resource type filtering
cli/azd/pkg/prompt/prompter.go Adds PromptLocationWithResourceTypes method to Prompter interface
cli/azd/pkg/azureutil/location.go Implements PromptLocationWithResourceTypeFilter with internal routing logic
cli/azd/test/mocks/mockaccount/mock_manager.go Updates mock to implement new GetLocationsWithFilter method
cli/azd/extensions/azure.ai.finetune/internal/utils/environment.go Minor whitespace formatting fix
cli/azd/extensions/azure.ai.finetune/go.mod Moves github.com/sethvargo/go-retry from indirect to direct dependency
Comments suppressed due to low confidence (2)

cli/azd/pkg/infra/provisioning/bicep/bicep_provider.go:148

  • In bicepparam mode, location prompting happens before compilation (line 144-147), but resource types are only extracted after compilation (line 156-162). This means bicepparam mode users won't benefit from resource type filtering since p.extractedResourceTypes will be empty when EnsureSubscriptionAndLocation is called. The PR description notes this limitation, but the implementation should either: 1) Pass p.extractedResourceTypes to EnsureSubscriptionAndLocation options for bicepparam mode (it will be empty but makes intent clear), or 2) Add a TODO comment explaining why bicepparam mode doesn't get filtering.
	if p.mode == bicepparamMode {
		if err := provisioning.EnsureSubscriptionAndLocation(
			ctx, p.envManager, p.env, p.prompters, provisioning.EnsureSubscriptionAndLocationOptions{}); err != nil {
			return err
		}
	}

cli/azd/pkg/account/subscriptions.go:214

  • Direct fmt.Printf calls bypass the structured logging system. Use the log package or a structured logger instead. This is inconsistent with the telemetry and logging infrastructure used elsewhere in azd.
			fmt.Printf(
				"warning: skipping invalid resource type format '%s' (expected 'Provider/Type')\n",
				resourceType)

if err != nil {
// Log error but continue with other locations to provide best-effort filtering.
// If all locations fail, an empty list will be returned, prompting the user to check permissions.
fmt.Printf("warning: failed to check resource availability for location %s: %v\n", location.Name, err)
Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

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

Direct fmt.Printf calls bypass the structured logging system. Use the log package or a structured logger instead. This is inconsistent with the telemetry and logging infrastructure used elsewhere in azd.

This issue also appears in the following locations of the same file:

  • line 212

Copilot uses AI. Check for mistakes.
}

// Check resource type availability for each location
filteredLocations := []Location{}
Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

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

When the capacity is unknown but likely to be less than len(allLocations), consider using make([]Location, 0, len(allLocations)) to pre-allocate the slice with capacity to avoid reallocations during append operations.

Copilot uses AI. Check for mistakes.
Comment on lines +216 to +218
var templateWithResources struct {
Resources []ArmTemplateResource `json:"resources"`
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This wouldn't work for any nested Bicep modules yet, which would require recursively looking at [resources == Microsoft.Resources/deployments].properties.template.resources

Comment on lines +224 to +226
// Use a map to track unique resource types
uniqueTypes := make(map[string]struct{})
for _, resource := range templateWithResources.Resources {
Copy link
Contributor

Choose a reason for hiding this comment

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

This wouldn't handle existing resources, which aren't being created or updated by the current ARM template

Comment on lines +175 to +177
for _, location := range allLocations {
supported, err := s.checkResourceTypesAvailability(
ctx, subscriptionId, tenantId, location.Name, options.ResourceTypes)
Copy link
Contributor

Choose a reason for hiding this comment

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

This is going to be real expensive. For each location, we are querying every single resource type to see if the location should be filtered, i.e. Number of queries = number of locations x number of resource types

ctx, p.env.GetSubscriptionId(), msg, func(loc account.Location) bool {
return locationParameterFilterImpl(allowedLocations, loc)
}, defaultPromptValue(param))
}, defaultPromptValue(param), p.extractedResourceTypes)
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not following the logic here entirely. This deserves a closer look

We are saying: For all location parameters in the Bicep (which could be used arbitarily inside the Bicep), ensure the location prompted works with ALL the resource types that are present in the Bicep (based on the current logic in subscriptions.go).

Current problems I'm seeing:

  1. The location can be arbitrarily used, it could be associated to no resources at all
  2. The constraint is currently an &&, i.e. the location must work with ALL resource types. However, in practice, the location may only be used for a single resource type, and that resource type should be filtered instead.

If we were to relax 2 to an ||condition, it may be not restrictive enough.

Copy link
Contributor

Choose a reason for hiding this comment

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

An alternative suggestion here is to add resourceType explicitly as a metadata to filter down the location

}

// Extract resource types from compiled template for location filtering
resourceTypes, err := azure.ExtractResourceTypes(compileResult.RawArmTemplate)
Copy link
Member

Choose a reason for hiding this comment

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

The benefits of this change for users getting errors during provision comes with a tax for other users on every call to provision.

I also wonder if we are linking location parameters to the resource using it and not applying the filter for all resources to every location parameter prompt. If not, we could potentially break scenarios where people is defining allowed locations for different location parameters.

IMO, this strategy should be added either as an extension or a new azd command (even maybe as an argument/flag for azd provision) to calculate and analyze regions - returning a report for the user based on the resources discovered within the infrastructure files. Then users would use the report to set allowed-values in the infra template or know what location to use for a template w/o allowed-list

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.

4 participants