[Feature] First-class idempotent "ensure" semantics for role assignments
- Dominant language
- Go
- Stars
- 569
- Forks
- 364
- Avg merge
- 2d 19h
- Merged PRs (30d)
- 136
Description
### 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 have** — `existing` 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`](https://github.com/Azure/azure-sdk-tools/tree/main/tools/sdk-ai-bots/deployment). We currently work around this with a TypeScript hook._
Contributor guide
Research direction
Start with hooks/lib/ensure-role-assignment.ts and the linked tools/sdk-ai-bots/deployment context to understand the current TypeScript workaround and how azd provision invokes it. Compare that workaround with the requested declarative role-assignment behavior; the issue is complete only when an agreed implementation target and acceptance criteria exist for ensure semantics.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- azure, typescript
- Domain
- authorization, cloud
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100