Skip to content

Commit d6e3aba

Browse files
authored
[ACR] az acr cache create/update: Add --identity parameter to support using user-assigned managed identity for cache rules (#33040)
1 parent dd2a672 commit d6e3aba

9 files changed

Lines changed: 1798 additions & 429 deletions

File tree

src/azure-cli/azure/cli/command_modules/acr/_constants.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313

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

16+
USER_ASSIGNED_IDENTITY_RESOURCE_ID_TEMPLATE = '/subscriptions/{sub_id}/resourceGroups/{rg}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/{identity_name}'
17+
1618
TASK_RESOURCE_TYPE = REGISTRY_RESOURCE_TYPE + '/tasks'
1719
TASK_VALID_VSTS_URLS = ['visualstudio.com', 'dev.azure.com']
1820
TASK_RESOURCE_ID_TEMPLATE = '/subscriptions/{sub_id}/resourceGroups/{rg}/providers/Microsoft.ContainerRegistry/registries/{reg}/tasks/{name}'

src/azure-cli/azure/cli/command_modules/acr/_params.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@
4141
validate_manifest_id,
4242
validate_repo_id,
4343
validate_repository,
44-
validate_permissive_repo_id
44+
validate_permissive_repo_id,
45+
validate_cache_credentials
4546
)
4647
from .scope_map import RepoScopeMapActions, GatewayScopeMapActions
4748

@@ -256,6 +257,8 @@ def load_arguments(self, _): # pylint: disable=too-many-statements
256257
c.argument('source_repo', options_list=['--source-repo', '-s'], help="The full source repository path such as 'docker.io/library/ubuntu'.")
257258
c.argument('target_repo', options_list=['--target-repo', '-t'], help="The target repository namespace such as 'ubuntu'.")
258259
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.')
260+
c.argument('identity', options_list=['--identity'], validator=validate_cache_credentials,
261+
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}.')
259262

260263
with self.argument_context('acr credential-set') as c:
261264
c.argument('registry_name', options_list=['--registry', '-r'])

src/azure-cli/azure/cli/command_modules/acr/_validators.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from knack.util import CLIError
99
from knack.log import get_logger
1010
from azure.cli.core.azclierror import FileOperationError, InvalidArgumentValueError
11-
from ._constants import ACR_NAME_VALIDATION_REGEX
11+
from ._constants import ACR_NAME_VALIDATION_REGEX, USER_ASSIGNED_IDENTITY_RESOURCE_ID_TEMPLATE
1212

1313
BAD_REPO_FQDN = "The positional parameter 'repo_id' must be a fully qualified repository specifier such"\
1414
" as 'myregistry.azurecr.io/hello-world'."
@@ -192,3 +192,36 @@ def validate_repository(namespace):
192192
def validate_docker_file_path(docker_file_path):
193193
if not os.path.isfile(docker_file_path):
194194
raise FileOperationError("Unable to find '{}'.".format(docker_file_path))
195+
196+
197+
def validate_cache_credentials(namespace):
198+
"""Validate cache credential options - allow both --identity and --cred-set, but --remove-cred-set is exclusive."""
199+
has_identity = namespace.identity is not None
200+
has_cred_set = namespace.cred_set is not None
201+
has_remove_cred_set = getattr(namespace, 'remove_cred_set', False)
202+
203+
if has_remove_cred_set and (has_identity or has_cred_set):
204+
raise InvalidArgumentValueError(
205+
"Cannot specify --remove-cred-set with other credential options. "
206+
"Use --remove-cred-set alone to remove credentials."
207+
)
208+
209+
# Validate identity format if provided
210+
if has_identity:
211+
identity_pattern = (
212+
r'^/subscriptions/[^/]+/resource[Gg]roups/[^/]+'
213+
r'/providers/Microsoft\.ManagedIdentity'
214+
r'/userAssignedIdentities/[^/]+$'
215+
)
216+
217+
if not re.match(identity_pattern, namespace.identity, re.IGNORECASE):
218+
example_format = USER_ASSIGNED_IDENTITY_RESOURCE_ID_TEMPLATE.format(
219+
sub_id='{subscriptionId}',
220+
rg='{resourceGroupName}',
221+
identity_name='{identityName}'
222+
)
223+
raise InvalidArgumentValueError(
224+
f"The --identity parameter must be a valid ARM resource ID "
225+
f"for a user-assigned managed identity. "
226+
f"Format: {example_format}"
227+
)

src/azure-cli/azure/cli/command_modules/acr/cache.py

Lines changed: 74 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@
88
from azure.cli.core.azclierror import InvalidArgumentValueError
99
from azure.cli.core.commands.client_factory import get_subscription_id
1010
from azure.core.serialization import NULL as AzureCoreNull
11+
from azure.mgmt.containerregistry.models import (
12+
CacheRule,
13+
CacheRuleProperties,
14+
CacheRuleUpdateParameters,
15+
CacheRuleUpdateProperties,
16+
IdentityProperties,
17+
UserIdentityProperties
18+
)
1119

1220

1321
def acr_cache_show(cmd,
@@ -54,9 +62,12 @@ def acr_cache_create(cmd,
5462
source_repo,
5563
target_repo,
5664
resource_group_name=None,
57-
cred_set=None):
65+
cred_set=None,
66+
identity=None):
5867

5968
rg = get_resource_group_name_by_registry_name(cmd.cli_ctx, registry_name, resource_group_name)
69+
70+
# Handle credential set
6071
if cred_set:
6172
sub_id = get_subscription_id(cmd.cli_ctx)
6273
# Format the credential set ID using subscription ID, resource group, registry name, and credential set name
@@ -69,13 +80,29 @@ def acr_cache_create(cmd,
6980
else:
7081
cred_set_id = AzureCoreNull
7182

72-
CacheRuleCreateParameters = cmd.get_models('CacheRule', operation_group='cache_rules')
83+
# Handle identity
84+
identity_properties = None
85+
if identity:
86+
# Create IdentityProperties with UserAssigned type
87+
identity_properties = IdentityProperties(
88+
type="UserAssigned",
89+
user_assigned_identities={
90+
identity: UserIdentityProperties()
91+
}
92+
)
93+
94+
# Create cache rule properties
95+
cache_rule_properties = CacheRuleProperties(
96+
source_repository=source_repo,
97+
target_repository=target_repo,
98+
credential_set_resource_id=cred_set_id
99+
)
73100

74-
cache_rule_create_params = CacheRuleCreateParameters()
75-
cache_rule_create_params.name = name
76-
cache_rule_create_params.source_repository = source_repo
77-
cache_rule_create_params.target_repository = target_repo
78-
cache_rule_create_params.credential_set_resource_id = cred_set_id
101+
# Create cache rule with direct SDK model
102+
cache_rule_create_params = CacheRule(
103+
properties=cache_rule_properties,
104+
identity=identity_properties
105+
)
79106

80107
return client.begin_create(resource_group_name=rg,
81108
registry_name=registry_name,
@@ -88,35 +115,57 @@ def acr_cache_update_custom(cmd,
88115
registry_name,
89116
resource_group_name=None,
90117
cred_set=None,
91-
remove_cred_set=False):
118+
remove_cred_set=False,
119+
identity=None):
92120

93-
if cred_set is None and remove_cred_set is False:
94-
raise InvalidArgumentValueError("You must either update the credential set ID or remove it.")
121+
# Check if any update parameters are provided
122+
has_cred_update = cred_set is not None or remove_cred_set
123+
has_identity_update = identity is not None
95124

96-
if remove_cred_set:
97-
cred_set_id = AzureCoreNull
98-
else:
99-
sub_id = get_subscription_id(cmd.cli_ctx)
100-
rg = get_resource_group_name_by_registry_name(cmd.cli_ctx, registry_name, resource_group_name)
101-
# Format the credential set ID using subscription ID, resource group, registry name, and credential set name
102-
cred_set_id = CREDENTIAL_SET_RESOURCE_ID_TEMPLATE.format(
103-
sub_id=sub_id,
104-
rg=rg,
105-
reg_name=registry_name,
106-
cred_set_name=cred_set
125+
if not has_cred_update and not has_identity_update:
126+
raise InvalidArgumentValueError(
127+
"You must provide at least one parameter to update "
128+
"(credential set, identity, or removal flag)."
107129
)
108130

109-
instance.credential_set_resource_id = cred_set_id
131+
# initialize properties if not already set
132+
if instance.properties is None:
133+
instance.properties = CacheRuleUpdateProperties()
134+
135+
# Handle credential set updates
136+
if has_cred_update:
137+
if remove_cred_set:
138+
instance.properties.credential_set_resource_id = AzureCoreNull
139+
else:
140+
sub_id = get_subscription_id(cmd.cli_ctx)
141+
rg = get_resource_group_name_by_registry_name(cmd.cli_ctx, registry_name, resource_group_name)
142+
# Format the credential set ID using subscription ID, resource group, registry name, and credential set name
143+
cred_set_id = CREDENTIAL_SET_RESOURCE_ID_TEMPLATE.format(
144+
sub_id=sub_id,
145+
rg=rg,
146+
reg_name=registry_name,
147+
cred_set_name=cred_set
148+
)
149+
instance.properties.credential_set_resource_id = cred_set_id
150+
151+
# Handle identity updates
152+
if has_identity_update and identity:
153+
# Create IdentityProperties with UserAssigned type
154+
identity_properties = IdentityProperties(
155+
type="UserAssigned",
156+
user_assigned_identities={
157+
identity: UserIdentityProperties()
158+
}
159+
)
160+
instance.identity = identity_properties
110161

111162
return instance
112163

113164

114-
def acr_cache_update_get(cmd):
165+
def acr_cache_update_get(cmd): # pylint: disable=unused-argument
115166
"""Returns an empty CacheRuleUpdateParameters object.
116167
"""
117168

118-
CacheRuleUpdateParameters = cmd.get_models('CacheRuleUpdateParameters', operation_group='cache_rules')
119-
120169
return CacheRuleUpdateParameters()
121170

122171

src/azure-cli/azure/cli/command_modules/acr/connected_registry.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,9 @@ def acr_connected_registry_create(cmd, # pylint: disable=too-many-locals, too-m
7878
registry_name), yes)
7979
acr_update_custom(cmd, registry, data_endpoint_enabled=True)
8080
registry_client = cf_acr_registries(cmd.cli_ctx)
81-
acr_update_set(cmd, registry_client, registry_name, resource_group_name, registry)
81+
LongRunningOperation(cmd.cli_ctx)(
82+
acr_update_set(cmd, registry_client, registry_name, resource_group_name, registry)
83+
)
8284

8385
from azure.core.exceptions import HttpResponseError as ErrorResponseException
8486
parent = None

src/azure-cli/azure/cli/command_modules/acr/tests/latest/recordings/test_acr_cache.yaml

Lines changed: 393 additions & 241 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)