-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Quantum: add az quantum workspace user list command
#10215
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
ad32f7c
0d61226
874c812
f8d6d7a
06fb090
aca2e71
3834ec1
b0ae63d
661bf7e
470196d
d8df847
e2aa1c8
0b931b5
811044a
c900ca6
939612f
97e89d5
3231c18
f58d004
b38065f
5038027
4f1f6a5
3f8cd48
5c29c68
b1f23f8
3bc00cc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,6 +28,10 @@ | |
| from ..vendored_sdks.azure_mgmt_quantum.models import Provider, ApiKeys, WorkspaceResourceProperties, KeyType | ||
| from .offerings import accept_terms, _get_publisher_and_offer_from_provider_id, _get_terms_from_marketplace, OFFER_NOT_AVAILABLE, PUBLISHER_NOT_AVAILABLE | ||
|
|
||
| from knack.log import get_logger | ||
|
|
||
| logger = get_logger(__name__) | ||
|
|
||
| DEFAULT_WORKSPACE_LOCATION = 'westus' | ||
| DEFAULT_STORAGE_SKU = 'Standard_LRS' | ||
| DEFAULT_STORAGE_SKU_TIER = 'Standard' | ||
|
|
@@ -38,12 +42,15 @@ | |
|
|
||
| POLLING_TIME_DURATION = 3 # Seconds | ||
| MAX_RETRIES_ROLE_ASSIGNMENT = 20 | ||
| MAX_RETRIES_USER_LOOKUP = 3 | ||
| MAX_POLLS_CREATE_WORKSPACE = 300 | ||
|
|
||
| # Built-in "Quantum Workspace Data Contributor" role. This is the role granted to | ||
| # users when they are added to a workspace in the Azure Quantum portal. | ||
| # Built-in "Quantum Workspace Data Contributor" role. | ||
| QUANTUM_WORKSPACE_DATA_CONTRIBUTOR_ROLE_ID = "c1410b24-3e69-4857-8f86-4d0a2e603250" | ||
|
|
||
| # Built-in "Quantum Workspace Owner" role. | ||
| QUANTUM_WORKSPACE_OWNER_ROLE_ID = "30b3bcf2-670a-4bdc-8669-7e0ae0c0dfda" | ||
|
|
||
| C4A_TERMS_ACCEPTANCE_MESSAGE = "\nBy continuing you accept the Azure Quantum terms and conditions and privacy policy and agree that " \ | ||
| "Microsoft can share your account details with the provider for their transactional purposes.\n\n" \ | ||
| "https://privacy.microsoft.com/privacystatement\n" \ | ||
|
|
@@ -497,3 +504,53 @@ def remove_user(cmd, resource_group_name=None, workspace_name=None, assignee=Non | |
| scope = _get_workspace_resource_id(info) | ||
| role = role or QUANTUM_WORKSPACE_DATA_CONTRIBUTOR_ROLE_ID | ||
| return delete_role_assignments(cmd, role=role, scope=scope, assignee=assignee, assignee_object_id=assignee_object_id) | ||
|
|
||
|
|
||
| def list_users(cmd, resource_group_name=None, workspace_name=None, include_inherited=True): | ||
| """ | ||
| List the users with access to an Azure Quantum workspace. | ||
| """ | ||
| from azure.cli.command_modules.role.custom import list_role_assignments | ||
|
|
||
| info = WorkspaceInfo(cmd, resource_group_name, workspace_name) | ||
| scope = _get_workspace_resource_id(info) | ||
| assignments = [] | ||
| for role_id in (QUANTUM_WORKSPACE_DATA_CONTRIBUTOR_ROLE_ID, QUANTUM_WORKSPACE_OWNER_ROLE_ID): | ||
| # fill_principal_name=False avoids a per-call Microsoft Graph lookup that _fill_user_display_names already does in one batch. | ||
| assignments += list_role_assignments(cmd, role=role_id, scope=scope, include_inherited=include_inherited, fill_principal_name=False) | ||
| users = [assignment for assignment in assignments if assignment.get("principalType") == "User"] | ||
| _fill_user_display_names(cmd, users) | ||
| return users | ||
|
|
||
|
|
||
|
Comment on lines
+511
to
+525
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fill_principal_name=False removed the duplicate Graph calls, but workspace.py:515-521 still loops and calls list_role_assignments twice, and fill_role_definition_name still defaults to True. That enumerates every role definition visible at the workspace scope (hundreds of built-ins) — twice. azure-cli's own help calls this out: "Fill roleDefinitionName property in addition to roleDefinitionId. This operation is expensive." A single call filtered client-side halves the ARM traffic, consider to changing to:
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| def _fill_user_display_names(cmd, users): | ||
| """ | ||
| Enrich user role assignments with the display name and email resolved from Microsoft Graph | ||
| in a single batched lookup. This avoids a per-user Microsoft Graph lookup that would be slow and could hit throttling limits. | ||
| """ | ||
| principal_ids = {user["principalId"] for user in users if user.get("principalId")} | ||
| if not principal_ids: | ||
|
kaverkiev marked this conversation as resolved.
|
||
| return | ||
|
|
||
| from azure.cli.command_modules.role import graph_client_factory | ||
| from azure.cli.command_modules.role.custom import _get_object_stubs | ||
|
|
||
| directory_objects = {} | ||
| for attempt in range(MAX_RETRIES_USER_LOOKUP): | ||
| try: | ||
| graph_client = graph_client_factory(cmd.cli_ctx) | ||
| directory_objects = {obj.get("id"): obj for obj in _get_object_stubs(graph_client, principal_ids)} | ||
| if principal_ids.issubset(directory_objects): | ||
| break | ||
| except Exception: # pylint: disable=broad-except | ||
| directory_objects = {} | ||
|
|
||
| if attempt < MAX_RETRIES_USER_LOOKUP - 1: | ||
| time.sleep(1) | ||
| else: | ||
| raise AzureInternalError("Could not resolve user names and email addresses from Microsoft Graph. Please try again later.") | ||
|
|
||
| for user in users: | ||
| obj = directory_objects.get(user.get("principalId"), {}) | ||
| user["displayName"] = obj.get("displayName") or obj.get("userPrincipalName") | ||
| user["mail"] = obj.get("mail") or obj.get("userPrincipalName") | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we change that to "Time Added"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the reason for this rename? It will break consistency with OS shell. Currently, we mirroring OS Shell structure, just as we discussed.