Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
e65d8b1
Generate az sig identity aaz code
william051200 Mar 30, 2026
0f21f1c
Update az sig create and show aaz code
william051200 Mar 30, 2026
3451492
Add test case for sig identity
william051200 Mar 31, 2026
0190058
Update aaz code
william051200 Apr 2, 2026
b8d5e0a
Set sig identity remove as registered command
william051200 Apr 3, 2026
06fe6c3
Add example to sig identity remove
william051200 Apr 3, 2026
8d57862
Update test case
william051200 Apr 3, 2026
51c90e0
Update sig identity aaz code
william051200 Apr 5, 2026
c24d102
Update test case
william051200 Apr 6, 2026
38baf30
Complete sig identity
william051200 Apr 6, 2026
4af5100
Record test case
william051200 Apr 6, 2026
13d680a
Update test case and test case recording
william051200 Apr 6, 2026
30a108b
Update sig create param group
william051200 Apr 6, 2026
416e0ec
Update command help
william051200 Apr 6, 2026
fe91c00
Add no wait to az sig create
william051200 Apr 6, 2026
cb360e4
Update sig create
william051200 Apr 6, 2026
4e3df5c
update code
william051200 Apr 6, 2026
f6d3c05
Update code style
william051200 Apr 6, 2026
cd72191
Merge remote-tracking branch 'origin/dev' into 32814-sig-identity
william051200 Apr 6, 2026
19e9f45
Update test case and recording
william051200 Apr 6, 2026
a47c24b
Regenerate sig identity aaz code
william051200 Apr 13, 2026
6b45e9b
Update test case
william051200 Apr 13, 2026
8f7d05d
Update code
william051200 Apr 13, 2026
f9b3af9
Record test case
william051200 Apr 13, 2026
27799cf
Update code style
william051200 Apr 13, 2026
a0ce7b9
Update code
william051200 Apr 13, 2026
a245190
Undo changes
william051200 Apr 13, 2026
384b144
Fix code style
william051200 Apr 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions src/azure-cli/azure/cli/command_modules/vm/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,32 @@
az image builder show -n mytemplate -g my-group
"""

helps['sig create'] = """
type: command
short-summary: Create a shared image gallery.
examples:
- name: Create a shared image gallery.
text: |
az sig create --resource-group MyResourceGroup --gallery-name MyGallery123
"""

helps['sig identity assign'] = """
type: command
short-summary: Assign the user or system managed identities.
"""

helps['sig identity remove'] = """
type: command
short-summary: Remove the user or system managed identities.
examples:
- name: Remove the system assigned identity.
text: |
az sig identity remove --resource-group myResourceGroup --gallery-name myGalleryName
- name: Remove a user assigned identity.
text: |
az sig identity remove --resource-group myResourceGroup --gallery-name myGalleryName --identities readerId
"""
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

The sig identity command group includes a show command (registered in commands.py), but there is no corresponding help entry here. Add helps['sig identity show'] (and examples if appropriate) to keep help coverage consistent with assign/remove.

Copilot uses AI. Check for mistakes.

helps['sig image-definition create'] = """
type: command
short-summary: create a gallery image definition
Expand Down
29 changes: 29 additions & 0 deletions src/azure-cli/azure/cli/command_modules/vm/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -1351,6 +1351,35 @@ def load_arguments(self, _):
c.argument('gallery_image_name', options_list=['--gallery-image-definition', '-i'], help='gallery image definition')
c.argument('gallery_image_version', options_list=['--gallery-image-version', '-e'], help='gallery image version')

with self.argument_context('sig create') as c:
c.argument('eula', arg_group='CommunityGalleryInfo', help='Community gallery publisher eula')
c.argument('public_name_prefix', arg_group='CommunityGalleryInfo', help='Community gallery public name prefix')
c.argument('publisher_contact', arg_group='CommunityGalleryInfo', options_list=["--publisher-email", "--publisher-contact"], help='Community gallery publisher contact email')
c.argument('publisher_uri', arg_group='CommunityGalleryInfo', help='Community gallery publisher uri')
c.argument('location', get_location_type(self.cli_ctx), arg_group='Gallery',
help='Location in which to create VM and related resources. If default location is not configured, will default to the resource group\'s location')
c.argument('tags', tags_type, arg_group='Gallery')
c.argument('description', arg_group='Properties', help='The description of the gallery.')
c.argument('permissions', arg_group='SharingProfile', arg_type=get_enum_type(['Community', 'Groups', 'Private']),
help='This property allows you to specify the permission of sharing gallery.')
c.argument('soft_delete', arg_group='SoftDeletePolicy', arg_type=get_three_state_flag(), help='Enable soft-deletion for resources in this gallery, allowing them to be recovered within retention time.')
c.argument('assign_identity', nargs='*', arg_group='Managed Service Identity', help="accept system or user assigned identities separated by spaces. Use '[system]' to refer system assigned identity, or a resource id to refer user assigned identity. Check out help for more examples")

with self.argument_context('sig identity remove') as c:
c.argument('identities', nargs='*', help="Space-separated identities to assign. Use '{0}' to refer to the system assigned identity. Default: '{0}'".format(MSI_LOCAL_ID))

with self.argument_context('sig identity assign') as c:
c.argument('assign_identity', options_list=['--identities'], nargs='*', help="Space-separated identities to assign. Use '{0}' to refer to the system assigned identity. Default: '{0}'".format(MSI_LOCAL_ID))
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

Help text for sig identity remove --identities says "identities to assign", but this command removes identities. This is user-facing and confusing; update the help string to say "remove" (and keep it consistent with sig identity assign).

Copilot uses AI. Check for mistakes.

for scope in ['sig create', 'sig identity assign']:
with self.argument_context(scope) as c:
arg_group = 'Managed Service Identity' if scope.split()[-1] == 'create' else None
c.argument('identity_scope', options_list=['--scope'], arg_group=arg_group,
help="Scope that the system assigned identity can access. ")
c.argument('identity_role', options_list=['--role'], arg_group=arg_group,
help='Role name or id the system assigned identity will have. ')
c.ignore('identity_role_id')

with self.argument_context('sig image-definition create') as c:
c.argument('offer', options_list=['--offer', '-f'], help='image offer')
c.argument('sku', options_list=['--sku', '-s'], help='image sku')
Expand Down
62 changes: 62 additions & 0 deletions src/azure-cli/azure/cli/command_modules/vm/_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -1411,6 +1411,68 @@ def _validate_vm_vmss_msi(cmd, namespace, is_identity_assign=False):
_enable_msi_for_trusted_launch(namespace)


def process_sig_create_namespace(cmd, namespace):
validate_tags(namespace)

if not namespace.location:
get_default_location_from_resource_group(cmd, namespace)

_validate_sig_msi(cmd, namespace)


def process_sig_assign_identity_namespace(cmd, namespace):
_validate_sig_msi(cmd, namespace, is_identity_assign=True)


def process_sig_remove_identity_namespace(cmd, namespace):
if namespace.identities:
from ._vm_utils import MSI_LOCAL_ID
for i, identity in enumerate(namespace.identities):
if identity != MSI_LOCAL_ID:
namespace.identities[i] = _get_resource_id(cmd.cli_ctx, identity,
namespace.resource_group_name,
'userAssignedIdentities',
'Microsoft.ManagedIdentity')


def _validate_sig_msi(cmd, namespace, is_identity_assign=False):

# For the creation of sig, "--role" and "--scope" should be passed in at the same time
# when assigning a role to the managed identity
if not is_identity_assign and namespace.assign_identity is not None:
if (namespace.identity_scope and not namespace.identity_role) or \
(not namespace.identity_scope and namespace.identity_role):
raise ArgumentUsageError(
"usage error: please specify both --role and --scope when assigning a role to the managed identity")

# For "az sig identity assign", "--role" and "--scope" should be passed in at the same time
# when assigning a role to the managed identity
if is_identity_assign:
if (namespace.identity_scope and not namespace.identity_role) or \
(not namespace.identity_scope and namespace.identity_role):
raise ArgumentUsageError(
"usage error: please specify both --role and --scope when assigning a role to the managed identity")

# Assign managed identity
if is_identity_assign or namespace.assign_identity is not None:
identities = namespace.assign_identity or []
from ._vm_utils import MSI_LOCAL_ID
for i, _ in enumerate(identities):
if identities[i] != MSI_LOCAL_ID:
identities[i] = _get_resource_id(cmd.cli_ctx, identities[i], namespace.resource_group_name,
'userAssignedIdentities', 'Microsoft.ManagedIdentity')

if namespace.identity_scope:
if identities and MSI_LOCAL_ID not in identities:
raise ArgumentUsageError("usage error: '--scope'/'--role' is only applicable when "
"assign system identity")
# keep 'identity_role' for output as logical name is more readable
setattr(namespace, 'identity_role_id', _resolve_role_id(cmd.cli_ctx, namespace.identity_role,
namespace.identity_scope))
elif namespace.identity_scope or namespace.identity_role:
raise ArgumentUsageError('usage error: --assign-identity [--scope SCOPE] [--role ROLE]')


def _enable_msi_for_trusted_launch(namespace):
# Enable system assigned msi by default when Trusted Launch configuration is met
is_trusted_launch = namespace.security_type and namespace.security_type.lower() == 'trustedlaunch' \
Expand Down
5 changes: 4 additions & 1 deletion src/azure-cli/azure/cli/command_modules/vm/_vm_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -777,7 +777,10 @@ def assign_identity(cli_ctx, getter, setter, identity_role=None, identity_scope=

# create role assignment:
if identity_scope:
principal_id = resource.get('identity', {}).get('principalId') or resource.get('identity', {}).get('principal_id')
principal_id = resource.get('identity', {}).get('principalId') \
or resource.get('identity', {}).get('principal_id') \
or resource.get('principal_id') \
or resource.get('principalId')
create_role_assignment(cli_ctx, principal_id, identity_role, identity_scope)
return resource

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ class Create(AAZCommand):
"""

_aaz_info = {
"version": "2021-10-01",
"version": "2025-03-03",
"resources": [
["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.compute/galleries/{}", "2021-10-01"],
["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.compute/galleries/{}", "2025-03-03"],
]
}

Expand All @@ -49,6 +49,9 @@ def _build_arguments_schema(cls, *args, **kwargs):
options=["-r", "--gallery-name"],
help="The name of the Shared Image Gallery to be deleted.",
required=True,
fmt=AAZStrArgFormat(
pattern="^[^\\W_][\\w._-]{0,79}(?<![-.])$",
),
)
_args_schema.resource_group = AAZResourceGroupNameArg(
required=True,
Expand Down Expand Up @@ -98,6 +101,25 @@ def _build_arguments_schema(cls, *args, **kwargs):
tags = cls._args_schema.tags
tags.Element = AAZStrArg()

# define Arg Group "Identity"

_args_schema = cls._args_schema
_args_schema.mi_system_assigned = AAZStrArg(
options=["--system-assigned", "--mi-system-assigned"],
arg_group="Identity",
help="Set the system managed identity.",
blank="True",
)
_args_schema.mi_user_assigned = AAZListArg(
options=["--user-assigned", "--mi-user-assigned"],
arg_group="Identity",
help="Set the user managed identities.",
blank=[],
)

mi_user_assigned = cls._args_schema.mi_user_assigned
mi_user_assigned.Element = AAZStrArg()

# define Arg Group "Properties"

_args_schema = cls._args_schema
Expand Down Expand Up @@ -156,7 +178,7 @@ def __call__(self, *args, **kwargs):
session,
self.on_200_201,
self.on_error,
lro_options={"final-state-via": "azure-async-operation"},
lro_options={"final-state-via": "location"},
path_format_arguments=self.url_parameters,
)
if session.http_response.status_code in [200, 201]:
Expand All @@ -165,7 +187,7 @@ def __call__(self, *args, **kwargs):
session,
self.on_200_201,
self.on_error,
lro_options={"final-state-via": "azure-async-operation"},
lro_options={"final-state-via": "location"},
path_format_arguments=self.url_parameters,
)

Expand Down Expand Up @@ -208,7 +230,7 @@ def url_parameters(self):
def query_parameters(self):
parameters = {
**self.serialize_query_param(
"api-version", "2021-10-01",
"api-version", "2025-03-03",
required=True,
),
}
Expand All @@ -233,10 +255,20 @@ def content(self):
typ=AAZObjectType,
typ_kwargs={"flags": {"required": True, "client_flatten": True}}
)
_builder.set_prop("identity", AAZIdentityObjectType)
_builder.set_prop("location", AAZStrType, ".location", typ_kwargs={"flags": {"required": True}})
_builder.set_prop("properties", AAZObjectType, typ_kwargs={"flags": {"client_flatten": True}})
_builder.set_prop("tags", AAZDictType, ".tags")

identity = _builder.get(".identity")
if identity is not None:
identity.set_prop("userAssigned", AAZListType, ".mi_user_assigned", typ_kwargs={"flags": {"action": "create"}})
identity.set_prop("systemAssigned", AAZStrType, ".mi_system_assigned", typ_kwargs={"flags": {"action": "create"}})

user_assigned = _builder.get(".identity.userAssigned")
if user_assigned is not None:
user_assigned.set_elements(AAZStrType, ".")

properties = _builder.get(".properties")
if properties is not None:
properties.set_prop("description", AAZStrType, ".description")
Expand Down Expand Up @@ -295,9 +327,11 @@ class _CreateHelper:
def _build_schema_gallery_read(cls, _schema):
if cls._schema_gallery_read is not None:
_schema.id = cls._schema_gallery_read.id
_schema.identity = cls._schema_gallery_read.identity
_schema.location = cls._schema_gallery_read.location
_schema.name = cls._schema_gallery_read.name
_schema.properties = cls._schema_gallery_read.properties
_schema.system_data = cls._schema_gallery_read.system_data
_schema.tags = cls._schema_gallery_read.tags
_schema.type = cls._schema_gallery_read.type
return
Expand All @@ -308,6 +342,7 @@ def _build_schema_gallery_read(cls, _schema):
gallery_read.id = AAZStrType(
flags={"read_only": True},
)
gallery_read.identity = AAZIdentityObjectType()
gallery_read.location = AAZStrType(
flags={"required": True},
)
Expand All @@ -317,11 +352,42 @@ def _build_schema_gallery_read(cls, _schema):
gallery_read.properties = AAZObjectType(
flags={"client_flatten": True},
)
gallery_read.system_data = AAZObjectType(
serialized_name="systemData",
flags={"read_only": True},
)
gallery_read.tags = AAZDictType()
gallery_read.type = AAZStrType(
flags={"read_only": True},
)

identity = _schema_gallery_read.identity
identity.principal_id = AAZStrType(
serialized_name="principalId",
flags={"read_only": True},
)
identity.tenant_id = AAZStrType(
serialized_name="tenantId",
flags={"read_only": True},
)
identity.type = AAZStrType()
identity.user_assigned_identities = AAZDictType(
serialized_name="userAssignedIdentities",
)

user_assigned_identities = _schema_gallery_read.identity.user_assigned_identities
user_assigned_identities.Element = AAZObjectType()

_element = _schema_gallery_read.identity.user_assigned_identities.Element
_element.client_id = AAZStrType(
serialized_name="clientId",
flags={"read_only": True},
)
_element.principal_id = AAZStrType(
serialized_name="principalId",
flags={"read_only": True},
)

properties = _schema_gallery_read.properties
properties.description = AAZStrType()
properties.identifier = AAZObjectType()
Expand Down Expand Up @@ -410,13 +476,35 @@ def _build_schema_gallery_read(cls, _schema):
serialized_name="isSoftDeleteEnabled",
)

system_data = _schema_gallery_read.system_data
system_data.created_at = AAZStrType(
serialized_name="createdAt",
)
system_data.created_by = AAZStrType(
serialized_name="createdBy",
)
system_data.created_by_type = AAZStrType(
serialized_name="createdByType",
)
system_data.last_modified_at = AAZStrType(
serialized_name="lastModifiedAt",
)
system_data.last_modified_by = AAZStrType(
serialized_name="lastModifiedBy",
)
system_data.last_modified_by_type = AAZStrType(
serialized_name="lastModifiedByType",
)

tags = _schema_gallery_read.tags
tags.Element = AAZStrType()

_schema.id = cls._schema_gallery_read.id
_schema.identity = cls._schema_gallery_read.identity
_schema.location = cls._schema_gallery_read.location
_schema.name = cls._schema_gallery_read.name
_schema.properties = cls._schema_gallery_read.properties
_schema.system_data = cls._schema_gallery_read.system_data
_schema.tags = cls._schema_gallery_read.tags
_schema.type = cls._schema_gallery_read.type

Expand Down
Loading
Loading