From a678029bd29e69df28bcea75c360d02110d0cd11 Mon Sep 17 00:00:00 2001 From: "patchback[bot]" <45432694+patchback[bot]@users.noreply.github.com> Date: Mon, 7 Feb 2022 17:48:11 +0100 Subject: [PATCH] Refactor all Proxmox modules to use shared `module_utils`. (#4029) (#4164) * Refactor Proxmox modules to use `module_utils`. * Fix tests. * Rename `node_check`. * Add `ignore_missing` to `get_vm`. * Refactor `proxmox` module. * Add changelog entry. * Add `choose_first_if_multiple` parameter for deprecation. (cherry picked from commit a61bdbadd590668d4bbceb002a94963b32908327) Co-authored-by: Markus Reiter --- .../fragments/4029-proxmox-refactor.yml | 3 + plugins/module_utils/proxmox.py | 45 + plugins/modules/cloud/misc/proxmox.py | 494 +++++------ plugins/modules/cloud/misc/proxmox_kvm.py | 787 ++++++++---------- plugins/modules/cloud/misc/proxmox_nic.py | 206 ++--- plugins/modules/cloud/misc/proxmox_snap.py | 179 ++-- .../modules/cloud/misc/proxmox_template.py | 146 ++-- .../modules/cloud/misc/test_proxmox_kvm.py | 2 + .../modules/cloud/misc/test_proxmox_snap.py | 23 +- .../cloud/misc/test_proxmox_tasks_info.py | 36 +- 10 files changed, 849 insertions(+), 1072 deletions(-) create mode 100644 changelogs/fragments/4029-proxmox-refactor.yml diff --git a/changelogs/fragments/4029-proxmox-refactor.yml b/changelogs/fragments/4029-proxmox-refactor.yml new file mode 100644 index 0000000000..c03a84abec --- /dev/null +++ b/changelogs/fragments/4029-proxmox-refactor.yml @@ -0,0 +1,3 @@ +--- +minor_changes: + - proxmox modules - move common code into ``module_utils`` (https://github.com/ansible-collections/community.general/pull/4029). diff --git a/plugins/module_utils/proxmox.py b/plugins/module_utils/proxmox.py index 83674bc6f1..94bd0b7943 100644 --- a/plugins/module_utils/proxmox.py +++ b/plugins/module_utils/proxmox.py @@ -21,6 +21,8 @@ except ImportError: from ansible.module_utils.basic import env_fallback, missing_required_lib +from ansible.module_utils.common.text.converters import to_native +from ansible_collections.community.general.plugins.module_utils.version import LooseVersion def proxmox_auth_argument_spec(): @@ -98,3 +100,46 @@ class ProxmoxAnsible(object): return ProxmoxAPI(api_host, verify_ssl=validate_certs, **auth_args) except Exception as e: self.module.fail_json(msg='%s' % e, exception=traceback.format_exc()) + + def version(self): + apireturn = self.proxmox_api.version.get() + return LooseVersion(apireturn['version']) + + def get_node(self, node): + nodes = [n for n in self.proxmox_api.nodes.get() if n['node'] == node] + return nodes[0] if nodes else None + + def get_nextvmid(self): + vmid = self.proxmox_api.cluster.nextid.get() + return vmid + + def get_vmid(self, name, ignore_missing=False, choose_first_if_multiple=False): + vms = [vm['vmid'] for vm in self.proxmox_api.cluster.resources.get(type='vm') if vm.get('name') == name] + + if not vms: + if ignore_missing: + return None + + self.module.fail_json(msg='No VM with name %s found' % name) + elif len(vms) > 1: + if choose_first_if_multiple: + self.module.deprecate( + 'Multiple VMs with name %s found, choosing the first one. ' % name + + 'This will be an error in the future. To ensure the correct VM is used, ' + + 'also pass the vmid parameter.', + version='5.0.0', collection_name='community.general') + else: + self.module.fail_json(msg='Multiple VMs with name %s found, provide vmid instead' % name) + + return vms[0] + + def get_vm(self, vmid, ignore_missing=False): + vms = [vm for vm in self.proxmox_api.cluster.resources.get(type='vm') if vm['vmid'] == int(vmid)] + + if vms: + return vms[0] + else: + if ignore_missing: + return None + + self.module.fail_json(msg='VM with vmid %s does not exist in cluster' % vmid) diff --git a/plugins/modules/cloud/misc/proxmox.py b/plugins/modules/cloud/misc/proxmox.py index 28e3be74b3..662e834803 100644 --- a/plugins/modules/cloud/misc/proxmox.py +++ b/plugins/modules/cloud/misc/proxmox.py @@ -1,5 +1,6 @@ #!/usr/bin/python # -*- coding: utf-8 -*- +# # Copyright: Ansible Project # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) @@ -392,229 +393,189 @@ import traceback from ansible_collections.community.general.plugins.module_utils.version import LooseVersion -try: - from proxmoxer import ProxmoxAPI - HAS_PROXMOXER = True -except ImportError: - HAS_PROXMOXER = False - from ansible.module_utils.basic import AnsibleModule, env_fallback from ansible.module_utils.common.text.converters import to_native from ansible_collections.community.general.plugins.module_utils.proxmox import ( - ansible_to_proxmox_bool -) - + ansible_to_proxmox_bool, proxmox_auth_argument_spec, ProxmoxAnsible) VZ_TYPE = None -def get_nextvmid(module, proxmox): - try: - vmid = proxmox.cluster.nextid.get() - return vmid - except Exception as e: - module.fail_json(msg="Unable to get next vmid. Failed with exception: %s" % to_native(e), - exception=traceback.format_exc()) +class ProxmoxLxcAnsible(ProxmoxAnsible): + def content_check(self, node, ostemplate, template_store): + return [True for cnt in self.proxmox_api.nodes(node).storage(template_store).content.get() if cnt['volid'] == ostemplate] + def is_template_container(self, node, vmid): + """Check if the specified container is a template.""" + proxmox_node = self.proxmox_api.nodes(node) + config = getattr(proxmox_node, VZ_TYPE)(vmid).config.get() + return config['template'] -def get_vmid(proxmox, hostname): - return [vm['vmid'] for vm in proxmox.cluster.resources.get(type='vm') if 'name' in vm and vm['name'] == hostname] + def create_instance(self, vmid, node, disk, storage, cpus, memory, swap, timeout, clone, **kwargs): + proxmox_node = self.proxmox_api.nodes(node) + # Remove all empty kwarg entries + kwargs = dict((k, v) for k, v in kwargs.items() if v is not None) -def get_instance(proxmox, vmid): - return [vm for vm in proxmox.cluster.resources.get(type='vm') if vm['vmid'] == int(vmid)] - - -def content_check(proxmox, node, ostemplate, template_store): - return [True for cnt in proxmox.nodes(node).storage(template_store).content.get() if cnt['volid'] == ostemplate] - - -def is_template_container(proxmox, node, vmid): - """Check if the specified container is a template.""" - proxmox_node = proxmox.nodes(node) - config = getattr(proxmox_node, VZ_TYPE)(vmid).config.get() - return config['template'] - - -def node_check(proxmox, node): - return [True for nd in proxmox.nodes.get() if nd['node'] == node] - - -def proxmox_version(proxmox): - apireturn = proxmox.version.get() - return LooseVersion(apireturn['version']) - - -def create_instance(module, proxmox, vmid, node, disk, storage, cpus, memory, swap, timeout, clone, **kwargs): - proxmox_node = proxmox.nodes(node) - - # Remove all empty kwarg entries - kwargs = dict((k, v) for k, v in kwargs.items() if v is not None) - - if VZ_TYPE == 'lxc': - kwargs['cpulimit'] = cpus - kwargs['rootfs'] = disk - if 'netif' in kwargs: - kwargs.update(kwargs['netif']) - del kwargs['netif'] - if 'mounts' in kwargs: - kwargs.update(kwargs['mounts']) - del kwargs['mounts'] - if 'pubkey' in kwargs: - if proxmox_version(proxmox) >= LooseVersion('4.2'): - kwargs['ssh-public-keys'] = kwargs['pubkey'] - del kwargs['pubkey'] - else: - kwargs['cpus'] = cpus - kwargs['disk'] = disk - - if clone is not None: - if VZ_TYPE != 'lxc': - module.fail_json(changed=False, msg="Clone operator is only supported for LXC enabled proxmox clusters.") - - clone_is_template = is_template_container(proxmox, node, clone) - - # By default, create a full copy only when the cloned container is not a template. - create_full_copy = not clone_is_template - - # Only accept parameters that are compatible with the clone endpoint. - valid_clone_parameters = ['hostname', 'pool', 'description'] - if module.params['storage'] is not None and clone_is_template: - # Cloning a template, so create a full copy instead of a linked copy - create_full_copy = True - elif module.params['storage'] is None and not clone_is_template: - # Not cloning a template, but also no defined storage. This isn't possible. - module.fail_json(changed=False, msg="Cloned container is not a template, storage needs to be specified.") - - if module.params['clone_type'] == 'linked': - if not clone_is_template: - module.fail_json(changed=False, msg="'linked' clone type is specified, but cloned container is not a template container.") - # Don't need to do more, by default create_full_copy is set to false already - elif module.params['clone_type'] == 'opportunistic': - if not clone_is_template: - # Cloned container is not a template, so we need our 'storage' parameter - valid_clone_parameters.append('storage') - elif module.params['clone_type'] == 'full': - create_full_copy = True - valid_clone_parameters.append('storage') - - clone_parameters = {} - - if create_full_copy: - clone_parameters['full'] = '1' + if VZ_TYPE == 'lxc': + kwargs['cpulimit'] = cpus + kwargs['rootfs'] = disk + if 'netif' in kwargs: + kwargs.update(kwargs['netif']) + del kwargs['netif'] + if 'mounts' in kwargs: + kwargs.update(kwargs['mounts']) + del kwargs['mounts'] + if 'pubkey' in kwargs: + if self.version() >= LooseVersion('4.2'): + kwargs['ssh-public-keys'] = kwargs['pubkey'] + del kwargs['pubkey'] else: - clone_parameters['full'] = '0' - for param in valid_clone_parameters: - if module.params[param] is not None: - clone_parameters[param] = module.params[param] + kwargs['cpus'] = cpus + kwargs['disk'] = disk - taskid = getattr(proxmox_node, VZ_TYPE)(clone).clone.post(newid=vmid, **clone_parameters) - else: - taskid = getattr(proxmox_node, VZ_TYPE).create(vmid=vmid, storage=storage, memory=memory, swap=swap, **kwargs) + if clone is not None: + if VZ_TYPE != 'lxc': + self.module.fail_json(changed=False, msg="Clone operator is only supported for LXC enabled proxmox clusters.") - while timeout: - if (proxmox_node.tasks(taskid).status.get()['status'] == 'stopped' and - proxmox_node.tasks(taskid).status.get()['exitstatus'] == 'OK'): - return True - timeout -= 1 - if timeout == 0: - module.fail_json(msg='Reached timeout while waiting for creating VM. Last line in task before timeout: %s' % - proxmox_node.tasks(taskid).log.get()[:1]) + clone_is_template = self.is_template_container(node, clone) - time.sleep(1) - return False + # By default, create a full copy only when the cloned container is not a template. + create_full_copy = not clone_is_template + # Only accept parameters that are compatible with the clone endpoint. + valid_clone_parameters = ['hostname', 'pool', 'description'] + if self.module.params['storage'] is not None and clone_is_template: + # Cloning a template, so create a full copy instead of a linked copy + create_full_copy = True + elif self.module.params['storage'] is None and not clone_is_template: + # Not cloning a template, but also no defined storage. This isn't possible. + self.module.fail_json(changed=False, msg="Cloned container is not a template, storage needs to be specified.") -def start_instance(module, proxmox, vm, vmid, timeout): - taskid = getattr(proxmox.nodes(vm[0]['node']), VZ_TYPE)(vmid).status.start.post() - while timeout: - if (proxmox.nodes(vm[0]['node']).tasks(taskid).status.get()['status'] == 'stopped' and - proxmox.nodes(vm[0]['node']).tasks(taskid).status.get()['exitstatus'] == 'OK'): - return True - timeout -= 1 - if timeout == 0: - module.fail_json(msg='Reached timeout while waiting for starting VM. Last line in task before timeout: %s' % - proxmox.nodes(vm[0]['node']).tasks(taskid).log.get()[:1]) + if self.module.params['clone_type'] == 'linked': + if not clone_is_template: + self.module.fail_json(changed=False, msg="'linked' clone type is specified, but cloned container is not a template container.") + # Don't need to do more, by default create_full_copy is set to false already + elif self.module.params['clone_type'] == 'opportunistic': + if not clone_is_template: + # Cloned container is not a template, so we need our 'storage' parameter + valid_clone_parameters.append('storage') + elif self.module.params['clone_type'] == 'full': + create_full_copy = True + valid_clone_parameters.append('storage') - time.sleep(1) - return False + clone_parameters = {} + if create_full_copy: + clone_parameters['full'] = '1' + else: + clone_parameters['full'] = '0' + for param in valid_clone_parameters: + if self.module.params[param] is not None: + clone_parameters[param] = self.module.params[param] -def stop_instance(module, proxmox, vm, vmid, timeout, force): - if force: - taskid = getattr(proxmox.nodes(vm[0]['node']), VZ_TYPE)(vmid).status.shutdown.post(forceStop=1) - else: - taskid = getattr(proxmox.nodes(vm[0]['node']), VZ_TYPE)(vmid).status.shutdown.post() - while timeout: - if (proxmox.nodes(vm[0]['node']).tasks(taskid).status.get()['status'] == 'stopped' and - proxmox.nodes(vm[0]['node']).tasks(taskid).status.get()['exitstatus'] == 'OK'): - return True - timeout -= 1 - if timeout == 0: - module.fail_json(msg='Reached timeout while waiting for stopping VM. Last line in task before timeout: %s' % - proxmox.nodes(vm[0]['node']).tasks(taskid).log.get()[:1]) + taskid = getattr(proxmox_node, VZ_TYPE)(clone).clone.post(newid=vmid, **clone_parameters) + else: + taskid = getattr(proxmox_node, VZ_TYPE).create(vmid=vmid, storage=storage, memory=memory, swap=swap, **kwargs) - time.sleep(1) - return False + while timeout: + if (proxmox_node.tasks(taskid).status.get()['status'] == 'stopped' and + proxmox_node.tasks(taskid).status.get()['exitstatus'] == 'OK'): + return True + timeout -= 1 + if timeout == 0: + self.module.fail_json(msg='Reached timeout while waiting for creating VM. Last line in task before timeout: %s' % + proxmox_node.tasks(taskid).log.get()[:1]) + time.sleep(1) + return False -def umount_instance(module, proxmox, vm, vmid, timeout): - taskid = getattr(proxmox.nodes(vm[0]['node']), VZ_TYPE)(vmid).status.umount.post() - while timeout: - if (proxmox.nodes(vm[0]['node']).tasks(taskid).status.get()['status'] == 'stopped' and - proxmox.nodes(vm[0]['node']).tasks(taskid).status.get()['exitstatus'] == 'OK'): - return True - timeout -= 1 - if timeout == 0: - module.fail_json(msg='Reached timeout while waiting for unmounting VM. Last line in task before timeout: %s' % - proxmox.nodes(vm[0]['node']).tasks(taskid).log.get()[:1]) + def start_instance(self, vm, vmid, timeout): + taskid = getattr(self.proxmox_api.nodes(vm['node']), VZ_TYPE)(vmid).status.start.post() + while timeout: + if (self.proxmox_api.nodes(vm['node']).tasks(taskid).status.get()['status'] == 'stopped' and + self.proxmox_api.nodes(vm['node']).tasks(taskid).status.get()['exitstatus'] == 'OK'): + return True + timeout -= 1 + if timeout == 0: + self.module.fail_json(msg='Reached timeout while waiting for starting VM. Last line in task before timeout: %s' % + self.proxmox_api.nodes(vm['node']).tasks(taskid).log.get()[:1]) - time.sleep(1) - return False + time.sleep(1) + return False + + def stop_instance(self, vm, vmid, timeout, force): + if force: + taskid = getattr(self.proxmox_api.nodes(vm['node']), VZ_TYPE)(vmid).status.shutdown.post(forceStop=1) + else: + taskid = getattr(self.proxmox_api.nodes(vm['node']), VZ_TYPE)(vmid).status.shutdown.post() + while timeout: + if (self.proxmox_api.nodes(vm['node']).tasks(taskid).status.get()['status'] == 'stopped' and + self.proxmox_api.nodes(vm['node']).tasks(taskid).status.get()['exitstatus'] == 'OK'): + return True + timeout -= 1 + if timeout == 0: + self.module.fail_json(msg='Reached timeout while waiting for stopping VM. Last line in task before timeout: %s' % + self.proxmox_api.nodes(vm['node']).tasks(taskid).log.get()[:1]) + + time.sleep(1) + return False + + def umount_instance(self, vm, vmid, timeout): + taskid = getattr(self.proxmox_api.nodes(vm['node']), VZ_TYPE)(vmid).status.umount.post() + while timeout: + if (self.proxmox_api.nodes(vm['node']).tasks(taskid).status.get()['status'] == 'stopped' and + self.proxmox_api.nodes(vm['node']).tasks(taskid).status.get()['exitstatus'] == 'OK'): + return True + timeout -= 1 + if timeout == 0: + self.module.fail_json(msg='Reached timeout while waiting for unmounting VM. Last line in task before timeout: %s' % + self.proxmox_api.nodes(vm['node']).tasks(taskid).log.get()[:1]) + + time.sleep(1) + return False def main(): + module_args = proxmox_auth_argument_spec() + proxmox_args = dict( + vmid=dict(type='int', required=False), + node=dict(), + pool=dict(), + password=dict(no_log=True), + hostname=dict(), + ostemplate=dict(), + disk=dict(type='str'), + cores=dict(type='int'), + cpus=dict(type='int'), + memory=dict(type='int'), + swap=dict(type='int'), + netif=dict(type='dict'), + mounts=dict(type='dict'), + ip_address=dict(), + onboot=dict(type='bool'), + features=dict(type='list', elements='str'), + storage=dict(default='local'), + cpuunits=dict(type='int'), + nameserver=dict(), + searchdomain=dict(), + timeout=dict(type='int', default=30), + force=dict(type='bool', default=False), + purge=dict(type='bool', default=False), + state=dict(default='present', choices=['present', 'absent', 'stopped', 'started', 'restarted']), + pubkey=dict(type='str', default=None), + unprivileged=dict(type='bool', default=False), + description=dict(type='str'), + hookscript=dict(type='str'), + proxmox_default_behavior=dict(type='str', default='no_defaults', choices=['compatibility', 'no_defaults']), + clone=dict(type='int'), + clone_type=dict(default='opportunistic', choices=['full', 'linked', 'opportunistic']), + ) + module_args.update(proxmox_args) + module = AnsibleModule( - argument_spec=dict( - api_host=dict(required=True), - api_password=dict(no_log=True, fallback=(env_fallback, ['PROXMOX_PASSWORD'])), - api_token_id=dict(no_log=True), - api_token_secret=dict(no_log=True), - api_user=dict(required=True), - vmid=dict(type='int', required=False), - validate_certs=dict(type='bool', default=False), - node=dict(), - pool=dict(), - password=dict(no_log=True), - hostname=dict(), - ostemplate=dict(), - disk=dict(type='str'), - cores=dict(type='int'), - cpus=dict(type='int'), - memory=dict(type='int'), - swap=dict(type='int'), - netif=dict(type='dict'), - mounts=dict(type='dict'), - ip_address=dict(), - onboot=dict(type='bool'), - features=dict(type='list', elements='str'), - storage=dict(default='local'), - cpuunits=dict(type='int'), - nameserver=dict(), - searchdomain=dict(), - timeout=dict(type='int', default=30), - force=dict(type='bool', default=False), - purge=dict(type='bool', default=False), - state=dict(default='present', choices=['present', 'absent', 'stopped', 'started', 'restarted']), - pubkey=dict(type='str', default=None), - unprivileged=dict(type='bool', default=False), - description=dict(type='str'), - hookscript=dict(type='str'), - proxmox_default_behavior=dict(type='str', default='no_defaults', choices=['compatibility', 'no_defaults']), - clone=dict(type='int'), - clone_type=dict(default='opportunistic', choices=['full', 'linked', 'opportunistic']), - ), + argument_spec=module_args, required_if=[ ('state', 'present', ['node', 'hostname']), ('state', 'present', ('clone', 'ostemplate'), True), # Require one of clone and ostemplate. Together with mutually_exclusive this ensures that we @@ -627,17 +588,13 @@ def main(): mutually_exclusive=[('clone', 'ostemplate')], # Creating a new container is done either by cloning an existing one, or based on a template. ) - if not HAS_PROXMOXER: - module.fail_json(msg='proxmoxer required for this module') + proxmox = ProxmoxLxcAnsible(module) + + global VZ_TYPE + VZ_TYPE = 'openvz' if proxmox.version() < LooseVersion('4.0') else 'lxc' state = module.params['state'] - api_host = module.params['api_host'] - api_password = module.params['api_password'] - api_token_id = module.params['api_token_id'] - api_token_secret = module.params['api_token_secret'] - api_user = module.params['api_user'] vmid = module.params['vmid'] - validate_certs = module.params['validate_certs'] node = module.params['node'] disk = module.params['disk'] cpus = module.params['cpus'] @@ -664,68 +621,54 @@ def main(): if module.params[param] is None: module.params[param] = value - auth_args = {'user': api_user} - if not api_token_id: - auth_args['password'] = api_password - else: - auth_args['token_name'] = api_token_id - auth_args['token_value'] = api_token_secret - - try: - proxmox = ProxmoxAPI(api_host, verify_ssl=validate_certs, **auth_args) - global VZ_TYPE - VZ_TYPE = 'openvz' if proxmox_version(proxmox) < LooseVersion('4.0') else 'lxc' - except Exception as e: - module.fail_json(msg='authorization on proxmox cluster failed with exception: %s' % e) - # If vmid not set get the Next VM id from ProxmoxAPI # If hostname is set get the VM id from ProxmoxAPI if not vmid and state == 'present': - vmid = get_nextvmid(module, proxmox) + vmid = proxmox.get_nextvmid() elif not vmid and hostname: - hosts = get_vmid(proxmox, hostname) - if len(hosts) == 0: - module.fail_json(msg="Vmid could not be fetched => Hostname doesn't exist (action: %s)" % state) - vmid = hosts[0] + vmid = proxmox.get_vmid(hostname, choose_first_if_multiple=True) elif not vmid: module.exit_json(changed=False, msg="Vmid could not be fetched for the following action: %s" % state) # Create a new container if state == 'present' and clone is None: try: - if get_instance(proxmox, vmid) and not module.params['force']: + if proxmox.get_vm(vmid, ignore_missing=True) and not module.params['force']: module.exit_json(changed=False, msg="VM with vmid = %s is already exists" % vmid) # If no vmid was passed, there cannot be another VM named 'hostname' - if not module.params['vmid'] and get_vmid(proxmox, hostname) and not module.params['force']: - module.exit_json(changed=False, msg="VM with hostname %s already exists and has ID number %s" % (hostname, get_vmid(proxmox, hostname)[0])) - elif not node_check(proxmox, node): + if (not module.params['vmid'] and + proxmox.get_vmid(hostname, ignore_missing=True, choose_first_if_multiple=True) and + not module.params['force']): + vmid = proxmox.get_vmid(hostname, choose_first_if_multiple=True) + module.exit_json(changed=False, msg="VM with hostname %s already exists and has ID number %s" % (hostname, vmid)) + elif not proxmox.get_node(node): module.fail_json(msg="node '%s' not exists in cluster" % node) - elif not content_check(proxmox, node, module.params['ostemplate'], template_store): + elif not proxmox.content_check(node, module.params['ostemplate'], template_store): module.fail_json(msg="ostemplate '%s' not exists on node %s and storage %s" % (module.params['ostemplate'], node, template_store)) except Exception as e: module.fail_json(msg="Pre-creation checks of {VZ_TYPE} VM {vmid} failed with exception: {e}".format(VZ_TYPE=VZ_TYPE, vmid=vmid, e=e)) try: - create_instance(module, proxmox, vmid, node, disk, storage, cpus, memory, swap, timeout, clone, - cores=module.params['cores'], - pool=module.params['pool'], - password=module.params['password'], - hostname=module.params['hostname'], - ostemplate=module.params['ostemplate'], - netif=module.params['netif'], - mounts=module.params['mounts'], - ip_address=module.params['ip_address'], - onboot=ansible_to_proxmox_bool(module.params['onboot']), - cpuunits=module.params['cpuunits'], - nameserver=module.params['nameserver'], - searchdomain=module.params['searchdomain'], - force=ansible_to_proxmox_bool(module.params['force']), - pubkey=module.params['pubkey'], - features=",".join(module.params['features']) if module.params['features'] is not None else None, - unprivileged=ansible_to_proxmox_bool(module.params['unprivileged']), - description=module.params['description'], - hookscript=module.params['hookscript']) + proxmox.create_instance(vmid, node, disk, storage, cpus, memory, swap, timeout, clone, + cores=module.params['cores'], + pool=module.params['pool'], + password=module.params['password'], + hostname=module.params['hostname'], + ostemplate=module.params['ostemplate'], + netif=module.params['netif'], + mounts=module.params['mounts'], + ip_address=module.params['ip_address'], + onboot=ansible_to_proxmox_bool(module.params['onboot']), + cpuunits=module.params['cpuunits'], + nameserver=module.params['nameserver'], + searchdomain=module.params['searchdomain'], + force=ansible_to_proxmox_bool(module.params['force']), + pubkey=module.params['pubkey'], + features=",".join(module.params['features']) if module.params['features'] is not None else None, + unprivileged=ansible_to_proxmox_bool(module.params['unprivileged']), + description=module.params['description'], + hookscript=module.params['hookscript']) module.exit_json(changed=True, msg="Deployed VM %s from template %s" % (vmid, module.params['ostemplate'])) except Exception as e: @@ -734,18 +677,21 @@ def main(): # Clone a container elif state == 'present' and clone is not None: try: - if get_instance(proxmox, vmid) and not module.params['force']: + if proxmox.get_vm(vmid, ignore_missing=True) and not module.params['force']: module.exit_json(changed=False, msg="VM with vmid = %s is already exists" % vmid) # If no vmid was passed, there cannot be another VM named 'hostname' - if not module.params['vmid'] and get_vmid(proxmox, hostname) and not module.params['force']: - module.exit_json(changed=False, msg="VM with hostname %s already exists and has ID number %s" % (hostname, get_vmid(proxmox, hostname)[0])) - if not get_instance(proxmox, clone): + if (not module.params['vmid'] and + proxmox.get_vmid(hostname, ignore_missing=True, choose_first_if_multiple=True) and + not module.params['force']): + vmid = proxmox.get_vmid(hostname, choose_first_if_multiple=True) + module.exit_json(changed=False, msg="VM with hostname %s already exists and has ID number %s" % (hostname, vmid)) + if not proxmox.get_vm(clone, ignore_missing=True): module.exit_json(changed=False, msg="Container to be cloned does not exist") except Exception as e: module.fail_json(msg="Pre-clone checks of {VZ_TYPE} VM {vmid} failed with exception: {e}".format(VZ_TYPE=VZ_TYPE, vmid=vmid, e=e)) try: - create_instance(module, proxmox, vmid, node, disk, storage, cpus, memory, swap, timeout, clone) + proxmox.create_instance(vmid, node, disk, storage, cpus, memory, swap, timeout, clone) module.exit_json(changed=True, msg="Cloned VM %s from %s" % (vmid, clone)) except Exception as e: @@ -753,64 +699,60 @@ def main(): elif state == 'started': try: - vm = get_instance(proxmox, vmid) - if not vm: - module.fail_json(msg='VM with vmid = %s not exists in cluster' % vmid) - if getattr(proxmox.nodes(vm[0]['node']), VZ_TYPE)(vmid).status.current.get()['status'] == 'running': + vm = proxmox.get_vm(vmid) + if getattr(proxmox.proxmox_api.nodes(vm['node']), VZ_TYPE)(vmid).status.current.get()['status'] == 'running': module.exit_json(changed=False, msg="VM %s is already running" % vmid) - if start_instance(module, proxmox, vm, vmid, timeout): + if proxmox.start_instance(vm, vmid, timeout): module.exit_json(changed=True, msg="VM %s started" % vmid) except Exception as e: module.fail_json(msg="starting of VM %s failed with exception: %s" % (vmid, e)) elif state == 'stopped': try: - vm = get_instance(proxmox, vmid) - if not vm: - module.fail_json(msg='VM with vmid = %s not exists in cluster' % vmid) + vm = proxmox.get_vm(vmid) - if getattr(proxmox.nodes(vm[0]['node']), VZ_TYPE)(vmid).status.current.get()['status'] == 'mounted': + if getattr(proxmox.proxmox_api.nodes(vm['node']), VZ_TYPE)(vmid).status.current.get()['status'] == 'mounted': if module.params['force']: - if umount_instance(module, proxmox, vm, vmid, timeout): + if proxmox.umount_instance(vm, vmid, timeout): module.exit_json(changed=True, msg="VM %s is shutting down" % vmid) else: module.exit_json(changed=False, msg=("VM %s is already shutdown, but mounted. " "You can use force option to umount it.") % vmid) - if getattr(proxmox.nodes(vm[0]['node']), VZ_TYPE)(vmid).status.current.get()['status'] == 'stopped': + if getattr(proxmox.proxmox_api.nodes(vm['node']), VZ_TYPE)(vmid).status.current.get()['status'] == 'stopped': module.exit_json(changed=False, msg="VM %s is already shutdown" % vmid) - if stop_instance(module, proxmox, vm, vmid, timeout, force=module.params['force']): + if proxmox.stop_instance(vm, vmid, timeout, force=module.params['force']): module.exit_json(changed=True, msg="VM %s is shutting down" % vmid) except Exception as e: module.fail_json(msg="stopping of VM %s failed with exception: %s" % (vmid, e)) elif state == 'restarted': try: - vm = get_instance(proxmox, vmid) - if not vm: - module.fail_json(msg='VM with vmid = %s not exists in cluster' % vmid) - if (getattr(proxmox.nodes(vm[0]['node']), VZ_TYPE)(vmid).status.current.get()['status'] == 'stopped' or - getattr(proxmox.nodes(vm[0]['node']), VZ_TYPE)(vmid).status.current.get()['status'] == 'mounted'): + vm = proxmox.get_vm(vmid) + + vm_status = getattr(proxmox.proxmox_api.nodes(vm['node']), VZ_TYPE)(vmid).status.current.get()['status'] + if vm_status in ['stopped', 'mounted']: module.exit_json(changed=False, msg="VM %s is not running" % vmid) - if (stop_instance(module, proxmox, vm, vmid, timeout, force=module.params['force']) and - start_instance(module, proxmox, vm, vmid, timeout)): + if (proxmox.stop_instance(vm, vmid, timeout, force=module.params['force']) and + proxmox.start_instance(vm, vmid, timeout)): module.exit_json(changed=True, msg="VM %s is restarted" % vmid) except Exception as e: module.fail_json(msg="restarting of VM %s failed with exception: %s" % (vmid, e)) elif state == 'absent': try: - vm = get_instance(proxmox, vmid) + vm = proxmox.get_vm(vmid, ignore_missing=True) if not vm: module.exit_json(changed=False, msg="VM %s does not exist" % vmid) - if getattr(proxmox.nodes(vm[0]['node']), VZ_TYPE)(vmid).status.current.get()['status'] == 'running': + vm_status = getattr(proxmox.proxmox_api.nodes(vm['node']), VZ_TYPE)(vmid).status.current.get()['status'] + if vm_status == 'running': module.exit_json(changed=False, msg="VM %s is running. Stop it before deletion." % vmid) - if getattr(proxmox.nodes(vm[0]['node']), VZ_TYPE)(vmid).status.current.get()['status'] == 'mounted': + if vm_status == 'mounted': module.exit_json(changed=False, msg="VM %s is mounted. Stop it with force option before deletion." % vmid) delete_params = {} @@ -818,16 +760,16 @@ def main(): if module.params['purge']: delete_params['purge'] = 1 - taskid = getattr(proxmox.nodes(vm[0]['node']), VZ_TYPE).delete(vmid, **delete_params) + taskid = getattr(proxmox.proxmox_api.nodes(vm['node']), VZ_TYPE).delete(vmid, **delete_params) while timeout: - if (proxmox.nodes(vm[0]['node']).tasks(taskid).status.get()['status'] == 'stopped' and - proxmox.nodes(vm[0]['node']).tasks(taskid).status.get()['exitstatus'] == 'OK'): + task_status = proxmox.proxmox_api.nodes(vm['node']).tasks(taskid).status.get() + if (task_status['status'] == 'stopped' and task_status['exitstatus'] == 'OK'): module.exit_json(changed=True, msg="VM %s removed" % vmid) timeout -= 1 if timeout == 0: module.fail_json(msg='Reached timeout while waiting for removing VM. Last line in task before timeout: %s' - % proxmox.nodes(vm[0]['node']).tasks(taskid).log.get()[:1]) + % proxmox.proxmox_api.nodes(vm['node']).tasks(taskid).log.get()[:1]) time.sleep(1) except Exception as e: diff --git a/plugins/modules/cloud/misc/proxmox_kvm.py b/plugins/modules/cloud/misc/proxmox_kvm.py index d532433223..96303ce517 100644 --- a/plugins/modules/cloud/misc/proxmox_kvm.py +++ b/plugins/modules/cloud/misc/proxmox_kvm.py @@ -1,6 +1,6 @@ #!/usr/bin/python # -*- coding: utf-8 -*- - +# # Copyright: (c) 2016, Abdoul Bah (@helldorado) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) @@ -728,72 +728,12 @@ import traceback from ansible.module_utils.six.moves.urllib.parse import quote from ansible_collections.community.general.plugins.module_utils.version import LooseVersion +from ansible_collections.community.general.plugins.module_utils.proxmox import (proxmox_auth_argument_spec, ProxmoxAnsible) -try: - from proxmoxer import ProxmoxAPI - HAS_PROXMOXER = True -except ImportError: - HAS_PROXMOXER = False - -from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible.module_utils.basic import AnsibleModule, missing_required_lib from ansible.module_utils.common.text.converters import to_native -def get_nextvmid(module, proxmox): - try: - vmid = proxmox.cluster.nextid.get() - return vmid - except Exception as e: - module.fail_json(msg="Unable to get next vmid. Failed with exception: %s" % to_native(e), - exception=traceback.format_exc()) - - -def get_vmid(proxmox, name): - return [vm['vmid'] for vm in proxmox.cluster.resources.get(type='vm') if vm.get('name') == name] - - -def get_vm(proxmox, vmid): - return [vm for vm in proxmox.cluster.resources.get(type='vm') if vm['vmid'] == int(vmid)] - - -def node_check(proxmox, node): - return [True for nd in proxmox.nodes.get() if nd['node'] == node] - - -def get_vminfo(module, proxmox, node, vmid, **kwargs): - global results - results = {} - mac = {} - devices = {} - try: - vm = proxmox.nodes(node).qemu(vmid).config.get() - except Exception as e: - module.fail_json(msg='Getting information for VM with vmid = %s failed with exception: %s' % (vmid, e)) - - # Sanitize kwargs. Remove not defined args and ensure True and False converted to int. - kwargs = dict((k, v) for k, v in kwargs.items() if v is not None) - - # Convert all dict in kwargs to elements. - # For hostpci[n], ide[n], net[n], numa[n], parallel[n], sata[n], scsi[n], serial[n], virtio[n] - for k in list(kwargs.keys()): - if isinstance(kwargs[k], dict): - kwargs.update(kwargs[k]) - del kwargs[k] - - # Split information by type - re_net = re.compile(r'net[0-9]') - re_dev = re.compile(r'(virtio|ide|scsi|sata)[0-9]') - for k in kwargs.keys(): - if re_net.match(k): - mac[k] = parse_mac(vm[k]) - elif re_dev.match(k): - devices[k] = parse_dev(vm[k]) - - results['mac'] = mac - results['devices'] = devices - results['vmid'] = int(vmid) - - def parse_mac(netstr): return re.search('=(.*?),', netstr).group(1) @@ -802,276 +742,294 @@ def parse_dev(devstr): return re.search('(.*?)(,|$)', devstr).group(1) -def settings(proxmox, vmid, node, **kwargs): - proxmox_node = proxmox.nodes(node) +class ProxmoxKvmAnsible(ProxmoxAnsible): + def get_vminfo(self, node, vmid, **kwargs): + global results + results = {} + mac = {} + devices = {} + try: + vm = self.proxmox_api.nodes(node).qemu(vmid).config.get() + except Exception as e: + self.module.fail_json(msg='Getting information for VM with vmid = %s failed with exception: %s' % (vmid, e)) - # Sanitize kwargs. Remove not defined args and ensure True and False converted to int. - kwargs = dict((k, v) for k, v in kwargs.items() if v is not None) + # Sanitize kwargs. Remove not defined args and ensure True and False converted to int. + kwargs = dict((k, v) for k, v in kwargs.items() if v is not None) - return proxmox_node.qemu(vmid).config.set(**kwargs) is None + # Convert all dict in kwargs to elements. + # For hostpci[n], ide[n], net[n], numa[n], parallel[n], sata[n], scsi[n], serial[n], virtio[n] + for k in list(kwargs.keys()): + if isinstance(kwargs[k], dict): + kwargs.update(kwargs[k]) + del kwargs[k] + # Split information by type + re_net = re.compile(r'net[0-9]') + re_dev = re.compile(r'(virtio|ide|scsi|sata)[0-9]') + for k in kwargs.keys(): + if re_net.match(k): + mac[k] = parse_mac(vm[k]) + elif re_dev.match(k): + devices[k] = parse_dev(vm[k]) -def wait_for_task(module, proxmox, node, taskid): - timeout = module.params['timeout'] + results['mac'] = mac + results['devices'] = devices + results['vmid'] = int(vmid) - while timeout: - task = proxmox.nodes(node).tasks(taskid).status.get() - if task['status'] == 'stopped' and task['exitstatus'] == 'OK': - # Wait an extra second as the API can be a ahead of the hypervisor + def settings(self, vmid, node, **kwargs): + proxmox_node = self.proxmox_api.nodes(node) + + # Sanitize kwargs. Remove not defined args and ensure True and False converted to int. + kwargs = dict((k, v) for k, v in kwargs.items() if v is not None) + + return proxmox_node.qemu(vmid).config.set(**kwargs) is None + + def wait_for_task(self, node, taskid): + timeout = self.module.params['timeout'] + + while timeout: + task = self.proxmox_api.nodes(node).tasks(taskid).status.get() + if task['status'] == 'stopped' and task['exitstatus'] == 'OK': + # Wait an extra second as the API can be a ahead of the hypervisor + time.sleep(1) + return True + timeout = timeout - 1 + if timeout == 0: + break time.sleep(1) - return True - timeout = timeout - 1 - if timeout == 0: - break - time.sleep(1) - return False + return False + def create_vm(self, vmid, newid, node, name, memory, cpu, cores, sockets, update, **kwargs): + # Available only in PVE 4 + only_v4 = ['force', 'protection', 'skiplock'] + only_v6 = ['ciuser', 'cipassword', 'sshkeys', 'ipconfig', 'tags'] -def create_vm(module, proxmox, vmid, newid, node, name, memory, cpu, cores, sockets, update, **kwargs): - # Available only in PVE 4 - only_v4 = ['force', 'protection', 'skiplock'] - only_v6 = ['ciuser', 'cipassword', 'sshkeys', 'ipconfig', 'tags'] + # valide clone parameters + valid_clone_params = ['format', 'full', 'pool', 'snapname', 'storage', 'target'] + clone_params = {} + # Default args for vm. Note: -args option is for experts only. It allows you to pass arbitrary arguments to kvm. + vm_args = "-serial unix:/var/run/qemu-server/{0}.serial,server,nowait".format(vmid) - # valide clone parameters - valid_clone_params = ['format', 'full', 'pool', 'snapname', 'storage', 'target'] - clone_params = {} - # Default args for vm. Note: -args option is for experts only. It allows you to pass arbitrary arguments to kvm. - vm_args = "-serial unix:/var/run/qemu-server/{0}.serial,server,nowait".format(vmid) + proxmox_node = self.proxmox_api.nodes(node) - proxmox_node = proxmox.nodes(node) + # Sanitize kwargs. Remove not defined args and ensure True and False converted to int. + kwargs = dict((k, v) for k, v in kwargs.items() if v is not None) + kwargs.update(dict([k, int(v)] for k, v in kwargs.items() if isinstance(v, bool))) - # Sanitize kwargs. Remove not defined args and ensure True and False converted to int. - kwargs = dict((k, v) for k, v in kwargs.items() if v is not None) - kwargs.update(dict([k, int(v)] for k, v in kwargs.items() if isinstance(v, bool))) + version = self.version() + pve_major_version = 3 if version < LooseVersion('4.0') else version.version[0] - # The features work only on PVE 4+ - if PVE_MAJOR_VERSION < 4: - for p in only_v4: - if p in kwargs: - del kwargs[p] + # The features work only on PVE 4+ + if pve_major_version < 4: + for p in only_v4: + if p in kwargs: + del kwargs[p] - # The features work only on PVE 6 - if PVE_MAJOR_VERSION < 6: - for p in only_v6: - if p in kwargs: - del kwargs[p] + # The features work only on PVE 6 + if pve_major_version < 6: + for p in only_v6: + if p in kwargs: + del kwargs[p] - # 'sshkeys' param expects an urlencoded string - if 'sshkeys' in kwargs: - urlencoded_ssh_keys = quote(kwargs['sshkeys'], safe='') - kwargs['sshkeys'] = str(urlencoded_ssh_keys) + # 'sshkeys' param expects an urlencoded string + if 'sshkeys' in kwargs: + urlencoded_ssh_keys = quote(kwargs['sshkeys'], safe='') + kwargs['sshkeys'] = str(urlencoded_ssh_keys) - # If update, don't update disk (virtio, ide, sata, scsi) and network interface - # pool parameter not supported by qemu//config endpoint on "update" (PVE 6.2) - only with "create" - if update: - if 'virtio' in kwargs: - del kwargs['virtio'] - if 'sata' in kwargs: - del kwargs['sata'] - if 'scsi' in kwargs: - del kwargs['scsi'] - if 'ide' in kwargs: - del kwargs['ide'] - if 'net' in kwargs: - del kwargs['net'] - if 'force' in kwargs: - del kwargs['force'] - if 'pool' in kwargs: - del kwargs['pool'] + # If update, don't update disk (virtio, ide, sata, scsi) and network interface + # pool parameter not supported by qemu//config endpoint on "update" (PVE 6.2) - only with "create" + if update: + if 'virtio' in kwargs: + del kwargs['virtio'] + if 'sata' in kwargs: + del kwargs['sata'] + if 'scsi' in kwargs: + del kwargs['scsi'] + if 'ide' in kwargs: + del kwargs['ide'] + if 'net' in kwargs: + del kwargs['net'] + if 'force' in kwargs: + del kwargs['force'] + if 'pool' in kwargs: + del kwargs['pool'] - # Convert all dict in kwargs to elements. - # For hostpci[n], ide[n], net[n], numa[n], parallel[n], sata[n], scsi[n], serial[n], virtio[n], ipconfig[n] - for k in list(kwargs.keys()): - if isinstance(kwargs[k], dict): - kwargs.update(kwargs[k]) - del kwargs[k] + # Convert all dict in kwargs to elements. + # For hostpci[n], ide[n], net[n], numa[n], parallel[n], sata[n], scsi[n], serial[n], virtio[n], ipconfig[n] + for k in list(kwargs.keys()): + if isinstance(kwargs[k], dict): + kwargs.update(kwargs[k]) + del kwargs[k] - # Rename numa_enabled to numa. According the API documentation - if 'numa_enabled' in kwargs: - kwargs['numa'] = kwargs['numa_enabled'] - del kwargs['numa_enabled'] + # Rename numa_enabled to numa. According the API documentation + if 'numa_enabled' in kwargs: + kwargs['numa'] = kwargs['numa_enabled'] + del kwargs['numa_enabled'] - # PVE api expects strings for the following params - if 'nameservers' in module.params: - nameservers = module.params.pop('nameservers') - if nameservers: - kwargs['nameserver'] = ' '.join(nameservers) - if 'searchdomains' in module.params: - searchdomains = module.params.pop('searchdomains') - if searchdomains: - kwargs['searchdomain'] = ' '.join(searchdomains) + # PVE api expects strings for the following params + if 'nameservers' in self.module.params: + nameservers = self.module.params.pop('nameservers') + if nameservers: + kwargs['nameserver'] = ' '.join(nameservers) + if 'searchdomains' in self.module.params: + searchdomains = self.module.params.pop('searchdomains') + if searchdomains: + kwargs['searchdomain'] = ' '.join(searchdomains) - # VM tags are expected to be valid and presented as a comma/semi-colon delimited string - if 'tags' in kwargs: - re_tag = re.compile(r'^[a-z0-9_][a-z0-9_\-\+\.]*$') - for tag in kwargs['tags']: - if not re_tag.match(tag): - module.fail_json(msg='%s is not a valid tag' % tag) - kwargs['tags'] = ",".join(kwargs['tags']) + # VM tags are expected to be valid and presented as a comma/semi-colon delimited string + if 'tags' in kwargs: + re_tag = re.compile(r'^[a-z0-9_][a-z0-9_\-\+\.]*$') + for tag in kwargs['tags']: + if not re_tag.match(tag): + self.module.fail_json(msg='%s is not a valid tag' % tag) + kwargs['tags'] = ",".join(kwargs['tags']) - # -args and skiplock require root@pam user - but can not use api tokens - if module.params['api_user'] == "root@pam" and module.params['args'] is None: - if not update and module.params['proxmox_default_behavior'] == 'compatibility': - kwargs['args'] = vm_args - elif module.params['api_user'] == "root@pam" and module.params['args'] is not None: - kwargs['args'] = module.params['args'] - elif module.params['api_user'] != "root@pam" and module.params['args'] is not None: - module.fail_json(msg='args parameter require root@pam user. ') + # -args and skiplock require root@pam user - but can not use api tokens + if self.module.params['api_user'] == "root@pam" and self.module.params['args'] is None: + if not update and self.module.params['proxmox_default_behavior'] == 'compatibility': + kwargs['args'] = vm_args + elif self.module.params['api_user'] == "root@pam" and self.module.params['args'] is not None: + kwargs['args'] = self.module.params['args'] + elif self.module.params['api_user'] != "root@pam" and self.module.params['args'] is not None: + self.module.fail_json(msg='args parameter require root@pam user. ') - if module.params['api_user'] != "root@pam" and module.params['skiplock'] is not None: - module.fail_json(msg='skiplock parameter require root@pam user. ') + if self.module.params['api_user'] != "root@pam" and self.module.params['skiplock'] is not None: + self.module.fail_json(msg='skiplock parameter require root@pam user. ') - if update: - if proxmox_node.qemu(vmid).config.set(name=name, memory=memory, cpu=cpu, cores=cores, sockets=sockets, **kwargs) is None: - return True + if update: + if proxmox_node.qemu(vmid).config.set(name=name, memory=memory, cpu=cpu, cores=cores, sockets=sockets, **kwargs) is None: + return True + else: + return False + elif self.module.params['clone'] is not None: + for param in valid_clone_params: + if self.module.params[param] is not None: + clone_params[param] = self.module.params[param] + clone_params.update(dict([k, int(v)] for k, v in clone_params.items() if isinstance(v, bool))) + taskid = proxmox_node.qemu(vmid).clone.post(newid=newid, name=name, **clone_params) else: + taskid = proxmox_node.qemu.create(vmid=vmid, name=name, memory=memory, cpu=cpu, cores=cores, sockets=sockets, **kwargs) + + if not self.wait_for_task(node, taskid): + self.module.fail_json(msg='Reached timeout while waiting for creating VM. Last line in task before timeout: %s' % + proxmox_node.tasks(taskid).log.get()[:1]) return False - elif module.params['clone'] is not None: - for param in valid_clone_params: - if module.params[param] is not None: - clone_params[param] = module.params[param] - clone_params.update(dict([k, int(v)] for k, v in clone_params.items() if isinstance(v, bool))) - taskid = proxmox_node.qemu(vmid).clone.post(newid=newid, name=name, **clone_params) - else: - taskid = proxmox_node.qemu.create(vmid=vmid, name=name, memory=memory, cpu=cpu, cores=cores, sockets=sockets, **kwargs) + return True - if not wait_for_task(module, proxmox, node, taskid): - module.fail_json(msg='Reached timeout while waiting for creating VM. Last line in task before timeout: %s' % - proxmox_node.tasks(taskid).log.get()[:1]) - return False - return True + def start_vm(self, vm): + vmid = vm['vmid'] + proxmox_node = self.proxmox_api.nodes(vm['node']) + taskid = proxmox_node.qemu(vmid).status.start.post() + if not self.wait_for_task(vm['node'], taskid): + self.module.fail_json(msg='Reached timeout while waiting for starting VM. Last line in task before timeout: %s' % + proxmox_node.tasks(taskid).log.get()[:1]) + return False + return True - -def start_vm(module, proxmox, vm): - vmid = vm[0]['vmid'] - proxmox_node = proxmox.nodes(vm[0]['node']) - taskid = proxmox_node.qemu(vmid).status.start.post() - if not wait_for_task(module, proxmox, vm[0]['node'], taskid): - module.fail_json(msg='Reached timeout while waiting for starting VM. Last line in task before timeout: %s' % - proxmox_node.tasks(taskid).log.get()[:1]) - return False - return True - - -def stop_vm(module, proxmox, vm, force): - vmid = vm[0]['vmid'] - proxmox_node = proxmox.nodes(vm[0]['node']) - taskid = proxmox_node.qemu(vmid).status.shutdown.post(forceStop=(1 if force else 0)) - if not wait_for_task(module, proxmox, vm[0]['node'], taskid): - module.fail_json(msg='Reached timeout while waiting for stopping VM. Last line in task before timeout: %s' % - proxmox_node.tasks(taskid).log.get()[:1]) - return False - return True - - -def proxmox_version(proxmox): - apireturn = proxmox.version.get() - return LooseVersion(apireturn['version']) + def stop_vm(self, vm, force): + vmid = vm['vmid'] + proxmox_node = self.proxmox_api.nodes(vm['node']) + taskid = proxmox_node.qemu(vmid).status.shutdown.post(forceStop=(1 if force else 0)) + if not self.wait_for_task(vm['node'], taskid): + self.module.fail_json(msg='Reached timeout while waiting for stopping VM. Last line in task before timeout: %s' % + proxmox_node.tasks(taskid).log.get()[:1]) + return False + return True def main(): + module_args = proxmox_auth_argument_spec() + kvm_args = dict( + acpi=dict(type='bool'), + agent=dict(type='bool'), + args=dict(type='str'), + autostart=dict(type='bool'), + balloon=dict(type='int'), + bios=dict(choices=['seabios', 'ovmf']), + boot=dict(type='str'), + bootdisk=dict(type='str'), + cicustom=dict(type='str'), + cipassword=dict(type='str', no_log=True), + citype=dict(type='str', choices=['nocloud', 'configdrive2']), + ciuser=dict(type='str'), + clone=dict(type='str'), + cores=dict(type='int'), + cpu=dict(type='str'), + cpulimit=dict(type='int'), + cpuunits=dict(type='int'), + delete=dict(type='str'), + description=dict(type='str'), + digest=dict(type='str'), + force=dict(type='bool'), + format=dict(type='str', choices=['cloop', 'cow', 'qcow', 'qcow2', 'qed', 'raw', 'vmdk', 'unspecified']), + freeze=dict(type='bool'), + full=dict(type='bool', default=True), + hostpci=dict(type='dict'), + hotplug=dict(type='str'), + hugepages=dict(choices=['any', '2', '1024']), + ide=dict(type='dict'), + ipconfig=dict(type='dict'), + keyboard=dict(type='str'), + kvm=dict(type='bool'), + localtime=dict(type='bool'), + lock=dict(choices=['migrate', 'backup', 'snapshot', 'rollback']), + machine=dict(type='str'), + memory=dict(type='int'), + migrate_downtime=dict(type='int'), + migrate_speed=dict(type='int'), + name=dict(type='str'), + nameservers=dict(type='list', elements='str'), + net=dict(type='dict'), + newid=dict(type='int'), + node=dict(), + numa=dict(type='dict'), + numa_enabled=dict(type='bool'), + onboot=dict(type='bool'), + ostype=dict(choices=['other', 'wxp', 'w2k', 'w2k3', 'w2k8', 'wvista', 'win7', 'win8', 'win10', 'l24', 'l26', 'solaris']), + parallel=dict(type='dict'), + pool=dict(type='str'), + protection=dict(type='bool'), + reboot=dict(type='bool'), + revert=dict(type='str'), + sata=dict(type='dict'), + scsi=dict(type='dict'), + scsihw=dict(choices=['lsi', 'lsi53c810', 'virtio-scsi-pci', 'virtio-scsi-single', 'megasas', 'pvscsi']), + serial=dict(type='dict'), + searchdomains=dict(type='list', elements='str'), + shares=dict(type='int'), + skiplock=dict(type='bool'), + smbios=dict(type='str'), + snapname=dict(type='str'), + sockets=dict(type='int'), + sshkeys=dict(type='str', no_log=False), + startdate=dict(type='str'), + startup=dict(), + state=dict(default='present', choices=['present', 'absent', 'stopped', 'started', 'restarted', 'current']), + storage=dict(type='str'), + tablet=dict(type='bool'), + tags=dict(type='list', elements='str'), + target=dict(type='str'), + tdf=dict(type='bool'), + template=dict(type='bool'), + timeout=dict(type='int', default=30), + update=dict(type='bool', default=False), + vcpus=dict(type='int'), + vga=dict(choices=['std', 'cirrus', 'vmware', 'qxl', 'serial0', 'serial1', 'serial2', 'serial3', 'qxl2', 'qxl3', 'qxl4']), + virtio=dict(type='dict'), + vmid=dict(type='int'), + watchdog=dict(), + proxmox_default_behavior=dict(type='str', default='no_defaults', choices=['compatibility', 'no_defaults']), + ) + module_args.update(kvm_args) + module = AnsibleModule( - argument_spec=dict( - acpi=dict(type='bool'), - agent=dict(type='bool'), - args=dict(type='str'), - api_host=dict(required=True), - api_password=dict(no_log=True, fallback=(env_fallback, ['PROXMOX_PASSWORD'])), - api_token_id=dict(no_log=True), - api_token_secret=dict(no_log=True), - api_user=dict(required=True), - autostart=dict(type='bool'), - balloon=dict(type='int'), - bios=dict(choices=['seabios', 'ovmf']), - boot=dict(type='str'), - bootdisk=dict(type='str'), - cicustom=dict(type='str'), - cipassword=dict(type='str', no_log=True), - citype=dict(type='str', choices=['nocloud', 'configdrive2']), - ciuser=dict(type='str'), - clone=dict(type='str'), - cores=dict(type='int'), - cpu=dict(type='str'), - cpulimit=dict(type='int'), - cpuunits=dict(type='int'), - delete=dict(type='str'), - description=dict(type='str'), - digest=dict(type='str'), - force=dict(type='bool'), - format=dict(type='str', choices=['cloop', 'cow', 'qcow', 'qcow2', 'qed', 'raw', 'vmdk', 'unspecified']), - freeze=dict(type='bool'), - full=dict(type='bool', default=True), - hostpci=dict(type='dict'), - hotplug=dict(type='str'), - hugepages=dict(choices=['any', '2', '1024']), - ide=dict(type='dict'), - ipconfig=dict(type='dict'), - keyboard=dict(type='str'), - kvm=dict(type='bool'), - localtime=dict(type='bool'), - lock=dict(choices=['migrate', 'backup', 'snapshot', 'rollback']), - machine=dict(type='str'), - memory=dict(type='int'), - migrate_downtime=dict(type='int'), - migrate_speed=dict(type='int'), - name=dict(type='str'), - nameservers=dict(type='list', elements='str'), - net=dict(type='dict'), - newid=dict(type='int'), - node=dict(), - numa=dict(type='dict'), - numa_enabled=dict(type='bool'), - onboot=dict(type='bool'), - ostype=dict(choices=['other', 'wxp', 'w2k', 'w2k3', 'w2k8', 'wvista', 'win7', 'win8', 'win10', 'l24', 'l26', 'solaris']), - parallel=dict(type='dict'), - pool=dict(type='str'), - protection=dict(type='bool'), - reboot=dict(type='bool'), - revert=dict(type='str'), - sata=dict(type='dict'), - scsi=dict(type='dict'), - scsihw=dict(choices=['lsi', 'lsi53c810', 'virtio-scsi-pci', 'virtio-scsi-single', 'megasas', 'pvscsi']), - serial=dict(type='dict'), - searchdomains=dict(type='list', elements='str'), - shares=dict(type='int'), - skiplock=dict(type='bool'), - smbios=dict(type='str'), - snapname=dict(type='str'), - sockets=dict(type='int'), - sshkeys=dict(type='str', no_log=False), - startdate=dict(type='str'), - startup=dict(), - state=dict(default='present', choices=['present', 'absent', 'stopped', 'started', 'restarted', 'current']), - storage=dict(type='str'), - tablet=dict(type='bool'), - tags=dict(type='list', elements='str'), - target=dict(type='str'), - tdf=dict(type='bool'), - template=dict(type='bool'), - timeout=dict(type='int', default=30), - update=dict(type='bool', default=False), - validate_certs=dict(type='bool', default=False), - vcpus=dict(type='int'), - vga=dict(choices=['std', 'cirrus', 'vmware', 'qxl', 'serial0', 'serial1', 'serial2', 'serial3', 'qxl2', 'qxl3', 'qxl4']), - virtio=dict(type='dict'), - vmid=dict(type='int'), - watchdog=dict(), - proxmox_default_behavior=dict(type='str', default='no_defaults', choices=['compatibility', 'no_defaults']), - ), + argument_spec=module_args, mutually_exclusive=[('delete', 'revert'), ('delete', 'update'), ('revert', 'update'), ('clone', 'update'), ('clone', 'delete'), ('clone', 'revert')], required_together=[('api_token_id', 'api_token_secret')], required_one_of=[('name', 'vmid'), ('api_password', 'api_token_id')], required_if=[('state', 'present', ['node'])], ) - if not HAS_PROXMOXER: - module.fail_json(msg='proxmoxer required for this module') - - api_host = module.params['api_host'] - api_password = module.params['api_password'] - api_token_id = module.params['api_token_id'] - api_token_secret = module.params['api_token_secret'] - api_user = module.params['api_user'] clone = module.params['clone'] cpu = module.params['cpu'] cores = module.params['cores'] @@ -1112,153 +1070,136 @@ def main(): if module.params['format'] == 'unspecified': module.params['format'] = None - auth_args = {'user': api_user} - if not (api_token_id and api_token_secret): - auth_args['password'] = api_password - else: - auth_args['token_name'] = api_token_id - auth_args['token_value'] = api_token_secret - - try: - proxmox = ProxmoxAPI(api_host, verify_ssl=validate_certs, **auth_args) - global PVE_MAJOR_VERSION - version = proxmox_version(proxmox) - PVE_MAJOR_VERSION = 3 if version < LooseVersion('4.0') else version.version[0] - except Exception as e: - module.fail_json(msg='authorization on proxmox cluster failed with exception: %s' % e) + proxmox = ProxmoxKvmAnsible(module) # If vmid is not defined then retrieve its value from the vm name, # the cloned vm name or retrieve the next free VM id from ProxmoxAPI. if not vmid: if state == 'present' and not update and not clone and not delete and not revert: try: - vmid = get_nextvmid(module, proxmox) + vmid = proxmox.get_nextvmid() except Exception: module.fail_json(msg="Can't get the next vmid for VM {0} automatically. Ensure your cluster state is good".format(name)) else: clone_target = clone or name - try: - vmid = get_vmid(proxmox, clone_target)[0] - except Exception: - vmid = -1 + vmid = proxmox.get_vmid(clone_target, ignore_missing=True, choose_first_if_multiple=True) if clone is not None: # If newid is not defined then retrieve the next free id from ProxmoxAPI if not newid: try: - newid = get_nextvmid(module, proxmox) + newid = proxmox.get_nextvmid() except Exception: module.fail_json(msg="Can't get the next vmid for VM {0} automatically. Ensure your cluster state is good".format(name)) # Ensure source VM name exists when cloning - if -1 == vmid: + if not vmid: module.fail_json(msg='VM with name = %s does not exist in cluster' % clone) # Ensure source VM id exists when cloning - if not get_vm(proxmox, vmid): - module.fail_json(vmid=vmid, msg='VM with vmid = %s does not exist in cluster' % vmid) + proxmox.get_vm(vmid) # Ensure the choosen VM name doesn't already exist when cloning - existing_vmid = get_vmid(proxmox, name) + existing_vmid = proxmox.get_vmid(name, choose_first_if_multiple=True) if existing_vmid: - module.exit_json(changed=False, vmid=existing_vmid[0], msg="VM with name <%s> already exists" % name) + module.exit_json(changed=False, vmid=existing_vmid, msg="VM with name <%s> already exists" % name) # Ensure the choosen VM id doesn't already exist when cloning - if get_vm(proxmox, newid): + if proxmox.get_vm(newid, ignore_errors=True): module.exit_json(changed=False, vmid=vmid, msg="vmid %s with VM name %s already exists" % (newid, name)) if delete is not None: try: - settings(proxmox, vmid, node, delete=delete) + proxmox.settings(vmid, node, delete=delete) module.exit_json(changed=True, vmid=vmid, msg="Settings has deleted on VM {0} with vmid {1}".format(name, vmid)) except Exception as e: module.fail_json(vmid=vmid, msg='Unable to delete settings on VM {0} with vmid {1}: '.format(name, vmid) + str(e)) if revert is not None: try: - settings(proxmox, vmid, node, revert=revert) + proxmox.settings(vmid, node, revert=revert) module.exit_json(changed=True, vmid=vmid, msg="Settings has reverted on VM {0} with vmid {1}".format(name, vmid)) except Exception as e: module.fail_json(vmid=vmid, msg='Unable to revert settings on VM {0} with vmid {1}: Maybe is not a pending task... '.format(name, vmid) + str(e)) if state == 'present': try: - if get_vm(proxmox, vmid) and not (update or clone): + if proxmox.get_vm(vmid, ignore_missing=True) and not (update or clone): module.exit_json(changed=False, vmid=vmid, msg="VM with vmid <%s> already exists" % vmid) - elif get_vmid(proxmox, name) and not (update or clone): - module.exit_json(changed=False, vmid=get_vmid(proxmox, name)[0], msg="VM with name <%s> already exists" % name) + elif proxmox.get_vmid(name, ignore_missing=True, choose_first_if_multiple=True) and not (update or clone): + module.exit_json(changed=False, vmid=proxmox.get_vmid(name, choose_first_if_multiple=True), msg="VM with name <%s> already exists" % name) elif not (node, name): module.fail_json(msg='node, name is mandatory for creating/updating vm') - elif not node_check(proxmox, node): + elif not proxmox.get_node(node): module.fail_json(msg="node '%s' does not exist in cluster" % node) - create_vm(module, proxmox, vmid, newid, node, name, memory, cpu, cores, sockets, update, - acpi=module.params['acpi'], - agent=module.params['agent'], - autostart=module.params['autostart'], - balloon=module.params['balloon'], - bios=module.params['bios'], - boot=module.params['boot'], - bootdisk=module.params['bootdisk'], - cicustom=module.params['cicustom'], - cipassword=module.params['cipassword'], - citype=module.params['citype'], - ciuser=module.params['ciuser'], - cpulimit=module.params['cpulimit'], - cpuunits=module.params['cpuunits'], - description=module.params['description'], - digest=module.params['digest'], - force=module.params['force'], - freeze=module.params['freeze'], - hostpci=module.params['hostpci'], - hotplug=module.params['hotplug'], - hugepages=module.params['hugepages'], - ide=module.params['ide'], - ipconfig=module.params['ipconfig'], - keyboard=module.params['keyboard'], - kvm=module.params['kvm'], - localtime=module.params['localtime'], - lock=module.params['lock'], - machine=module.params['machine'], - migrate_downtime=module.params['migrate_downtime'], - migrate_speed=module.params['migrate_speed'], - net=module.params['net'], - numa=module.params['numa'], - numa_enabled=module.params['numa_enabled'], - onboot=module.params['onboot'], - ostype=module.params['ostype'], - parallel=module.params['parallel'], - pool=module.params['pool'], - protection=module.params['protection'], - reboot=module.params['reboot'], - sata=module.params['sata'], - scsi=module.params['scsi'], - scsihw=module.params['scsihw'], - serial=module.params['serial'], - shares=module.params['shares'], - skiplock=module.params['skiplock'], - smbios1=module.params['smbios'], - snapname=module.params['snapname'], - sshkeys=module.params['sshkeys'], - startdate=module.params['startdate'], - startup=module.params['startup'], - tablet=module.params['tablet'], - tags=module.params['tags'], - target=module.params['target'], - tdf=module.params['tdf'], - template=module.params['template'], - vcpus=module.params['vcpus'], - vga=module.params['vga'], - virtio=module.params['virtio'], - watchdog=module.params['watchdog']) + proxmox.create_vm(vmid, newid, node, name, memory, cpu, cores, sockets, update, + acpi=module.params['acpi'], + agent=module.params['agent'], + autostart=module.params['autostart'], + balloon=module.params['balloon'], + bios=module.params['bios'], + boot=module.params['boot'], + bootdisk=module.params['bootdisk'], + cicustom=module.params['cicustom'], + cipassword=module.params['cipassword'], + citype=module.params['citype'], + ciuser=module.params['ciuser'], + cpulimit=module.params['cpulimit'], + cpuunits=module.params['cpuunits'], + description=module.params['description'], + digest=module.params['digest'], + force=module.params['force'], + freeze=module.params['freeze'], + hostpci=module.params['hostpci'], + hotplug=module.params['hotplug'], + hugepages=module.params['hugepages'], + ide=module.params['ide'], + ipconfig=module.params['ipconfig'], + keyboard=module.params['keyboard'], + kvm=module.params['kvm'], + localtime=module.params['localtime'], + lock=module.params['lock'], + machine=module.params['machine'], + migrate_downtime=module.params['migrate_downtime'], + migrate_speed=module.params['migrate_speed'], + net=module.params['net'], + numa=module.params['numa'], + numa_enabled=module.params['numa_enabled'], + onboot=module.params['onboot'], + ostype=module.params['ostype'], + parallel=module.params['parallel'], + pool=module.params['pool'], + protection=module.params['protection'], + reboot=module.params['reboot'], + sata=module.params['sata'], + scsi=module.params['scsi'], + scsihw=module.params['scsihw'], + serial=module.params['serial'], + shares=module.params['shares'], + skiplock=module.params['skiplock'], + smbios1=module.params['smbios'], + snapname=module.params['snapname'], + sshkeys=module.params['sshkeys'], + startdate=module.params['startdate'], + startup=module.params['startup'], + tablet=module.params['tablet'], + tags=module.params['tags'], + target=module.params['target'], + tdf=module.params['tdf'], + template=module.params['template'], + vcpus=module.params['vcpus'], + vga=module.params['vga'], + virtio=module.params['virtio'], + watchdog=module.params['watchdog']) if not clone: - get_vminfo(module, proxmox, node, vmid, - ide=module.params['ide'], - net=module.params['net'], - sata=module.params['sata'], - scsi=module.params['scsi'], - virtio=module.params['virtio']) + proxmox.get_vminfo(node, vmid, + ide=module.params['ide'], + net=module.params['net'], + sata=module.params['sata'], + scsi=module.params['scsi'], + virtio=module.params['virtio']) if update: module.exit_json(changed=True, vmid=vmid, msg="VM %s with vmid %s updated" % (name, vmid)) elif clone is not None: @@ -1276,16 +1217,14 @@ def main(): elif state == 'started': status = {} try: - if -1 == vmid: + if not vmid: module.fail_json(msg='VM with name = %s does not exist in cluster' % name) - vm = get_vm(proxmox, vmid) - if not vm: - module.fail_json(vmid=vmid, msg='VM with vmid <%s> does not exist in cluster' % vmid) - status['status'] = vm[0]['status'] - if vm[0]['status'] == 'running': + vm = proxmox.get_vm(vmid) + status['status'] = vm['status'] + if vm['status'] == 'running': module.exit_json(changed=False, vmid=vmid, msg="VM %s is already running" % vmid, **status) - if start_vm(module, proxmox, vm): + if proxmox.start_vm(vm): module.exit_json(changed=True, vmid=vmid, msg="VM %s started" % vmid, **status) except Exception as e: module.fail_json(vmid=vmid, msg="starting of VM %s failed with exception: %s" % (vmid, e), **status) @@ -1293,18 +1232,16 @@ def main(): elif state == 'stopped': status = {} try: - if -1 == vmid: + if not vmid: module.fail_json(msg='VM with name = %s does not exist in cluster' % name) - vm = get_vm(proxmox, vmid) - if not vm: - module.fail_json(vmid=vmid, msg='VM with vmid = %s does not exist in cluster' % vmid) + vm = proxmox.get_vm(vmid) - status['status'] = vm[0]['status'] - if vm[0]['status'] == 'stopped': + status['status'] = vm['status'] + if vm['status'] == 'stopped': module.exit_json(changed=False, vmid=vmid, msg="VM %s is already stopped" % vmid, **status) - if stop_vm(module, proxmox, vm, force=module.params['force']): + if proxmox.stop_vm(vm, force=module.params['force']): module.exit_json(changed=True, vmid=vmid, msg="VM %s is shutting down" % vmid, **status) except Exception as e: module.fail_json(vmid=vmid, msg="stopping of VM %s failed with exception: %s" % (vmid, e), **status) @@ -1312,17 +1249,15 @@ def main(): elif state == 'restarted': status = {} try: - if -1 == vmid: + if not vmid: module.fail_json(msg='VM with name = %s does not exist in cluster' % name) - vm = get_vm(proxmox, vmid) - if not vm: - module.fail_json(vmid=vmid, msg='VM with vmid = %s does not exist in cluster' % vmid) - status['status'] = vm[0]['status'] - if vm[0]['status'] == 'stopped': + vm = proxmox.get_vm(vmid) + status['status'] = vm['status'] + if vm['status'] == 'stopped': module.exit_json(changed=False, vmid=vmid, msg="VM %s is not running" % vmid, **status) - if stop_vm(module, proxmox, vm, force=module.params['force']) and start_vm(module, proxmox, vm): + if proxmox.stop_vm(vm, force=module.params['force']) and proxmox.start_vm(vm): module.exit_json(changed=True, vmid=vmid, msg="VM %s is restarted" % vmid, **status) except Exception as e: module.fail_json(vmid=vmid, msg="restarting of VM %s failed with exception: %s" % (vmid, e), **status) @@ -1330,19 +1265,19 @@ def main(): elif state == 'absent': status = {} try: - vm = get_vm(proxmox, vmid) + vm = proxmox.get_vm(vmid, ignore_missing=True) if not vm: module.exit_json(changed=False, vmid=vmid) - proxmox_node = proxmox.nodes(vm[0]['node']) - status['status'] = vm[0]['status'] - if vm[0]['status'] == 'running': + proxmox_node = proxmox.proxmox_api.nodes(vm['node']) + status['status'] = vm['status'] + if vm['status'] == 'running': if module.params['force']: - stop_vm(module, proxmox, vm, True) + proxmox.stop_vm(vm, True) else: module.exit_json(changed=False, vmid=vmid, msg="VM %s is running. Stop it before deletion or use force=yes." % vmid) taskid = proxmox_node.qemu.delete(vmid) - if not wait_for_task(module, proxmox, vm[0]['node'], taskid): + if not proxmox.wait_for_task(vm['node'], taskid): module.fail_json(msg='Reached timeout while waiting for removing VM. Last line in task before timeout: %s' % proxmox_node.tasks(taskid).log.get()[:1]) else: @@ -1352,14 +1287,12 @@ def main(): elif state == 'current': status = {} - if -1 == vmid: + if not vmid: module.fail_json(msg='VM with name = %s does not exist in cluster' % name) - vm = get_vm(proxmox, vmid) - if not vm: - module.fail_json(msg='VM with vmid = %s does not exist in cluster' % vmid) + vm = proxmox.get_vm(vmid) if not name: - name = vm[0]['name'] - current = proxmox.nodes(vm[0]['node']).qemu(vmid).status.current.get()['status'] + name = vm['name'] + current = proxmox.proxmox_api.nodes(vm['node']).qemu(vmid).status.current.get()['status'] status['status'] = current if status: module.exit_json(changed=False, vmid=vmid, msg="VM %s with vmid = %s is %s" % (name, vmid, current), **status) diff --git a/plugins/modules/cloud/misc/proxmox_nic.py b/plugins/modules/cloud/misc/proxmox_nic.py index 23be9473eb..e83d0dfef1 100644 --- a/plugins/modules/cloud/misc/proxmox_nic.py +++ b/plugins/modules/cloud/misc/proxmox_nic.py @@ -1,6 +1,6 @@ #!/usr/bin/python # -*- coding: utf-8 -*- - +# # Copyright: (c) 2021, Lammert Hellinga (@Kogelvis) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) @@ -136,120 +136,96 @@ msg: sample: "Nic net0 unchanged on VM with vmid 103" ''' -try: - from proxmoxer import ProxmoxAPI - HAS_PROXMOXER = True -except ImportError: - HAS_PROXMOXER = False - from ansible.module_utils.basic import AnsibleModule, env_fallback -from ansible_collections.community.general.plugins.module_utils.proxmox import proxmox_auth_argument_spec +from ansible_collections.community.general.plugins.module_utils.proxmox import (proxmox_auth_argument_spec, ProxmoxAnsible) -def get_vmid(module, proxmox, name): - try: - vms = [vm['vmid'] for vm in proxmox.cluster.resources.get(type='vm') if vm.get('name') == name] - except Exception as e: - module.fail_json(msg='Error: %s occurred while retrieving VM with name = %s' % (e, name)) +class ProxmoxNicAnsible(ProxmoxAnsible): + def update_nic(self, vmid, interface, model, **kwargs): + vm = self.get_vm(vmid) - if not vms: - module.fail_json(msg='No VM found with name: %s' % name) - elif len(vms) > 1: - module.fail_json(msg='Multiple VMs found with name: %s, provide vmid instead' % name) + try: + vminfo = self.proxmox_api.nodes(vm['node']).qemu(vmid).config.get() + except Exception as e: + self.module.fail_json(msg='Getting information for VM with vmid = %s failed with exception: %s' % (vmid, e)) - return vms[0] + if interface in vminfo: + # Convert the current config to a dictionary + config = vminfo[interface].split(',') + config.sort() + config_current = {} -def get_vm(proxmox, vmid): - return [vm for vm in proxmox.cluster.resources.get(type='vm') if vm['vmid'] == int(vmid)] + for i in config: + kv = i.split('=') + try: + config_current[kv[0]] = kv[1] + except IndexError: + config_current[kv[0]] = '' + # determine the current model nic and mac-address + models = ['e1000', 'e1000-82540em', 'e1000-82544gc', 'e1000-82545em', 'i82551', 'i82557b', + 'i82559er', 'ne2k_isa', 'ne2k_pci', 'pcnet', 'rtl8139', 'virtio', 'vmxnet3'] + current_model = set(models) & set(config_current.keys()) + current_model = current_model.pop() + current_mac = config_current[current_model] -def update_nic(module, proxmox, vmid, interface, model, **kwargs): - vm = get_vm(proxmox, vmid) + # build nic config string + config_provided = "{0}={1}".format(model, current_mac) + else: + config_provided = model - try: - vminfo = proxmox.nodes(vm[0]['node']).qemu(vmid).config.get() - except Exception as e: - module.fail_json(msg='Getting information for VM with vmid = %s failed with exception: %s' % (vmid, e)) + if kwargs['mac']: + config_provided = "{0}={1}".format(model, kwargs['mac']) - if interface in vminfo: - # Convert the current config to a dictionary - config = vminfo[interface].split(',') - config.sort() + if kwargs['bridge']: + config_provided += ",bridge={0}".format(kwargs['bridge']) - config_current = {} + if kwargs['firewall']: + config_provided += ",firewall=1" - for i in config: - kv = i.split('=') - try: - config_current[kv[0]] = kv[1] - except IndexError: - config_current[kv[0]] = '' + if kwargs['link_down']: + config_provided += ',link_down=1' - # determine the current model nic and mac-address - models = ['e1000', 'e1000-82540em', 'e1000-82544gc', 'e1000-82545em', 'i82551', 'i82557b', - 'i82559er', 'ne2k_isa', 'ne2k_pci', 'pcnet', 'rtl8139', 'virtio', 'vmxnet3'] - current_model = set(models) & set(config_current.keys()) - current_model = current_model.pop() - current_mac = config_current[current_model] + if kwargs['mtu']: + config_provided += ",mtu={0}".format(kwargs['mtu']) + if model != 'virtio': + self.module.warn( + 'Ignoring MTU for nic {0} on VM with vmid {1}, ' + 'model should be set to \'virtio\': '.format(interface, vmid)) - # build nic config string - config_provided = "{0}={1}".format(model, current_mac) - else: - config_provided = model + if kwargs['queues']: + config_provided += ",queues={0}".format(kwargs['queues']) - if kwargs['mac']: - config_provided = "{0}={1}".format(model, kwargs['mac']) + if kwargs['rate']: + config_provided += ",rate={0}".format(kwargs['rate']) - if kwargs['bridge']: - config_provided += ",bridge={0}".format(kwargs['bridge']) + if kwargs['tag']: + config_provided += ",tag={0}".format(kwargs['tag']) - if kwargs['firewall']: - config_provided += ",firewall=1" + if kwargs['trunks']: + config_provided += ",trunks={0}".format(';'.join(str(x) for x in kwargs['trunks'])) - if kwargs['link_down']: - config_provided += ',link_down=1' + net = {interface: config_provided} + vm = self.get_vm(vmid) - if kwargs['mtu']: - config_provided += ",mtu={0}".format(kwargs['mtu']) - if model != 'virtio': - module.warn( - 'Ignoring MTU for nic {0} on VM with vmid {1}, ' - 'model should be set to \'virtio\': '.format(interface, vmid)) + if ((interface not in vminfo) or (vminfo[interface] != config_provided)): + if not self.module.check_mode: + self.proxmox_api.nodes(vm['node']).qemu(vmid).config.set(**net) + return True - if kwargs['queues']: - config_provided += ",queues={0}".format(kwargs['queues']) + return False - if kwargs['rate']: - config_provided += ",rate={0}".format(kwargs['rate']) + def delete_nic(self, vmid, interface): + vm = self.get_vm(vmid) + vminfo = self.proxmox_api.nodes(vm['node']).qemu(vmid).config.get() - if kwargs['tag']: - config_provided += ",tag={0}".format(kwargs['tag']) + if interface in vminfo: + if not self.module.check_mode: + self.proxmox_api.nodes(vm['node']).qemu(vmid).config.set(vmid=vmid, delete=interface) + return True - if kwargs['trunks']: - config_provided += ",trunks={0}".format(';'.join(str(x) for x in kwargs['trunks'])) - - net = {interface: config_provided} - vm = get_vm(proxmox, vmid) - - if ((interface not in vminfo) or (vminfo[interface] != config_provided)): - if not module.check_mode: - proxmox.nodes(vm[0]['node']).qemu(vmid).config.set(**net) - return True - - return False - - -def delete_nic(module, proxmox, vmid, interface): - vm = get_vm(proxmox, vmid) - vminfo = proxmox.nodes(vm[0]['node']).qemu(vmid).config.get() - - if interface in vminfo: - if not module.check_mode: - proxmox.nodes(vm[0]['node']).qemu(vmid).config.set(vmid=vmid, delete=interface) - return True - - return False + return False def main(): @@ -281,53 +257,33 @@ def main(): supports_check_mode=True, ) - if not HAS_PROXMOXER: - module.fail_json(msg='proxmoxer required for this module') + proxmox = ProxmoxNicAnsible(module) - api_host = module.params['api_host'] - api_password = module.params['api_password'] - api_token_id = module.params['api_token_id'] - api_token_secret = module.params['api_token_secret'] - api_user = module.params['api_user'] interface = module.params['interface'] model = module.params['model'] name = module.params['name'] state = module.params['state'] - validate_certs = module.params['validate_certs'] vmid = module.params['vmid'] - auth_args = {'user': api_user} - if not (api_token_id and api_token_secret): - auth_args['password'] = api_password - else: - auth_args['token_name'] = api_token_id - auth_args['token_value'] = api_token_secret - - try: - proxmox = ProxmoxAPI(api_host, verify_ssl=validate_certs, **auth_args) - except Exception as e: - module.fail_json(msg='authorization on proxmox cluster failed with exception: %s' % e) - # If vmid is not defined then retrieve its value from the vm name, if not vmid: - vmid = get_vmid(module, proxmox, name) + vmid = proxmox.get_vmid(name) # Ensure VM id exists - if not get_vm(proxmox, vmid): - module.fail_json(vmid=vmid, msg='VM with vmid = %s does not exist in cluster' % vmid) + proxmox.get_vm(vmid) if state == 'present': try: - if update_nic(module, proxmox, vmid, interface, model, - bridge=module.params['bridge'], - firewall=module.params['firewall'], - link_down=module.params['link_down'], - mac=module.params['mac'], - mtu=module.params['mtu'], - queues=module.params['queues'], - rate=module.params['rate'], - tag=module.params['tag'], - trunks=module.params['trunks']): + if proxmox.update_nic(vmid, interface, model, + bridge=module.params['bridge'], + firewall=module.params['firewall'], + link_down=module.params['link_down'], + mac=module.params['mac'], + mtu=module.params['mtu'], + queues=module.params['queues'], + rate=module.params['rate'], + tag=module.params['tag'], + trunks=module.params['trunks']): module.exit_json(changed=True, vmid=vmid, msg="Nic {0} updated on VM with vmid {1}".format(interface, vmid)) else: module.exit_json(vmid=vmid, msg="Nic {0} unchanged on VM with vmid {1}".format(interface, vmid)) @@ -336,7 +292,7 @@ def main(): elif state == 'absent': try: - if delete_nic(module, proxmox, vmid, interface): + if proxmox.delete_nic(vmid, interface): module.exit_json(changed=True, vmid=vmid, msg="Nic {0} deleted on VM with vmid {1}".format(interface, vmid)) else: module.exit_json(vmid=vmid, msg="Nic {0} does not exist on VM with vmid {1}".format(interface, vmid)) diff --git a/plugins/modules/cloud/misc/proxmox_snap.py b/plugins/modules/cloud/misc/proxmox_snap.py index 54818bac09..cf570bd151 100644 --- a/plugins/modules/cloud/misc/proxmox_snap.py +++ b/plugins/modules/cloud/misc/proxmox_snap.py @@ -1,6 +1,6 @@ #!/usr/bin/python # -*- coding: utf-8 -*- - +# # Copyright: (c) 2020, Jeffrey van Pelt (@Thulium-Drake) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) @@ -16,22 +16,6 @@ description: - Allows you to create/delete snapshots from instances in Proxmox VE cluster. - Supports both KVM and LXC, OpenVZ has not been tested, as it is no longer supported on Proxmox VE. options: - api_host: - description: - - The host of the Proxmox VE cluster. - required: true - type: str - api_user: - description: - - The user to authenticate with. - required: true - type: str - api_password: - description: - - The password to authenticate with. - - You can use PROXMOX_PASSWORD environment variable. - type: str - required: yes hostname: description: - The instance name. @@ -41,11 +25,6 @@ options: - The instance id. - If not set, will be fetched from PromoxAPI based on the hostname. type: str - validate_certs: - description: - - Enable / disable https certificate verification. - type: bool - default: no state: description: - Indicate desired state of the instance snapshot. @@ -83,6 +62,8 @@ notes: - Supports C(check_mode). requirements: [ "proxmoxer", "python >= 2.7", "requests" ] author: Jeffrey van Pelt (@Thulium-Drake) +extends_documentation_fragment: + - community.general.proxmox.documentation ''' EXAMPLES = r''' @@ -110,102 +91,76 @@ RETURN = r'''#''' import time import traceback -PROXMOXER_IMP_ERR = None -try: - from proxmoxer import ProxmoxAPI - HAS_PROXMOXER = True -except ImportError: - PROXMOXER_IMP_ERR = traceback.format_exc() - HAS_PROXMOXER = False - from ansible.module_utils.basic import AnsibleModule, missing_required_lib, env_fallback from ansible.module_utils.common.text.converters import to_native +from ansible_collections.community.general.plugins.module_utils.proxmox import (proxmox_auth_argument_spec, ProxmoxAnsible, HAS_PROXMOXER, PROXMOXER_IMP_ERR) -VZ_TYPE = None +class ProxmoxSnapAnsible(ProxmoxAnsible): + def snapshot(self, vm, vmid): + return getattr(self.proxmox_api.nodes(vm['node']), vm['type'])(vmid).snapshot - -def get_vmid(proxmox, hostname): - return [vm['vmid'] for vm in proxmox.cluster.resources.get(type='vm') if 'name' in vm and vm['name'] == hostname] - - -def get_instance(proxmox, vmid): - return [vm for vm in proxmox.cluster.resources.get(type='vm') if int(vm['vmid']) == int(vmid)] - - -def snapshot_create(module, proxmox, vm, vmid, timeout, snapname, description, vmstate): - if module.check_mode: - return True - - if VZ_TYPE == 'lxc': - taskid = getattr(proxmox.nodes(vm[0]['node']), VZ_TYPE)(vmid).snapshot.post(snapname=snapname, description=description) - else: - taskid = getattr(proxmox.nodes(vm[0]['node']), VZ_TYPE)(vmid).snapshot.post(snapname=snapname, description=description, vmstate=int(vmstate)) - while timeout: - if (proxmox.nodes(vm[0]['node']).tasks(taskid).status.get()['status'] == 'stopped' and - proxmox.nodes(vm[0]['node']).tasks(taskid).status.get()['exitstatus'] == 'OK'): + def snapshot_create(self, vm, vmid, timeout, snapname, description, vmstate): + if self.module.check_mode: return True - timeout -= 1 - if timeout == 0: - module.fail_json(msg='Reached timeout while waiting for creating VM snapshot. Last line in task before timeout: %s' % - proxmox.nodes(vm[0]['node']).tasks(taskid).log.get()[:1]) - time.sleep(1) - return False + if vm['type'] == 'lxc': + taskid = self.snapshot(vm, vmid).post(snapname=snapname, description=description) + else: + taskid = self.snapshot(vm, vmid).post(snapname=snapname, description=description, vmstate=int(vmstate)) + while timeout: + if (self.proxmox_api.nodes(vm['node']).tasks(taskid).status.get()['status'] == 'stopped' and + self.proxmox_api.nodes(vm['node']).tasks(taskid).status.get()['exitstatus'] == 'OK'): + return True + timeout -= 1 + if timeout == 0: + self.module.fail_json(msg='Reached timeout while waiting for creating VM snapshot. Last line in task before timeout: %s' % + self.proxmox_api.nodes(vm['node']).tasks(taskid).log.get()[:1]) + time.sleep(1) + return False -def snapshot_remove(module, proxmox, vm, vmid, timeout, snapname, force): - if module.check_mode: - return True - - taskid = getattr(proxmox.nodes(vm[0]['node']), VZ_TYPE)(vmid).snapshot.delete(snapname, force=int(force)) - while timeout: - if (proxmox.nodes(vm[0]['node']).tasks(taskid).status.get()['status'] == 'stopped' and - proxmox.nodes(vm[0]['node']).tasks(taskid).status.get()['exitstatus'] == 'OK'): + def snapshot_remove(self, vm, vmid, timeout, snapname, force): + if self.module.check_mode: return True - timeout -= 1 - if timeout == 0: - module.fail_json(msg='Reached timeout while waiting for removing VM snapshot. Last line in task before timeout: %s' % - proxmox.nodes(vm[0]['node']).tasks(taskid).log.get()[:1]) - time.sleep(1) - return False + taskid = self.snapshot(vm, vmid).delete(snapname, force=int(force)) + while timeout: + if (self.proxmox_api.nodes(vm['node']).tasks(taskid).status.get()['status'] == 'stopped' and + self.proxmox_api.nodes(vm['node']).tasks(taskid).status.get()['exitstatus'] == 'OK'): + return True + timeout -= 1 + if timeout == 0: + self.module.fail_json(msg='Reached timeout while waiting for removing VM snapshot. Last line in task before timeout: %s' % + self.proxmox_api.nodes(vm['node']).tasks(taskid).log.get()[:1]) - -def setup_api(api_host, api_user, api_password, validate_certs): - api = ProxmoxAPI(api_host, user=api_user, password=api_password, verify_ssl=validate_certs) - return api + time.sleep(1) + return False def main(): + module_args = proxmox_auth_argument_spec() + snap_args = dict( + vmid=dict(required=False), + hostname=dict(), + timeout=dict(type='int', default=30), + state=dict(default='present', choices=['present', 'absent']), + description=dict(type='str'), + snapname=dict(type='str', default='ansible_snap'), + force=dict(type='bool', default='no'), + vmstate=dict(type='bool', default='no'), + ) + module_args.update(snap_args) + module = AnsibleModule( - argument_spec=dict( - api_host=dict(required=True), - api_user=dict(required=True), - api_password=dict(no_log=True, required=True, fallback=(env_fallback, ['PROXMOX_PASSWORD'])), - vmid=dict(required=False), - validate_certs=dict(type='bool', default='no'), - hostname=dict(), - timeout=dict(type='int', default=30), - state=dict(default='present', choices=['present', 'absent']), - description=dict(type='str'), - snapname=dict(type='str', default='ansible_snap'), - force=dict(type='bool', default='no'), - vmstate=dict(type='bool', default='no'), - ), + argument_spec=module_args, supports_check_mode=True ) - if not HAS_PROXMOXER: - module.fail_json(msg=missing_required_lib('proxmoxer'), - exception=PROXMOXER_IMP_ERR) + proxmox = ProxmoxSnapAnsible(module) state = module.params['state'] - api_user = module.params['api_user'] - api_host = module.params['api_host'] - api_password = module.params['api_password'] vmid = module.params['vmid'] - validate_certs = module.params['validate_certs'] hostname = module.params['hostname'] description = module.params['description'] snapname = module.params['snapname'] @@ -213,37 +168,21 @@ def main(): force = module.params['force'] vmstate = module.params['vmstate'] - try: - proxmox = setup_api(api_host, api_user, api_password, validate_certs) - - except Exception as e: - module.fail_json(msg='authorization on proxmox cluster failed with exception: %s' % to_native(e)) - # If hostname is set get the VM id from ProxmoxAPI if not vmid and hostname: - hosts = get_vmid(proxmox, hostname) - if len(hosts) == 0: - module.fail_json(msg="Vmid could not be fetched => Hostname does not exist (action: %s)" % state) - vmid = hosts[0] + vmid = proxmox.get_vmid(hostname, choose_first_if_multiple=True) elif not vmid: module.exit_json(changed=False, msg="Vmid could not be fetched for the following action: %s" % state) - vm = get_instance(proxmox, vmid) - - global VZ_TYPE - VZ_TYPE = vm[0]['type'] + vm = proxmox.get_vm(vmid) if state == 'present': try: - vm = get_instance(proxmox, vmid) - if not vm: - module.fail_json(msg='VM with vmid = %s not exists in cluster' % vmid) - - for i in getattr(proxmox.nodes(vm[0]['node']), VZ_TYPE)(vmid).snapshot.get(): + for i in proxmox.snapshot(vm, vmid).get(): if i['name'] == snapname: module.exit_json(changed=False, msg="Snapshot %s is already present" % snapname) - if snapshot_create(module, proxmox, vm, vmid, timeout, snapname, description, vmstate): + if proxmox.snapshot_create(vm, vmid, timeout, snapname, description, vmstate): if module.check_mode: module.exit_json(changed=False, msg="Snapshot %s would be created" % snapname) else: @@ -254,13 +193,9 @@ def main(): elif state == 'absent': try: - vm = get_instance(proxmox, vmid) - if not vm: - module.fail_json(msg='VM with vmid = %s not exists in cluster' % vmid) - snap_exist = False - for i in getattr(proxmox.nodes(vm[0]['node']), VZ_TYPE)(vmid).snapshot.get(): + for i in proxmox.snapshot(vm, vmid).get(): if i['name'] == snapname: snap_exist = True continue @@ -268,7 +203,7 @@ def main(): if not snap_exist: module.exit_json(changed=False, msg="Snapshot %s does not exist" % snapname) else: - if snapshot_remove(module, proxmox, vm, vmid, timeout, snapname, force): + if proxmox.snapshot_remove(vm, vmid, timeout, snapname, force): if module.check_mode: module.exit_json(changed=False, msg="Snapshot %s would be removed" % snapname) else: diff --git a/plugins/modules/cloud/misc/proxmox_template.py b/plugins/modules/cloud/misc/proxmox_template.py index bee2583908..32ff8e7edb 100644 --- a/plugins/modules/cloud/misc/proxmox_template.py +++ b/plugins/modules/cloud/misc/proxmox_template.py @@ -2,7 +2,6 @@ # -*- coding: utf-8 -*- # # Copyright: Ansible Project -# # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function @@ -117,112 +116,81 @@ EXAMPLES = ''' import os import time -try: - from proxmoxer import ProxmoxAPI - HAS_PROXMOXER = True -except ImportError: - HAS_PROXMOXER = False - from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible_collections.community.general.plugins.module_utils.proxmox import (proxmox_auth_argument_spec, ProxmoxAnsible) -def get_template(proxmox, node, storage, content_type, template): - return [True for tmpl in proxmox.nodes(node).storage(storage).content.get() - if tmpl['volid'] == '%s:%s/%s' % (storage, content_type, template)] +class ProxmoxTemplateAnsible(ProxmoxAnsible): + def get_template(self, node, storage, content_type, template): + return [True for tmpl in self.proxmox_api.nodes(node).storage(storage).content.get() + if tmpl['volid'] == '%s:%s/%s' % (storage, content_type, template)] + def task_status(self, node, taskid, timeout): + """ + Check the task status and wait until the task is completed or the timeout is reached. + """ + while timeout: + task_status = self.proxmox_api.nodes(node).tasks(taskid).status.get() + if task_status['status'] == 'stopped' and task_status['exitstatus'] == 'OK': + return True + timeout = timeout - 1 + if timeout == 0: + self.module.fail_json(msg='Reached timeout while waiting for uploading/downloading template. Last line in task before timeout: %s' % + self.proxmox_api.node(node).tasks(taskid).log.get()[:1]) -def task_status(module, proxmox, node, taskid, timeout): - """ - Check the task status and wait until the task is completed or the timeout is reached. - """ - while timeout: - task_status = proxmox.nodes(node).tasks(taskid).status.get() - if task_status['status'] == 'stopped' and task_status['exitstatus'] == 'OK': - return True - timeout = timeout - 1 - if timeout == 0: - module.fail_json(msg='Reached timeout while waiting for uploading/downloading template. Last line in task before timeout: %s' - % proxmox.node(node).tasks(taskid).log.get()[:1]) + time.sleep(1) + return False - time.sleep(1) - return False + def upload_template(self, node, storage, content_type, realpath, timeout): + taskid = self.proxmox_api.nodes(node).storage(storage).upload.post(content=content_type, filename=open(realpath, 'rb')) + return self.task_status(node, taskid, timeout) + def download_template(self, node, storage, template, timeout): + taskid = self.proxmox_api.nodes(node).aplinfo.post(storage=storage, template=template) + return self.task_status(node, taskid, timeout) -def upload_template(module, proxmox, node, storage, content_type, realpath, timeout): - taskid = proxmox.nodes(node).storage(storage).upload.post(content=content_type, filename=open(realpath, 'rb')) - return task_status(module, proxmox, node, taskid, timeout) + def delete_template(self, node, storage, content_type, template, timeout): + volid = '%s:%s/%s' % (storage, content_type, template) + self.proxmox_api.nodes(node).storage(storage).content.delete(volid) + while timeout: + if not self.get_template(node, storage, content_type, template): + return True + timeout = timeout - 1 + if timeout == 0: + self.module.fail_json(msg='Reached timeout while waiting for deleting template.') - -def download_template(module, proxmox, node, storage, template, timeout): - taskid = proxmox.nodes(node).aplinfo.post(storage=storage, template=template) - return task_status(module, proxmox, node, taskid, timeout) - - -def delete_template(module, proxmox, node, storage, content_type, template, timeout): - volid = '%s:%s/%s' % (storage, content_type, template) - proxmox.nodes(node).storage(storage).content.delete(volid) - while timeout: - if not get_template(proxmox, node, storage, content_type, template): - return True - timeout = timeout - 1 - if timeout == 0: - module.fail_json(msg='Reached timeout while waiting for deleting template.') - - time.sleep(1) - return False + time.sleep(1) + return False def main(): + module_args = proxmox_auth_argument_spec() + template_args = dict( + node=dict(), + src=dict(type='path'), + template=dict(), + content_type=dict(default='vztmpl', choices=['vztmpl', 'iso']), + storage=dict(default='local'), + timeout=dict(type='int', default=30), + force=dict(type='bool', default=False), + state=dict(default='present', choices=['present', 'absent']), + ) + module_args.update(template_args) + module = AnsibleModule( - argument_spec=dict( - api_host=dict(required=True), - api_password=dict(no_log=True, fallback=(env_fallback, ['PROXMOX_PASSWORD'])), - api_token_id=dict(no_log=True), - api_token_secret=dict(no_log=True), - api_user=dict(required=True), - validate_certs=dict(type='bool', default=False), - node=dict(), - src=dict(type='path'), - template=dict(), - content_type=dict(default='vztmpl', choices=['vztmpl', 'iso']), - storage=dict(default='local'), - timeout=dict(type='int', default=30), - force=dict(type='bool', default=False), - state=dict(default='present', choices=['present', 'absent']), - ), + argument_spec=module_args, required_together=[('api_token_id', 'api_token_secret')], required_one_of=[('api_password', 'api_token_id')], required_if=[('state', 'absent', ['template'])] ) - if not HAS_PROXMOXER: - module.fail_json(msg='proxmoxer required for this module') + proxmox = ProxmoxTemplateAnsible(module) state = module.params['state'] - api_host = module.params['api_host'] - api_password = module.params['api_password'] - api_token_id = module.params['api_token_id'] - api_token_secret = module.params['api_token_secret'] - api_user = module.params['api_user'] - validate_certs = module.params['validate_certs'] node = module.params['node'] storage = module.params['storage'] timeout = module.params['timeout'] - auth_args = {'user': api_user} - if not (api_token_id and api_token_secret): - auth_args['password'] = api_password - else: - auth_args['token_name'] = api_token_id - auth_args['token_value'] = api_token_secret - - try: - proxmox = ProxmoxAPI(api_host, verify_ssl=validate_certs, **auth_args) - # Used to test the validity of the token if given - proxmox.version.get() - except Exception as e: - module.fail_json(msg='authorization on proxmox cluster failed with exception: %s' % e) - if state == 'present': try: content_type = module.params['content_type'] @@ -235,21 +203,21 @@ def main(): if not template: module.fail_json(msg='template param for downloading appliance template is mandatory') - if get_template(proxmox, node, storage, content_type, template) and not module.params['force']: + if proxmox.get_template(node, storage, content_type, template) and not module.params['force']: module.exit_json(changed=False, msg='template with volid=%s:%s/%s already exists' % (storage, content_type, template)) - if download_template(module, proxmox, node, storage, template, timeout): + if proxmox.download_template(node, storage, template, timeout): module.exit_json(changed=True, msg='template with volid=%s:%s/%s downloaded' % (storage, content_type, template)) template = os.path.basename(src) - if get_template(proxmox, node, storage, content_type, template) and not module.params['force']: + if proxmox.get_template(node, storage, content_type, template) and not module.params['force']: module.exit_json(changed=False, msg='template with volid=%s:%s/%s is already exists' % (storage, content_type, template)) elif not src: module.fail_json(msg='src param to uploading template file is mandatory') elif not (os.path.exists(src) and os.path.isfile(src)): module.fail_json(msg='template file on path %s not exists' % src) - if upload_template(module, proxmox, node, storage, content_type, src, timeout): + if proxmox.upload_template(node, storage, content_type, src, timeout): module.exit_json(changed=True, msg='template with volid=%s:%s/%s uploaded' % (storage, content_type, template)) except Exception as e: module.fail_json(msg="uploading/downloading of template %s failed with exception: %s" % (template, e)) @@ -259,10 +227,10 @@ def main(): content_type = module.params['content_type'] template = module.params['template'] - if not get_template(proxmox, node, storage, content_type, template): + if not proxmox.get_template(node, storage, content_type, template): module.exit_json(changed=False, msg='template with volid=%s:%s/%s is already deleted' % (storage, content_type, template)) - if delete_template(module, proxmox, node, storage, content_type, template, timeout): + if proxmox.delete_template(node, storage, content_type, template, timeout): module.exit_json(changed=True, msg='template with volid=%s:%s/%s deleted' % (storage, content_type, template)) except Exception as e: module.fail_json(msg="deleting of template %s failed with exception: %s" % (template, e)) diff --git a/tests/unit/plugins/modules/cloud/misc/test_proxmox_kvm.py b/tests/unit/plugins/modules/cloud/misc/test_proxmox_kvm.py index d486000ed1..4aaf326db3 100644 --- a/tests/unit/plugins/modules/cloud/misc/test_proxmox_kvm.py +++ b/tests/unit/plugins/modules/cloud/misc/test_proxmox_kvm.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- +# # Copyright: (c) 2021, Ansible Project # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) diff --git a/tests/unit/plugins/modules/cloud/misc/test_proxmox_snap.py b/tests/unit/plugins/modules/cloud/misc/test_proxmox_snap.py index f8cd4f6951..efd6e4ecb6 100644 --- a/tests/unit/plugins/modules/cloud/misc/test_proxmox_snap.py +++ b/tests/unit/plugins/modules/cloud/misc/test_proxmox_snap.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- +# # Copyright: (c) 2019, Ansible Project # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import (absolute_import, division, print_function) @@ -6,8 +8,9 @@ __metaclass__ = type import json import pytest -from ansible_collections.community.general.tests.unit.compat.mock import MagicMock +from ansible_collections.community.general.tests.unit.compat.mock import MagicMock, patch from ansible_collections.community.general.plugins.modules.cloud.misc import proxmox_snap +import ansible_collections.community.general.plugins.module_utils.proxmox as proxmox_utils from ansible_collections.community.general.tests.unit.plugins.modules.utils import set_module_args @@ -32,8 +35,8 @@ def get_resources(type): "status": "running"}] -def fake_api(api_host, api_user, api_password, validate_certs): - r = MagicMock() +def fake_api(mocker): + r = mocker.MagicMock() r.cluster.resources.get = MagicMock(side_effect=get_resources) return r @@ -48,7 +51,8 @@ def test_proxmox_snap_without_argument(capfd): assert json.loads(out)['failed'] -def test_create_snapshot_check_mode(capfd, mocker): +@patch('ansible_collections.community.general.plugins.module_utils.proxmox.ProxmoxAnsible._connect') +def test_create_snapshot_check_mode(connect_mock, capfd, mocker): set_module_args({"hostname": "test-lxc", "api_user": "root@pam", "api_password": "secret", @@ -58,8 +62,8 @@ def test_create_snapshot_check_mode(capfd, mocker): "timeout": "1", "force": True, "_ansible_check_mode": True}) - proxmox_snap.HAS_PROXMOXER = True - proxmox_snap.setup_api = mocker.MagicMock(side_effect=fake_api) + proxmox_utils.HAS_PROXMOXER = True + connect_mock.side_effect = lambda: fake_api(mocker) with pytest.raises(SystemExit) as results: proxmox_snap.main() @@ -68,7 +72,8 @@ def test_create_snapshot_check_mode(capfd, mocker): assert not json.loads(out)['changed'] -def test_remove_snapshot_check_mode(capfd, mocker): +@patch('ansible_collections.community.general.plugins.module_utils.proxmox.ProxmoxAnsible._connect') +def test_remove_snapshot_check_mode(connect_mock, capfd, mocker): set_module_args({"hostname": "test-lxc", "api_user": "root@pam", "api_password": "secret", @@ -78,8 +83,8 @@ def test_remove_snapshot_check_mode(capfd, mocker): "timeout": "1", "force": True, "_ansible_check_mode": True}) - proxmox_snap.HAS_PROXMOXER = True - proxmox_snap.setup_api = mocker.MagicMock(side_effect=fake_api) + proxmox_utils.HAS_PROXMOXER = True + connect_mock.side_effect = lambda: fake_api(mocker) with pytest.raises(SystemExit) as results: proxmox_snap.main() diff --git a/tests/unit/plugins/modules/cloud/misc/test_proxmox_tasks_info.py b/tests/unit/plugins/modules/cloud/misc/test_proxmox_tasks_info.py index 1f296e8e88..c712460593 100644 --- a/tests/unit/plugins/modules/cloud/misc/test_proxmox_tasks_info.py +++ b/tests/unit/plugins/modules/cloud/misc/test_proxmox_tasks_info.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# # Copyright: (c) 2021, Andreas Botzner (@paginabianca) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # @@ -130,6 +131,14 @@ def test_without_required_parameters(connect_mock, capfd, mocker): assert json.loads(out)['failed'] +def mock_api_tasks_response(mocker): + m = mocker.MagicMock() + g = mocker.MagicMock() + m.nodes = mocker.MagicMock(return_value=g) + g.tasks.get = mocker.MagicMock(return_value=TASKS) + return m + + @patch('ansible_collections.community.general.plugins.module_utils.proxmox.ProxmoxAnsible._connect') def test_get_tasks(connect_mock, capfd, mocker): set_module_args({'api_host': 'proxmoxhost', @@ -137,14 +146,7 @@ def test_get_tasks(connect_mock, capfd, mocker): 'api_password': 'supersecret', 'node': NODE}) - def f(): - m = mocker.MagicMock() - g = mocker.MagicMock() - m.nodes = mocker.MagicMock(return_value=g) - g.tasks.get = mocker.MagicMock(return_value=TASKS) - return m - - connect_mock.side_effect = f + connect_mock.side_effect = lambda: mock_api_tasks_response(mocker) proxmox_utils.HAS_PROXMOXER = True with pytest.raises(SystemExit): @@ -163,14 +165,7 @@ def test_get_single_task(connect_mock, capfd, mocker): 'node': NODE, 'task': TASK_UPID}) - def f(): - m = mocker.MagicMock() - g = mocker.MagicMock() - m.nodes = mocker.MagicMock(return_value=g) - g.tasks.get = mocker.MagicMock(return_value=TASKS) - return m - - connect_mock.side_effect = f + connect_mock.side_effect = lambda: mock_api_tasks_response(mocker) proxmox_utils.HAS_PROXMOXER = True with pytest.raises(SystemExit): @@ -190,14 +185,7 @@ def test_get_non_existent_task(connect_mock, capfd, mocker): 'node': NODE, 'task': 'UPID:nonexistent'}) - def f(): - m = mocker.MagicMock() - g = mocker.MagicMock() - m.nodes = mocker.MagicMock(return_value=g) - g.tasks.get = mocker.MagicMock(return_value=TASKS) - return m - - connect_mock.side_effect = f + connect_mock.side_effect = lambda: mock_api_tasks_response(mocker) proxmox_utils.HAS_PROXMOXER = True with pytest.raises(SystemExit):