Skip to content

[Feature] First-class idempotent "ensure" semantics for role assignments #9253

Description

@qiaozha

Summary

Role assignments are not idempotent to declare, and neither Bicep nor azd can express "create this grant only if an equivalent one doesn't already exist". We need upsert-by-tuple ensure semantics.

What happens

azd provision fails with RoleAssignmentExists when a matching (principalId, roleDefinitionId, scope) tuple already exists under a different assignment name (a GUID) — e.g. an out-of-band grant applied by another process/team in a locked resource group.

Why a Bicep if condition does not solve it

A Bicep if (...) only gates whether a resource is declared; to gate it correctly you'd need to answer "does a role assignment for this (principal, role, scope) already exist, under any name?" — which ARM/Bicep can't:

  1. No query/enumeration primitive — there's no listRoleAssignments() to test whether a matching tuple exists, so the if condition has no truthful input.
  2. existing requires the name, which you don't haveexisting by a computed deterministic GUID only matches assignments created with that name; the out-of-band grant has a different, arbitrary name, so it finds nothing.
  3. Deterministic naming (guid(scope, principalId, roleId)) only makes your own assignments idempotent — it does nothing about a conflicting assignment created by someone else under a different name; ARM still returns RoleAssignmentExists on the tuple collision.
  4. No native upsert/ensure — ARM/Bicep has no "ensure a grant for this tuple exists; treat an existing one under any name as success."

In short, an if condition needs a boolean derived from live RBAC state, and Bicep has no way to obtain it — the gate has no input.

Current workaround

hooks/lib/ensure-role-assignment.ts — a thin wrapper over az role assignment create that treats RoleAssignmentExists as success. We removed the offending roleAssignments from Bicep entirely.

Ask

First-class idempotent "ensure" semantics for role assignments — an upsert keyed on the (principal, role, scope) tuple that succeeds if the grant already exists under any name, expressible declaratively rather than via an imperative az hook.

Note: this may be more of a Bicep/ARM platform gap than a pure azd gap; filing here for visibility and because azd is where the failure surfaces.


Context: encountered while deploying a multi-component AI/chat service (App Service + Function App + Logic App + Foundry hosted agent) end-to-end with azd provision/deploy in Azure/azure-sdk-tools tools/sdk-ai-bots/deployment. We currently work around this with a TypeScript hook.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions