Skip to content

fix: run interactive Azure context setup in unified azure.yaml adoption path#8933

Open
trangevi wants to merge 6 commits into
mainfrom
trangevi/issue-8922-azd-ai-agent-init-m-azure-yaml-skips-int-c30848
Open

fix: run interactive Azure context setup in unified azure.yaml adoption path#8933
trangevi wants to merge 6 commits into
mainfrom
trangevi/issue-8922-azd-ai-agent-init-m-azure-yaml-skips-int-c30848

Conversation

@trangevi

@trangevi trangevi commented Jul 1, 2026

Copy link
Copy Markdown
Member

Motivation

When azd ai agent init -m <azure.yaml> points at a unified Foundry azure.yaml, the command scaffolds the project but returns early without running subscription selection, Foundry project setup, or model deployment verification. Users are left with an environment that cannot provision without manual configuration.

Approach

This PR adds two capabilities to the unified azure.yaml adoption path (runInitFromAzureYaml):

Phase 1 -- Subscription and Foundry project setup:
Extracts the shared subscription + Foundry project selection logic from configureModelChoice's !hasModelResources branch into a standalone configureFoundryProject helper. Both the agent-manifest path and the unified azure.yaml adoption path now call this helper, eliminating duplication and ensuring the adoption path runs the same interactive flow.

Phase 2 -- Model deployment verification:
After project selection, parses model deployments from the azure.yaml and verifies each one against the selected Foundry project's existing deployments. For each declared deployment, the user can:

  • Use an existing matching deployment -- removes it from azure.yaml (provisioning won't re-create it)
  • Deploy as specified -- keeps it in azure.yaml for provisioning
  • Choose a different model -- full catalog browse via PromptAiDeployment
  • Skip -- removes from azure.yaml entirely

The first selected deployment is persisted as AZURE_AI_MODEL_DEPLOYMENT_NAME (same env var as the manifest path).

In --no-prompt mode, existing matches are auto-used and unmatched models are auto-deployed.

Key design decisions

  • configureFoundryProject returns a foundryProjectSetupResult with Credential and FoundryProjectInfo, allowing callers to chain dependent operations (like listing deployments).
  • YAML parsing uses typed structs with explicit yaml tags on project.Deployment rather than map[string]any, so field mapping is a compile-time contract.
  • The azure.yaml is updated via SetServiceConfigValue with path deployments -- same mechanism used by setServiceUses for the uses: field.

Files changed

  • init_foundry_project_setup.go (new) -- extracted configureFoundryProject helper
  • init.go -- refactored !hasModelResources branch to delegate to the helper
  • init_adopt.go -- wired both phases into runInitFromAzureYaml; added deployment parsing + verification logic
  • init_adopt_test.go -- unit tests for foundryDeployments parser
  • project/config.go -- added yaml struct tags to Deployment types

Fixes: #8922

trangevi and others added 5 commits July 1, 2026 15:38
…on path

When 'azd ai agent init -m <azure.yaml>' points at a unified Foundry
azure.yaml, the adoption path now runs subscription selection and
Foundry project configuration (existing vs new) after scaffolding,
matching the agent-manifest flow.

Extracts the shared subscription + Foundry project selection logic from
configureModelChoice's !hasModelResources branch into a standalone
configureFoundryProject helper. Both the adoption path and the
agent-manifest path now call this helper, eliminating duplication.

Fixes #8922

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…project

After selecting a Foundry project, the adoption path now checks each model
deployment declared in the azure.yaml against existing deployments in the
project. For each deployment, the user can:

- Use an existing matching deployment (removes it from azure.yaml)
- Deploy as specified (keeps it for provisioning)
- Choose a different model (full catalog + deployment prompt)
- Skip (removes from azure.yaml)

The first selected deployment's name is persisted as
AZURE_AI_MODEL_DEPLOYMENT_NAME (same env var as the manifest path).

In --no-prompt mode, existing matches are auto-used and unmatched models
are auto-deployed.

Closes #8922

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Tests cover:
- foundryDeployments: single/multiple deployments, missing sections,
  non-project hosts, empty/malformed content, partial fields
- stringField: correct type, wrong type, missing, empty
- intField: int, float64, wrong type, missing

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace map[string]any manual extraction with typed structs
(azureYamlServices, azureYamlService) that unmarshal directly into
project.Deployment. This gives strong typing at parse time and removes
the stringField/intField helpers.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Makes the YAML field mapping an explicit contract rather than relying
on yaml.v3's lowercase-field-name fallback behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions github-actions Bot added the ext-agents azure.ai.agents extension label Jul 1, 2026
@trangevi trangevi marked this pull request as ready for review July 1, 2026 23:52
@trangevi trangevi requested a review from JeffreyCA as a code owner July 1, 2026 23:52
Copilot AI review requested due to automatic review settings July 1, 2026 23:52
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

📋 Prioritization Note

Thanks for the contribution! The linked issue isn't in the current milestone yet.
Thank you for logging this issue; our team is reviewing it. If you need urgent prioritization, tag @RickWinter and @kristenwomack to let us know.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 fixes issue #8922, where azd ai agent init -m <azure.yaml> pointed at a unified Foundry azure.yaml scaffolded the project but returned early without running any interactive Azure setup (subscription selection, Foundry project selection, model verification), leaving an environment that could not provision. It wires the interactive setup into the unified adoption path (runInitFromAzureYaml) so it matches the agent-manifest flow.

Changes:

  • Extracts the shared subscription + Foundry project selection logic out of configureModelChoice's !hasModelResources branch into a new reusable configureFoundryProject helper (returning Credential + FoundryProject), and delegates to it from init.go.
  • Adds a model-deployment verification phase to the adoption path: parses deployments from azure.yaml, compares them against the selected project's existing deployments, and lets the user use-existing / deploy / choose-different / skip (auto-resolved under --no-prompt), rewriting azure.yaml and persisting AZURE_AI_MODEL_DEPLOYMENT_NAME.
  • Adds explicit yaml struct tags to project.Deployment/DeploymentModel/DeploymentSku and a table-driven test for the new foundryDeployments parser.

Reviewed changes

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

Show a summary per file
File Description
cli/azd/extensions/azure.ai.agents/internal/project/config.go Adds yaml tags to Deployment types (additive; matches yaml.v3 default lowercasing).
cli/azd/extensions/azure.ai.agents/internal/cmd/init.go Refactors the !hasModelResources branch to delegate to configureFoundryProject.
cli/azd/extensions/azure.ai.agents/internal/cmd/init_foundry_project_setup.go New file; the extracted configureFoundryProject helper (project-id / deferred / no-prompt / interactive modes).
cli/azd/extensions/azure.ai.agents/internal/cmd/init_adopt.go Wires subscription/Foundry setup and deployment verification into runInitFromAzureYaml; adds parsing, prompting, and azure.yaml rewrite logic.
cli/azd/extensions/azure.ai.agents/internal/cmd/init_adopt_test.go Tests for foundryDeployments; adds two skipped placeholder stubs for removed helpers.

Key findings:

  • Critical: the "rewrite azure.yaml" guard compares only list lengths (init_adopt.go:747). The "Choose a different model" flow replaces a deployment without changing the count, so a single-model manifest where the user swaps the model never gets rewritten — provisioning keeps the original model while AZURE_AI_MODEL_DEPLOYMENT_NAME points at the new one.
  • Nit: two always-skipped placeholder tests for removed helpers add no coverage and should be deleted.

}

// Update the azure.yaml if the kept deployments differ from the original.
if len(deploymentsToKeep) != len(deploymentEntries) || len(deploymentsToKeep) == 0 {
Comment on lines +420 to +428
func TestStringField(t *testing.T) {
// stringField was removed — this test is no longer needed.
t.Skip("stringField helper removed in favor of typed YAML unmarshal")
}

func TestIntField(t *testing.T) {
// intField was removed — this test is no longer needed.
t.Skip("intField helper removed in favor of typed YAML unmarshal")
}

@jongio jongio left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The extraction of configureFoundryProject is a clean refactor that removes real duplication. The deployment verification flow for the adoption path is well-structured and the typed YAML parsing is a good choice over map[string]any. Two issues to address before merging.

  1. The azure.yaml rewrite guard has a logic gap when the user swaps a model (count stays the same but content changes).
  2. Non-deterministic service assignment for replacement deployments that don't match any original entry.

}

// Update the azure.yaml if the kept deployments differ from the original.
if len(deploymentsToKeep) != len(deploymentEntries) || len(deploymentsToKeep) == 0 {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This guard compares only list lengths:

if len(deploymentsToKeep) != len(deploymentEntries) || len(deploymentsToKeep) == 0 {

When the user picks "Choose a different model", the replacement is appended to deploymentsToKeep (same count as original), so this condition is false and the rewrite is skipped. The azure.yaml keeps the old model while AZURE_AI_MODEL_DEPLOYMENT_NAME points to the new one.

A content-aware comparison would fix this. For example, track whether any deployment was actually modified:

Suggested change
if len(deploymentsToKeep) != len(deploymentEntries) || len(deploymentsToKeep) == 0 {
deploymentsChanged := len(deploymentsToKeep) != len(deploymentEntries) || len(deploymentsToKeep) == 0
if !deploymentsChanged {
for i, kept := range deploymentsToKeep {
if kept.Name != deploymentEntries[i].Deployment.Name ||
kept.Model.Name != deploymentEntries[i].Deployment.Model.Name {
deploymentsChanged = true
break
}
}
}
if deploymentsChanged {

Note: the order in deploymentsToKeep may not match deploymentEntries order, so a set-based comparison (or a simple modified flag set in the "change" branch of verifyAzureYamlDeployments) might be more reliable.

}
if !found {
// New model chosen — assign to the first service.
for svc := range byService {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

When a replacement deployment from "Choose a different model" doesn't match any original entry by name or model name, it's assigned to whichever service the map iterator returns first:

for svc := range byService {
    byService[svc] = append(byService[svc], dep)
    break
}

Go map iteration is non-deterministic. For a multi-service azure.yaml this could assign the replacement to the wrong service across runs. Consider tracking which service originally owned the replaced deployment (e.g., by pairing the original entry.ServiceName with each kept deployment in verifyAzureYamlDeployments) so the replacement inherits the correct service.

func TestIntField(t *testing.T) {
// intField was removed — this test is no longer needed.
t.Skip("intField helper removed in favor of typed YAML unmarshal")
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

These always-skipped stubs (TestStringField, TestIntField) don't test anything. They'll show as passing in CI which masks the fact that no coverage exists for this area. Delete them entirely; the git history preserves the context of why they were removed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ext-agents azure.ai.agents extension

Projects

None yet

Development

Successfully merging this pull request may close these issues.

azd ai agent init -m <azure.yaml> skips interactive environment setup (subscription, Foundry project, model)

3 participants