Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,23 @@ func setEnvValue(ctx context.Context, azdClient *azdext.AzdClient, envName, key,
return nil
}

// getEnvValue retrieves a single environment variable value. Returns empty string if not found.
func getEnvValue(ctx context.Context, azdClient *azdext.AzdClient, envName, key string) (string, error) {
envValues, err := azdClient.Environment().GetValues(ctx, &azdext.GetEnvironmentRequest{
Name: envName,
})
if err != nil {
return "", fmt.Errorf("failed to get environment values: %w", err)
}

for _, kv := range envValues.KeyValues {
if kv.Key == key {
return kv.Value, nil
}
}
return "", nil
}

// projectResourceIdRegex is the precompiled regex for parsing Foundry project ARM resource IDs.
var projectResourceIdRegex = regexp.MustCompile(
`^/subscriptions/([^/]+)/resourceGroups/([^/]+)/providers/Microsoft\.CognitiveServices/accounts/([^/]+)/projects/([^/]+)$`,
Expand Down Expand Up @@ -661,11 +678,18 @@ func loadAzureContext(
envValueMap[value.Key] = value.Value
}

// Prefer AZURE_AI_DEPLOYMENTS_LOCATION for model/project operations;
// fall back to AZURE_LOCATION for backward compatibility.
location := envValueMap["AZURE_AI_DEPLOYMENTS_LOCATION"]
if location == "" {
location = envValueMap["AZURE_LOCATION"]
}

return &azdext.AzureContext{
Scope: &azdext.AzureScope{
TenantId: envValueMap["AZURE_TENANT_ID"],
SubscriptionId: envValueMap["AZURE_SUBSCRIPTION_ID"],
Location: envValueMap["AZURE_LOCATION"],
Location: location,
},
Resources: []string{},
}, nil
Expand Down Expand Up @@ -770,11 +794,19 @@ func ensureLocation(
}

if azureContext.Scope.Location != "" && locationAllowed(azureContext.Scope.Location, allowedLocations) {
if err := setEnvValue(ctx, azdClient, envName, "AZURE_AI_DEPLOYMENTS_LOCATION", azureContext.Scope.Location); err != nil {
return err
}
// Defensively seed AZURE_LOCATION (resource group location) if not already set,
// so that downstream provisioning always has a valid location.
if existing, _ := getEnvValue(ctx, azdClient, envName, "AZURE_LOCATION"); existing == "" {
return setEnvValue(ctx, azdClient, envName, "AZURE_LOCATION", azureContext.Scope.Location)
}
return nil
}
Comment thread
v1212 marked this conversation as resolved.
if azureContext.Scope.Location != "" {
fmt.Printf("%s", output.WithWarningFormat(
"The current AZURE_LOCATION '%s' is not supported for this agent setup. Please choose a different location.\n",
"The current location '%s' is not supported for this agent setup. Please choose a different location.\n",
azureContext.Scope.Location,
))
azureContext.Scope.Location = ""
Expand All @@ -789,7 +821,13 @@ func ensureLocation(

azureContext.Scope.Location = locationName

return setEnvValue(ctx, azdClient, envName, "AZURE_LOCATION", azureContext.Scope.Location)
// Set both AZURE_LOCATION (resource group) and AZURE_AI_DEPLOYMENTS_LOCATION (model/project resources)
// to the same value by default. AZURE_AI_DEPLOYMENTS_LOCATION can be changed independently later
// (e.g. during model selection) without affecting the resource group location.
if err := setEnvValue(ctx, azdClient, envName, "AZURE_LOCATION", azureContext.Scope.Location); err != nil {
return err
}
return setEnvValue(ctx, azdClient, envName, "AZURE_AI_DEPLOYMENTS_LOCATION", azureContext.Scope.Location)
}

// ensureSubscriptionAndLocation ensures both subscription and location are set.
Expand Down Expand Up @@ -1148,8 +1186,17 @@ func selectFoundryProject(

// Set location from the selected project
azureContext.Scope.Location = selectedProject.Location
if err := setEnvValue(ctx, azdClient, envName, "AZURE_LOCATION", selectedProject.Location); err != nil {
return nil, fmt.Errorf("failed to set AZURE_LOCATION: %w", err)
if err := setEnvValue(ctx, azdClient, envName, "AZURE_AI_DEPLOYMENTS_LOCATION", selectedProject.Location); err != nil {
return nil, fmt.Errorf("failed to set AZURE_AI_DEPLOYMENTS_LOCATION: %w", err)
}
// Seed AZURE_LOCATION (used for resource group) only if not already set.
// If the user already has an existing RG in a different region, we must not
// overwrite it — ARM cannot change the location of an existing resource group.
currentLocation, _ := getEnvValue(ctx, azdClient, envName, "AZURE_LOCATION")
Comment thread
v1212 marked this conversation as resolved.
if currentLocation == "" {
if err := setEnvValue(ctx, azdClient, envName, "AZURE_LOCATION", selectedProject.Location); err != nil {
return nil, fmt.Errorf("failed to set AZURE_LOCATION: %w", err)
}
}

// Configure all Foundry project environment variables
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,11 @@ func (a *modelSelector) updateEnvLocation(ctx context.Context, selectedLocation

_, err := a.azdClient.Environment().SetValue(ctx, &azdext.SetEnvRequest{
EnvName: envName,
Key: "AZURE_LOCATION",
Key: "AZURE_AI_DEPLOYMENTS_LOCATION",
Value: selectedLocation,
})
Comment thread
v1212 marked this conversation as resolved.
if err != nil {
return fmt.Errorf("failed to update AZURE_LOCATION in azd environment: %w", err)
return fmt.Errorf("failed to update AZURE_AI_DEPLOYMENTS_LOCATION in azd environment: %w", err)
}

if a.azureContext == nil {
Expand All @@ -97,7 +97,7 @@ func (a *modelSelector) updateEnvLocation(ctx context.Context, selectedLocation
}
a.azureContext.Scope.Location = selectedLocation

fmt.Println(output.WithSuccessFormat("Updated AZURE_LOCATION to '%s' in your azd environment.", selectedLocation))
fmt.Println(output.WithSuccessFormat("Updated AZURE_AI_DEPLOYMENTS_LOCATION to '%s' in your azd environment.", selectedLocation))
return nil
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,3 +262,58 @@ func TestPersistFirstDeploymentName(t *testing.T) {
})
}
}

func TestUpdateEnvLocation(t *testing.T) {
t.Parallel()

tests := []struct {
name string
selectedLocation string
existingContext *azdext.AzureContext
wantLocation string // expected azureContext.Scope.Location after call
}{
{
name: "sets AZURE_AI_DEPLOYMENTS_LOCATION and updates azureContext",
selectedLocation: "westus2",
existingContext: &azdext.AzureContext{Scope: &azdext.AzureScope{Location: "eastus"}},
wantLocation: "westus2",
},
{
name: "nil azureContext gets initialized",
selectedLocation: "swedencentral",
existingContext: nil,
wantLocation: "swedencentral",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

envName := "test-env"
envServer := &testEnvironmentServiceServer{
values: map[string]map[string]string{
envName: {},
},
}
azdClient := newTestAzdClient(t, envServer, &testWorkflowServiceServer{})

ms := &modelSelector{
azdClient: azdClient,
environment: &azdext.Environment{Name: envName},
azureContext: tt.existingContext,
}

err := ms.updateEnvLocation(t.Context(), tt.selectedLocation)
require.NoError(t, err)

// Verify env var was persisted
assert.Equal(t, tt.selectedLocation, envServer.values[envName]["AZURE_AI_DEPLOYMENTS_LOCATION"])

// Verify azureContext was updated
require.NotNil(t, ms.azureContext)
require.NotNil(t, ms.azureContext.Scope)
assert.Equal(t, tt.wantLocation, ms.azureContext.Scope.Location)
})
}
}
Loading