Skip to content

Commit

Permalink
{vm-repair} Fix public IP turn off option (#8160)
Browse files Browse the repository at this point in the history
  • Loading branch information
Sandido authored Oct 25, 2024
1 parent fce9952 commit ea4af4c
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 10 deletions.
1 change: 1 addition & 0 deletions src/vm-repair/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Release History
1.1.1
++++++
Migrated VM Repair off of the `msrestazure` API to `azure.core` and `azure.mgmt` APIs.
Fixed a bug with `--associate-public-ip` where it was always creating a public IP. Now a private IP will be used if `--associate-public-ip` is not specified.

1.1.0
++++++
Expand Down
6 changes: 3 additions & 3 deletions src/vm-repair/azext_vm_repair/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ def load_arguments(self, _):
c.argument('unlock_encrypted_vm', help='Option to auto-unlock encrypted VMs using current subscription auth.')
c.argument('encrypt_recovery_key', help='Option to auto-unlock encrypted VMs using provided recovery password.')
c.argument('enable_nested', help='enable nested hyperv.')
c.argument('associate_public_ip', help='Option to create repair vm with public ip')
c.argument('associate_public_ip', help='Option to create a repair vm with a public ip. If this parameter is not used, a private ip will be made.')
c.argument('distro', help='Option to create repair vm from a specific linux distro (rhel7|rhel8|sles12|sles15|ubuntu20|centos7|centos8|oracle7)')
c.argument('yes', help='Option to skip prompt for associating public ip and confirm yes to it in no Tty mode')
c.argument('yes', help='Option to skip prompt for associating public ip in no Tty mode')
c.argument('disable_trusted_launch', help='Option to disable Trusted Launch security type on the repair vm by setting the security type to Standard.')

with self.argument_context('vm repair restore') as c:
c.argument('repair_vm_id', help='Repair VM resource id.')
c.argument('disk_name', help='Name of fixed data disk. Defaults to the first data disk in the repair VM.')
Expand Down
6 changes: 4 additions & 2 deletions src/vm-repair/azext_vm_repair/_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,13 +228,15 @@ 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 = namespace.repair_vm_name + "PublicIP"
namespace.associate_public_ip = _return_public_ip_name(namespace)
else:
namespace.associate_public_ip = '""'

except NoTTYException:
raise ValidationError('Please specify the associate-public-ip parameter in non-interactive mode.')

def _return_public_ip_name(namespace):
return namespace.repair_vm_name + "PublicIP"

def _classic_vm_exists(cmd, resource_group_name, vm_name):
classic_vm_provider = 'Microsoft.ClassicCompute'
Expand Down Expand Up @@ -428,9 +430,9 @@ def validate_repair_and_restore(cmd, namespace):

validate_vm_username(namespace.repair_username, is_linux)
validate_vm_password(namespace.repair_password, is_linux)

# Prompt input for public ip usage
namespace.associate_public_ip = False

# Validate repair run command
source_vm = _validate_and_get_vm(cmd, namespace.resource_group_name, namespace.vm_name)
is_linux = _is_linux_os(source_vm)
14 changes: 9 additions & 5 deletions src/vm-repair/azext_vm_repair/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@
_select_distro_linux_Arm64,
_fetch_vm_security_profile_parameters,
_fetch_osdisk_security_profile_parameters,
_fetch_compatible_windows_os_urn_v2
_fetch_compatible_windows_os_urn_v2,
_make_public_ip_name
)
from .exceptions import AzCommandError, RunScriptNotFoundForIdError, SupportingResourceNotFoundError, CommandCanceledByUserError
logger = get_logger(__name__)
Expand All @@ -59,7 +60,6 @@ def create(cmd, vm_name, resource_group_name, repair_password=None, repair_usern
logger.warning('After the November 2024 release, if the image of the source Windows VM is not found, the \'az vm repair create\' command will default to use a 2022-Datacenter image for the repair VM.')
# log all the parameters not logging the bitlocker key
logger.debug('vm repair create command parameters: vm_name: %s, resource_group_name: %s, repair_password: %s, repair_username: %s, repair_vm_name: %s, copy_disk_name: %s, repair_group_name: %s, unlock_encrypted_vm: %s, enable_nested: %s, associate_public_ip: %s, distro: %s, yes: %s, disable_trusted_launch: %s', vm_name, resource_group_name, repair_password, repair_username, repair_vm_name, copy_disk_name, repair_group_name, unlock_encrypted_vm, enable_nested, associate_public_ip, distro, yes, disable_trusted_launch)

# Init command helper object
command = command_helper(logger, cmd, 'vm repair create')
# Main command calling block
Expand Down Expand Up @@ -98,13 +98,16 @@ def create(cmd, vm_name, resource_group_name, repair_password=None, repair_usern
os_image_urn = _fetch_compatible_windows_os_urn(source_vm)
os_type = 'Windows'

# Set public IP address for repair VM
public_ip_name = _make_public_ip_name(repair_vm_name, associate_public_ip)

# 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} --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())
.format(g=repair_group_name, n=repair_vm_name, tag=resource_tag, image=os_image_urn, username=repair_username, password=repair_password, option=public_ip_name, 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} --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)
.format(g=repair_group_name, n=repair_vm_name, tag=resource_tag, image=os_image_urn, username=repair_username, password=repair_password, option=public_ip_name)

# Fetch VM size of repair VM
sku = _fetch_compatible_sku(source_vm, enable_nested)
Expand Down Expand Up @@ -343,13 +346,14 @@ def restore(cmd, vm_name, resource_group_name, disk_name=None, repair_vm_id=None
# Fetch source and repair VM data
source_vm = get_vm(cmd, resource_group_name, vm_name)
is_managed = _uses_managed_disk(source_vm)
logger.info('Repair VM ID: %s', repair_vm_id)
if repair_vm_id:
logger.info('Repair VM ID: %s', repair_vm_id)
repair_vm_id = parse_resource_id(repair_vm_id)
repair_vm_name = repair_vm_id['name']
repair_resource_group = repair_vm_id['resource_group']
source_disk = None

# MANAGED DISK
if is_managed:
source_disk = source_vm.storage_profile.os_disk.name
Expand Down
7 changes: 7 additions & 0 deletions src/vm-repair/azext_vm_repair/repair_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -798,3 +798,10 @@ def _fetch_osdisk_security_profile_parameters(source_vm):
create_repair_vm_command += ' --os-disk-secure-vm-disk-encryption-set {val}'.format(val=source_vm.storage_profile.os_disk.managed_disk.security_profile.disk_encryption_set.id)

return create_repair_vm_command


def _make_public_ip_name(repair_vm_name, associate_public_ip):
public_ip_name = '""'
if associate_public_ip:
public_ip_name = repair_vm_name + "PublicIP"
return public_ip_name
116 changes: 116 additions & 0 deletions src/vm-repair/azext_vm_repair/tests/latest/test_repair_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
from azure.cli.testsdk import LiveScenarioTest, ResourceGroupPreparer
import json
import re
import uuid
import secrets
import string

STATUS_SUCCESS = 'SUCCESS'

Expand Down Expand Up @@ -955,3 +958,116 @@ def test_vmrepair_WindowsRunWithRepairIdAPIChange(self, resource_group):
vms = self.cmd('vm list -g {} -o json'.format(rgname)).get_output_in_json()
source_vm = vms[0]
assert source_vm['storageProfile']['osDisk']['name'] == result['copied_disk_name']




@pytest.mark.winNoPublicIPByDefault
class WindowsNoPubIPByDefault(LiveScenarioTest):

@ResourceGroupPreparer(location='westus2')
def test_vmrepair_WindowsNoPublicIPByDefault(self, resource_group):
base_password = "Passw0rd2024"
guid_suffix = str(uuid.uuid4())
secure_password = base_password + guid_suffix
username_length = 8
secure_username = ''.join(secrets.choice(string.ascii_letters + string.digits) for _ in range(username_length))
self.kwargs.update({
'vm': 'vm1',
'admin_password': secure_password,
'admin_username': secure_username
})

# Create test VM without public IP
self.cmd('vm create -g {rg} -n {vm} --admin-username {admin_username} --admin-password {admin_password} --image MicrosoftWindowsServer:windowsserver:2022-datacenter:latest')
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

# Create Repair VM
repair_vm = self.cmd('vm repair create -g {rg} -n {vm} --repair-username {admin_username} --repair-password {admin_password} --yes -o json').get_output_in_json()
assert repair_vm['status'] == STATUS_SUCCESS, repair_vm['error_message']
# Check repair VM
self.kwargs.update({
'vm': 'vm1',
'admin_password': secure_password,
'admin_username': secure_username,
'repair_resource_group': repair_vm['repair_resource_group']
})
# need to verify there is no public ip on the vm
public_ip_list = self.cmd('network public-ip list -g {repair_resource_group} -o json').get_output_in_json()
assert len(public_ip_list) == 0

repair_vms = self.cmd('vm list -g {repair_resource_group} -o json').get_output_in_json()
assert len(repair_vms) == 1
repair_vm = repair_vms[0]
repair_vm_id = repair_vm['id']
self.kwargs.update({
'vm': 'vm1',
'admin_password': secure_password,
'admin_username': secure_username,
'repair_vm_id': repair_vm_id
})

# Run a script for testing repair-vm-id
result_run = self.cmd('vm repair run -g {rg} -n {vm} --run-id win-hello-world --repair-vm-id {repair_vm_id} --run-on-repair -o json').get_output_in_json()
assert result_run['status'] == STATUS_SUCCESS, result_run['error_message']

# Call Restore
self.cmd('vm repair restore -g {rg} -n {vm} --yes')


@pytest.mark.winPublicIPWithParameter
class WindowsPublicIPWithParameter(LiveScenarioTest):

@ResourceGroupPreparer(location='westus2')
def test_vmrepair_WindowsPublicIPWithParameter(self, resource_group):

base_password = "Passw0rd2024"
guid_suffix = str(uuid.uuid4())
secure_password = base_password + guid_suffix
username_length = 8
secure_username = ''.join(secrets.choice(string.ascii_letters + string.digits) for _ in range(username_length))
self.kwargs.update({
'vm': 'vm1',
'admin_password': secure_password,
'admin_username': secure_username
})

# Create test VM with public IP
self.cmd('vm create -g {rg} -n {vm} --admin-username {admin_username} --admin-password {admin_password} --image MicrosoftWindowsServer:windowsserver:2022-datacenter:latest')
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

# Create Repair VM
repair_vm = self.cmd('vm repair create -g {rg} -n {vm} --repair-username {admin_username} --repair-password {admin_password} --debug --verbose --associate-public-ip --yes -o json').get_output_in_json()
assert repair_vm['status'] == STATUS_SUCCESS, repair_vm['error_message']
# Check repair VM
self.kwargs.update({
'vm': 'vm1',
'admin_password': secure_password,
'admin_username': secure_username,
'repair_resource_group': repair_vm['repair_resource_group']
})
# need to verify there is no public ip on the vm
public_ip_list = self.cmd('network public-ip list -g {repair_resource_group} --debug --verbose -o json').get_output_in_json()
assert len(public_ip_list) == 1

repair_vms = self.cmd('vm list -g {repair_resource_group} -o json').get_output_in_json()
assert len(repair_vms) == 1
repair_vm = repair_vms[0]
repair_vm_id = repair_vm['id']
self.kwargs.update({
'vm': 'vm1',
'admin_password': secure_password,
'admin_username': secure_username,
'repair_vm_id': repair_vm_id
})

# Run a script for testing repair-vm-id
result_run = self.cmd('vm repair run -g {rg} -n {vm} --run-id win-hello-world --repair-vm-id {repair_vm_id} --run-on-repair --debug --verbose -o json').get_output_in_json()
assert result_run['status'] == STATUS_SUCCESS, result_run['error_message']

# Call Restore
self.cmd('vm repair restore -g {rg} -n {vm} --yes')

0 comments on commit ea4af4c

Please sign in to comment.