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 6 7import uuid 8import tempfile 9 10import os 11 12from knack.log import get_logger 13from knack.util import CLIError 14from azure.cli.core.commands import LongRunningOperation 15 16from ._utils import validate_managed_registry, get_validate_platform, get_custom_registry_credentials 17from ._stream_utils import stream_logs 18from ._archive_utils import upload_source_code, check_remote_source_code 19 20logger = get_logger(__name__) 21 22 23BUILD_NOT_SUPPORTED = 'Builds are only supported for managed registries.' 24 25 26def acr_build(cmd, # pylint: disable=too-many-locals 27 client, 28 registry_name, 29 source_location, 30 image_names=None, 31 resource_group_name=None, 32 timeout=None, 33 arg=None, 34 secret_arg=None, 35 docker_file_path='', 36 no_format=False, 37 no_push=False, 38 no_logs=False, 39 no_wait=False, 40 platform=None, 41 target=None, 42 auth_mode=None): 43 _, resource_group_name = validate_managed_registry( 44 cmd, registry_name, resource_group_name, BUILD_NOT_SUPPORTED) 45 46 from ._client_factory import cf_acr_registries 47 client_registries = cf_acr_registries(cmd.cli_ctx) 48 49 if os.path.exists(source_location): 50 if not os.path.isdir(source_location): 51 raise CLIError("Source location should be a local directory path or remote URL.") 52 53 # NOTE: If docker_file_path is not specified, the default is Dockerfile in source_location. 54 # Otherwise, it's based on current working directory. 55 if not docker_file_path: 56 docker_file_path = os.path.join(source_location, "Dockerfile") 57 logger.info("'--file or -f' is not provided. '%s' is used.", docker_file_path) 58 59 _check_local_docker_file(docker_file_path) 60 61 tar_file_path = os.path.join(tempfile.gettempdir( 62 ), 'build_archive_{}.tar.gz'.format(uuid.uuid4().hex)) 63 64 try: 65 # NOTE: os.path.basename is unable to parse "\" in the file path 66 original_docker_file_name = os.path.basename( 67 docker_file_path.replace("\\", "/")) 68 docker_file_in_tar = '{}_{}'.format( 69 uuid.uuid4().hex, original_docker_file_name) 70 71 source_location = upload_source_code( 72 client_registries, registry_name, resource_group_name, 73 source_location, tar_file_path, 74 docker_file_path, docker_file_in_tar) 75 # For local source, the docker file is added separately into tar as the new file name (docker_file_in_tar) 76 # So we need to update the docker_file_path 77 docker_file_path = docker_file_in_tar 78 except Exception as err: 79 raise CLIError(err) 80 finally: 81 try: 82 logger.debug("Deleting the archived source code from '%s'...", tar_file_path) 83 os.remove(tar_file_path) 84 except OSError: 85 pass 86 else: 87 # NOTE: If docker_file_path is not specified, the default is Dockerfile. It's the same as docker build command. 88 if not docker_file_path: 89 docker_file_path = "Dockerfile" 90 logger.info("'--file or -f' is not provided. '%s' is used.", docker_file_path) 91 92 source_location = check_remote_source_code(source_location) 93 logger.warning("Sending context to registry: %s...", registry_name) 94 95 is_push_enabled = _get_push_enabled_status(no_push, image_names) 96 97 platform_os, platform_arch, platform_variant = get_validate_platform(cmd, platform) 98 99 DockerBuildRequest, PlatformProperties = cmd.get_models('DockerBuildRequest', 'PlatformProperties') 100 docker_build_request = DockerBuildRequest( 101 image_names=image_names, 102 is_push_enabled=is_push_enabled, 103 source_location=source_location, 104 platform=PlatformProperties( 105 os=platform_os, 106 architecture=platform_arch, 107 variant=platform_variant 108 ), 109 docker_file_path=docker_file_path, 110 timeout=timeout, 111 arguments=(arg if arg else []) + (secret_arg if secret_arg else []), 112 target=target, 113 credentials=get_custom_registry_credentials( 114 cmd=cmd, 115 auth_mode=auth_mode 116 ) 117 ) 118 119 queued = LongRunningOperation(cmd.cli_ctx)(client_registries.schedule_run( 120 resource_group_name=resource_group_name, 121 registry_name=registry_name, 122 run_request=docker_build_request)) 123 124 run_id = queued.run_id 125 logger.warning("Queued a build with ID: %s", run_id) 126 127 if no_wait: 128 return queued 129 130 logger.warning("Waiting for an agent...") 131 132 if no_logs: 133 from ._run_polling import get_run_with_polling 134 return get_run_with_polling(cmd, client, run_id, registry_name, resource_group_name) 135 136 return stream_logs(client, run_id, registry_name, resource_group_name, no_format, True) 137 138 139def _warn_unsupported_image_name(image_names): 140 for img in image_names: 141 if ".Build.ID" in img: 142 logger.warning(".Build.ID is no longer supported as a valid substitution, use .Run.ID instead.") 143 break 144 145 146def _get_push_enabled_status(no_push, image_names): 147 if no_push: 148 is_push_enabled = False 149 else: 150 if image_names: 151 is_push_enabled = True 152 _warn_unsupported_image_name(image_names) 153 else: 154 is_push_enabled = False 155 logger.warning("'--image or -t' is not provided. Skipping image push after build.") 156 return is_push_enabled 157 158 159def _check_local_docker_file(docker_file_path): 160 if not os.path.isfile(docker_file_path): 161 raise CLIError("Unable to find '{}'.".format(docker_file_path)) 162