Skip to content

{Role} az role assignment create: Use RoleAssignmentExists error code to implement idempotence - #32027

Merged
Jiashuo Li (jiasli) merged 1 commit into
Azure:devfrom
jiasli:RoleAssignmentExists
Sep 5, 2025
Merged

Jiashuo Li (jiasli) merged 1 commit into
Azure:devfrom
jiasli:RoleAssignmentExists

Conversation

@jiasli

@jiasli Jiashuo Li (jiasli) commented Aug 29, 2025

Copy link
Copy Markdown
Contributor

Related command
az role assignment create

Description
Resolve #31995

az role assignment create command relies on the response message ('role assignment already exists' in ex.message) to implement idempotence. The code was introduced by #9108 in 2019 and hasn't been changed since then.

However, recently ARM is making a breaking change to the response message on the existing API version 2022-04-01 without an API version update. This breaks the idempotence of az role assignment create command.

This PR uses the error code (ex.error.code == 'RoleAssignmentExists') to detect if there is an existing role assignment.

Testing Guide

az role assignment create --assignee a7003d8c-e50f-4371-91a6-ef37bba4ab23 --role reader --scope /subscriptions/0b1f6471-1bf0-4dda-aec3-cb9272f09590
# Run the command again and make sure no error is returned
az role assignment create --assignee a7003d8c-e50f-4371-91a6-ef37bba4ab23 --role reader --scope /subscriptions/0b1f6471-1bf0-4dda-aec3-cb9272f09590

History Notes

[Component Name 1] BREAKING CHANGE: az command a: Make some customer-facing breaking change
[Component Name 2] az command b: Add some customer-facing feature


This checklist is used to make sure that common guidelines for a pull request are followed.

@azure-client-tools-bot-prd

azure-client-tools-bot-prd Bot commented Aug 29, 2025

Copy link
Copy Markdown
️✔️AzureCLI-FullTest
️✔️acr
️✔️latest
️✔️3.12
️✔️3.13
️✔️acs
️✔️latest
️✔️3.12
️✔️3.13
️✔️advisor
️✔️latest
️✔️3.12
️✔️3.13
️✔️ams
️✔️latest
️✔️3.12
️✔️3.13
️✔️apim
️✔️latest
️✔️3.12
️✔️3.13
️✔️appconfig
️✔️latest
️✔️3.12
️✔️3.13
️✔️appservice
️✔️latest
️✔️3.12
️✔️3.13
️✔️aro
️✔️latest
️✔️3.12
️✔️3.13
️✔️backup
️✔️latest
️✔️3.12
️✔️3.13
️✔️batch
️✔️latest
️✔️3.12
️✔️3.13
️✔️batchai
️✔️latest
️✔️3.12
️✔️3.13
️✔️billing
️✔️latest
️✔️3.12
️✔️3.13
️✔️botservice
️✔️latest
️✔️3.12
️✔️3.13
️✔️cdn
️✔️latest
️✔️3.12
️✔️3.13
️✔️cloud
️✔️latest
️✔️3.12
️✔️3.13
️✔️cognitiveservices
️✔️latest
️✔️3.12
️✔️3.13
️✔️compute_recommender
️✔️latest
️✔️3.12
️✔️3.13
️✔️computefleet
️✔️latest
️✔️3.12
️✔️3.13
️✔️config
️✔️latest
️✔️3.12
️✔️3.13
️✔️configure
️✔️latest
️✔️3.12
️✔️3.13
️✔️consumption
️✔️latest
️✔️3.12
️✔️3.13
️✔️container
️✔️latest
️✔️3.12
️✔️3.13
️✔️containerapp
️✔️latest
️✔️3.12
️✔️3.13
️✔️core
️✔️latest
️✔️3.12
️✔️3.13
️✔️cosmosdb
️✔️latest
️✔️3.12
️✔️3.13
️✔️databoxedge
️✔️latest
️✔️3.12
️✔️3.13
️✔️dls
️✔️latest
️✔️3.12
️✔️3.13
️✔️dms
️✔️latest
️✔️3.12
️✔️3.13
️✔️eventgrid
️✔️latest
️✔️3.12
️✔️3.13
️✔️eventhubs
️✔️latest
️✔️3.12
️✔️3.13
️✔️feedback
️✔️latest
️✔️3.12
️✔️3.13
️✔️find
️✔️latest
️✔️3.12
️✔️3.13
️✔️hdinsight
️✔️latest
️✔️3.12
️✔️3.13
️✔️identity
️✔️latest
️✔️3.12
️✔️3.13
️✔️iot
️✔️latest
️✔️3.12
️✔️3.13
️✔️keyvault
️✔️latest
️✔️3.12
️✔️3.13
️✔️lab
️✔️latest
️✔️3.12
️✔️3.13
️✔️managedservices
️✔️latest
️✔️3.12
️✔️3.13
️✔️maps
️✔️latest
️✔️3.12
️✔️3.13
️✔️marketplaceordering
️✔️latest
️✔️3.12
️✔️3.13
️✔️monitor
️✔️latest
️✔️3.12
️✔️3.13
️✔️mysql
️✔️latest
️✔️3.12
️✔️3.13
️✔️netappfiles
️✔️latest
️✔️3.12
️✔️3.13
️✔️network
️✔️latest
️✔️3.12
️✔️3.13
️✔️policyinsights
️✔️latest
️✔️3.12
️✔️3.13
️✔️privatedns
️✔️latest
️✔️3.12
️✔️3.13
️✔️profile
️✔️latest
️✔️3.12
️✔️3.13
️✔️rdbms
️✔️latest
️✔️3.12
️✔️3.13
️✔️redis
️✔️latest
️✔️3.12
️✔️3.13
️✔️relay
️✔️latest
️✔️3.12
️✔️3.13
️✔️resource
️✔️latest
️✔️3.12
️✔️3.13
️✔️role
️✔️latest
️✔️3.12
️✔️3.13
️✔️search
️✔️latest
️✔️3.12
️✔️3.13
️✔️security
️✔️latest
️✔️3.12
️✔️3.13
️✔️servicebus
️✔️latest
️✔️3.12
️✔️3.13
️✔️serviceconnector
️✔️latest
️✔️3.12
️✔️3.13
️✔️servicefabric
️✔️latest
️✔️3.12
️✔️3.13
️✔️signalr
️✔️latest
️✔️3.12
️✔️3.13
️✔️sql
️✔️latest
️✔️3.12
️✔️3.13
️✔️sqlvm
️✔️latest
️✔️3.12
️✔️3.13
️✔️storage
️✔️latest
️✔️3.12
️✔️3.13
️✔️synapse
️✔️latest
️✔️3.12
️✔️3.13
️✔️telemetry
️✔️latest
️✔️3.12
️✔️3.13
️✔️util
️✔️latest
️✔️3.12
️✔️3.13
️✔️vm
️✔️latest
️✔️3.12
️✔️3.13

@azure-client-tools-bot-prd

azure-client-tools-bot-prd Bot commented Aug 29, 2025

Copy link
Copy Markdown
️✔️AzureCLI-BreakingChangeTest
️✔️Non Breaking Changes

@yonzhan

Copy link
Copy Markdown
Collaborator

Thank you for your contribution! We will review the pull request and get back to you soon.

@github-actions

Copy link
Copy Markdown

The git hooks are available for azure-cli and azure-cli-extensions repos. They could help you run required checks before creating the PR.

Please sync the latest code with latest dev branch (for azure-cli) or main branch (for azure-cli-extensions).
After that please run the following commands to enable git hooks:

pip install azdev --upgrade
azdev setup -c <your azure-cli repo path> -r <your azure-cli-extensions repo path>

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 addresses a breaking change in ARM's response message for existing role assignments by updating the error detection logic in az role assignment create command to use error codes instead of message content for implementing idempotence.

  • Updates error detection from message-based to error code-based approach
  • Maintains backward compatibility while fixing broken idempotence behavior
  • Ensures reliable detection of existing role assignments regardless of message format changes

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.


def _error_caused_by_role_assignment_exists(ex):
return getattr(ex, 'status_code', None) == 409 and 'role assignment already exists' in ex.message
return getattr(ex, 'status_code', None) == 409 and ex.error.code == 'RoleAssignmentExists'

Copilot AI Sep 1, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code assumes ex.error exists but doesn't handle the case where ex.error might be None. This could cause an AttributeError if the exception object doesn't have an error attribute. Consider using getattr(ex, 'error', None) and checking if it's not None before accessing the code attribute.

Suggested change
return getattr(ex, 'status_code', None) == 409 and ex.error.code == 'RoleAssignmentExists'
error = getattr(ex, 'error', None)
return getattr(ex, 'status_code', None) == 409 and error is not None and getattr(error, 'code', None) == 'RoleAssignmentExists'

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Xing Zhou (@zhoxing-ms), is it possible that ex.error is None?

@zhoxing-ms Xing Zhou (zhoxing-ms) Sep 5, 2025

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.

As far as I know, if the type of ex must be HttpResponseError, ex.error is guaranteed to exist, since many old codes are directly used ex.error.code for HttpResponseError. And because if ex has status_code attribute, it should indicate that ex is HttpResponseError, so, I personally speculate that this usage should be safe

@jiasli

Copy link
Copy Markdown
Contributor Author

az role assignment create's idempotence is tested by

# verify role assignment create is idempotent
self.cmd('role assignment create --assignee {upn} --role {role} --scope {rg_id}',
self.check("principalName", self.kwargs["upn"]))

if ex.error.code == 'RoleAssignmentExists':
logger.info('Role assignment already exists')
break
if retry_time < retry_times and ' does not exist in the directory ' in ex.message:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar bad pattern of relying on error message instead of error code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new code is indeed relying on error code.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant, this line still relies on message:
if retry_time < retry_times and ' does not exist in the directory ' in ex.message

@jiasli Jiashuo Li (jiasli) Sep 5, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah. I see your point. This PR only aims to use RoleAssignmentExists error code. I will inform the ARM owner fix this line. Xing Zhou (@zhoxing-ms)

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.

Okay, I'll take a look when I have time and fix it in other PR

@FumingZhang FumingZhang left a comment

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.

approved for acs(aks)

@jiasli
Jiashuo Li (jiasli) merged commit cf11272 into Azure:dev Sep 5, 2025
48 checks passed
@jiasli
Jiashuo Li (jiasli) deleted the RoleAssignmentExists branch September 5, 2025 06:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Auto-Assign Auto assign by bot RBAC az role

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Idempotency of az role assignment create is not working

7 participants