diff --git a/src/vm-repair/azext_vm_repair/_params.py b/src/vm-repair/azext_vm_repair/_params.py index 982524642ca..3714404e979 100644 --- a/src/vm-repair/azext_vm_repair/_params.py +++ b/src/vm-repair/azext_vm_repair/_params.py @@ -30,6 +30,7 @@ def load_arguments(self, _): c.argument('repair_group_name', help='Repair resource group name.') c.argument('unlock_encrypted_vm', help='Option to auto-unlock encrypted VMs using current subscription auth.') c.argument('enable_nested', help='enable nested hyperv.') + c.argument('associate_public_ip', help='Option to create repair vm with public ip') with self.argument_context('vm repair restore') as c: c.argument('repair_vm_id', help='Repair VM resource id.') diff --git a/src/vm-repair/azext_vm_repair/_validators.py b/src/vm-repair/azext_vm_repair/_validators.py index b66729d2fb3..29f7e868c51 100644 --- a/src/vm-repair/azext_vm_repair/_validators.py +++ b/src/vm-repair/azext_vm_repair/_validators.py @@ -8,6 +8,7 @@ from re import match, search, findall from knack.log import get_logger from knack.util import CLIError +from azure.cli.core.azclierror import ValidationError from azure.cli.command_modules.vm.custom import get_vm, _is_linux_os from azure.cli.command_modules.resource._client_factory import _resource_client_factory @@ -85,6 +86,9 @@ def validate_create(cmd, namespace): _prompt_repair_password(namespace) # Validate vm password validate_vm_password(namespace.repair_password, is_linux) + # Prompt input for public ip usage + if not namespace.associate_public_ip: + _prompt_public_ip(namespace) def validate_restore(cmd, namespace): @@ -201,6 +205,18 @@ def _prompt_repair_password(namespace): raise CLIError('Please specify the password parameter in non-interactive mode.') +def _prompt_public_ip(namespace): + from knack.prompting import prompt_y_n, NoTTYException + try: + if prompt_y_n('Does repair vm requires public ip?'): + namespace.associate_public_ip = "yes" + else: + namespace.associate_public_ip = '""' + + except NoTTYException: + raise ValidationError('Please specify the associate-public-ip parameter in non-interactive mode.') + + def _classic_vm_exists(cmd, resource_group_name, vm_name): classic_vm_provider = 'Microsoft.ClassicCompute' vm_resource_type = 'virtualMachines' diff --git a/src/vm-repair/azext_vm_repair/custom.py b/src/vm-repair/azext_vm_repair/custom.py index 6cd07404c34..d72b6607522 100644 --- a/src/vm-repair/azext_vm_repair/custom.py +++ b/src/vm-repair/azext_vm_repair/custom.py @@ -38,7 +38,7 @@ logger = get_logger(__name__) -def create(cmd, vm_name, resource_group_name, repair_password=None, repair_username=None, repair_vm_name=None, copy_disk_name=None, repair_group_name=None, unlock_encrypted_vm=False, enable_nested=False): +def create(cmd, vm_name, resource_group_name, repair_password=None, repair_username=None, repair_vm_name=None, copy_disk_name=None, repair_group_name=None, unlock_encrypted_vm=False, enable_nested=False, associate_public_ip=False): # Init command helper object command = command_helper(logger, cmd, 'vm repair create') # Main command calling block @@ -65,11 +65,11 @@ def create(cmd, vm_name, resource_group_name, repair_password=None, repair_usern # Set up base create vm command if is_linux: - create_repair_vm_command = 'az vm create -g {g} -n {n} --tag {tag} --image {image} --admin-username {username} --admin-password {password} --custom-data {cloud_init_script}' \ - .format(g=repair_group_name, n=repair_vm_name, tag=resource_tag, image=os_image_urn, username=repair_username, password=repair_password, cloud_init_script=_get_cloud_init_script()) + create_repair_vm_command = 'az vm create -g {g} -n {n} --tag {tag} --image {image} --admin-username {username} --admin-password {password} --public-ip-address {option} --custom-data {cloud_init_script}' \ + .format(g=repair_group_name, n=repair_vm_name, tag=resource_tag, image=os_image_urn, username=repair_username, password=repair_password, option=associate_public_ip, cloud_init_script=_get_cloud_init_script()) else: - create_repair_vm_command = 'az vm create -g {g} -n {n} --tag {tag} --image {image} --admin-username {username} --admin-password {password}' \ - .format(g=repair_group_name, n=repair_vm_name, tag=resource_tag, image=os_image_urn, username=repair_username, password=repair_password) + create_repair_vm_command = 'az vm create -g {g} -n {n} --tag {tag} --image {image} --admin-username {username} --admin-password {password} --public-ip-address {option}' \ + .format(g=repair_group_name, n=repair_vm_name, tag=resource_tag, image=os_image_urn, username=repair_username, password=repair_password, option=associate_public_ip) # Fetch VM size of repair VM sku = _fetch_compatible_sku(source_vm, enable_nested) diff --git a/src/vm-repair/azext_vm_repair/tests/latest/test_repair_commands.py b/src/vm-repair/azext_vm_repair/tests/latest/test_repair_commands.py index 1bd00e3e3a6..27345b74ede 100644 --- a/src/vm-repair/azext_vm_repair/tests/latest/test_repair_commands.py +++ b/src/vm-repair/azext_vm_repair/tests/latest/test_repair_commands.py @@ -145,6 +145,142 @@ def test_vmrepair_LinuxUnmanagedCreateRestore(self, resource_group): assert source_vm['storageProfile']['osDisk']['vhd']['uri'] == result['copied_disk_uri'] +class WindowsManagedDiskCreateRestoreTestwithpublicip(LiveScenarioTest): + + @ResourceGroupPreparer(location='westus2') + def test_vmrepair_WinManagedCreateRestore(self, resource_group): + self.kwargs.update({ + 'vm': 'vm1' + }) + + # Create test VM + self.cmd('vm create -g {rg} -n {vm} --admin-username azureadmin --image Win2016Datacenter --admin-password !Passw0rd2018') + vms = self.cmd('vm list -g {rg} -o json').get_output_in_json() + # Something wrong with vm create command if it fails here + assert len(vms) == 1 + + # Test create + result = self.cmd('vm repair create -g {rg} -n {vm} --repair-username azureadmin --repair-password !Passw0rd2018 --associate-public-ip -o json').get_output_in_json() + assert result['status'] == STATUS_SUCCESS, result['error_message'] + + # Check repair VM + repair_vms = self.cmd('vm list -g {} -o json'.format(result['repair_resource_group'])).get_output_in_json() + assert len(repair_vms) == 1 + repair_vm = repair_vms[0] + # Check attached data disk + assert repair_vm['storageProfile']['dataDisks'][0]['name'] == result['copied_disk_name'] + + # Call Restore + self.cmd('vm repair restore -g {rg} -n {vm} --yes') + + # Check swapped OS disk + vms = self.cmd('vm list -g {rg} -o json').get_output_in_json() + source_vm = vms[0] + assert source_vm['storageProfile']['osDisk']['name'] == result['copied_disk_name'] + + +class WindowsUnmanagedDiskCreateRestoreTestwithpublicip(LiveScenarioTest): + + @ResourceGroupPreparer(location='westus2') + def test_vmrepair_WinUnmanagedCreateRestore(self, resource_group): + self.kwargs.update({ + 'vm': 'vm1' + }) + + # Create test VM + self.cmd('vm create -g {rg} -n {vm} --admin-username azureadmin --image Win2016Datacenter --admin-password !Passw0rd2018 --use-unmanaged-disk') + vms = self.cmd('vm list -g {rg} -o json').get_output_in_json() + # Something wrong with vm create command if it fails here + assert len(vms) == 1 + + # Test create + result = self.cmd('vm repair create -g {rg} -n {vm} --repair-username azureadmin --repair-password !Passw0rd2018 --associate-public-ip -o json').get_output_in_json() + assert result['status'] == STATUS_SUCCESS, result['error_message'] + + # Check repair VM + repair_vms = self.cmd('vm list -g {} -o json'.format(result['repair_resource_group'])).get_output_in_json() + assert len(repair_vms) == 1 + repair_vm = repair_vms[0] + # Check attached data disk + assert repair_vm['storageProfile']['dataDisks'][0]['name'] == result['copied_disk_name'] + + # Call Restore + self.cmd('vm repair restore -g {rg} -n {vm} --yes') + + # Check swapped OS disk + vms = self.cmd('vm list -g {rg} -o json').get_output_in_json() + source_vm = vms[0] + assert source_vm['storageProfile']['osDisk']['vhd']['uri'] == result['copied_disk_uri'] + + +class LinuxManagedDiskCreateRestoreTestwithpublicip(LiveScenarioTest): + + @ResourceGroupPreparer(location='westus2') + def test_vmrepair_LinuxManagedCreateRestore(self, resource_group): + self.kwargs.update({ + 'vm': 'vm1' + }) + + # Create test VM + self.cmd('vm create -g {rg} -n {vm} --image UbuntuLTS --admin-username azureadmin --admin-password !Passw0rd2018') + vms = self.cmd('vm list -g {rg} -o json').get_output_in_json() + # Something wrong with vm create command if it fails here + assert len(vms) == 1 + + # Test create + result = self.cmd('vm repair create -g {rg} -n {vm} --repair-username azureadmin --repair-password !Passw0rd2018 --associate-public-ip -o json').get_output_in_json() + assert result['status'] == STATUS_SUCCESS, result['error_message'] + + # Check repair VM + repair_vms = self.cmd('vm list -g {} -o json'.format(result['repair_resource_group'])).get_output_in_json() + assert len(repair_vms) == 1 + repair_vm = repair_vms[0] + # Check attached data disk + assert repair_vm['storageProfile']['dataDisks'][0]['name'] == result['copied_disk_name'] + + # Call Restore + self.cmd('vm repair restore -g {rg} -n {vm} --yes') + + # Check swapped OS disk + vms = self.cmd('vm list -g {rg} -o json').get_output_in_json() + source_vm = vms[0] + assert source_vm['storageProfile']['osDisk']['name'] == result['copied_disk_name'] + + +class LinuxUnmanagedDiskCreateRestoreTestwithpublicip(LiveScenarioTest): + + @ResourceGroupPreparer(location='westus2') + def test_vmrepair_LinuxUnmanagedCreateRestore(self, resource_group): + self.kwargs.update({ + 'vm': 'vm1' + }) + + # Create test VM + self.cmd('vm create -g {rg} -n {vm} --image UbuntuLTS --admin-username azureadmin --admin-password !Passw0rd2018 --use-unmanaged-disk') + vms = self.cmd('vm list -g {rg} -o json').get_output_in_json() + # Something wrong with vm create command if it fails here + assert len(vms) == 1 + + # Test create + result = self.cmd('vm repair create -g {rg} -n {vm} --repair-username azureadmin --repair-password !Passw0rd2018 --associate-public-ip -o json').get_output_in_json() + assert result['status'] == STATUS_SUCCESS, result['error_message'] + + # Check repair VM + repair_vms = self.cmd('vm list -g {} -o json'.format(result['repair_resource_group'])).get_output_in_json() + assert len(repair_vms) == 1 + repair_vm = repair_vms[0] + # Check attached data disk + assert repair_vm['storageProfile']['dataDisks'][0]['name'] == result['copied_disk_name'] + + # Call Restore + self.cmd('vm repair restore -g {rg} -n {vm} --yes') + + # Check swapped OS disk + vms = self.cmd('vm list -g {rg} -o json').get_output_in_json() + source_vm = vms[0] + assert source_vm['storageProfile']['osDisk']['vhd']['uri'] == result['copied_disk_uri'] + + class WindowsSinglepassKekEncryptedManagedDiskCreateRestoreTest(LiveScenarioTest): @ResourceGroupPreparer(location='westus2') diff --git a/src/vm-repair/setup.py b/src/vm-repair/setup.py index 7c6ab3ef63c..c427ab66a2d 100644 --- a/src/vm-repair/setup.py +++ b/src/vm-repair/setup.py @@ -33,7 +33,7 @@ name='vm-repair', version=VERSION, description='Auto repair commands to fix VMs.', - long_description='VM repair command will enable Azure users to self-repair non-bootable VMs by copying the source VM\'s OS disk and attaching it to a newly created repair VM.'+ '\n\n' + HISTORY, + long_description='VM repair command will enable Azure users to self-repair non-bootable VMs by copying the source VM\'s OS disk and attaching it to a newly created repair VM.' + '\n\n' + HISTORY, license='MIT', author='Microsoft Corporation', author_email='caiddev@microsoft.com',