Skip to content

feat(foundry): eject infrastructure for existing projects - #9683

Open
Zhijie Huang (hund030) wants to merge 5 commits into
Azure:mainfrom
hund030:feature/existing-project-infra-eject
Open

feat(foundry): eject infrastructure for existing projects#9683
Zhijie Huang (hund030) wants to merge 5 commits into
Azure:mainfrom
hund030:feature/existing-project-infra-eject

Conversation

@hund030

Copy link
Copy Markdown
Contributor

Summary

Existing Foundry projects can now eject editable Bicep or Terraform with azd ai agent init --infra, instead of being limited to opaque brownfield provisioning. The generated infrastructure references the existing account and project without taking ownership of them.

Design and UX

  • --infra generates a root infra/ layout for Foundry-only projects or an isolated infra/foundry layer when infrastructure already exists.
  • Embedded and ejected Bicep use the same existing-project resource graph; Terraform generates an equivalent mode-specific graph.
  • Generation preserves the ACR decision from init: create, reuse and connect, use an existing connection, or omit ACR.
  • Generated templates manage only declared project children and adjunct resources. Existing accounts, projects, registries, and connections remain references.
  • Eject is staged, never overwrites collisions, and refuses repeat eject so user edits remain protected.
  • Provisioning validates that azure.yaml, AZURE_AI_PROJECT_ID, and FOUNDRY_PROJECT_ENDPOINT identify the same project.
  • azd down removes only ownership-verified adjunct resources and their matching ACR connection; reused resources remain untouched.

Changes

  • azure.ai.agents init: add existing-project Bicep and Terraform eject flows, layered-project migration, mode-specific ACR generation, and collision-safe installation.
  • microsoft.foundry provider: unify embedded and on-disk existing-project provisioning, preserve endpoint-only compatibility, align planned outputs, and harden preview, state, and teardown behavior.
  • synthesis templates: replace the brownfield templates with shared existing-project ARM/Bicep modules and editable Terraform templates.
  • dependency outputs: scope connection readiness to the active Foundry project across embedded and ejected infrastructure.

Manual Validation

  • Initialized a hosted agent against a disposable existing Foundry project and validated embedded, ejected Bicep, and ejected Terraform flows.
  • Compiled generated Bicep, ran terraform validate, and completed preview and provision for both IaC formats without modifying the reused account or project.
  • Provisioned create-mode ACR infrastructure, confirmed the dedicated resource group, registry, role assignment, connection, and environment outputs, then verified azd down removed only those owned resources.
  • Verified already-connected and endpoint-only/prebuilt-image paths, endpoint mismatch rejection, idempotent preview, repeat-eject refusal, and preservation of the existing project binding after cleanup.

- unify existing-project provisioning across embedded and on-disk graphs

- generate mode-specific Bicep and Terraform ACR infrastructure
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 2 pipeline(s).
19 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

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

Adds editable Bicep and Terraform infrastructure eject flows for existing Foundry projects while preserving ownership boundaries.

Changes:

  • Adds existing-project IaC templates and ACR modes.
  • Aligns provisioning, validation, outputs, and teardown behavior.
  • Expands eject, synthesis, and dependency tests.

Reviewed changes

Copilot reviewed 62 out of 64 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
azure.ai.projects/.../terraform/outputs.tf.tmpl Reorders connection outputs.
azure.ai.projects/.../terraform/container-registry.tf Adds greenfield ACR resources.
azure.ai.projects/.../terraform-existing-project/variables.tf Defines existing-project inputs.
azure.ai.projects/.../terraform-existing-project/provider.tf Configures cross-subscription providers.
azure.ai.projects/.../terraform-existing-project/outputs.tf.tmpl Emits existing-project outputs.
azure.ai.projects/.../terraform-existing-project/main.tf Manages model deployments.
azure.ai.projects/.../terraform-existing-project/container-registry-reuse.tf Reuses ACR with role assignment.
azure.ai.projects/.../terraform-existing-project/container-registry-create.tf Creates adjunct ACR infrastructure.
azure.ai.projects/.../terraform-existing-project/container-registry-connect.tf Connects an existing ACR.
azure.ai.projects/.../terraform-existing-project/connections.tf Manages declared connections.
azure.ai.projects/.../modules/foundry-project.bicep Adds shared project-child module.
azure.ai.projects/.../modules/container-registry.bicep Adds shared ACR module.
azure.ai.projects/.../modules/container-registry-eject.bicep.tmpl Adds mode-specific eject module.
azure.ai.projects/.../templates/main.bicep Adds connection endpoint output.
azure.ai.projects/.../templates/main.arm.json Regenerates compiled ARM.
azure.ai.projects/.../templates/existing-project.bicep Adds embedded existing-project graph.
azure.ai.projects/.../templates/existing-project.arm.json Adds compiled existing-project ARM.
azure.ai.projects/.../templates/existing-project-eject.bicep.tmpl Adds editable Bicep graph.
azure.ai.projects/.../templates/brownfield.bicep Removes legacy brownfield template.
azure.ai.projects/.../templates/brownfield.arm.json Removes legacy compiled ARM.
azure.ai.projects/.../synthesis/templates_embed.go Embeds new template sets.
azure.ai.projects/.../synthesis/synthesizer.go Adds existing-project synthesis.
azure.ai.projects/.../synthesis/synthesizer_test.go Updates template embedding tests.
azure.ai.projects/.../synthesis/schema_test.go Updates ARM drift checks.
azure.ai.projects/.../synthesis/parity_test.go Tightens byte parity checks.
azure.ai.projects/.../provisioning/resource_group_location_check.go Checks create-mode adjunct groups.
azure.ai.projects/.../resource_group_location_check_validate_test.go Tests adjunct-group validation.
azure.ai.projects/.../provisioning/ondisk_template.go Filters host parameters by template.
azure.ai.projects/.../provisioning/ondisk_template_test.go Tests parameter filtering.
azure.ai.projects/.../foundry_provisioning_provider_test.go Expands existing-project lifecycle tests.
azure.ai.projects/.../foundry_provisioning_provider_brownfield_acr_test.go Removes obsolete brownfield tests.
azure.ai.projects/.../exterrors/codes.go Adds connection operation codes.
azure.ai.agents/.../terraform/outputs.tf.tmpl Reorders connection outputs.
azure.ai.agents/.../terraform/container-registry.tf Adds greenfield ACR resources.
azure.ai.agents/.../terraform-existing-project/variables.tf Defines existing-project inputs.
azure.ai.agents/.../terraform-existing-project/provider.tf Configures cross-subscription providers.
azure.ai.agents/.../terraform-existing-project/outputs.tf.tmpl Emits existing-project outputs.
azure.ai.agents/.../terraform-existing-project/main.tf Manages model deployments.
azure.ai.agents/.../terraform-existing-project/container-registry-reuse.tf Reuses ACR with role assignment.
azure.ai.agents/.../terraform-existing-project/container-registry-create.tf Creates adjunct ACR infrastructure.
azure.ai.agents/.../terraform-existing-project/container-registry-connect.tf Connects an existing ACR.
azure.ai.agents/.../terraform-existing-project/connections.tf Manages declared connections.
azure.ai.agents/.../modules/foundry-project.bicep Adds shared project-child module.
azure.ai.agents/.../modules/container-registry.bicep Adds shared ACR module.
azure.ai.agents/.../modules/container-registry-eject.bicep.tmpl Adds mode-specific eject module.
azure.ai.agents/.../templates/main.bicep Adds connection endpoint output.
azure.ai.agents/.../templates/main.arm.json Regenerates compiled ARM.
azure.ai.agents/.../templates/existing-project.bicep Adds embedded existing-project graph.
azure.ai.agents/.../templates/existing-project-eject.bicep.tmpl Adds editable Bicep graph.
azure.ai.agents/.../templates/brownfield.bicep Removes legacy brownfield template.
azure.ai.agents/.../templates/brownfield.arm.json Removes legacy compiled ARM.
azure.ai.agents/.../synthesis/templates_embed.go Embeds new template sets.
azure.ai.agents/.../synthesis/synthesizer.go Adds existing-project synthesis.
azure.ai.agents/.../synthesis/synthesizer_test.go Updates embedding tests.
azure.ai.agents/.../synthesis/schema_test.go Updates ARM drift checks.
azure.ai.agents/.../project/foundry_dependencies.go Scopes connection readiness.
azure.ai.agents/.../project/foundry_dependencies_test.go Tests cross-project rejection.
azure.ai.agents/.../cmd/init.go Supplies environment state during eject.
azure.ai.agents/.../cmd/init_infra_test.go Adds eject-mode coverage.
azure.ai.agents/.../cmd/init_foundry_resources_helpers.go Persists ACR mode and assignment state.
azure.ai.agents/docs/infrastructure-eject.md Documents existing-project eject behavior.
Suppressed comments (1)

cli/azd/extensions/azure.ai.agents/internal/cmd/init_infra_test.go:1063

  • [azd-code-reviewer] Checking only for az makes this test fail on machines that have Azure CLI but have not installed its Bicep component; az bicep build may prompt or attempt a download rather than skip. Detect an already-installed standalone or ~/.azure/bin Bicep binary, as the synthesis drift tests do, and invoke it directly so this unit test stays offline and deterministic.
	az, err := exec.LookPath("az")
	if err != nil {
		t.Skip("Azure CLI not found; skipping generated Bicep compilation")

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread cli/azd/extensions/azure.ai.agents/internal/synthesis/templates_embed.go Outdated
Comment thread cli/azd/extensions/azure.ai.projects/internal/synthesis/templates_embed.go Outdated
Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/init_infra_test.go
Copilot AI review requested due to automatic review settings August 21, 2026 08:03
@github-actions github-actions Bot added ext-agents azure.ai.agents extension ext-projects azure.ai.projects extension labels Aug 21, 2026

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

Copilot reviewed 62 out of 64 changed files in this pull request and generated no new comments.

Copilot AI review requested due to automatic review settings August 21, 2026 09:29

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

Copilot reviewed 63 out of 65 changed files in this pull request and generated no new comments.

Suppressed comments (3)

cli/azd/extensions/azure.ai.agents/internal/cmd/init_infra.go:2233

  • An existing-project eject is explicitly marked as needing environment state, but this branch silently continues without it. ejectInfra then skips the project identity checks and, for container-based agents, infers create mode, so the generated templates may target an unvalidated project and fail to preserve the ACR choice made during init. Return a validation error requiring an active environment instead. [azd-code-reviewer]
	if current == nil || current.Environment == nil || current.Environment.Name == "" {
		return nil, nil
	}

cli/azd/extensions/azure.ai.projects/internal/provisioning/resource_group_location_check.go:185

  • This can report a blocking location mismatch for a prebuilt-image environment. The provider forces existing-project ACR mode to none whenever AZD_AGENT_SKIP_ACR=true, but this check consults the persisted mode first; an environment switched from create to prebuilt can therefore retain AZD_FOUNDRY_ACR_MODE=create and be treated as creating an adjunct resource group even though provisioning will not create one. Check the skip flag before the mode. [azd-code-reviewer]
    cli/azd/extensions/azure.ai.projects/internal/provisioning/foundry_provisioning_provider.go:510
  • The new AZD_FOUNDRY_ACR_MODE and AZD_FOUNDRY_ACR_PULL_ASSIGNED environment inputs are not documented in cli/azd/docs/environment-variables.md, which is the repository’s required source of truth for every environment variable azd reads. Add both variables there with their accepted values/default behavior (and document the new connection-project endpoint key introduced by this change as well). [azd-code-reviewer]

}
return false, exterrors.ServiceFromAzure(err, exterrors.OpResourceGroupGet)
p.foundryRGOwnerID = fmt.Sprintf("/subscriptions/%s/resourceGroups/%s", p.subID, p.rgName)
return p.setEnv(recoveryCtx, envKeyFoundryRGOwner, p.foundryRGOwnerID)

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.

[suggestion] [azd-code-reviewer] Persisting only the owner ID does not make a partially created adjunct group removable after a failed deployment. When AZURE_FOUNDRY_RESOURCE_GROUP was initially absent, the next Initialize derives the default name but leaves rgExplicit=false, so Destroy rejects the cleanup before it checks this recovered ownership marker. Persist envKeyFoundryRG together with envKeyFoundryRGOwner (or allow the exact owned ID to establish the group name), and cover the failed-first-provision cleanup path.

p.existingProjectConnectionOnly = true
p.synthResult = connectionOnlyResult
p.foundryName = projectNameFromEndpoint(endpoint)
return p.resolveEnvName(ctx)

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.

[suggestion] [azd-code-reviewer] This early return leaves armTemplate unset, but Preview no longer has the old brownfield no-op path and always calls resolveTemplate. As a result, azd provision --preview for an existing project with no deployments, connections, or ACR work now fails with CodeOnDiskTemplateMissing instead of returning an empty preview. Add an existingProjectConnectionOnly fast path to Preview (and a regression test), matching State and Deploy.

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 ext-projects azure.ai.projects extension

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Agents Extension] Unable to eject infra with existing foundry project

3 participants