diff --git a/src/image-copy/azext_imagecopy/__init__.py b/src/image-copy/azext_imagecopy/__init__.py index 9525a5baaa7..fc0c43d50b5 100644 --- a/src/image-copy/azext_imagecopy/__init__.py +++ b/src/image-copy/azext_imagecopy/__init__.py @@ -39,6 +39,8 @@ def load_arguments(self, _): help='Number of parallel copy operations') c.argument('cleanup', options_list=['--cleanup'], action='store_true', default=False, help='Include this switch to delete temporary resources upon completion') + c.argument('target_name', options_list=['--target-name'], + help='Name of the final image that will be created') COMMAND_LOADER_CLS = ImageCopyCommandsLoader diff --git a/src/image-copy/azext_imagecopy/cli_utils.py b/src/image-copy/azext_imagecopy/cli_utils.py index fe163c464dc..28e92747636 100644 --- a/src/image-copy/azext_imagecopy/cli_utils.py +++ b/src/image-copy/azext_imagecopy/cli_utils.py @@ -12,6 +12,8 @@ from knack.log import get_logger logger = get_logger(__name__) +EXTENSION_TAG_STRING = 'created_by=image-copy-extension' + # pylint: disable=inconsistent-return-statements def run_cli_command(cmd, return_as_json=False): @@ -36,7 +38,7 @@ def run_cli_command(cmd, return_as_json=False): raise -def prepare_cli_command(cmd, output_as_json=True): +def prepare_cli_command(cmd, output_as_json=True, tags=None): full_cmd = [sys.executable, '-m', 'azure.cli'] + cmd if output_as_json: @@ -46,6 +48,9 @@ def prepare_cli_command(cmd, output_as_json=True): # tag newly created resources, containers don't have tags if 'create' in cmd and ('container' not in cmd): - full_cmd += ['--tags', 'created_by=image-copy-extension'] + full_cmd += ['--tags', EXTENSION_TAG_STRING] + + if tags is not None: + full_cmd += tags.split() return full_cmd diff --git a/src/image-copy/azext_imagecopy/create_target.py b/src/image-copy/azext_imagecopy/create_target.py index 71673d34bcc..f78b136aed5 100644 --- a/src/image-copy/azext_imagecopy/create_target.py +++ b/src/image-copy/azext_imagecopy/create_target.py @@ -12,19 +12,20 @@ from knack.log import get_logger logger = get_logger(__name__) -PROGRESS_LINE_LENGTH = 40 +STORAGE_ACCOUNT_NAME_LENGTH = 24 # pylint: disable=too-many-locals def create_target_image(location, transient_resource_group_name, source_type, source_object_name, source_os_disk_snapshot_name, source_os_disk_snapshot_url, source_os_type, - target_resource_group_name, azure_pool_frequency): + target_resource_group_name, azure_pool_frequency, tags, target_name): subscription_id = get_subscription_id() subscription_hash = hashlib.sha1( subscription_id.encode("UTF-8")).hexdigest() - unique_subscription_string = subscription_hash[:7] + unique_subscription_string = subscription_hash[:( + STORAGE_ACCOUNT_NAME_LENGTH - len(location))] # create the target storage account logger.warn( @@ -92,7 +93,7 @@ def create_target_image(location, transient_resource_group_name, source_type, so wait_for_blob_copy_operation(blob_name, target_container_name, target_storage_account_name, azure_pool_frequency, location) msg = "{0} - Copy time: {1}".format( - location, datetime.datetime.now() - start_datetime).ljust(PROGRESS_LINE_LENGTH) + location, datetime.datetime.now() - start_datetime) logger.warn(msg) # Create the snapshot in the target region from the copied blob @@ -112,10 +113,13 @@ def create_target_image(location, transient_resource_group_name, source_type, so # Create the final image logger.warn("%s - Creating final image", location) - target_image_name = source_object_name - if source_type != 'image': - target_image_name += '-image' - target_image_name += '-' + location + if target_name is None: + target_image_name = source_object_name + if source_type != 'image': + target_image_name += '-image' + target_image_name += '-' + location + else: + target_image_name = target_name cli_cmd = prepare_cli_command(['image', 'create', '--resource-group', target_resource_group_name, @@ -123,8 +127,8 @@ def create_target_image(location, transient_resource_group_name, source_type, so '--location', location, '--source', target_blob_path, '--os-type', source_os_type, - '--source', target_snapshot_id]) - + '--source', target_snapshot_id], tags=tags) + logger.warn("command: %s", cli_cmd) run_cli_command(cli_cmd) @@ -147,8 +151,7 @@ def wait_for_blob_copy_operation(blob_name, target_container_name, target_storag if current_progress != prev_progress: msg = "{0} - Copy progress: {1}%"\ - .format(location, str(current_progress))\ - .ljust(PROGRESS_LINE_LENGTH) # need to justify since messages overide each other + .format(location, str(current_progress)) logger.warn(msg) prev_progress = current_progress diff --git a/src/image-copy/azext_imagecopy/custom.py b/src/image-copy/azext_imagecopy/custom.py index 7a503982b54..bc8b6f04a5b 100644 --- a/src/image-copy/azext_imagecopy/custom.py +++ b/src/image-copy/azext_imagecopy/custom.py @@ -8,13 +8,15 @@ from azext_imagecopy.cli_utils import run_cli_command, prepare_cli_command from azext_imagecopy.create_target import create_target_image +from knack.util import CLIError from knack.log import get_logger logger = get_logger(__name__) # pylint: disable=too-many-statements def imagecopy(source_resource_group_name, source_object_name, target_location, - target_resource_group_name, source_type='image', cleanup='false', parallel_degree=-1): + target_resource_group_name, source_type='image', cleanup='false', + parallel_degree=-1, tags=None, target_name=None): # get the os disk id from source vm/image logger.warn("Getting os disk id of the source vm/image") @@ -24,6 +26,14 @@ def imagecopy(source_resource_group_name, source_object_name, target_location, json_cmd_output = run_cli_command(cli_cmd, return_as_json=True) + if 'id' not in json_cmd_output['storageProfile']['osDisk']['managedDisk']: + logger.error( + "It looks like the source resource isn't backed by a managed OS disk. Quitting...") + raise CLIError('Source with no Managed OS disk') + + if json_cmd_output['storageProfile']['dataDisks']: + logger.warn("Data disks in the source detected, but are ignored by this extension!") + source_os_disk_id = json_cmd_output['storageProfile']['osDisk']['managedDisk']['id'] source_os_type = json_cmd_output['storageProfile']['osDisk']['osType'] logger.debug("source_os_disk_id: %s. source_os_type: %s", @@ -80,7 +90,8 @@ def imagecopy(source_resource_group_name, source_object_name, target_location, location = location.strip() tasks.append((location, transient_resource_group_name, source_type, source_object_name, source_os_disk_snapshot_name, source_os_disk_snapshot_url, - source_os_type, target_resource_group_name, azure_pool_frequency)) + source_os_type, target_resource_group_name, azure_pool_frequency, + tags, target_name)) logger.warn("Starting async process for all locations") diff --git a/src/image-copy/setup.py b/src/image-copy/setup.py index 986ffc256d9..46b084c9c13 100644 --- a/src/image-copy/setup.py +++ b/src/image-copy/setup.py @@ -8,7 +8,7 @@ from codecs import open from setuptools import setup, find_packages -VERSION = "0.0.5" +VERSION = "0.0.6" CLASSIFIERS = [ 'Development Status :: 4 - Beta', @@ -29,8 +29,8 @@ setup( name='image-copy-extension', version=VERSION, - description='An Azure CLI Extension that copies images from region to region.', - long_description='An Azure CLI Extension that copies images from region to region.', + description='Support for copying managed vm images between regions', + long_description='Support for copying managed vm images between regions', license='MIT', author='Tamir Kamara', author_email='tamir.kamara@microsoft.com',