Skip to content
Merged
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
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 @@ -13,6 +13,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 @@ -256,6 +257,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
35 changes: 34 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,36 @@ 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/[^/]+'
r'/providers/Microsoft\.ManagedIdentity'
r'/userAssignedIdentities/[^/]+$'
)

if not re.match(identity_pattern, namespace.identity, re.IGNORECASE):
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 "
f"for a user-assigned managed identity. "
f"Format: {example_format}"
)
99 changes: 74 additions & 25 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 @@ -69,13 +80,29 @@ def acr_cache_create(cmd,
else:
cred_set_id = AzureCoreNull

CacheRuleCreateParameters = cmd.get_models('CacheRule', operation_group='cache_rules')
# Handle identity
identity_properties = None
if identity:
# Create IdentityProperties with UserAssigned type
identity_properties = IdentityProperties(
type="UserAssigned",
user_assigned_identities={
identity: UserIdentityProperties()
}
)

# 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,35 +115,57 @@ def acr_cache_update_custom(cmd,
registry_name,
resource_group_name=None,
cred_set=None,
remove_cred_set=False):
remove_cred_set=False,
identity=None):

if cred_set is None and remove_cred_set is False:
raise InvalidArgumentValueError("You must either update the credential set ID or remove it.")
# 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 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
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)."
)

instance.credential_set_resource_id = cred_set_id
# initialize 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 and identity:
# Create IdentityProperties with UserAssigned type
identity_properties = IdentityProperties(
type="UserAssigned",
user_assigned_identities={
identity: UserIdentityProperties()
}
)
instance.identity = identity_properties

return instance


def acr_cache_update_get(cmd):
def acr_cache_update_get(cmd): # pylint: disable=unused-argument
"""Returns an empty CacheRuleUpdateParameters object.
"""

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

return CacheRuleUpdateParameters()


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,9 @@ def acr_connected_registry_create(cmd, # pylint: disable=too-many-locals, too-m
registry_name), yes)
acr_update_custom(cmd, registry, data_endpoint_enabled=True)
registry_client = cf_acr_registries(cmd.cli_ctx)
acr_update_set(cmd, registry_client, registry_name, resource_group_name, registry)
LongRunningOperation(cmd.cli_ctx)(
acr_update_set(cmd, registry_client, registry_name, resource_group_name, registry)
)

from azure.core.exceptions import HttpResponseError as ErrorResponseException
parent = None
Expand Down

Large diffs are not rendered by default.

Loading
Loading