diff --git a/src/azure-cli/azure/cli/command_modules/acr/_help.py b/src/azure-cli/azure/cli/command_modules/acr/_help.py index 1d2f424b390..5378428d526 100644 --- a/src/azure-cli/azure/cli/command_modules/acr/_help.py +++ b/src/azure-cli/azure/cli/command_modules/acr/_help.py @@ -1692,45 +1692,6 @@ text: > az acr connected-registry permissions show -r mycloudregistry -n myconnectedregistry """ - -# To be deprecated -helps['acr connected-registry install'] = """ -type: group -short-summary: Help to access the necessary information for installing a connected registry. Please see https://aka.ms/acr/connected-registry for more information. -""" - -helps['acr connected-registry install info'] = """ -type: command -short-summary: Retrieve information required to activate a connected registry. -examples: - - name: Set http as the parent protocol, and prints the values required to activate a connected registry in json format - text: > - az acr connected-registry install info --registry mycloudregistry --name myconnectedregistry --parent-protocol http -""" - -helps['acr connected-registry install renew-credentials'] = """ -type: command -short-summary: Retrieve information required to activate a connected registry, and renews the sync token credentials. -examples: - - name: Set http as the parent protocol, and prints the values in json format required to activate a connected registry and the newly generated sync token credentials. - text: > - az acr connected-registry install renew-credentials -r mycloudregistry -n myconnectedregistry --parent-protocol http -""" - -helps['acr connected-registry repo'] = """ -type: command -short-summary: Update all the necessary connected registry sync scope maps repository permissions. -examples: - - name: Add permissions to synchronize images from 'repo1' and 'repo2' to the connected registry 'myconnectedregistry' and its ancestors. - text: > - az acr connected-registry repo -r mycloudregistry -n myconnectedregistry --add repo1 repo2 - - name: Remove permissions to synchronize images from 'repo1' and 'repo2' to the connected registry 'myconnectedregistry' and its descendants. - text: > - az acr connected-registry repo -r mycloudregistry -n myconnectedregistry --remove repo1 repo2 - - name: Remove permissions to synchronize 'repo1' images and adds permissions for 'repo2' images. - text: > - az acr connected-registry repo -r mycloudregistry -n myconnectedregistry --remove repo1 --add repo2 -""" # endregion # region private-endpoint-connection diff --git a/src/azure-cli/azure/cli/command_modules/acr/_params.py b/src/azure-cli/azure/cli/command_modules/acr/_params.py index 84a56a1c13a..9f82a496d71 100644 --- a/src/azure-cli/azure/cli/command_modules/acr/_params.py +++ b/src/azure-cli/azure/cli/command_modules/acr/_params.py @@ -542,7 +542,7 @@ def load_arguments(self, _): # pylint: disable=too-many-statements with self.argument_context('acr connected-registry create') as c: c.argument('log_level', help='Set the log level for logging on the instance. Accepted log levels are Debug, Information, Warning, Error, and None.', required=False, default="Information") - c.argument('mode', arg_type=get_enum_type(['ReadOnly', 'ReadWrite']), options_list=['--mode', '-m'], help='Determine the access it will have when synchronized.', required=False, default="ReadWrite") + c.argument('mode', arg_type=get_enum_type(['ReadOnly', 'ReadWrite']), options_list=['--mode', '-m'], help='Determine the access it will have when synchronized.', required=False, default="ReadOnly") c.argument('client_token_list', options_list=['--client-tokens'], nargs='+', help='Specify the client access to the repositories in the connected registry. It can be in the format [TOKEN_NAME01] [TOKEN_NAME02]...') c.argument('sync_window', options_list=['--sync-window', '-w'], help='Required parameter if --sync-schedule is present. Used to determine the schedule duration. Uses ISO 8601 duration format.') c.argument('sync_schedule', options_list=['--sync-schedule', '-s'], help='Optional parameter to define the sync schedule. Uses cron expression to determine the schedule. If not specified, the instance is considered always online and attempts to sync every minute.', required=False, default="* * * * *") @@ -569,12 +569,6 @@ def load_arguments(self, _): # pylint: disable=too-many-statements c.argument('remove_repos', options_list=['--remove'], nargs='*', help='repository permissions to be removed from the targeted connected registry and it\'s succesors sync scope maps. Use the format "--remove [REPO1 REPO2 ...]" per flag. ' + repo_valid_actions) - with self.argument_context('acr connected-registry repo') as c: - c.argument('add_repos', options_list=['--add'], nargs='*', - help='repository permissions to be added to the targeted connected registry and it\'s ancestors sync scope maps. Use the format "--add [REPO1 REPO2 ...]" per flag. ' + repo_valid_actions) - c.argument('remove_repos', options_list=['--remove'], nargs='*', - help='repository permissions to be removed from the targeted connected registry and it\'s succesors sync scope maps. Use the format "--remove [REPO1 REPO2 ...]" per flag. ' + repo_valid_actions) - def _get_helm_default_install_location(): exe_name = 'helm' diff --git a/src/azure-cli/azure/cli/command_modules/acr/commands.py b/src/azure-cli/azure/cli/command_modules/acr/commands.py index e47a318e169..bbad9d94621 100644 --- a/src/azure-cli/azure/cli/command_modules/acr/commands.py +++ b/src/azure-cli/azure/cli/command_modules/acr/commands.py @@ -431,26 +431,24 @@ def _helm_deprecate_message(self): with self.command_group('acr connected-registry', acr_connected_registry_util, is_preview=True) as g: g.command('create', 'acr_connected_registry_create') + g.command('update', 'acr_connected_registry_update') + g.generic_update_command('update', + getter_name='acr_connected_registry_update_get', + setter_name='acr_connected_registry_update_set', + custom_func_name='acr_connected_registry_update', + custom_func_type=acr_connected_registry_util, + client_factory=cf_acr_connected_registries) g.command('delete', 'acr_connected_registry_delete') g.show_command('show', 'acr_connected_registry_show') g.command('deactivate', 'acr_connected_registry_deactivate') - g.command('update', 'acr_connected_registry_update') g.command('get-settings', 'acr_connected_registry_get_settings') - g.command('permissions update', 'acr_connected_registry_permissions_update') - g.show_command('permissions show', 'acr_connected_registry_permissions_show', - table_transformer=scope_map_output_format) g.command('list', 'acr_connected_registry_list', table_transformer=connected_registry_list_output_format) g.command('list-client-tokens', 'acr_connected_registry_list_client_tokens', table_transformer=token_output_format) - g.command('repo', 'acr_connected_registry_permissions_update', - deprecate_info=self.deprecate(redirect='permissions update', hide=True)) - - with self.command_group('acr connected-registry install', acr_connected_registry_util, - deprecate_info=self.deprecate(redirect='acr connected-registry get-settings', - hide=True)) as g: - g.command('info', 'acr_connected_registry_install_info') - g.command('renew-credentials', 'acr_connected_registry_install_renew_credentials') + g.command('permissions update', 'acr_connected_registry_permissions_update') + g.show_command('permissions show', 'acr_connected_registry_permissions_show', + table_transformer=scope_map_output_format) def _metadata_deprecate_message(self): msg = "This {} has been deprecated and will be removed in future release.".format(self.object_type) diff --git a/src/azure-cli/azure/cli/command_modules/acr/connected_registry.py b/src/azure-cli/azure/cli/command_modules/acr/connected_registry.py index ef04e365294..3241abbda18 100644 --- a/src/azure-cli/azure/cli/command_modules/acr/connected_registry.py +++ b/src/azure-cli/azure/cli/command_modules/acr/connected_registry.py @@ -4,7 +4,6 @@ # -------------------------------------------------------------------------------------------- from enum import Enum -import re from msrest.exceptions import ValidationError from knack.log import get_logger from knack.util import CLIError @@ -12,6 +11,7 @@ from azure.cli.core.commands import LongRunningOperation from azure.cli.core.commands.client_factory import get_subscription_id from azure.cli.core.util import user_confirmation +from azure.core.exceptions import HttpResponseError from ._client_factory import cf_acr_tokens, cf_acr_scope_maps from ._utils import ( build_token_id, @@ -22,6 +22,7 @@ parse_scope_map_actions, validate_managed_registry ) +from .custom import acr_update_custom class ConnectedRegistryModes(Enum): @@ -59,33 +60,31 @@ def acr_connected_registry_create(cmd, # pylint: disable=too-many-locals, too-m sync_window=None, log_level=None, sync_audit_logs_enabled=False, - notifications=None): + notifications=None, + yes=False): if bool(sync_token_name) == bool(repositories): raise CLIError("argument error: either --sync-token or --repository must be provided, but not both.") # Check needed since the sync token gateway actions must be at least 5 characters long. if len(connected_registry_name) < 5: raise InvalidArgumentValueError("argument error: Connected registry name must be at least 5 characters long.") - if re.match(r'\w*[A-Z]\w*', connected_registry_name): - raise InvalidArgumentValueError("argument error: Connected registry name must use only lowercase.") - registry, resource_group_name = get_registry_by_name(cmd.cli_ctx, registry_name, resource_group_name) - subscription_id = get_subscription_id(cmd.cli_ctx) + subscription_id = get_subscription_id(cmd.cli_ctx) + registry, resource_group_name = get_registry_by_name(cmd.cli_ctx, registry_name, resource_group_name) if not registry.data_endpoint_enabled: - raise CLIError("Can't create the connected registry '{}' ".format(connected_registry_name) + - "because the cloud registry '{}' data endpoint is disabled. ".format(registry_name) + - "Enabling the data endpoint might affect your firewall rules.\nTo enable data endpoint run:" + - "\n\taz acr update -n {} --data-endpoint-enabled true".format(registry_name)) + user_confirmation("Dedicated data enpoints must be enabled to use connected-registry. Enabling might " + + "impact your firewall rules. Are you sure you want to enable it for '{}' registry?".format( + registry_name), yes) + acr_update_custom(cmd, registry, resource_group_name, data_endpoint_enabled=True) - from azure.core.exceptions import HttpResponseError as ErrorResponseException - parent = None mode = mode.lower() + parent = None if parent_name: try: parent = acr_connected_registry_show(cmd, client, parent_name, registry_name, resource_group_name) connected_registry_list = list(client.list(resource_group_name, registry_name)) family_tree, _ = _get_family_tree(connected_registry_list, None) - except ErrorResponseException as ex: + except HttpResponseError as ex: if ex.response.status_code == 404: raise CLIError("The parent connected registry '{}' could not be found.".format(parent_name)) raise CLIError(ex) @@ -145,6 +144,7 @@ def acr_connected_registry_create(cmd, # pylint: disable=too-many-locals, too-m def acr_connected_registry_update(cmd, # pylint: disable=too-many-locals, too-many-statements client, + instance, registry_name, connected_registry_name, add_client_token_list=None, @@ -163,7 +163,7 @@ def acr_connected_registry_update(cmd, # pylint: disable=too-many-locals, too-m current_connected_registry = acr_connected_registry_show( cmd, client, connected_registry_name, registry_name, resource_group_name) - # Add or remove from the current client token id list + # create add client token set and remove client token set if add_client_token_list is not None: for i, client_token_name in enumerate(add_client_token_list): add_client_token_list[i] = build_token_id( @@ -178,61 +178,76 @@ def acr_connected_registry_update(cmd, # pylint: disable=too-many-locals, too-m remove_client_token_set = set(remove_client_token_list) else: remove_client_token_set = set() - + # check for intersection between add and remove. duplicate_client_token = set.intersection(add_client_token_set, remove_client_token_set) if duplicate_client_token: errors = sorted(map(lambda action: action[action.rfind('/') + 1:], duplicate_client_token)) raise CLIError( 'Update ambiguity. Duplicate client token ids were provided with ' + '--add-client-tokens and --remove-client-tokens arguments.\n{}'.format(errors)) - + # get updated client token list current_client_token_set = set(current_connected_registry.client_token_ids) \ if current_connected_registry.client_token_ids else set() client_token_set = current_client_token_set.union(add_client_token_set).difference(remove_client_token_set) - client_token_list = list(client_token_set) if client_token_set != current_client_token_set else None + # update client token properties + instance.client_token_ids = client_token_list - # Add or remove from the current notifications list + # create add notifications set and remove notifications set add_notifications_set = set(list(add_notifications)) \ if add_notifications else set() - remove_notifications_set = set(list(remove_notifications)) \ if remove_notifications else set() - + # check for intersection between add and remove. duplicate_notifications = set.intersection(add_notifications_set, remove_notifications_set) if duplicate_notifications: errors = sorted(duplicate_notifications) raise ArgumentUsageError( 'Update ambiguity. Duplicate notifications list were provided with ' + '--add-notifications and --remove-notifications arguments.\n{}'.format(errors)) - + # get updated notifications list current_notifications_set = set(current_connected_registry.notifications_list) \ if current_connected_registry.notifications_list else set() notifications_set = current_notifications_set.union(add_notifications_set).difference(remove_notifications_set) - notifications_list = list(notifications_set) if notifications_set != current_notifications_set else None + # update notifications properties + instance.notifications_list = notifications_list - ConnectedRegistryUpdateParameters, SyncUpdateProperties, LoggingProperties = cmd.get_models( - 'ConnectedRegistryUpdateParameters', 'SyncUpdateProperties', 'LoggingProperties') - connected_registry_update_parameters = ConnectedRegistryUpdateParameters( - sync_properties=SyncUpdateProperties( + # update sync properties and logging properties + SyncUpdateProperties, LoggingProperties = cmd.get_models('SyncUpdateProperties', 'LoggingProperties') + if bool(sync_schedule) or bool(sync_window) or bool(sync_message_ttl): + instance.sync_properties = SyncUpdateProperties( schedule=sync_schedule, message_ttl=sync_message_ttl, sync_window=sync_window - ), - logging=LoggingProperties( + ) + if bool(log_level) or bool(sync_audit_logs_enabled): + instance.logging = LoggingProperties( log_level=log_level, audit_log_status=sync_audit_logs_enabled - ), - client_token_ids=client_token_list, - notifications_list=notifications_list - ) + ) + return instance + + +def acr_connected_registry_update_get(cmd): + """Returns an empty RegistryUpdateParameters object. + """ + ConnectedRegistryUpdateParameters = cmd.get_models('ConnectedRegistryUpdateParameters') + return ConnectedRegistryUpdateParameters() + +def acr_connected_registry_update_set(cmd, + client, + registry_name, + connected_registry_name, + resource_group_name=None, + parameters=None): + _, resource_group_name = get_registry_by_name(cmd.cli_ctx, registry_name, resource_group_name) try: return client.begin_update(resource_group_name=resource_group_name, registry_name=registry_name, connected_registry_name=connected_registry_name, - connected_registry_update_parameters=connected_registry_update_parameters) + connected_registry_update_parameters=parameters) except ValidationError as e: raise CLIError(e) @@ -430,27 +445,6 @@ def _get_descendants(family_tree, parent_id): # region connected-registry install subgroup -def acr_connected_registry_install_info(cmd, - client, - connected_registry_name, - registry_name, - parent_protocol, - resource_group_name=None): - return acr_connected_registry_get_settings(cmd, client, connected_registry_name, registry_name, parent_protocol, - None, False, resource_group_name) - - -def acr_connected_registry_install_renew_credentials(cmd, - client, - connected_registry_name, - registry_name, - parent_protocol, - yes=False, - resource_group_name=None): - return acr_connected_registry_get_settings(cmd, client, connected_registry_name, registry_name, parent_protocol, - '1', yes, resource_group_name) - - def acr_connected_registry_get_settings(cmd, client, connected_registry_name, diff --git a/src/azure-cli/azure/cli/command_modules/acr/linter_exclusions.yml b/src/azure-cli/azure/cli/command_modules/acr/linter_exclusions.yml index 8e106774af2..b0276bb485f 100644 --- a/src/azure-cli/azure/cli/command_modules/acr/linter_exclusions.yml +++ b/src/azure-cli/azure/cli/command_modules/acr/linter_exclusions.yml @@ -11,5 +11,4 @@ acr update: allow_trusted_services: rule_exclusions: - option_length_too_long - ... \ No newline at end of file diff --git a/src/azure-cli/azure/cli/command_modules/acr/tests/latest/test_acr_connectedregistry_commands.py b/src/azure-cli/azure/cli/command_modules/acr/tests/latest/test_acr_connectedregistry_commands.py index 379105f00bb..c0e1bbca8b8 100644 --- a/src/azure-cli/azure/cli/command_modules/acr/tests/latest/test_acr_connectedregistry_commands.py +++ b/src/azure-cli/azure/cli/command_modules/acr/tests/latest/test_acr_connectedregistry_commands.py @@ -51,7 +51,7 @@ def test_acr_connectedregistry(self, resource_group): checks=self.check('dataEndpointEnabled', True)) # Create a default connected registry. - self.cmd('acr connected-registry create -n {cr_name} -r {registry_name} --repository {repo_1} {repo_2} {repo_3}', + self.cmd('acr connected-registry create -n {cr_name} -r {registry_name} -m ReadWrite --repository {repo_1} {repo_2} {repo_3}', checks=[self.check('name', '{cr_name}'), self.check('mode', 'ReadWrite'), self.check('logging.logLevel', 'Information'), @@ -61,7 +61,7 @@ def test_acr_connectedregistry(self, resource_group): # Create a custom connected-registry with a previously created token. self.cmd('acr token create -r {registry_name} -n {syncToken} --repository {repo_1} content/read metadata/read --gateway {root_name} config/read config/write message/read message/write --no-passwords') self.cmd('acr token create -r {registry_name} -n {clientToken} --repository {repo_1} content/read --no-passwords') - self.cmd('acr connected-registry create -n {root_name} -r {registry_name} --sync-token {syncToken} -m ReadOnly --log-level Warning -s "{syncSchedule}" -w PT4H --client-tokens {clientToken} --notifications {notificationStr}', + self.cmd('acr connected-registry create -n {root_name} -r {registry_name} --sync-token {syncToken} --log-level Warning -s "{syncSchedule}" -w PT4H --client-tokens {clientToken} --notifications {notificationStr}', checks=[self.check('name', '{root_name}'), self.check('mode', 'ReadOnly'), self.check('provisioningState', 'Succeeded'),