diff --git a/.flake8 b/.flake8 new file mode 100644 index 00000000000..f0e476b386d --- /dev/null +++ b/.flake8 @@ -0,0 +1,11 @@ +[flake8] +max-line-length = 120 +max-complexity = 10 +ignore = + E126, + E501, + E722, + E741, + F401, + F811, + C901 diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index bd090e10614..b349363eb8a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,4 +1,5 @@ # See for instructions on this file https://help.github.com/articles/about-codeowners/ -index.json @derekbekoe +/src/index.json @derekbekoe +/src/image-copy/ @tamirkamara diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000000..d6415fe5f12 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,7 @@ +--- + +This checklist is used to make sure that common guidelines for a pull request are followed. + +### General Guidelines + +- [ ] Have you run `./scripts/ci/test_static.sh` locally? (`pip install pylint flake8` required) diff --git a/.gitignore b/.gitignore index 7bbc71c0920..8b2dc2f0065 100644 --- a/.gitignore +++ b/.gitignore @@ -97,5 +97,8 @@ ENV/ # mkdocs documentation /site +# VS Code +.vscode/settings.json + # mypy .mypy_cache/ diff --git a/.travis.yml b/.travis.yml index 3ccb28a58fd..f7ca6fb9aaf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,15 @@ dist: trusty sudo: off language: python -python: - - "2.7" - - "3.5" - - "3.6" -install: true -script: - - ls +install: + - pip install pylint flake8 +jobs: + include: + - stage: verify + script: ./scripts/ci/test_static.sh + env: PURPOSE='VerifySource-StaticCheck' + python: 3.6 + - stage: verify + script: ./scripts/ci/test_static.sh + env: PURPOSE='VerifySource-StaticCheck' + python: 2.7 diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst new file mode 100644 index 00000000000..8191b7f1909 --- /dev/null +++ b/CONTRIBUTING.rst @@ -0,0 +1,9 @@ +Contribute Code +=================================== + +This project has adopted the `Microsoft Open Source Code of Conduct `__. + +For more information see the `Code of Conduct FAQ `__ or contact `opencode@microsoft.com `__ with any additional questions or comments. + +If you would like to become an active contributor to this project please +follow the instructions provided in `Microsoft Azure Projects Contribution Guidelines `__ diff --git a/pylintrc b/pylintrc new file mode 100644 index 00000000000..054f0f4333b --- /dev/null +++ b/pylintrc @@ -0,0 +1,48 @@ +[MASTER] +reports=no +score=no + +[MESSAGES CONTROL] +# For all codes, run 'pylint --list-msgs' or go to 'https://pylint.readthedocs.io/en/latest/reference_guide/features.html' +# locally-disabled: Warning locally suppressed using disable-msg +# cyclic-import: because of https://github.com/PyCQA/pylint/issues/850 +# too-many-arguments: Due to the nature of the CLI many commands have large arguments set which reflect in large arguments set in corresponding methods. +disable=missing-docstring,locally-disabled,fixme,cyclic-import,too-many-arguments,invalid-name,duplicate-code + +[TYPECHECK] +# For Azure CLI extensions, we ignore import errors for azure.cli as they'll be available in the environment of the CLI +ignored-modules=azure.cli + +[FORMAT] +max-line-length=120 + +[VARIABLES] +# Tells whether we should check for unused import in __init__ files. +init-import=yes + +[DESIGN] +# Maximum number of locals for function / method body +max-locals=25 +# Maximum number of branch for function / method body +max-branches=20 + +[SIMILARITIES] +min-similarity-lines=10 + +[BASIC] +# Naming hints based on PEP 8 (https://www.python.org/dev/peps/pep-0008/#naming-conventions). +# Consider these guidelines and not hard rules. Read PEP 8 for more details. + +# The invalid-name checker must be **enabled** for these hints to be used. +include-naming-hint=yes + +module-name-hint=lowercase (keep short; underscores are discouraged) +const-name-hint=UPPER_CASE_WITH_UNDERSCORES +class-name-hint=CapitalizedWords +class-attribute-name-hint=lower_case_with_underscores +attr-name-hint=lower_case_with_underscores +method-name-hint=lower_case_with_underscores +function-name-hint=lower_case_with_underscores +argument-name-hint=lower_case_with_underscores +variable-name-hint=lower_case_with_underscores +inlinevar-name-hint=lower_case_with_underscores (short is OK) diff --git a/scripts/ci/test_static.sh b/scripts/ci/test_static.sh new file mode 100755 index 00000000000..8d8b96efc2c --- /dev/null +++ b/scripts/ci/test_static.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -e + +proc_number=`python -c 'import multiprocessing; print(multiprocessing.cpu_count())'` +pylint ./src/*/azext_*/ --rcfile=./pylintrc -j $proc_number +flake8 --statistics --append-config=./.flake8 ./src/*/azext_*/ + +pylint ./scripts/ci/*.py --rcfile=./pylintrc +flake8 --append-config=./.flake8 ./scripts/ci/*.py + +python ./scripts/ci/verify_codeowners.py +python ./scripts/ci/verify_license.py diff --git a/scripts/ci/util.py b/scripts/ci/util.py new file mode 100644 index 00000000000..c873d3bd83f --- /dev/null +++ b/scripts/ci/util.py @@ -0,0 +1,13 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import os + + +def get_repo_root(): + current_dir = os.path.dirname(os.path.abspath(__file__)) + while not os.path.exists(os.path.join(current_dir, 'CONTRIBUTING.rst')): + current_dir = os.path.dirname(current_dir) + return current_dir diff --git a/scripts/ci/verify_codeowners.py b/scripts/ci/verify_codeowners.py new file mode 100644 index 00000000000..da1bf1a1fdb --- /dev/null +++ b/scripts/ci/verify_codeowners.py @@ -0,0 +1,42 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from __future__ import print_function + +import os +import sys + +from util import get_repo_root + +REPO_ROOT = get_repo_root() +CODEOWNERS = os.path.join(REPO_ROOT, '.github', 'CODEOWNERS') +SRC_DIR = os.path.join(REPO_ROOT, 'src') + + +def get_src_dir_codeowners(): + contents = [] + with open(CODEOWNERS) as f: + contents = [x.strip() for x in f.readlines()] + return dict([x.split(' ') for x in contents if x.startswith('/src/') and x.split(' ')[0].endswith('/')]) + + +def main(): + owners = get_src_dir_codeowners() + dangling_entries = [e for e in owners if not os.path.isdir(os.path.join(REPO_ROOT, e[1:]))] + missing_entries = ['/src/{}/'.format(p) for p in os.listdir(SRC_DIR) + if os.path.isdir(os.path.join(SRC_DIR, p)) and '/src/{}/'.format(p) not in owners] + if dangling_entries or missing_entries: + print('Errors whilst verifying {}!'.format(CODEOWNERS)) + if dangling_entries: + print("Remove the following {} as these directories don't exist.".format(dangling_entries), + file=sys.stderr) + if missing_entries: + print("The following directories are missing codeowners {}.".format(missing_entries), + file=sys.stderr) + sys.exit(1) + + +if __name__ == '__main__': + main() diff --git a/scripts/ci/verify_license.py b/scripts/ci/verify_license.py new file mode 100644 index 00000000000..e7e1d0aca9b --- /dev/null +++ b/scripts/ci/verify_license.py @@ -0,0 +1,44 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from __future__ import print_function + +import os +import sys + +from util import get_repo_root + +REPO_ROOT = get_repo_root() +SRC_DIR = os.path.join(REPO_ROOT, 'src') + +LICENSE_HEADER = """# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +""" + + +def main(): + env_path = os.path.join(REPO_ROOT, 'env') + + files_without_header = [] + for current_dir, _, files in os.walk(get_repo_root()): + if current_dir.startswith(env_path): + continue + file_itr = (os.path.join(current_dir, p) for p in files if p.endswith('.py')) + for python_file in file_itr: + with open(python_file, 'r') as f: + file_text = f.read() + if file_text and LICENSE_HEADER not in file_text: + files_without_header.append(os.path.join(current_dir, python_file)) + + if files_without_header: + print("Error: The following files don't have the required license headers: \n{}".format( + '\n'.join(files_without_header)), file=sys.stderr) + sys.exit(1) + + +if __name__ == '__main__': + main() diff --git a/src/image-copy/azext_imagecopy/__init__.py b/src/image-copy/azext_imagecopy/__init__.py index 6b540870c11..79e589c1019 100644 --- a/src/image-copy/azext_imagecopy/__init__.py +++ b/src/image-copy/azext_imagecopy/__init__.py @@ -8,19 +8,29 @@ helps['image copy'] = """ type: command - short-summary: Allows to copy a managed image (or vm) to other regions. Keep in mind that it requires the source disk to be available. + short-summary: Copy a managed image (or vm) to other regions + long-summary: > + Allows to copy a managed image (or vm) to other regions. + Keep in mind that it requires the source disk to be available. """ + def load_params(_): with ParametersContext('image copy') as c: - c.register('source_resource_group_name', '--source-resource-group', help='Name of the resource group of the source resource') - c.register('source_object_name', '--source-object-name', help='The name of the image or vm resource') - c.register('target_location', '--target-location', nargs='+', help='Space separated location list to create the image in (use location short codes like westeurope etc.)') + c.register('source_resource_group_name', '--source-resource-group', + help='Name of the resource group of the source resource') + c.register('source_object_name', '--source-object-name', + help='The name of the image or vm resource') + c.register('target_location', '--target-location', nargs='+', + help='Space separated location list to create the image in (e.g. westeurope etc.)') c.register('source_type', '--source-type', default='image', choices=['image', 'vm'], help='image or vm') - c.register('target_resource_group_name', '--target-resource-group', help='Name of the resource group to create images in') - c.register('parallel_degree', '--parallel-degree', type=int, default=-1, help='Number of parallel copy operations') - c.register('cleanup', '--cleanup', action='store_true', default=False, \ - help='Include this switch to delete temporary resources upon completion') + c.register('target_resource_group_name', '--target-resource-group', + help='Name of the resource group to create images in') + c.register('parallel_degree', '--parallel-degree', type=int, default=-1, + help='Number of parallel copy operations') + c.register('cleanup', '--cleanup', action='store_true', default=False, + help='Include this switch to delete temporary resources upon completion') + def load_commands(): from azure.cli.core.commands import cli_command diff --git a/src/image-copy/azext_imagecopy/cli_utils.py b/src/image-copy/azext_imagecopy/cli_utils.py index 32276cfa29a..d1e33b247c5 100644 --- a/src/image-copy/azext_imagecopy/cli_utils.py +++ b/src/image-copy/azext_imagecopy/cli_utils.py @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + import sys import json @@ -29,6 +34,7 @@ def run_cli_command(cmd, return_as_json=False): logger.error('command ended with an error: %s', cmd) raise + def prepare_cli_command(cmd, output_as_json=True): full_cmd = [sys.executable, '-m', 'azure.cli'] + cmd diff --git a/src/image-copy/azext_imagecopy/create_target.py b/src/image-copy/azext_imagecopy/create_target.py index e81e45fda90..40539c2c009 100644 --- a/src/image-copy/azext_imagecopy/create_target.py +++ b/src/image-copy/azext_imagecopy/create_target.py @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + import hashlib import datetime import time @@ -10,33 +15,33 @@ PROGRESS_LINE_LENGTH = 40 -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): + +# 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): subscription_id = get_subscription_id() subscription_hash = hashlib.sha1(subscription_id.encode("UTF-8")).hexdigest() unique_subscription_string = subscription_hash[:7] - # create the target storage account logger.warn("{0} - Creating target storage account (can be slow sometimes)".format(location)) target_storage_account_name = location + unique_subscription_string - cmd = prepare_cli_command(['storage', 'account', 'create', \ - '--name', target_storage_account_name, \ - '--resource-group', transient_resource_group_name, \ - '--location', location, \ - '--sku', 'Standard_LRS']) + cmd = prepare_cli_command(['storage', 'account', 'create', + '--name', target_storage_account_name, + '--resource-group', transient_resource_group_name, + '--location', location, + '--sku', 'Standard_LRS']) json_output = run_cli_command(cmd, return_as_json=True) target_blob_endpoint = json_output['primaryEndpoints']['blob'] - # Setup the target storage account - cmd = prepare_cli_command(['storage', 'account', 'keys', 'list', \ - '--account-name', target_storage_account_name, \ - '--resource-group', transient_resource_group_name]) + cmd = prepare_cli_command(['storage', 'account', 'keys', 'list', + '--account-name', target_storage_account_name, + '--resource-group', transient_resource_group_name]) json_output = run_cli_command(cmd, return_as_json=True) @@ -46,59 +51,55 @@ def create_target_image(location, transient_resource_group_name, source_type, so expiry_format = "%Y-%m-%dT%H:%MZ" expiry = datetime.datetime.utcnow() + datetime.timedelta(hours=1) - cmd = prepare_cli_command(['storage', 'account', 'generate-sas', \ - '--account-name', target_storage_account_name, \ - '--account-key', target_storage_account_key, \ - '--expiry', expiry.strftime(expiry_format), \ - '--permissions', 'aclrpuw', '--resource-types', \ - 'sco', '--services', 'b', '--https-only'], \ - output_as_json=False) + cmd = prepare_cli_command(['storage', 'account', 'generate-sas', + '--account-name', target_storage_account_name, + '--account-key', target_storage_account_key, + '--expiry', expiry.strftime(expiry_format), + '--permissions', 'aclrpuw', '--resource-types', + 'sco', '--services', 'b', '--https-only'], + output_as_json=False) sas_token = run_cli_command(cmd) - sas_token = sas_token.rstrip("\n\r") #STRANGE + sas_token = sas_token.rstrip("\n\r") # STRANGE logger.debug("sas token: " + sas_token) - # create a container in the target blob storage account logger.warn("{0} - Creating container in the target storage account".format(location)) target_container_name = 'snapshots' - cmd = prepare_cli_command(['storage', 'container', 'create', \ - '--name', target_container_name, \ - '--account-name', target_storage_account_name]) + cmd = prepare_cli_command(['storage', 'container', 'create', + '--name', target_container_name, + '--account-name', target_storage_account_name]) run_cli_command(cmd) - # Copy the snapshot to the target region using the SAS URL blob_name = source_os_disk_snapshot_name + '.vhd' logger.warn("{0} - Copying blob to target storage account".format(location)) - cmd = prepare_cli_command(['storage', 'blob', 'copy', 'start', \ - '--source-uri', source_os_disk_snapshot_url, \ - '--destination-blob', blob_name, \ - '--destination-container', target_container_name, \ - '--account-name', target_storage_account_name, \ - '--sas-token', sas_token]) + cmd = prepare_cli_command(['storage', 'blob', 'copy', 'start', + '--source-uri', source_os_disk_snapshot_url, + '--destination-blob', blob_name, + '--destination-container', target_container_name, + '--account-name', target_storage_account_name, + '--sas-token', sas_token]) run_cli_command(cmd) - # Wait for the copy to complete start_datetime = datetime.datetime.now() - 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) + 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) logger.warn(msg) - # Create the snapshot in the target region from the copied blob logger.warn("{0} - Creating snapshot in target region from the copied blob".format(location)) target_blob_path = target_blob_endpoint + target_container_name + '/' + blob_name target_snapshot_name = source_os_disk_snapshot_name + '-' + location - cmd = prepare_cli_command(['snapshot', 'create', \ - '--resource-group', transient_resource_group_name, \ - '--name', target_snapshot_name, \ - '--location', location, \ - '--source', target_blob_path]) + cmd = prepare_cli_command(['snapshot', 'create', + '--resource-group', transient_resource_group_name, + '--name', target_snapshot_name, + '--location', location, + '--source', target_blob_path]) json_output = run_cli_command(cmd, return_as_json=True) target_snapshot_id = json_output['id'] @@ -110,36 +111,37 @@ def create_target_image(location, transient_resource_group_name, source_type, so target_image_name += '-image' target_image_name += '-' + location - cmd = prepare_cli_command(['image', 'create', \ - '--resource-group', target_resource_group_name, \ - '--name', target_image_name, \ - '--location', location, \ - '--source', target_blob_path, \ - '--os-type', source_os_type, \ - '--source', target_snapshot_id]) + cmd = prepare_cli_command(['image', 'create', + '--resource-group', target_resource_group_name, + '--name', target_image_name, + '--location', location, + '--source', target_blob_path, + '--os-type', source_os_type, + '--source', target_snapshot_id]) run_cli_command(cmd) -def wait_for_blob_copy_operation(blob_name, target_container_name, target_storage_account_name, azure_pool_frequency, location): +def wait_for_blob_copy_operation(blob_name, target_container_name, target_storage_account_name, + azure_pool_frequency, location): progress_controller = APPLICATION.get_progress_controller() copy_status = "pending" prev_progress = -1 while copy_status == "pending": - cmd = prepare_cli_command(['storage', 'blob', 'show', \ - '--name', blob_name, \ - '--container-name', target_container_name, \ - '--account-name', target_storage_account_name]) + cmd = prepare_cli_command(['storage', 'blob', 'show', + '--name', blob_name, + '--container-name', target_container_name, + '--account-name', target_storage_account_name]) json_output = run_cli_command(cmd, return_as_json=True) copy_status = json_output["properties"]["copy"]["status"] copy_progress_1, copy_progress_2 = json_output["properties"]["copy"]["progress"].split("/") - current_progress = round(int(copy_progress_1)/int(copy_progress_2), 1) + current_progress = round(int(copy_progress_1) / int(copy_progress_2), 1) 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 + .ljust(PROGRESS_LINE_LENGTH) # need to justify since messages overide each other progress_controller.add(message=msg) prev_progress = current_progress @@ -150,7 +152,6 @@ def wait_for_blob_copy_operation(blob_name, target_container_name, target_storag progress_controller.stop() return - if copy_status == 'success': progress_controller.stop() else: diff --git a/src/image-copy/azext_imagecopy/custom.py b/src/image-copy/azext_imagecopy/custom.py index e8ea2952c7a..01de885922d 100644 --- a/src/image-copy/azext_imagecopy/custom.py +++ b/src/image-copy/azext_imagecopy/custom.py @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + from multiprocessing import Pool from azext_imagecopy.cli_utils import run_cli_command, prepare_cli_command @@ -7,14 +12,15 @@ logger = azlogging.get_az_logger(__name__) -def imagecopy(source_resource_group_name, source_object_name, target_location, \ - target_resource_group_name, source_type='image', cleanup='false', parallel_degree=-1): +# 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): # get the os disk id from source vm/image logger.warn("Getting os disk id of the source vm/image") - cmd = prepare_cli_command([source_type, 'show', \ - '--name', source_object_name, \ - '--resource-group', source_resource_group_name]) + cmd = prepare_cli_command([source_type, 'show', + '--name', source_object_name, + '--resource-group', source_resource_group_name]) json_cmd_output = run_cli_command(cmd, return_as_json=True) @@ -22,30 +28,27 @@ def imagecopy(source_resource_group_name, source_object_name, target_location, \ source_os_type = json_cmd_output['storageProfile']['osDisk']['osType'] logger.debug("source_os_disk_id: %s. source_os_type: %s", source_os_disk_id, source_os_type) - # create source snapshots logger.warn("Creating source snapshot") source_os_disk_snapshot_name = source_object_name + '_os_disk_snapshot' - cmd = prepare_cli_command(['snapshot', 'create', \ - '--name', source_os_disk_snapshot_name, \ - '--resource-group', source_resource_group_name, \ - '--source', source_os_disk_id]) + cmd = prepare_cli_command(['snapshot', 'create', + '--name', source_os_disk_snapshot_name, + '--resource-group', source_resource_group_name, + '--source', source_os_disk_id]) run_cli_command(cmd) - # Get SAS URL for the snapshotName logger.warn("Getting sas url for the source snapshot") - cmd = prepare_cli_command(['snapshot', 'grant-access', \ - '--name', source_os_disk_snapshot_name, \ - '--resource-group', source_resource_group_name, \ - '--duration-in-seconds', '3600']) + cmd = prepare_cli_command(['snapshot', 'grant-access', + '--name', source_os_disk_snapshot_name, + '--resource-group', source_resource_group_name, + '--duration-in-seconds', '3600']) json_output = run_cli_command(cmd, return_as_json=True) source_os_disk_snapshot_url = json_output['accessSas'] - logger.debug("source os disk snapshot url: %s" , source_os_disk_snapshot_url) - + logger.debug("source os disk snapshot url: %s", source_os_disk_snapshot_url) # Start processing in the target locations @@ -66,15 +69,15 @@ def imagecopy(source_resource_group_name, source_object_name, target_location, \ azure_pool_frequency = 5 if target_locations_count >= 5: azure_pool_frequency = 15 - elif target_locations_count >= 3: + elif target_locations_count >= 3: azure_pool_frequency = 10 tasks = [] for location in 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)) + 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)) logger.warn("Starting async process for all locations") @@ -87,45 +90,45 @@ def imagecopy(source_resource_group_name, source_object_name, target_location, \ except KeyboardInterrupt: logger.warn('User cancelled the operation') if cleanup: - logger.warn('To cleanup temporary resources look for ones tagged with "image-copy-extension". \nYou can use the following command: az resource list --tag created_by=image-copy-extension') + logger.warn('To cleanup temporary resources look for ones tagged with "image-copy-extension". \n' + 'You can use the following command: az resource list --tag created_by=image-copy-extension') pool.terminate() return - # Cleanup if cleanup: logger.warn('Deleting transient resources') # Delete resource group - cmd = prepare_cli_command(['group', 'delete', '--no-wait', '--yes', \ - '--name', transient_resource_group_name]) + cmd = prepare_cli_command(['group', 'delete', '--no-wait', '--yes', + '--name', transient_resource_group_name]) run_cli_command(cmd) # Revoke sas for source snapshot - cmd = prepare_cli_command(['snapshot', 'revoke-access', \ - '--name', source_os_disk_snapshot_name, \ - '--resource-group', source_resource_group_name]) + cmd = prepare_cli_command(['snapshot', 'revoke-access', + '--name', source_os_disk_snapshot_name, + '--resource-group', source_resource_group_name]) run_cli_command(cmd) # Delete source snapshot - cmd = prepare_cli_command(['snapshot', 'delete', \ - '--name', source_os_disk_snapshot_name, \ - '--resource-group', source_resource_group_name]) + cmd = prepare_cli_command(['snapshot', 'delete', + '--name', source_os_disk_snapshot_name, + '--resource-group', source_resource_group_name]) run_cli_command(cmd) def create_resource_group(resource_group_name, location): # check if target resource group exists - cmd = prepare_cli_command(['group', 'exists', \ - '--name', resource_group_name], output_as_json=False) + cmd = prepare_cli_command(['group', 'exists', + '--name', resource_group_name], output_as_json=False) cmd_output = run_cli_command(cmd) if 'false' in cmd_output: # create the target resource group logger.warn("Creating resource group: %s", resource_group_name) - cmd = prepare_cli_command(['group', 'create', \ - '--name', resource_group_name, \ - '--location', location]) + cmd = prepare_cli_command(['group', 'create', + '--name', resource_group_name, + '--location', location]) run_cli_command(cmd)