def create_data_disk(self, lun, source): blob_uri, disk, snapshot = self.resolve_storage_source(source) if blob_uri or disk or snapshot: snapshot_resource = SubResource(snapshot) if snapshot else None managed_disk = SubResource(disk) if disk else None return ImageDataDisk(lun, blob_uri=blob_uri, snapshot=snapshot_resource, managed_disk=managed_disk)
def create_os_disk(self): blob_uri, disk, snapshot = self.resolve_storage_source(self.source) snapshot_resource = SubResource(snapshot) if snapshot else None managed_disk = SubResource(disk) if disk else None return ImageOSDisk(os_type=self.os_type, os_state=OperatingSystemStateTypes.generalized, snapshot=snapshot_resource, managed_disk=managed_disk, blob_uri=blob_uri)
def exec_module(self, **kwargs): for key in self.module_arg_spec: setattr(self, key, kwargs[key]) results = None changed = False image = None if not self.location: # Set default location resource_group = self.get_resource_group(self.resource_group) self.location = resource_group.location self.log('Fetching image {0}'.format(self.name)) image = self.get_image() if image: self.check_provisioning_state(image, self.state) results = image.id # update is not supported if self.state == 'absent': changed = True # the image does not exist and create a new one elif self.state == 'present': changed = True self.results['changed'] = changed self.results['id'] = results if changed: if self.state == 'present': image_instance = None # create from virtual machine vm = self.get_source_vm() if vm: if self.data_disk_sources: self.fail('data_disk_sources is not allowed when capturing image from vm') image_instance = Image(self.location, source_virtual_machine=SubResource(vm.id)) else: if not self.os_type: self.fail('os_type is required to create the image') os_disk = self.create_os_disk() data_disks = self.create_data_disks() storage_profile = ImageStorageProfile(os_disk=os_disk, data_disks=data_disks) image_instance = Image(self.location, storage_profile=storage_profile, tags=self.tags) # finally make the change if not check mode if not self.check_mode and image_instance: new_image = self.create_image(image_instance) self.results['id'] = new_image.id elif self.state == 'absent': if not self.check_mode: # delete image self.delete_image() # the delete does not actually return anything. if no exception, then we'll assume it worked. self.results['id'] = None return self.results
def capture_image(self): try: vms = self.compute_client.virtual_machines vms.get(resource_group_name=self.resource_group, vm_name=self.vm_name) except CloudError: self.fail("VM {} not found!".format(self.vm_name)) except Exception as e: self.fail("An exception occurred: {}".format(str(e))) image_names = self._list_images() found = any(elem['name'] == self.name for elem in image_names) if not found: images = self.compute_client.images source_vm = vms.get(resource_group_name=self.resource_group, vm_name=self.vm_name).id params = Image(location=self.location, source_virtual_machine=SubResource(source_vm)) if self.check_mode: return_data = dict(name=self.name, status="Succeeded", location=self.location, resource_group=self.resource_group, changed=True) else: vms.deallocate(self.resource_group, self.vm_name).wait() vms.generalize(self.resource_group, self.vm_name) operation = images.create_or_update( resource_group_name=self.resource_group, image_name=self.name, parameters=params) operation.wait() result = operation.result() return_data = dict(name=result.name, status=result.provisioning_state, location=result.location, resource_group=self.resource_group, changed=True) else: return_data = dict(name=self.name, status="Image already exists", changed=False) return return_data
def create_image_from_vm(vm_name): # Deallocate async_vm_deallocate = compute_client.virtual_machines.begin_deallocate(test_group_name, vm_name) async_vm_deallocate.wait() # Generalize (possible because deallocated) compute_client.virtual_machines.generalize(test_group_name, vm_name) vm = compute_client.virtual_machines.get(test_group_name, vm_name) sub_resource = SubResource(id=vm.id) params = Image(location=location, source_virtual_machine=sub_resource) img_name = "vm_image_"+(str(datetime.datetime.now().time())).replace(":","").replace(".","") compute_client.images.begin_create_or_update( test_group_name, img_name, params ) return img_name
def enable( resource_group_name, vm_name, # pylint: disable=too-many-arguments,too-many-locals, too-many-statements aad_client_id, disk_encryption_keyvault, aad_client_secret=None, aad_client_cert_thumbprint=None, key_encryption_keyvault=None, key_encryption_key=None, key_encryption_algorithm='RSA-OAEP', volume_type=None): ''' Enable disk encryption on OS disk, Data disks, or both :param str aad_client_id: Client ID of AAD app with permissions to write secrets to KeyVault :param str aad_client_secret: Client Secret of AAD app with permissions to write secrets to KeyVault :param str aad_client_cert_thumbprint: Thumbprint of AAD app certificate with permissions to write secrets to KeyVault :param str disk_encryption_keyvault:the KeyVault where generated encryption key will be placed :param str key_encryption_key: KeyVault key name or URL used to encrypt the disk encryption key :param str key_encryption_keyvault: the KeyVault containing the key encryption key used to encrypt the disk encryption key. If missing, CLI will use --disk-encryption-keyvault ''' # pylint: disable=no-member compute_client = _compute_client_factory() vm = compute_client.virtual_machines.get(resource_group_name, vm_name) os_type = vm.storage_profile.os_disk.os_type.value is_linux = _is_linux_vm(os_type) extension = extension_info[os_type] # 1. First validate arguments if not aad_client_cert_thumbprint and not aad_client_secret: raise CLIError( 'Please provide either --aad-client-id or --aad-client-cert-thumbprint' ) if volume_type is None: if vm.storage_profile.data_disks: raise CLIError('VM has data disks, please supply --volume-type') else: volume_type = 'OS' # encryption is not supported on all linux distros, but service never tells you # so let us verify at the client side if is_linux: image_reference = getattr(vm.storage_profile, 'image_reference', None) if image_reference: result, message = _check_encrypt_is_supported( image_reference, volume_type) if not result: logger.warning(message) # sequence_version should be unique sequence_version = uuid.uuid4() # retrieve keyvault details disk_encryption_keyvault_url = _get_key_vault_base_url( (parse_resource_id(disk_encryption_keyvault))['name']) # disk encryption key itself can be further protected, so let us verify if key_encryption_key: key_encryption_keyvault = key_encryption_keyvault or disk_encryption_keyvault if '://' not in key_encryption_key: # appears a key name key_encryption_key = _get_keyvault_key_url( (parse_resource_id(key_encryption_keyvault))['name'], key_encryption_key) # 2. we are ready to provision/update the disk encryption extensions # The following logic was mostly ported from xplat-cli public_config = { 'AADClientID': aad_client_id, 'AADClientCertThumbprint': aad_client_cert_thumbprint, 'KeyVaultURL': disk_encryption_keyvault_url, 'VolumeType': volume_type, 'EncryptionOperation': 'EnableEncryption', 'KeyEncryptionKeyURL': key_encryption_key, 'KeyEncryptionAlgorithm': key_encryption_algorithm, 'SequenceVersion': sequence_version, } private_config = { 'AADClientSecret': aad_client_secret if is_linux else (aad_client_secret or '') } from azure.mgmt.compute.models import (VirtualMachineExtension, DiskEncryptionSettings, KeyVaultSecretReference, KeyVaultKeyReference, SubResource) ext = VirtualMachineExtension( vm.location, # pylint: disable=no-member publisher=extension['publisher'], virtual_machine_extension_type=extension['name'], protected_settings=private_config, type_handler_version=extension['version'], settings=public_config, auto_upgrade_minor_version=True) poller = compute_client.virtual_machine_extensions.create_or_update( resource_group_name, vm_name, extension['name'], ext) poller.result() # verify the extension was ok extension_result = compute_client.virtual_machine_extensions.get( resource_group_name, vm_name, extension['name'], 'instanceView') if extension_result.provisioning_state != 'Succeeded': raise CLIError( 'Extension needed for disk encryption was not provisioned correctly' ) if not (extension_result.instance_view.statuses and extension_result.instance_view.statuses[0].message): raise CLIError( 'Could not found url pointing to the secret for disk encryption') # 3. update VM's storage profile with the secrets status_url = extension_result.instance_view.statuses[0].message vm = compute_client.virtual_machines.get(resource_group_name, vm_name) secret_ref = KeyVaultSecretReference( secret_url=status_url, source_vault=SubResource(disk_encryption_keyvault)) key_encryption_key_obj = None if key_encryption_key: key_encryption_key_obj = KeyVaultKeyReference( key_encryption_key, SubResource(key_encryption_keyvault)) disk_encryption_settings = DiskEncryptionSettings( disk_encryption_key=secret_ref, key_encryption_key=key_encryption_key_obj, enabled=True) vm.storage_profile.os_disk.encryption_settings = disk_encryption_settings set_vm(vm) if is_linux and volume_type != _DATA_VOLUME_TYPE: # TODO: expose a 'wait' command to do the monitor and handle the reboot logger.warning( "The encryption request was accepted. Please use 'show' command to monitor " "the progress. If you see 'VMRestartPending', please restart the VM, and " "the encryption will finish shortly")
def exec_module(self, **kwargs): for key in list(self.module_arg_spec.keys()) + ['tags']: setattr(self, key, kwargs[key]) # make sure options are lower case self.remove_on_absent = set( [resource.lower() for resource in self.remove_on_absent]) changed = False results = dict() vmss = None disable_ssh_password = None vmss_dict = None virtual_network = None subnet = None image_reference = None custom_image = False resource_group = self.get_resource_group(self.resource_group) if not self.location: # Set default location self.location = resource_group.location if self.state == 'present': # Verify parameters and resolve any defaults if self.vm_size and not self.vm_size_is_valid(): self.fail( "Parameter error: vm_size {0} is not valid for your subscription and location." .format(self.vm_size)) # if self.virtual_network_name: # virtual_network = self.get_virtual_network(self.virtual_network_name) if self.ssh_public_keys: msg = "Parameter error: expecting ssh_public_keys to be a list of type dict where " \ "each dict contains keys: path, key_data." for key in self.ssh_public_keys: if not isinstance(key, dict): self.fail(msg) if not key.get('path') or not key.get('key_data'): self.fail(msg) if self.image and isinstance(self.image, dict): if all(key in self.image for key in ('publisher', 'offer', 'sku', 'version')): marketplace_image = self.get_marketplace_image_version() if self.image['version'] == 'latest': self.image['version'] = marketplace_image.name self.log("Using image version {0}".format( self.image['version'])) image_reference = ImageReference( publisher=self.image['publisher'], offer=self.image['offer'], sku=self.image['sku'], version=self.image['version']) elif self.image.get('name'): custom_image = True image_reference = self.get_custom_image_reference( self.image.get('name'), self.image.get('resource_group')) else: self.fail( "parameter error: expecting image to contain [publisher, offer, sku, version] or [name, resource_group]" ) elif self.image and isinstance(self.image, str): custom_image = True image_reference = self.get_custom_image_reference(self.image) elif self.image: self.fail( "parameter error: expecting image to be a string or dict not {0}" .format(type(self.image).__name__)) disable_ssh_password = not self.ssh_password_enabled try: self.log("Fetching virtual machine scale set {0}".format( self.name)) vmss = self.compute_client.virtual_machine_scale_sets.get( self.resource_group, self.name) self.check_provisioning_state(vmss, self.state) vmss_dict = self.serialize_vmss(vmss) if self.state == 'present': differences = [] results = vmss_dict if self.os_disk_caching and \ self.os_disk_caching != vmss_dict['properties']['virtualMachineProfile']['storageProfile']['osDisk']['caching']: self.log( 'CHANGED: virtual machine scale set {0} - OS disk caching' .format(self.name)) differences.append('OS Disk caching') changed = True vmss_dict['properties']['virtualMachineProfile'][ 'storageProfile']['osDisk'][ 'caching'] = self.os_disk_caching if self.capacity and \ self.capacity != vmss_dict['sku']['capacity']: self.log( 'CHANGED: virtual machine scale set {0} - Capacity'. format(self.name)) differences.append('Capacity') changed = True vmss_dict['sku']['capacity'] = self.capacity if self.data_disks and \ len(self.data_disks) != len(vmss_dict['properties']['virtualMachineProfile']['storageProfile']['dataDisks']): self.log( 'CHANGED: virtual machine scale set {0} - Data Disks'. format(self.name)) differences.append('Data Disks') changed = True update_tags, vmss_dict['tags'] = self.update_tags( vmss_dict.get('tags', dict())) if update_tags: differences.append('Tags') changed = True self.differences = differences elif self.state == 'absent': self.log( "CHANGED: virtual machine scale set {0} exists and requested state is 'absent'" .format(self.name)) results = dict() changed = True except CloudError: self.log('Virtual machine scale set {0} does not exist'.format( self.name)) if self.state == 'present': self.log( "CHANGED: virtual machine scale set {0} does not exist but state is 'present'." .format(self.name)) changed = True self.results['changed'] = changed self.results['ansible_facts']['azure_vmss'] = results if self.check_mode: return self.results if changed: if self.state == 'present': if not vmss: # Create the VMSS self.log("Create virtual machine scale set {0}".format( self.name)) self.results['actions'].append('Created VMSS {0}'.format( self.name)) # Validate parameters if not self.admin_username: self.fail( "Parameter error: admin_username required when creating a virtual machine scale set." ) if self.os_type == 'Linux': if disable_ssh_password and not self.ssh_public_keys: self.fail( "Parameter error: ssh_public_keys required when disabling SSH password." ) if not self.virtual_network_name: default_vnet = self.create_default_vnet() virtual_network = default_vnet.id self.virtual_network_name = default_vnet.name if self.subnet_name: subnet = self.get_subnet(self.virtual_network_name, self.subnet_name) load_balancer_backend_address_pools = None load_balancer_inbound_nat_pools = None if self.load_balancer: load_balancer = self.get_load_balancer( self.load_balancer) load_balancer_backend_address_pools = ([ SubResource(resource.id) for resource in load_balancer.backend_address_pools ] if load_balancer.backend_address_pools else None) load_balancer_inbound_nat_pools = ([ SubResource(resource.id) for resource in load_balancer.inbound_nat_pools ] if load_balancer.inbound_nat_pools else None) if not self.short_hostname: self.short_hostname = self.name if not image_reference: self.fail( "Parameter error: an image is required when creating a virtual machine." ) managed_disk = VirtualMachineScaleSetManagedDiskParameters( storage_account_type=self.managed_disk_type) vmss_resource = VirtualMachineScaleSet( self.location, tags=self.tags, upgrade_policy=UpgradePolicy(mode=self.upgrade_policy), sku=Sku( name=self.vm_size, capacity=self.capacity, tier=self.tier, ), virtual_machine_profile=VirtualMachineScaleSetVMProfile( os_profile=VirtualMachineScaleSetOSProfile( admin_username=self.admin_username, computer_name_prefix=self.short_hostname, ), storage_profile= VirtualMachineScaleSetStorageProfile( os_disk=VirtualMachineScaleSetOSDisk( managed_disk=managed_disk, create_option=DiskCreateOptionTypes. from_image, caching=self.os_disk_caching, ), image_reference=image_reference, ), network_profile= VirtualMachineScaleSetNetworkProfile( network_interface_configurations=[ VirtualMachineScaleSetNetworkConfiguration( name=self.name, primary=True, ip_configurations=[ VirtualMachineScaleSetIPConfiguration( name='default', subnet=ApiEntityReference( id=subnet.id), primary=True, load_balancer_backend_address_pools =load_balancer_backend_address_pools, load_balancer_inbound_nat_pools= load_balancer_inbound_nat_pools ) ]) ]))) if self.admin_password: vmss_resource.virtual_machine_profile.os_profile.admin_password = self.admin_password if self.os_type == 'Linux': vmss_resource.virtual_machine_profile.os_profile.linux_configuration = LinuxConfiguration( disable_password_authentication=disable_ssh_password ) if self.ssh_public_keys: ssh_config = SshConfiguration() ssh_config.public_keys = \ [SshPublicKey(path=key['path'], key_data=key['key_data']) for key in self.ssh_public_keys] vmss_resource.virtual_machine_profile.os_profile.linux_configuration.ssh = ssh_config if self.data_disks: data_disks = [] for data_disk in self.data_disks: data_disk_managed_disk = VirtualMachineScaleSetManagedDiskParameters( storage_account_type=data_disk[ 'managed_disk_type']) data_disk['caching'] = data_disk.get( 'caching', CachingTypes.read_only) data_disks.append( VirtualMachineScaleSetDataDisk( lun=data_disk['lun'], caching=data_disk['caching'], create_option=DiskCreateOptionTypes.empty, disk_size_gb=data_disk['disk_size_gb'], managed_disk=data_disk_managed_disk, )) vmss_resource.virtual_machine_profile.storage_profile.data_disks = data_disks self.log("Create virtual machine with parameters:") self.create_or_update_vmss(vmss_resource) elif self.differences and len(self.differences) > 0: self.log("Update virtual machine scale set {0}".format( self.name)) self.results['actions'].append('Updated VMSS {0}'.format( self.name)) vmss_resource = self.get_vmss() vmss_resource.virtual_machine_profile.storage_profile.os_disk.caching = self.os_disk_caching vmss_resource.sku.capacity = self.capacity data_disks = [] for data_disk in self.data_disks: data_disks.append( VirtualMachineScaleSetDataDisk( lun=data_disk['lun'], caching=data_disk['caching'], create_option=DiskCreateOptionTypes.empty, disk_size_gb=data_disk['disk_size_gb'], managed_disk= VirtualMachineScaleSetManagedDiskParameters( storage_account_type=data_disk[ 'managed_disk_type']), )) vmss_resource.virtual_machine_profile.storage_profile.data_disks = data_disks self.log("Update virtual machine with parameters:") self.create_or_update_vmss(vmss_resource) self.results['ansible_facts'][ 'azure_vmss'] = self.serialize_vmss(self.get_vmss()) elif self.state == 'absent': # delete the VM self.log("Delete virtual machine scale set {0}".format( self.name)) self.results['ansible_facts']['azure_vmss'] = None self.delete_vmss(vmss) # until we sort out how we want to do this globally del self.results['actions'] return self.results