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
9 changes: 7 additions & 2 deletions src/azure-cli-core/azure/cli/core/profiles/_shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ def default_api_version(self):
'task_runs': '2025-03-01-preview',
'runs': '2025-03-01-preview',
'network_rule': '2021-08-01-preview',
'cache_rules': '2023-01-01-preview',
'cache_rules': '2026-01-01-preview',
'credential_sets': '2023-01-01-preview'
}),
# The order does make things different.
Expand Down Expand Up @@ -244,7 +244,8 @@ def default_api_version(self):
'VERSION_2023_11_01_PREVIEW': "2023-11-01-preview",
'VERSION_2024_11_01_PREVIEW': "2024-11-01-preview",
'VERSION_2025_03_01_PREVIEW': "2025-03-01-preview",
'VERSION_2025_04_01': "2025-04-01"
'VERSION_2025_04_01': "2025-04-01",
'VERSION_2026_01_01_PREVIEW': "2026-01-01-preview"
},
ResourceType.MGMT_MSI: {
'user_assigned_identities': '2022-01-31-preview',
Expand Down Expand Up @@ -281,6 +282,10 @@ def __getattr__(self, item):
self._resolve()
return self._operations_groups_value[item]
except KeyError:
# For new-style SDKs without property-based operation groups,
# return the default API version for any unlisted group.
if self._sdk_profile and self._sdk_profile.default_api_version:
return self._post_process(self._sdk_profile.default_api_version)
raise AttributeError('Attribute {} does not exist.'.format(item))


Expand Down
4 changes: 4 additions & 0 deletions src/azure-cli/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ Release History
2.84.0
++++++

**ACR**

* `az acr cache create/update`: Add `--identity` parameter to support using user-assigned managed identity for cache rules

**AKS**

* `az aks create`: Add `--enable-container-network-logs` parameter to enable container network logs (#32700)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
VERSION_2024_11_01_PREVIEW = "2024-11-01-preview"
VERSION_2025_03_01_PREVIEW = "2025-03-01-preview"
VERSION_2025_04_01 = "2025-04-01"
VERSION_2026_01_01_PREVIEW = "2026-01-01-preview"


def get_acr_service_client(cli_ctx, api_version=None):
Expand All @@ -28,7 +29,7 @@ def cf_acr_registries(cli_ctx, *_):


def cf_acr_cache(cli_ctx, *_):
return get_acr_service_client(cli_ctx, api_version=VERSION_2023_01_01_PREVIEW).cache_rules
return get_acr_service_client(cli_ctx, api_version=VERSION_2026_01_01_PREVIEW).cache_rules


def cf_acr_cred_sets(cli_ctx, *_):
Expand Down
2 changes: 2 additions & 0 deletions src/azure-cli/azure/cli/command_modules/acr/_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

CREDENTIAL_SET_RESOURCE_ID_TEMPLATE = '/subscriptions/{sub_id}/resourceGroups/{rg}/providers/Microsoft.ContainerRegistry/registries/{reg_name}/credentialSets/{cred_set_name}'

USER_ASSIGNED_IDENTITY_RESOURCE_ID_TEMPLATE = '/subscriptions/{sub_id}/resourcegroups/{rg}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/{identity_name}'

TASK_RESOURCE_TYPE = REGISTRY_RESOURCE_TYPE + '/tasks'
TASK_VALID_VSTS_URLS = ['visualstudio.com', 'dev.azure.com']
TASK_RESOURCE_ID_TEMPLATE = '/subscriptions/{sub_id}/resourceGroups/{rg}/providers/Microsoft.ContainerRegistry/registries/{reg}/tasks/{name}'
Expand Down
5 changes: 4 additions & 1 deletion src/azure-cli/azure/cli/command_modules/acr/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@
validate_manifest_id,
validate_repo_id,
validate_repository,
validate_permissive_repo_id
validate_permissive_repo_id,
validate_cache_credentials
)
from .scope_map import RepoScopeMapActions, GatewayScopeMapActions

Expand Down Expand Up @@ -257,6 +258,8 @@ def load_arguments(self, _): # pylint: disable=too-many-statements
c.argument('source_repo', options_list=['--source-repo', '-s'], help="The full source repository path such as 'docker.io/library/ubuntu'.")
c.argument('target_repo', options_list=['--target-repo', '-t'], help="The target repository namespace such as 'ubuntu'.")
c.argument('remove_cred_set', action="store_true", help='Optional boolean indicating whether to remove the credential set from the cache rule. False by default.')
c.argument('identity', options_list=['--identity'], validator=validate_cache_credentials,
help='User-assigned managed identity resource ID for ACR to authenticate with the upstream registry. Format: /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/{identityName}.')

with self.argument_context('acr credential-set') as c:
c.argument('registry_name', options_list=['--registry', '-r'])
Expand Down
29 changes: 28 additions & 1 deletion src/azure-cli/azure/cli/command_modules/acr/_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from knack.util import CLIError
from knack.log import get_logger
from azure.cli.core.azclierror import FileOperationError, InvalidArgumentValueError
from ._constants import ACR_NAME_VALIDATION_REGEX
from ._constants import ACR_NAME_VALIDATION_REGEX, USER_ASSIGNED_IDENTITY_RESOURCE_ID_TEMPLATE

BAD_REPO_FQDN = "The positional parameter 'repo_id' must be a fully qualified repository specifier such"\
" as 'myregistry.azurecr.io/hello-world'."
Expand Down Expand Up @@ -192,3 +192,30 @@ def validate_repository(namespace):
def validate_docker_file_path(docker_file_path):
if not os.path.isfile(docker_file_path):
raise FileOperationError("Unable to find '{}'.".format(docker_file_path))


def validate_cache_credentials(namespace):
"""Validate cache credential options - allow both --identity and --cred-set, but --remove-cred-set is exclusive."""
has_identity = namespace.identity is not None
has_cred_set = namespace.cred_set is not None
has_remove_cred_set = getattr(namespace, 'remove_cred_set', False)

if has_remove_cred_set and (has_identity or has_cred_set):
raise InvalidArgumentValueError(
"Cannot specify --remove-cred-set with other credential options. Use --remove-cred-set alone to remove credentials."
)

# Validate identity format if provided
if has_identity:
identity_pattern = r'^/subscriptions/[^/]+/resource[Gg]roups/[^/]+/providers/Microsoft\.ManagedIdentity/userAssignedIdentities/[^/]+$'

if not re.match(identity_pattern, namespace.identity):
example_format = USER_ASSIGNED_IDENTITY_RESOURCE_ID_TEMPLATE.format(
sub_id='{subscriptionId}',
rg='{resourceGroupName}',
identity_name='{identityName}'
)
raise InvalidArgumentValueError(
f"The --identity parameter must be a valid ARM resource ID for a user-assigned managed identity. "
f"Format: {example_format}"
)
106 changes: 78 additions & 28 deletions src/azure-cli/azure/cli/command_modules/acr/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@
from azure.cli.core.azclierror import InvalidArgumentValueError
from azure.cli.core.commands.client_factory import get_subscription_id
from azure.core.serialization import NULL as AzureCoreNull
from azure.mgmt.containerregistry.models import (
CacheRule,
CacheRuleProperties,
CacheRuleUpdateParameters,
CacheRuleUpdateProperties,
IdentityProperties,
UserIdentityProperties
)


def acr_cache_show(cmd,
Expand Down Expand Up @@ -54,9 +62,12 @@ def acr_cache_create(cmd,
source_repo,
target_repo,
resource_group_name=None,
cred_set=None):
cred_set=None,
identity=None):

rg = get_resource_group_name_by_registry_name(cmd.cli_ctx, registry_name, resource_group_name)

# Handle credential set
if cred_set:
sub_id = get_subscription_id(cmd.cli_ctx)
# Format the credential set ID using subscription ID, resource group, registry name, and credential set name
Expand All @@ -68,14 +79,30 @@ def acr_cache_create(cmd,
)
else:
cred_set_id = AzureCoreNull

# Handle identity
identity_properties = None
if identity:
# Create IdentityProperties with UserAssigned type
identity_properties = IdentityProperties(
type="UserAssigned",
user_assigned_identities={
identity: UserIdentityProperties()
}
)

CacheRuleCreateParameters = cmd.get_models('CacheRule', operation_group='cache_rules')
# Create cache rule properties
cache_rule_properties = CacheRuleProperties(
source_repository=source_repo,
target_repository=target_repo,
credential_set_resource_id=cred_set_id
)

cache_rule_create_params = CacheRuleCreateParameters()
cache_rule_create_params.name = name
cache_rule_create_params.source_repository = source_repo
cache_rule_create_params.target_repository = target_repo
cache_rule_create_params.credential_set_resource_id = cred_set_id
# Create cache rule with direct SDK model
cache_rule_create_params = CacheRule(
properties=cache_rule_properties,
identity=identity_properties
)

return client.begin_create(resource_group_name=rg,
registry_name=registry_name,
Expand All @@ -88,25 +115,50 @@ def acr_cache_update_custom(cmd,
registry_name,
resource_group_name=None,
cred_set=None,
remove_cred_set=False):

if cred_set is None and remove_cred_set is False:
raise InvalidArgumentValueError("You must either update the credential set ID or remove it.")

if remove_cred_set:
cred_set_id = AzureCoreNull
else:
sub_id = get_subscription_id(cmd.cli_ctx)
rg = get_resource_group_name_by_registry_name(cmd.cli_ctx, registry_name, resource_group_name)
# Format the credential set ID using subscription ID, resource group, registry name, and credential set name
cred_set_id = CREDENTIAL_SET_RESOURCE_ID_TEMPLATE.format(
sub_id=sub_id,
rg=rg,
reg_name=registry_name,
cred_set_name=cred_set
)

instance.credential_set_resource_id = cred_set_id
remove_cred_set=False,
identity=None):

# Check if any update parameters are provided
has_cred_update = cred_set is not None or remove_cred_set
has_identity_update = identity is not None

if not has_cred_update and not has_identity_update:
raise InvalidArgumentValueError("You must provide at least one parameter to update (credential set, identity, or removal flag).")

#initilize properties if not already set
if instance.properties is None:
instance.properties = CacheRuleUpdateProperties()

# Handle credential set updates
if has_cred_update:
if remove_cred_set:
instance.properties.credential_set_resource_id = AzureCoreNull
else:
sub_id = get_subscription_id(cmd.cli_ctx)
rg = get_resource_group_name_by_registry_name(cmd.cli_ctx, registry_name, resource_group_name)
# Format the credential set ID using subscription ID, resource group, registry name, and credential set name
cred_set_id = CREDENTIAL_SET_RESOURCE_ID_TEMPLATE.format(
sub_id=sub_id,
rg=rg,
reg_name=registry_name,
cred_set_name=cred_set
)
instance.properties.credential_set_resource_id = cred_set_id

# Handle identity updates
if has_identity_update:
if identity:
# Create IdentityProperties with UserAssigned type
identity_properties = IdentityProperties(
type="UserAssigned",
user_assigned_identities={
identity: UserIdentityProperties()
}
)
instance.identity = identity_properties
else:
# Remove identity by setting to None
instance.identity = None

return instance

Expand All @@ -115,8 +167,6 @@ def acr_cache_update_get(cmd):
"""Returns an empty CacheRuleUpdateParameters object.
"""

CacheRuleUpdateParameters = cmd.get_models('CacheRuleUpdateParameters', operation_group='cache_rules')

return CacheRuleUpdateParameters()


Expand Down
Loading
Loading