1# -------------------------------------------------------------------------------------------- 2# Copyright (c) Microsoft Corporation. All rights reserved. 3# Licensed under the MIT License. See License.txt in the project root for license information. 4# -------------------------------------------------------------------------------------------- 5 6from knack.util import CLIError 7from knack.log import get_logger 8 9from azure.cli.core.commands import LongRunningOperation 10 11from ._constants import get_classic_sku, get_managed_sku, get_premium_sku 12from ._utils import ( 13 arm_deploy_template_new_storage, 14 arm_deploy_template_existing_storage, 15 random_storage_account_name, 16 get_registry_by_name, 17 validate_managed_registry, 18 validate_sku_update, 19 get_resource_group_name_by_registry_name, 20 get_resource_id_by_storage_account_name 21) 22from ._docker_utils import get_login_credentials 23from .network_rule import NETWORK_RULE_NOT_SUPPORTED 24 25logger = get_logger(__name__) 26 27 28def acr_check_name(client, registry_name): 29 return client.check_name_availability(registry_name) 30 31 32def acr_list(client, resource_group_name=None): 33 if resource_group_name: 34 return client.list_by_resource_group(resource_group_name) 35 return client.list() 36 37 38def acr_create(cmd, 39 client, 40 registry_name, 41 resource_group_name, 42 sku, 43 location=None, 44 storage_account_name=None, 45 admin_enabled=False, 46 default_action=None, 47 deployment_name=None): 48 if default_action and sku not in get_premium_sku(cmd): 49 raise CLIError(NETWORK_RULE_NOT_SUPPORTED) 50 51 if sku in get_managed_sku(cmd) and storage_account_name: 52 raise CLIError("Please specify '--sku {}' without providing an existing storage account " 53 "to create a managed registry, or specify '--sku Classic --storage-account-name {}' " 54 "to create a Classic registry using storage account `{}`." 55 .format(sku, storage_account_name, storage_account_name)) 56 57 if sku in get_classic_sku(cmd): 58 result = client.check_name_availability(registry_name) 59 if not result.name_available: 60 raise CLIError(result.message) 61 62 logger.warning( 63 "Due to the planned deprecation of the Classic registry SKU, we recommend using " 64 "Basic, Standard, or Premium for all new registries. See https://aka.ms/acr/skus for details.") 65 if storage_account_name is None: 66 storage_account_name = random_storage_account_name(cmd.cli_ctx, registry_name) 67 logger.warning( 68 "A new storage account '%s' will be created in resource group '%s'.", 69 storage_account_name, 70 resource_group_name) 71 LongRunningOperation(cmd.cli_ctx)( 72 arm_deploy_template_new_storage( 73 cmd.cli_ctx, 74 resource_group_name, 75 registry_name, 76 location, 77 sku, 78 storage_account_name, 79 admin_enabled, 80 deployment_name) 81 ) 82 else: 83 LongRunningOperation(cmd.cli_ctx)( 84 arm_deploy_template_existing_storage( 85 cmd.cli_ctx, 86 resource_group_name, 87 registry_name, 88 location, 89 sku, 90 storage_account_name, 91 admin_enabled, 92 deployment_name) 93 ) 94 return client.get(resource_group_name, registry_name) 95 else: 96 if storage_account_name: 97 logger.warning( 98 "The registry '%s' in '%s' SKU is a managed registry. The specified storage account will be ignored.", 99 registry_name, sku) 100 Registry, Sku, NetworkRuleSet = cmd.get_models('Registry', 'Sku', 'NetworkRuleSet') 101 registry = Registry(location=location, sku=Sku(name=sku), admin_user_enabled=admin_enabled) 102 if default_action: 103 registry.network_rule_set = NetworkRuleSet(default_action=default_action) 104 return client.create(resource_group_name, registry_name, registry) 105 106 107def acr_delete(cmd, client, registry_name, resource_group_name=None): 108 resource_group_name = get_resource_group_name_by_registry_name(cmd.cli_ctx, registry_name, resource_group_name) 109 return client.delete(resource_group_name, registry_name) 110 111 112def acr_show(cmd, client, registry_name, resource_group_name=None): 113 resource_group_name = get_resource_group_name_by_registry_name(cmd.cli_ctx, registry_name, resource_group_name) 114 return client.get(resource_group_name, registry_name) 115 116 117def acr_update_custom(cmd, 118 instance, 119 sku=None, 120 storage_account_name=None, 121 admin_enabled=None, 122 default_action=None, 123 tags=None): 124 if sku is not None: 125 Sku = cmd.get_models('Sku') 126 instance.sku = Sku(name=sku) 127 128 if storage_account_name is not None: 129 StorageAccountProperties = cmd.get_models('StorageAccountProperties') 130 instance.storage_account = StorageAccountProperties( 131 id=get_resource_id_by_storage_account_name(cmd.cli_ctx, storage_account_name)) 132 133 if admin_enabled is not None: 134 instance.admin_user_enabled = admin_enabled 135 136 if tags is not None: 137 instance.tags = tags 138 139 if default_action is not None: 140 NetworkRuleSet = cmd.get_models('NetworkRuleSet') 141 instance.network_rule_set = NetworkRuleSet(default_action=default_action) 142 143 return instance 144 145 146def acr_update_get(cmd): 147 """Returns an empty RegistryUpdateParameters object. 148 """ 149 RegistryUpdateParameters = cmd.get_models('RegistryUpdateParameters') 150 return RegistryUpdateParameters() 151 152 153def acr_update_set(cmd, 154 client, 155 registry_name, 156 resource_group_name=None, 157 parameters=None): 158 registry, resource_group_name = get_registry_by_name(cmd.cli_ctx, registry_name, resource_group_name) 159 160 if parameters.network_rule_set and registry.sku.name not in get_premium_sku(cmd): 161 raise CLIError(NETWORK_RULE_NOT_SUPPORTED) 162 163 validate_sku_update(cmd, registry.sku.name, parameters.sku) 164 165 if registry.sku.name in get_managed_sku(cmd) and parameters.storage_account is not None: 166 parameters.storage_account = None 167 logger.warning( 168 "The registry '%s' in '%s' SKU is a managed registry. The specified storage account will be ignored.", 169 registry_name, registry.sku.name) 170 171 return client.update(resource_group_name, registry_name, parameters) 172 173 174def acr_login(cmd, 175 registry_name, 176 resource_group_name=None, # pylint: disable=unused-argument 177 tenant_suffix=None, 178 username=None, 179 password=None): 180 from azure.cli.core.util import in_cloud_console 181 if in_cloud_console(): 182 raise CLIError('This command requires running the docker daemon, which is not supported in Azure Cloud Shell.') 183 184 docker_command, _ = get_docker_command() 185 186 login_server, username, password = get_login_credentials( 187 cmd=cmd, 188 registry_name=registry_name, 189 tenant_suffix=tenant_suffix, 190 username=username, 191 password=password) 192 193 from subprocess import PIPE, Popen 194 p = Popen([docker_command, "login", 195 "--username", username, 196 "--password", password, 197 login_server], stderr=PIPE) 198 _, stderr = p.communicate() 199 200 if stderr: 201 if b'error storing credentials' in stderr and b'stub received bad data' in stderr \ 202 and _check_wincred(login_server): 203 # Retry once after disabling wincred 204 p = Popen([docker_command, "login", 205 "--username", username, 206 "--password", password, 207 login_server]) 208 p.wait() 209 else: 210 if b'--password-stdin' in stderr: 211 errors = [err for err in stderr.decode().split('\n') if '--password-stdin' not in err] 212 stderr = '\n'.join(errors).encode() 213 214 import sys 215 output = getattr(sys.stderr, 'buffer', sys.stderr) 216 output.write(stderr) 217 218 219def acr_show_usage(cmd, client, registry_name, resource_group_name=None): 220 _, resource_group_name = validate_managed_registry(cmd, 221 registry_name, 222 resource_group_name, 223 "Usage is only supported for managed registries.") 224 return client.list_usages(resource_group_name, registry_name) 225 226 227def get_docker_command(is_diagnostics_context=False): 228 from ._errors import DOCKER_COMMAND_ERROR, DOCKER_DAEMON_ERROR 229 docker_command = 'docker' 230 231 from subprocess import PIPE, Popen, CalledProcessError 232 try: 233 p = Popen([docker_command, "ps"], stdout=PIPE, stderr=PIPE) 234 _, stderr = p.communicate() 235 except OSError as e: 236 logger.debug("Could not run '%s' command. Exception: %s", docker_command, str(e)) 237 # The executable may not be discoverable in WSL so retry *.exe once 238 try: 239 docker_command = 'docker.exe' 240 p = Popen([docker_command, "ps"], stdout=PIPE, stderr=PIPE) 241 _, stderr = p.communicate() 242 except OSError as inner: 243 logger.debug("Could not run '%s' command. Exception: %s", docker_command, str(inner)) 244 if is_diagnostics_context: 245 return None, DOCKER_COMMAND_ERROR 246 raise CLIError(DOCKER_COMMAND_ERROR.get_error_message()) 247 except CalledProcessError as inner: 248 logger.debug("Could not run '%s' command. Exception: %s", docker_command, str(inner)) 249 if is_diagnostics_context: 250 return docker_command, DOCKER_DAEMON_ERROR 251 raise CLIError(DOCKER_DAEMON_ERROR.get_error_message()) 252 except CalledProcessError as e: 253 logger.debug("Could not run '%s' command. Exception: %s", docker_command, str(e)) 254 if is_diagnostics_context: 255 return docker_command, DOCKER_DAEMON_ERROR 256 raise CLIError(DOCKER_DAEMON_ERROR.get_error_message()) 257 258 if stderr: 259 if is_diagnostics_context: 260 return None, DOCKER_COMMAND_ERROR.set_error_message(stderr.decode()) 261 raise CLIError(DOCKER_COMMAND_ERROR.set_error_message(stderr.decode()).get_error_message()) 262 263 return docker_command, None 264 265 266def _check_wincred(login_server): 267 import platform 268 if platform.system() == 'Windows': 269 import json 270 from os.path import expanduser, isfile, join 271 docker_directory = join(expanduser('~'), '.docker') 272 config_path = join(docker_directory, 'config.json') 273 logger.debug("Docker config file path %s", config_path) 274 if isfile(config_path): 275 with open(config_path) as input_file: 276 content = json.load(input_file) 277 input_file.close() 278 wincred = content.pop('credsStore', None) 279 if wincred and wincred.lower() == 'wincred': 280 # Ask for confirmation 281 from knack.prompting import prompt_y_n, NoTTYException 282 message = "This operation will disable wincred and use file system to store docker credentials." \ 283 " All registries that are currently logged in will be logged out." \ 284 "\nAre you sure you want to continue?" 285 try: 286 if prompt_y_n(message): 287 with open(config_path, 'w') as output_file: 288 json.dump(content, output_file, indent=4) 289 output_file.close() 290 return True 291 return False 292 except NoTTYException: 293 return False 294 # Don't update config file or retry as this doesn't seem to be a wincred issue 295 return False 296 else: 297 import os 298 content = { 299 "auths": { 300 login_server: {} 301 } 302 } 303 try: 304 os.makedirs(docker_directory) 305 except OSError as e: 306 logger.debug("Could not create docker directory '%s'. Exception: %s", docker_directory, str(e)) 307 with open(config_path, 'w') as output_file: 308 json.dump(content, output_file, indent=4) 309 output_file.close() 310 return True 311 312 return False 313