Skip to content

feat(agents): create Foundry connections at provision time#9046

Merged
trangevi merged 7 commits into
mainfrom
feat/foundry-connections-provision-time
Jul 10, 2026
Merged

feat(agents): create Foundry connections at provision time#9046
trangevi merged 7 commits into
mainfrom
feat/foundry-connections-provision-time

Conversation

@hund030

@hund030 hund030 commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Foundry project connections declared via host: azure.ai.connection were only ever created at azd deploy (and only for None/ApiKey/CustomKeys), so OAuth2 and identity-based connections were silently skipped and azd provision alone never produced a usable connection. This moves connection creation to provision time via Bicep, for both greenfield and brownfield projects, and makes azd deploy a no-op for connections.

Changes

  • synthesis/synthesizer.go + new modules/connections.bicep: Synthesize/BrownfieldConnections collect host: azure.ai.connection services into a connections param, resolving ${VAR} on provision and preserving it on --infra eject; supports every auth type, not just the deploy-time subset.
  • azure.ai.connections/service_target.go: Deploy is now a no-op -- connections are provisioned by microsoft.foundry, mirroring azure.ai.project's existing no-op. Removed the now-dead upsert helpers and their tests.
  • foundry_provisioning_provider.go: repaired AZURE_AI_PROJECT_CONNECTION_NAMES casing (canonicalOutputNames) and fixed a misleading brownfield progress message that claimed "reconciling model deployments" even with zero deployments declared (brownfieldReconcileMessage).
  • init_infra.go: excludes brownfield.bicep/brownfield.arm.json from --infra eject -- dead weight in a greenfield tree, never compiled or read by the brownfield path.

hund030 added 6 commits July 9, 2026 16:38
The microsoft.foundry synthesizer now parses host: azure.ai.connection
services from azure.yaml and creates them as project connections during
azd provision, for both greenfield and brownfield (endpoint:) projects.

- synthesizer: collect azure.ai.connection services into a connections
  param, resolving ${VAR} on the provision path and keeping it verbatim
  on eject (--infra), while preserving Foundry ${{...}} expressions
- add modules/connections.bicep (a for-loop over the connections param,
  pinned to 2025-04-01-preview like the ACR connection) and wire it
  through main.bicep + resources.bicep; --infra ejects it automatically
- brownfield: BrownfieldConnections + brownfield.bicep connections loop
  so existing projects reach the same (all-auth-type) coverage
- emit AZURE_AI_PROJECT_CONNECTION_NAMES as a comma-joined string
  (not an array) so it round-trips through .env without JSON
  double-encoding
- regenerate main.arm.json and brownfield.arm.json
Foundry connections declared as host: azure.ai.connection services are
now created at provision time by the microsoft.foundry provider, so the
deploy-time service target no longer upserts them. This removes the
redundant ARM write on every azd deploy and makes provision the single
source of truth, mirroring the azure.ai.project target (also a no-op
because the project is provisioned by the same provider).

- Deploy returns an empty result with an informational message; the
  target still owns the azure.ai.connection host so azd can walk the
  entry in the deploy graph (uses: ordering for toolboxes/agents)
- drop the now-dead upsert helpers (parse/env/credential/metadata) and
  replace their tests with no-op coverage

Depends on the agents change (provision-time synthesis) landing first so
connections are always created by provisioning.
ARM's management SDK mangles output-name casing (e.g. AZURE_AI_PROJECT_ID
comes back as azurE_AI_PROJECT_ID). armOutputsToProto repairs known names
via a case-insensitive match against canonicalOutputNames, but the new
AZURE_AI_PROJECT_CONNECTION_NAMES output was missing from that list, so it
landed in .env as azurE_AI_PROJECT_CONNECTION_NAMES instead of the
canonical name. Found live while running the e2e test plan for
provision-time connections.

Add the missing entry and a matching case in
TestArmOutputsToProto_RepairsMangledKeyCase.
Both files are dead weight in a greenfield eject: ejectInfra already
refuses to eject a brownfield (endpoint:) project, main.bicep has no
module reference to brownfield.bicep, and the provider's brownfield
deploy/preview path always loads the embedded
synthesis.BrownfieldARMTemplate() rather than anything under infra/ -
even after later adding endpoint: while infra/main.bicep still exists
on disk. Shipping them in every eject only invited confusion about
whether they were required.

writeEmbeddedTemplates now excludes brownfield.bicep and
brownfield.arm.json the same way it already excluded main.arm.json.
Verified live: a fresh --infra eject no longer writes either file.
…ns are pending

deployBrownfield's progress-message switch only covered
deployments+ACR/ACR-only/default, so a brownfield project declaring just a
connection (no deployments, no ACR) printed "reconciling declared model
deployments..." even though zero deployments existed. Found live while
testing brownfield connection creation.

Replace the switch with brownfieldReconcileMessage(hasDeployments, createACR,
hasConnections), which names only what's actually pending. Callers only
reach it when at least one is true (the earlier guard skips provisioning
entirely otherwise), so the message never claims work that isn't happening.

Add TestBrownfieldReconcileMessage covering all 7 reachable combinations,
including the connections-only regression case. Verified live: provisioning
now prints "reconciling connections..." for that case.
No logic changes -- reviewed the full branch diff against origin/main for
unnecessary code and comment verbosity.

- azure.ai.connections/service_target.go: the same fact ("connections are
  created at provision by microsoft.foundry, so Deploy is a no-op") was
  stated three times (const, struct, and method doc comments). Kept the full
  explanation on Deploy only; trimmed the other two to brief pointers.
- synthesizer.go: shortened the Connection type, BrownfieldConnections, and
  collectConnections doc comments to match the concision of sibling
  functions (e.g. BrownfieldDeployments).
- foundry_provisioning_provider.go: trimmed brownfieldReconcileMessage's
  doc comment.
- init_infra.go (+ matching test comment): trimmed the brownfield.bicep
  exclusion rationale, which had grown to a 13-line bullet repeating detail
  already covered by the commit history.

No unnecessary/dead code was found beyond comments -- the connectionType
Bicep duplication across main.bicep/resources.bicep/brownfield.bicep/
connections.bicep matches the pre-existing deploymentType pattern (Bicep has
no cross-file type import), and the Connection/connectionService type split
in synthesizer.go mirrors existing splits like agentBlock/serviceBlock.

Verified: go build, full test suite, gofmt, and golangci-lint all pass
unchanged on both azure.ai.agents and azure.ai.connections.
Copilot AI review requested due to automatic review settings July 9, 2026 09:05
@github-actions github-actions Bot added ext-agents azure.ai.agents extension ext-connections azure.ai.connections extension labels Jul 9, 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

This PR moves Foundry project connection creation (services declared with host: azure.ai.connection) from deploy time to provision time via Bicep, for both greenfield and brownfield projects. Previously, connections were only created during azd deploy and only for the None/ApiKey/CustomKeys auth subset, so OAuth2/identity connections were silently skipped and azd provision alone never produced a usable connection. The new approach synthesizes a connections ARM parameter, supports every auth type, resolves ${VAR} on provision (preserving it on --infra eject), and makes the azure.ai.connection deploy step a no-op (mirroring azure.ai.project).

Changes:

  • Synthesizer collects host: azure.ai.connection services into a connections param (greenfield via new modules/connections.bicep; brownfield inline), with ${VAR} expansion and Foundry ${{...}} pass-through; adds BrownfieldConnections and a new AZURE_AI_PROJECT_CONNECTION_NAMES output.
  • azure.ai.connection service target Deploy becomes a no-op; dead upsert helpers and their tests are removed.
  • Provider wires brownfield connections into brownfieldParams, adds brownfieldReconcileMessage (fixes a misleading "reconciling model deployments" line), repairs the new output's ARM key casing, and excludes brownfield.bicep/brownfield.arm.json from greenfield --infra eject.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
azure.ai.connections/internal/cmd/service_target.go Deploy is now a no-op; removes upsert helpers (no remaining references).
azure.ai.connections/internal/cmd/service_target_test.go Replaces upsert tests with no-op assertions for Deploy/Package/Publish/Endpoints.
azure.ai.agents/.../templates/modules/connections.bicep New module creating one connection resource per entry (2025-04-01-preview).
azure.ai.agents/.../templates/modules/resources.bicep Adds connection types/param, conditional module, and connection-names output.
azure.ai.agents/.../templates/main.bicep Threads connections param and output through to the resources module.
azure.ai.agents/.../templates/main.arm.json Recompiled ARM (guarded by schema_test.go).
azure.ai.agents/.../templates/brownfield.bicep Adds inline project connections on the existing project + output.
azure.ai.agents/.../templates/brownfield.arm.json Recompiled brownfield ARM (guarded by schema_test.go).
azure.ai.agents/internal/synthesis/synthesizer.go Adds Connection, collectConnections, BrownfieldConnections, and ${VAR} expand helpers.
azure.ai.agents/internal/synthesis/synthesizer_test.go Covers collect/sort, resolve vs. preserve, missing-var, brownfield.
azure.ai.agents/internal/project/foundry_provisioning_provider.go Captures/wires brownfield connections; adds brownfieldReconcileMessage; adds output to canonicalOutputNames.
azure.ai.agents/internal/project/foundry_provisioning_provider_test.go Adds key-case repair assertion for the new output.
azure.ai.agents/.../foundry_provisioning_provider_brownfield_acr_test.go Tests brownfield params + reconcile-message matrix.
azure.ai.agents/internal/cmd/init_infra.go Excludes brownfield.bicep/brownfield.arm.json from eject.
azure.ai.agents/internal/cmd/init_infra_test.go Asserts brownfield files excluded and connection services ejected verbatim.

Note (not in this diff): the azure.ai.connections CHANGELOG.md entry for #8818 still describes the removed deploy-time upsert behavior; a follow-up changelog update would keep it accurate.

Comment thread cli/azd/extensions/azure.ai.agents/internal/synthesis/synthesizer.go Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@github-actions

github-actions Bot commented Jul 9, 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.

@hund030
hund030 requested a review from trangevi July 10, 2026 08:15
@trangevi
trangevi merged commit 48a5979 into main Jul 10, 2026
41 checks passed
@hund030
hund030 deleted the feat/foundry-connections-provision-time branch July 24, 2026 05:51
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-connections azure.ai.connections extension

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Changes to agent.yaml tool connections are not updated during azd provision

3 participants