From 3fb76a92d78c2806a5e7329b2a7ecb82d85ba888 Mon Sep 17 00:00:00 2001 From: trisavo-msft <141691062+trisavo-msft@users.noreply.github.com> Date: Tue, 17 Dec 2024 01:08:42 -0600 Subject: [PATCH] [ACR] `az acr create`: Add validation for registry name to support domain name label (#30404) --- .../cli/command_modules/acr/_constants.py | 1 + .../cli/command_modules/acr/_validators.py | 11 ++++++ .../azure/cli/command_modules/acr/custom.py | 3 ++ .../acr/tests/latest/test_acr_commands.py | 17 ++++++++- .../acr/tests/latest/test_acr_validators.py | 35 +++++++++++++++++++ 5 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 src/azure-cli/azure/cli/command_modules/acr/tests/latest/test_acr_validators.py diff --git a/src/azure-cli/azure/cli/command_modules/acr/_constants.py b/src/azure-cli/azure/cli/command_modules/acr/_constants.py index 3819e66f54e..62be148e507 100644 --- a/src/azure-cli/azure/cli/command_modules/acr/_constants.py +++ b/src/azure-cli/azure/cli/command_modules/acr/_constants.py @@ -30,6 +30,7 @@ ACR_AUDIENCE_RESOURCE_NAME = "containerregistry" # Regex pattern to validate that registry name is alphanumeric and between 5 and 50 characters +# Dashes "-" are allowed to accomodate for domain name label scope, but is blocked on registry creation "acr create" ACR_NAME_VALIDATION_REGEX = r'^[a-zA-Z0-9-]{5,50}$' ALLOWED_TASK_FILE_TYPES = ('.yaml', '.yml', '.toml', '.json', '.sh', '.bash', '.zsh', '.ps1', diff --git a/src/azure-cli/azure/cli/command_modules/acr/_validators.py b/src/azure-cli/azure/cli/command_modules/acr/_validators.py index 3296f8ee72e..a20740ed78f 100644 --- a/src/azure-cli/azure/cli/command_modules/acr/_validators.py +++ b/src/azure-cli/azure/cli/command_modules/acr/_validators.py @@ -116,6 +116,17 @@ def validate_registry_name(cmd, namespace): if pos > 0: logger.warning("The login server endpoint suffix '%s' is automatically omitted.", acr_suffix) namespace.registry_name = registry[:pos] + registry = registry[:pos] + # If registry contains '-' due to Domain Name Label Scope, + # ex: "myregistry-dnlhash123.azurecr.io", strip "-dnlhash123" + dnl_hash = registry.find("-") + if registry and dnl_hash > 0: + logger.warning( + "The domain name label suffix '%s' is automatically omitted. Registry name is %s.", + registry[dnl_hash:], + registry[:dnl_hash]) + namespace.registry_name = registry[:dnl_hash] + registry = namespace.registry_name if not re.match(ACR_NAME_VALIDATION_REGEX, registry): raise InvalidArgumentValueError(BAD_REGISTRY_NAME) diff --git a/src/azure-cli/azure/cli/command_modules/acr/custom.py b/src/azure-cli/azure/cli/command_modules/acr/custom.py index 813e8c7ce27..1371f43dbb2 100644 --- a/src/azure-cli/azure/cli/command_modules/acr/custom.py +++ b/src/azure-cli/azure/cli/command_modules/acr/custom.py @@ -69,6 +69,9 @@ def acr_create(cmd, if re.match(r'\w*[A-Z]\w*', registry_name): raise InvalidArgumentValueError("argument error: Registry name must use only lowercase.") + if re.match(r'\w*[-]\w*', registry_name): + raise InvalidArgumentValueError("argument error: Registry name cannot contain dashes.") + Registry, Sku, NetworkRuleSet = cmd.get_models('Registry', 'Sku', 'NetworkRuleSet') registry = Registry(location=location, sku=Sku(name=sku), admin_user_enabled=admin_enabled, zone_redundancy=zone_redundancy, tags=tags) diff --git a/src/azure-cli/azure/cli/command_modules/acr/tests/latest/test_acr_commands.py b/src/azure-cli/azure/cli/command_modules/acr/tests/latest/test_acr_commands.py index 5e3a0a280b7..92a02a94555 100644 --- a/src/azure-cli/azure/cli/command_modules/acr/tests/latest/test_acr_commands.py +++ b/src/azure-cli/azure/cli/command_modules/acr/tests/latest/test_acr_commands.py @@ -4,7 +4,7 @@ # -------------------------------------------------------------------------------------------- from azure.cli.testsdk.scenario_tests import AllowLargeResponse -from azure.cli.testsdk import ScenarioTest, ResourceGroupPreparer, KeyVaultPreparer, record_only +from azure.cli.testsdk import ScenarioTest, ResourceGroupPreparer, KeyVaultPreparer, record_only, live_only from azure.cli.command_modules.acr.custom import DEF_DIAG_SETTINGS_NAME_TEMPLATE @@ -713,6 +713,21 @@ def test_acr_with_anonymous_pull(self, resource_group, resource_group_location): self.cmd('acr update --name {registry_name} --resource-group {rg} --anonymous-pull-enabled false', checks=[self.check('anonymousPullEnabled', False)]) + @ResourceGroupPreparer() + @live_only() + def test_acr_create_invalid_name(self, resource_group): + from azure.cli.core.azclierror import InvalidArgumentValueError + + # Block registry creation if there is '-'. + self.kwargs.update({ + 'registry_name': self.create_random_name('testreg', 20) + '-' + self.create_random_name('dnlhash', 20) + }) + + with self.assertRaises(Exception) as ex: + self.cmd('acr create --name {registry_name} --resource-group {rg} --sku premium -l eastus') + + self.assertTrue(InvalidArgumentValueError, ex.exception) + @ResourceGroupPreparer(location='eastus2') def test_acr_with_private_endpoint(self, resource_group): self.kwargs.update({ diff --git a/src/azure-cli/azure/cli/command_modules/acr/tests/latest/test_acr_validators.py b/src/azure-cli/azure/cli/command_modules/acr/tests/latest/test_acr_validators.py new file mode 100644 index 00000000000..9e098a897e6 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/acr/tests/latest/test_acr_validators.py @@ -0,0 +1,35 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import unittest +from types import SimpleNamespace +from unittest.mock import Mock +from azure.cli.core.cloud import HARD_CODED_CLOUD_LIST +from azure.cli.command_modules.acr._validators import validate_registry_name + +class AcrValidatorsTests(unittest.TestCase): + def test_registry_name_with_dnl_suffix(self): + acr_supported_clouds = [cloud for cloud in HARD_CODED_CLOUD_LIST if cloud.name != 'AzureGermanCloud'] + for hard_coded_cloud in acr_supported_clouds: + namespace = SimpleNamespace( + **{ + "registry_name": "myacr-dnlhash123", + } + ) + cmd = Mock(cli_ctx=Mock(cloud=hard_coded_cloud)) + validate_registry_name(cmd, namespace) + self.assertEqual(namespace.registry_name, 'myacr') + + def test_registry_name_with_dnl_suffix_loginserver(self): + namespace = SimpleNamespace( + **{ + "registry_name": "myacr-dnlhash123.azurecr.io", + } + ) + azure_public_cloud = HARD_CODED_CLOUD_LIST[0] + cmd = Mock(cli_ctx=Mock(cloud=azure_public_cloud)) + validate_registry_name(cmd, namespace) + self.assertEqual(namespace.registry_name, 'myacr') +