Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,6 @@ def cf_rolling_upgrade_commands(cli_ctx, _):
return _compute_client_factory(cli_ctx).virtual_machine_scale_set_rolling_upgrades


def cf_galleries(cli_ctx, _):
return _compute_client_factory(cli_ctx).galleries


def cf_gallery_images(cli_ctx, _):
return _compute_client_factory(cli_ctx).gallery_images

Expand Down
46 changes: 46 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,52 @@
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
- name: Create a shared image gallery with a system assigned identity.
text: |
az sig create --resource-group MyResourceGroup --gallery-name MyGallery123 --assign-identity
- name: Create a shared image gallery with a system assigned identity with the 'Reader' role.
text: |
az sig create --resource-group MyResourceGroup --gallery-name MyGallery123 --assign-identity
--role Reader --scope /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup
- name: Create a shared image gallery with a user assigned identity.
text: |
az sig create --resource-group MyResourceGroup --gallery-name MyGallery123 --assign-identity id1
- name: Create a shared image gallery with both system and user assigned identity.
text: |
az sig create --resource-group MyResourceGroup --gallery-name MyGallery123 --assign-identity [system] id1
"""

helps['sig identity assign'] = """
type: command
short-summary: Assign the user or system managed identities.
examples:
- name: Enable the system assigned identity with the 'Reader' role.
text: |
az sig identity assign -g MyResourceGroup -r MyGalleryName --role Reader --scope /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup
- name: Enable the system assigned identity and a user assigned identity with the 'Reader' role.
text: |
az sig identity assign -g MyResourceGroup -r MyGalleryName --role Reader --identities [system] myAssignedId --scope /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup
"""

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
"""
Comment on lines +604 to +626
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 remove. 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))
Comment on lines +1368 to +1372
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
6 changes: 5 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,11 @@ 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