Fix: ACR not created/linked for hosted container agents (#8861)#8880
Conversation
When a hosted container agent reuses an existing (brownfield) Foundry project, no Azure Container Registry was provisioned and AZURE_CONTAINER_REGISTRY_ENDPOINT was never set, so 'azd up' failed at container publish with 'could not determine container registry endpoint'. The unified (bicepless) init flow skipped ACR connection discovery for existing projects, and the brownfield provision path only reconciled model deployments. This wires the existing design end to end: - init: configure ACR for an existing project when the agent builds a container (discover a project connection, or prompt for a login server / create-on-provision), instead of skipping it with the rest of the bicepless connection setup. - brownfield.bicep: optionally create a Premium ACR, grant AcrPull to the project managed identity, and add a ContainerRegistry connection. - provider: deployBrownfield creates the ACR when init flagged 'acr' in AI_AGENT_PENDING_PROVISION and no endpoint is set, then surfaces the registry outputs.
Follow-up to the brownfield ACR support. For an existing-project (endpoint:) config, 'azd provision --preview' reported 'nothing to provision' even when a container registry would be created, and 'azd ai agent init --infra' failed with a cryptic synthesizer error. - Preview: previewBrownfield runs a resource-group-scoped what-if on brownfield.arm.json so the registry and any model deployments show as Creates. Deploy and Preview now share brownfieldParams. - init --infra: refuse with CodeInfraEjectBrownfieldUnsupported and a clear message instead of the raw synthesis error. An ejected ./infra/ is never compiled for an endpoint: project, so ejecting would mislead. - Fix a latent 64-char ARM deployment-name overflow for long project names via brownfieldDeploymentName (affected real deploys too).
deriveIncludeAcr only set includeAcr when an agent declared a docker: block, but docker: is not part of the azure.ai.agent schema. A hand-authored greenfield project that follows the schema (kind: hosted built from source, no docker:/image:/codeConfiguration: -- as in schemas/examples/simple.azure.yaml) therefore got no container registry and failed at container publish. Key the decision on the schema fields instead, mirroring the deploy-time contract: codeConfiguration => code/ZIP (no ACR), image => pre-built (no ACR), otherwise a hosted agent builds from source and needs an ACR. init-scaffolded projects (which always write docker:) are unaffected.
📋 Prioritization NoteThanks for the contribution! The linked issue isn't in the current milestone yet. |
There was a problem hiding this comment.
Pull request overview
Fixes missing Azure Container Registry (ACR) provisioning/wiring for hosted container agents when reusing an existing (brownfield) Foundry project, and improves preview/eject UX for that brownfield path.
Changes:
- Adds optional ACR creation + project connection wiring to the brownfield ARM/Bicep template, surfaced via new outputs.
- Updates the Foundry provisioning provider to deploy/preview brownfield resources (models and/or ACR) based on
AI_AGENT_PENDING_PROVISION, and caps brownfield deployment name length. - Adjusts ACR inclusion detection during synthesis to use schema-conformant signals (
image/codeConfiguration) instead of the undocumenteddocker:block; adds/updates unit tests.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| cli/azd/extensions/azure.ai.agents/internal/synthesis/templates/brownfield.bicep | Extends brownfield template to optionally create/wire an ACR and emit related outputs. |
| cli/azd/extensions/azure.ai.agents/internal/synthesis/templates/brownfield.arm.json | Regenerated ARM JSON reflecting the updated brownfield Bicep template. |
| cli/azd/extensions/azure.ai.agents/internal/synthesis/synthesizer.go | Changes ACR inclusion derivation to align with schema fields and hosted-agent behavior. |
| cli/azd/extensions/azure.ai.agents/internal/synthesis/synthesizer_test.go | Adds coverage for schema-conformant hosted agent shapes and ACR gating behavior. |
| cli/azd/extensions/azure.ai.agents/internal/project/foundry_provisioning_provider.go | Implements brownfield ACR provisioning and what-if preview; adds capped brownfield deployment name and merges outputs safely. |
| cli/azd/extensions/azure.ai.agents/internal/project/foundry_provisioning_provider_brownfield_acr_test.go | Adds unit tests for brownfield ACR request detection, naming, params, and deployment-name length capping. |
| cli/azd/extensions/azure.ai.agents/internal/exterrors/codes.go | Adds a dedicated error code for unsupported brownfield --infra eject. |
| cli/azd/extensions/azure.ai.agents/internal/cmd/init_infra.go | Refuses azd ai agent init --infra for brownfield projects with a clearer error. |
| cli/azd/extensions/azure.ai.agents/internal/cmd/init_infra_test.go | Adds test asserting eject refusal for endpoint-based brownfield projects. |
| cli/azd/extensions/azure.ai.agents/internal/cmd/init_foundry_resources_helpers.go | Ensures bicepless init still configures ACR for existing projects to unblock hosted container agents. |
| cli/azd/extensions/azure.ai.agents/internal/cmd/init_foundry_resources_helpers_test.go | Updates bicepless short-circuit test to reflect the new ACR contract. |
jongio
left a comment
There was a problem hiding this comment.
Solid fix for the brownfield ACR gap. The two root causes are correctly identified and addressed: the bicepless flow not configuring ACR for container agents, and deriveIncludeAcr keying on the absent docker: block instead of the schema fields (codeConfiguration/image/kind). The brownfield Bicep template additions (conditional ACR + AcrPull role + connection) are well-structured.
A few observations:
-
brownfieldLocationcan return""when bothAZURE_LOCATIONand the resource-group lookup fail. The ARM template hasdefaultValue: resourceGroup().locationfor thelocationparam, but passing an explicit empty string overrides that default rather than falling back to it. If there's a realistic path where neither source succeeds, the deploy would fail with a confusing ARM error about an invalid location. -
brownfield.arm.jsonis missing a trailing newline (the diff showsNo newline at end of file). Minor, but linters and some editors flag this. -
The
agentNeedsAcrhelper defaults emptykindto "hosted" for back-compat. If a new kind is added in the future that also doesn't build containers (e.g., "serverless"), it would incorrectly trigger ACR creation. A comment noting this would help future contributors remember to update the function.
Test coverage is thorough: TestBrownfieldACRRequested, TestBrownfieldACRName, TestBrownfieldDeploymentName, and the synthesizer cases all cover the key scenarios well.
- brownfieldParams: only set the ARM 'location' parameter when resolved. An empty value (AZURE_LOCATION unset + RG lookup failure) would override the template default resourceGroup().location and fail the deployment. - Correct the configureFoundryProjectEnv comment that still said the provider does not create an ACR for an existing project.
… newline - agentNeedsAcr: note that a future non-container kind that omits kind: would need an explicit allowlist instead of defaulting to hosted. - brownfield.arm.json: add trailing newline.
|
Thanks for the review @jongio. Addressed all three observations:
Also addressed the two Copilot comments (same location fix + the stale |
CI (ext-azure-ai-agents-lint / go-fix) enforces 'go fix ./...' cleanliness.
Fixes #8861
Issue
A hosted container agent (built from source) could deploy without an Azure Container Registry being created/wired, leaving
AZURE_CONTAINER_REGISTRY_ENDPOINTunset.azd upthen failed at container publish:Root cause
An ACR is provisioned/wired in two different paths, and both had holes:
endpoint:set): the bicep-less init flow defers ACR to the provider, but the provider's brownfield path only reconciled model deployments and never created an ACR — so the env var was never produced and the ACR question was never asked.deriveIncludeAcrgatedincludeAcron adocker:block, butdocker:isn't part of theazure.ai.agentschema. A schema-conformant hand-authored agent (kind: hosted, nodocker:/image:/codeConfiguration:, likeschemas/examples/simple.azure.yaml) gotincludeAcr=false.Two related brownfield UX gaps surfaced once provisioning was wired:
provision --previewreported "nothing to provision", andinit --infrafailed with a crypticservice has endpoint: (brownfield)error.What changed
AcrPullto the project identity, add aContainerRegistryconnection, and surface the registry outputs when theacrpending-provision reason is set.provision --previewruns a what-if so the ACR/deployments show asCreates; also fixed a latent 64-char ARM deployment-name overflow.codeConfiguration/image/hosted) instead ofdocker:, matching the deploy-time contract.Validation
--infrarefusal, and expanded synthesizer cases (schema-conformant hosted,kindomitted,image:,codeConfiguration:).go build,gofmt -s,golangci-lint(0 issues), fullgo test ./..., and the bicep drift test all pass.azd upnow creates the ACR + connection, setsAZURE_CONTAINER_REGISTRY_ENDPOINT, and publish/deploy succeed;provision --previewrenders the Create plan;init --infrashows the clear message. Test resources were cleaned up.