mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
Refactor all Proxmox modules to use shared module_utils
. (#4029)
* 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.
This commit is contained in:
parent
15f7e25b3c
commit
a61bdbadd5
10 changed files with 849 additions and 1072 deletions
3
changelogs/fragments/4029-proxmox-refactor.yml
Normal file
3
changelogs/fragments/4029-proxmox-refactor.yml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
---
|
||||||
|
minor_changes:
|
||||||
|
- proxmox modules - move common code into ``module_utils`` (https://github.com/ansible-collections/community.general/pull/4029).
|
|
@ -21,6 +21,8 @@ except ImportError:
|
||||||
|
|
||||||
|
|
||||||
from ansible.module_utils.basic import env_fallback, missing_required_lib
|
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():
|
def proxmox_auth_argument_spec():
|
||||||
|
@ -98,3 +100,46 @@ class ProxmoxAnsible(object):
|
||||||
return ProxmoxAPI(api_host, verify_ssl=validate_certs, **auth_args)
|
return ProxmoxAPI(api_host, verify_ssl=validate_certs, **auth_args)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.module.fail_json(msg='%s' % e, exception=traceback.format_exc())
|
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)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
# Copyright: Ansible Project
|
# Copyright: Ansible Project
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
# 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
|
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.basic import AnsibleModule, env_fallback
|
||||||
from ansible.module_utils.common.text.converters import to_native
|
from ansible.module_utils.common.text.converters import to_native
|
||||||
|
|
||||||
from ansible_collections.community.general.plugins.module_utils.proxmox import (
|
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
|
VZ_TYPE = None
|
||||||
|
|
||||||
|
|
||||||
def get_nextvmid(module, proxmox):
|
class ProxmoxLxcAnsible(ProxmoxAnsible):
|
||||||
try:
|
def content_check(self, node, ostemplate, template_store):
|
||||||
vmid = proxmox.cluster.nextid.get()
|
return [True for cnt in self.proxmox_api.nodes(node).storage(template_store).content.get() if cnt['volid'] == ostemplate]
|
||||||
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 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):
|
def create_instance(self, vmid, node, disk, storage, cpus, memory, swap, timeout, clone, **kwargs):
|
||||||
return [vm['vmid'] for vm in proxmox.cluster.resources.get(type='vm') if 'name' in vm and vm['name'] == hostname]
|
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):
|
if VZ_TYPE == 'lxc':
|
||||||
return [vm for vm in proxmox.cluster.resources.get(type='vm') if vm['vmid'] == int(vmid)]
|
kwargs['cpulimit'] = cpus
|
||||||
|
kwargs['rootfs'] = disk
|
||||||
|
if 'netif' in kwargs:
|
||||||
def content_check(proxmox, node, ostemplate, template_store):
|
kwargs.update(kwargs['netif'])
|
||||||
return [True for cnt in proxmox.nodes(node).storage(template_store).content.get() if cnt['volid'] == ostemplate]
|
del kwargs['netif']
|
||||||
|
if 'mounts' in kwargs:
|
||||||
|
kwargs.update(kwargs['mounts'])
|
||||||
def is_template_container(proxmox, node, vmid):
|
del kwargs['mounts']
|
||||||
"""Check if the specified container is a template."""
|
if 'pubkey' in kwargs:
|
||||||
proxmox_node = proxmox.nodes(node)
|
if self.version() >= LooseVersion('4.2'):
|
||||||
config = getattr(proxmox_node, VZ_TYPE)(vmid).config.get()
|
kwargs['ssh-public-keys'] = kwargs['pubkey']
|
||||||
return config['template']
|
del kwargs['pubkey']
|
||||||
|
|
||||||
|
|
||||||
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'
|
|
||||||
else:
|
else:
|
||||||
clone_parameters['full'] = '0'
|
kwargs['cpus'] = cpus
|
||||||
for param in valid_clone_parameters:
|
kwargs['disk'] = disk
|
||||||
if module.params[param] is not None:
|
|
||||||
clone_parameters[param] = module.params[param]
|
|
||||||
|
|
||||||
taskid = getattr(proxmox_node, VZ_TYPE)(clone).clone.post(newid=vmid, **clone_parameters)
|
if clone is not None:
|
||||||
else:
|
if VZ_TYPE != 'lxc':
|
||||||
taskid = getattr(proxmox_node, VZ_TYPE).create(vmid=vmid, storage=storage, memory=memory, swap=swap, **kwargs)
|
self.module.fail_json(changed=False, msg="Clone operator is only supported for LXC enabled proxmox clusters.")
|
||||||
|
|
||||||
while timeout:
|
clone_is_template = self.is_template_container(node, clone)
|
||||||
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])
|
|
||||||
|
|
||||||
time.sleep(1)
|
# By default, create a full copy only when the cloned container is not a template.
|
||||||
return False
|
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):
|
if self.module.params['clone_type'] == 'linked':
|
||||||
taskid = getattr(proxmox.nodes(vm[0]['node']), VZ_TYPE)(vmid).status.start.post()
|
if not clone_is_template:
|
||||||
while timeout:
|
self.module.fail_json(changed=False, msg="'linked' clone type is specified, but cloned container is not a template container.")
|
||||||
if (proxmox.nodes(vm[0]['node']).tasks(taskid).status.get()['status'] == 'stopped' and
|
# Don't need to do more, by default create_full_copy is set to false already
|
||||||
proxmox.nodes(vm[0]['node']).tasks(taskid).status.get()['exitstatus'] == 'OK'):
|
elif self.module.params['clone_type'] == 'opportunistic':
|
||||||
return True
|
if not clone_is_template:
|
||||||
timeout -= 1
|
# Cloned container is not a template, so we need our 'storage' parameter
|
||||||
if timeout == 0:
|
valid_clone_parameters.append('storage')
|
||||||
module.fail_json(msg='Reached timeout while waiting for starting VM. Last line in task before timeout: %s' %
|
elif self.module.params['clone_type'] == 'full':
|
||||||
proxmox.nodes(vm[0]['node']).tasks(taskid).log.get()[:1])
|
create_full_copy = True
|
||||||
|
valid_clone_parameters.append('storage')
|
||||||
|
|
||||||
time.sleep(1)
|
clone_parameters = {}
|
||||||
return False
|
|
||||||
|
|
||||||
|
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):
|
taskid = getattr(proxmox_node, VZ_TYPE)(clone).clone.post(newid=vmid, **clone_parameters)
|
||||||
if force:
|
else:
|
||||||
taskid = getattr(proxmox.nodes(vm[0]['node']), VZ_TYPE)(vmid).status.shutdown.post(forceStop=1)
|
taskid = getattr(proxmox_node, VZ_TYPE).create(vmid=vmid, storage=storage, memory=memory, swap=swap, **kwargs)
|
||||||
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])
|
|
||||||
|
|
||||||
time.sleep(1)
|
while timeout:
|
||||||
return False
|
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):
|
def start_instance(self, vm, vmid, timeout):
|
||||||
taskid = getattr(proxmox.nodes(vm[0]['node']), VZ_TYPE)(vmid).status.umount.post()
|
taskid = getattr(self.proxmox_api.nodes(vm['node']), VZ_TYPE)(vmid).status.start.post()
|
||||||
while timeout:
|
while timeout:
|
||||||
if (proxmox.nodes(vm[0]['node']).tasks(taskid).status.get()['status'] == 'stopped' and
|
if (self.proxmox_api.nodes(vm['node']).tasks(taskid).status.get()['status'] == 'stopped' and
|
||||||
proxmox.nodes(vm[0]['node']).tasks(taskid).status.get()['exitstatus'] == 'OK'):
|
self.proxmox_api.nodes(vm['node']).tasks(taskid).status.get()['exitstatus'] == 'OK'):
|
||||||
return True
|
return True
|
||||||
timeout -= 1
|
timeout -= 1
|
||||||
if timeout == 0:
|
if timeout == 0:
|
||||||
module.fail_json(msg='Reached timeout while waiting for unmounting VM. Last line in task before timeout: %s' %
|
self.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])
|
self.proxmox_api.nodes(vm['node']).tasks(taskid).log.get()[:1])
|
||||||
|
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
return False
|
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():
|
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(
|
module = AnsibleModule(
|
||||||
argument_spec=dict(
|
argument_spec=module_args,
|
||||||
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']),
|
|
||||||
),
|
|
||||||
required_if=[
|
required_if=[
|
||||||
('state', 'present', ['node', 'hostname']),
|
('state', 'present', ['node', 'hostname']),
|
||||||
('state', 'present', ('clone', 'ostemplate'), True), # Require one of clone and ostemplate. Together with mutually_exclusive this ensures that we
|
('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.
|
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:
|
proxmox = ProxmoxLxcAnsible(module)
|
||||||
module.fail_json(msg='proxmoxer required for this module')
|
|
||||||
|
global VZ_TYPE
|
||||||
|
VZ_TYPE = 'openvz' if proxmox.version() < LooseVersion('4.0') else 'lxc'
|
||||||
|
|
||||||
state = module.params['state']
|
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']
|
vmid = module.params['vmid']
|
||||||
validate_certs = module.params['validate_certs']
|
|
||||||
node = module.params['node']
|
node = module.params['node']
|
||||||
disk = module.params['disk']
|
disk = module.params['disk']
|
||||||
cpus = module.params['cpus']
|
cpus = module.params['cpus']
|
||||||
|
@ -664,68 +621,54 @@ def main():
|
||||||
if module.params[param] is None:
|
if module.params[param] is None:
|
||||||
module.params[param] = value
|
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 vmid not set get the Next VM id from ProxmoxAPI
|
||||||
# If hostname is set get the VM id from ProxmoxAPI
|
# If hostname is set get the VM id from ProxmoxAPI
|
||||||
if not vmid and state == 'present':
|
if not vmid and state == 'present':
|
||||||
vmid = get_nextvmid(module, proxmox)
|
vmid = proxmox.get_nextvmid()
|
||||||
elif not vmid and hostname:
|
elif not vmid and hostname:
|
||||||
hosts = get_vmid(proxmox, hostname)
|
vmid = proxmox.get_vmid(hostname, choose_first_if_multiple=True)
|
||||||
if len(hosts) == 0:
|
|
||||||
module.fail_json(msg="Vmid could not be fetched => Hostname doesn't exist (action: %s)" % state)
|
|
||||||
vmid = hosts[0]
|
|
||||||
elif not vmid:
|
elif not vmid:
|
||||||
module.exit_json(changed=False, msg="Vmid could not be fetched for the following action: %s" % state)
|
module.exit_json(changed=False, msg="Vmid could not be fetched for the following action: %s" % state)
|
||||||
|
|
||||||
# Create a new container
|
# Create a new container
|
||||||
if state == 'present' and clone is None:
|
if state == 'present' and clone is None:
|
||||||
try:
|
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)
|
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 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']:
|
if (not module.params['vmid'] and
|
||||||
module.exit_json(changed=False, msg="VM with hostname %s already exists and has ID number %s" % (hostname, get_vmid(proxmox, hostname)[0]))
|
proxmox.get_vmid(hostname, ignore_missing=True, choose_first_if_multiple=True) and
|
||||||
elif not node_check(proxmox, node):
|
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)
|
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.fail_json(msg="ostemplate '%s' not exists on node %s and storage %s"
|
||||||
% (module.params['ostemplate'], node, template_store))
|
% (module.params['ostemplate'], node, template_store))
|
||||||
except Exception as e:
|
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))
|
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:
|
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,
|
||||||
cores=module.params['cores'],
|
cores=module.params['cores'],
|
||||||
pool=module.params['pool'],
|
pool=module.params['pool'],
|
||||||
password=module.params['password'],
|
password=module.params['password'],
|
||||||
hostname=module.params['hostname'],
|
hostname=module.params['hostname'],
|
||||||
ostemplate=module.params['ostemplate'],
|
ostemplate=module.params['ostemplate'],
|
||||||
netif=module.params['netif'],
|
netif=module.params['netif'],
|
||||||
mounts=module.params['mounts'],
|
mounts=module.params['mounts'],
|
||||||
ip_address=module.params['ip_address'],
|
ip_address=module.params['ip_address'],
|
||||||
onboot=ansible_to_proxmox_bool(module.params['onboot']),
|
onboot=ansible_to_proxmox_bool(module.params['onboot']),
|
||||||
cpuunits=module.params['cpuunits'],
|
cpuunits=module.params['cpuunits'],
|
||||||
nameserver=module.params['nameserver'],
|
nameserver=module.params['nameserver'],
|
||||||
searchdomain=module.params['searchdomain'],
|
searchdomain=module.params['searchdomain'],
|
||||||
force=ansible_to_proxmox_bool(module.params['force']),
|
force=ansible_to_proxmox_bool(module.params['force']),
|
||||||
pubkey=module.params['pubkey'],
|
pubkey=module.params['pubkey'],
|
||||||
features=",".join(module.params['features']) if module.params['features'] is not None else None,
|
features=",".join(module.params['features']) if module.params['features'] is not None else None,
|
||||||
unprivileged=ansible_to_proxmox_bool(module.params['unprivileged']),
|
unprivileged=ansible_to_proxmox_bool(module.params['unprivileged']),
|
||||||
description=module.params['description'],
|
description=module.params['description'],
|
||||||
hookscript=module.params['hookscript'])
|
hookscript=module.params['hookscript'])
|
||||||
|
|
||||||
module.exit_json(changed=True, msg="Deployed VM %s from template %s" % (vmid, module.params['ostemplate']))
|
module.exit_json(changed=True, msg="Deployed VM %s from template %s" % (vmid, module.params['ostemplate']))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -734,18 +677,21 @@ def main():
|
||||||
# Clone a container
|
# Clone a container
|
||||||
elif state == 'present' and clone is not None:
|
elif state == 'present' and clone is not None:
|
||||||
try:
|
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)
|
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 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']:
|
if (not module.params['vmid'] and
|
||||||
module.exit_json(changed=False, msg="VM with hostname %s already exists and has ID number %s" % (hostname, get_vmid(proxmox, hostname)[0]))
|
proxmox.get_vmid(hostname, ignore_missing=True, choose_first_if_multiple=True) and
|
||||||
if not get_instance(proxmox, clone):
|
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")
|
module.exit_json(changed=False, msg="Container to be cloned does not exist")
|
||||||
except Exception as e:
|
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))
|
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:
|
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))
|
module.exit_json(changed=True, msg="Cloned VM %s from %s" % (vmid, clone))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -753,64 +699,60 @@ def main():
|
||||||
|
|
||||||
elif state == 'started':
|
elif state == 'started':
|
||||||
try:
|
try:
|
||||||
vm = get_instance(proxmox, vmid)
|
vm = proxmox.get_vm(vmid)
|
||||||
if not vm:
|
if getattr(proxmox.proxmox_api.nodes(vm['node']), VZ_TYPE)(vmid).status.current.get()['status'] == 'running':
|
||||||
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':
|
|
||||||
module.exit_json(changed=False, msg="VM %s is already running" % vmid)
|
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)
|
module.exit_json(changed=True, msg="VM %s started" % vmid)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
module.fail_json(msg="starting of VM %s failed with exception: %s" % (vmid, e))
|
module.fail_json(msg="starting of VM %s failed with exception: %s" % (vmid, e))
|
||||||
|
|
||||||
elif state == 'stopped':
|
elif state == 'stopped':
|
||||||
try:
|
try:
|
||||||
vm = get_instance(proxmox, vmid)
|
vm = proxmox.get_vm(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'] == 'mounted':
|
if getattr(proxmox.proxmox_api.nodes(vm['node']), VZ_TYPE)(vmid).status.current.get()['status'] == 'mounted':
|
||||||
if module.params['force']:
|
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)
|
module.exit_json(changed=True, msg="VM %s is shutting down" % vmid)
|
||||||
else:
|
else:
|
||||||
module.exit_json(changed=False, msg=("VM %s is already shutdown, but mounted. "
|
module.exit_json(changed=False, msg=("VM %s is already shutdown, but mounted. "
|
||||||
"You can use force option to umount it.") % vmid)
|
"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)
|
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)
|
module.exit_json(changed=True, msg="VM %s is shutting down" % vmid)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
module.fail_json(msg="stopping of VM %s failed with exception: %s" % (vmid, e))
|
module.fail_json(msg="stopping of VM %s failed with exception: %s" % (vmid, e))
|
||||||
|
|
||||||
elif state == 'restarted':
|
elif state == 'restarted':
|
||||||
try:
|
try:
|
||||||
vm = get_instance(proxmox, vmid)
|
vm = proxmox.get_vm(vmid)
|
||||||
if not vm:
|
|
||||||
module.fail_json(msg='VM with vmid = %s not exists in cluster' % vmid)
|
vm_status = getattr(proxmox.proxmox_api.nodes(vm['node']), VZ_TYPE)(vmid).status.current.get()['status']
|
||||||
if (getattr(proxmox.nodes(vm[0]['node']), VZ_TYPE)(vmid).status.current.get()['status'] == 'stopped' or
|
if vm_status in ['stopped', 'mounted']:
|
||||||
getattr(proxmox.nodes(vm[0]['node']), VZ_TYPE)(vmid).status.current.get()['status'] == 'mounted'):
|
|
||||||
module.exit_json(changed=False, msg="VM %s is not running" % vmid)
|
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
|
if (proxmox.stop_instance(vm, vmid, timeout, force=module.params['force']) and
|
||||||
start_instance(module, proxmox, vm, vmid, timeout)):
|
proxmox.start_instance(vm, vmid, timeout)):
|
||||||
module.exit_json(changed=True, msg="VM %s is restarted" % vmid)
|
module.exit_json(changed=True, msg="VM %s is restarted" % vmid)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
module.fail_json(msg="restarting of VM %s failed with exception: %s" % (vmid, e))
|
module.fail_json(msg="restarting of VM %s failed with exception: %s" % (vmid, e))
|
||||||
|
|
||||||
elif state == 'absent':
|
elif state == 'absent':
|
||||||
try:
|
try:
|
||||||
vm = get_instance(proxmox, vmid)
|
vm = proxmox.get_vm(vmid, ignore_missing=True)
|
||||||
if not vm:
|
if not vm:
|
||||||
module.exit_json(changed=False, msg="VM %s does not exist" % vmid)
|
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)
|
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)
|
module.exit_json(changed=False, msg="VM %s is mounted. Stop it with force option before deletion." % vmid)
|
||||||
|
|
||||||
delete_params = {}
|
delete_params = {}
|
||||||
|
@ -818,16 +760,16 @@ def main():
|
||||||
if module.params['purge']:
|
if module.params['purge']:
|
||||||
delete_params['purge'] = 1
|
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:
|
while timeout:
|
||||||
if (proxmox.nodes(vm[0]['node']).tasks(taskid).status.get()['status'] == 'stopped' and
|
task_status = proxmox.proxmox_api.nodes(vm['node']).tasks(taskid).status.get()
|
||||||
proxmox.nodes(vm[0]['node']).tasks(taskid).status.get()['exitstatus'] == 'OK'):
|
if (task_status['status'] == 'stopped' and task_status['exitstatus'] == 'OK'):
|
||||||
module.exit_json(changed=True, msg="VM %s removed" % vmid)
|
module.exit_json(changed=True, msg="VM %s removed" % vmid)
|
||||||
timeout -= 1
|
timeout -= 1
|
||||||
if timeout == 0:
|
if timeout == 0:
|
||||||
module.fail_json(msg='Reached timeout while waiting for removing VM. Last line in task before timeout: %s'
|
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)
|
time.sleep(1)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
# Copyright: (c) 2016, Abdoul Bah (@helldorado) <bahabdoul at gmail.com>
|
# Copyright: (c) 2016, Abdoul Bah (@helldorado) <bahabdoul at gmail.com>
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
# 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.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.version import LooseVersion
|
||||||
|
from ansible_collections.community.general.plugins.module_utils.proxmox import (proxmox_auth_argument_spec, ProxmoxAnsible)
|
||||||
|
|
||||||
try:
|
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||||
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.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):
|
def parse_mac(netstr):
|
||||||
return re.search('=(.*?),', netstr).group(1)
|
return re.search('=(.*?),', netstr).group(1)
|
||||||
|
|
||||||
|
@ -802,276 +742,294 @@ def parse_dev(devstr):
|
||||||
return re.search('(.*?)(,|$)', devstr).group(1)
|
return re.search('(.*?)(,|$)', devstr).group(1)
|
||||||
|
|
||||||
|
|
||||||
def settings(proxmox, vmid, node, **kwargs):
|
class ProxmoxKvmAnsible(ProxmoxAnsible):
|
||||||
proxmox_node = proxmox.nodes(node)
|
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.
|
# 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 = 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):
|
results['mac'] = mac
|
||||||
timeout = module.params['timeout']
|
results['devices'] = devices
|
||||||
|
results['vmid'] = int(vmid)
|
||||||
|
|
||||||
while timeout:
|
def settings(self, vmid, node, **kwargs):
|
||||||
task = proxmox.nodes(node).tasks(taskid).status.get()
|
proxmox_node = self.proxmox_api.nodes(node)
|
||||||
if task['status'] == 'stopped' and task['exitstatus'] == 'OK':
|
|
||||||
# Wait an extra second as the API can be a ahead of the hypervisor
|
# 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)
|
time.sleep(1)
|
||||||
return True
|
return False
|
||||||
timeout = timeout - 1
|
|
||||||
if timeout == 0:
|
|
||||||
break
|
|
||||||
time.sleep(1)
|
|
||||||
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):
|
# valide clone parameters
|
||||||
# Available only in PVE 4
|
valid_clone_params = ['format', 'full', 'pool', 'snapname', 'storage', 'target']
|
||||||
only_v4 = ['force', 'protection', 'skiplock']
|
clone_params = {}
|
||||||
only_v6 = ['ciuser', 'cipassword', 'sshkeys', 'ipconfig', 'tags']
|
# 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
|
proxmox_node = self.proxmox_api.nodes(node)
|
||||||
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 = 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.
|
version = self.version()
|
||||||
kwargs = dict((k, v) for k, v in kwargs.items() if v is not None)
|
pve_major_version = 3 if version < LooseVersion('4.0') else version.version[0]
|
||||||
kwargs.update(dict([k, int(v)] for k, v in kwargs.items() if isinstance(v, bool)))
|
|
||||||
|
|
||||||
# The features work only on PVE 4+
|
# The features work only on PVE 4+
|
||||||
if PVE_MAJOR_VERSION < 4:
|
if pve_major_version < 4:
|
||||||
for p in only_v4:
|
for p in only_v4:
|
||||||
if p in kwargs:
|
if p in kwargs:
|
||||||
del kwargs[p]
|
del kwargs[p]
|
||||||
|
|
||||||
# The features work only on PVE 6
|
# The features work only on PVE 6
|
||||||
if PVE_MAJOR_VERSION < 6:
|
if pve_major_version < 6:
|
||||||
for p in only_v6:
|
for p in only_v6:
|
||||||
if p in kwargs:
|
if p in kwargs:
|
||||||
del kwargs[p]
|
del kwargs[p]
|
||||||
|
|
||||||
# 'sshkeys' param expects an urlencoded string
|
# 'sshkeys' param expects an urlencoded string
|
||||||
if 'sshkeys' in kwargs:
|
if 'sshkeys' in kwargs:
|
||||||
urlencoded_ssh_keys = quote(kwargs['sshkeys'], safe='')
|
urlencoded_ssh_keys = quote(kwargs['sshkeys'], safe='')
|
||||||
kwargs['sshkeys'] = str(urlencoded_ssh_keys)
|
kwargs['sshkeys'] = str(urlencoded_ssh_keys)
|
||||||
|
|
||||||
# If update, don't update disk (virtio, ide, sata, scsi) and network interface
|
# If update, don't update disk (virtio, ide, sata, scsi) and network interface
|
||||||
# pool parameter not supported by qemu/<vmid>/config endpoint on "update" (PVE 6.2) - only with "create"
|
# pool parameter not supported by qemu/<vmid>/config endpoint on "update" (PVE 6.2) - only with "create"
|
||||||
if update:
|
if update:
|
||||||
if 'virtio' in kwargs:
|
if 'virtio' in kwargs:
|
||||||
del kwargs['virtio']
|
del kwargs['virtio']
|
||||||
if 'sata' in kwargs:
|
if 'sata' in kwargs:
|
||||||
del kwargs['sata']
|
del kwargs['sata']
|
||||||
if 'scsi' in kwargs:
|
if 'scsi' in kwargs:
|
||||||
del kwargs['scsi']
|
del kwargs['scsi']
|
||||||
if 'ide' in kwargs:
|
if 'ide' in kwargs:
|
||||||
del kwargs['ide']
|
del kwargs['ide']
|
||||||
if 'net' in kwargs:
|
if 'net' in kwargs:
|
||||||
del kwargs['net']
|
del kwargs['net']
|
||||||
if 'force' in kwargs:
|
if 'force' in kwargs:
|
||||||
del kwargs['force']
|
del kwargs['force']
|
||||||
if 'pool' in kwargs:
|
if 'pool' in kwargs:
|
||||||
del kwargs['pool']
|
del kwargs['pool']
|
||||||
|
|
||||||
# Convert all dict in kwargs to elements.
|
# 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 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()):
|
for k in list(kwargs.keys()):
|
||||||
if isinstance(kwargs[k], dict):
|
if isinstance(kwargs[k], dict):
|
||||||
kwargs.update(kwargs[k])
|
kwargs.update(kwargs[k])
|
||||||
del kwargs[k]
|
del kwargs[k]
|
||||||
|
|
||||||
# Rename numa_enabled to numa. According the API documentation
|
# Rename numa_enabled to numa. According the API documentation
|
||||||
if 'numa_enabled' in kwargs:
|
if 'numa_enabled' in kwargs:
|
||||||
kwargs['numa'] = kwargs['numa_enabled']
|
kwargs['numa'] = kwargs['numa_enabled']
|
||||||
del kwargs['numa_enabled']
|
del kwargs['numa_enabled']
|
||||||
|
|
||||||
# PVE api expects strings for the following params
|
# PVE api expects strings for the following params
|
||||||
if 'nameservers' in module.params:
|
if 'nameservers' in self.module.params:
|
||||||
nameservers = module.params.pop('nameservers')
|
nameservers = self.module.params.pop('nameservers')
|
||||||
if nameservers:
|
if nameservers:
|
||||||
kwargs['nameserver'] = ' '.join(nameservers)
|
kwargs['nameserver'] = ' '.join(nameservers)
|
||||||
if 'searchdomains' in module.params:
|
if 'searchdomains' in self.module.params:
|
||||||
searchdomains = module.params.pop('searchdomains')
|
searchdomains = self.module.params.pop('searchdomains')
|
||||||
if searchdomains:
|
if searchdomains:
|
||||||
kwargs['searchdomain'] = ' '.join(searchdomains)
|
kwargs['searchdomain'] = ' '.join(searchdomains)
|
||||||
|
|
||||||
# VM tags are expected to be valid and presented as a comma/semi-colon delimited string
|
# VM tags are expected to be valid and presented as a comma/semi-colon delimited string
|
||||||
if 'tags' in kwargs:
|
if 'tags' in kwargs:
|
||||||
re_tag = re.compile(r'^[a-z0-9_][a-z0-9_\-\+\.]*$')
|
re_tag = re.compile(r'^[a-z0-9_][a-z0-9_\-\+\.]*$')
|
||||||
for tag in kwargs['tags']:
|
for tag in kwargs['tags']:
|
||||||
if not re_tag.match(tag):
|
if not re_tag.match(tag):
|
||||||
module.fail_json(msg='%s is not a valid tag' % tag)
|
self.module.fail_json(msg='%s is not a valid tag' % tag)
|
||||||
kwargs['tags'] = ",".join(kwargs['tags'])
|
kwargs['tags'] = ",".join(kwargs['tags'])
|
||||||
|
|
||||||
# -args and skiplock require root@pam user - but can not use api tokens
|
# -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 self.module.params['api_user'] == "root@pam" and self.module.params['args'] is None:
|
||||||
if not update and module.params['proxmox_default_behavior'] == 'compatibility':
|
if not update and self.module.params['proxmox_default_behavior'] == 'compatibility':
|
||||||
kwargs['args'] = vm_args
|
kwargs['args'] = vm_args
|
||||||
elif module.params['api_user'] == "root@pam" and module.params['args'] is not None:
|
elif self.module.params['api_user'] == "root@pam" and self.module.params['args'] is not None:
|
||||||
kwargs['args'] = module.params['args']
|
kwargs['args'] = self.module.params['args']
|
||||||
elif module.params['api_user'] != "root@pam" and module.params['args'] is not None:
|
elif self.module.params['api_user'] != "root@pam" and self.module.params['args'] is not None:
|
||||||
module.fail_json(msg='args parameter require root@pam user. ')
|
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:
|
if self.module.params['api_user'] != "root@pam" and self.module.params['skiplock'] is not None:
|
||||||
module.fail_json(msg='skiplock parameter require root@pam user. ')
|
self.module.fail_json(msg='skiplock parameter require root@pam user. ')
|
||||||
|
|
||||||
if update:
|
if update:
|
||||||
if proxmox_node.qemu(vmid).config.set(name=name, memory=memory, cpu=cpu, cores=cores, sockets=sockets, **kwargs) is None:
|
if proxmox_node.qemu(vmid).config.set(name=name, memory=memory, cpu=cpu, cores=cores, sockets=sockets, **kwargs) is None:
|
||||||
return True
|
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:
|
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
|
return False
|
||||||
elif module.params['clone'] is not None:
|
return True
|
||||||
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)
|
|
||||||
|
|
||||||
if not wait_for_task(module, proxmox, node, taskid):
|
def start_vm(self, vm):
|
||||||
module.fail_json(msg='Reached timeout while waiting for creating VM. Last line in task before timeout: %s' %
|
vmid = vm['vmid']
|
||||||
proxmox_node.tasks(taskid).log.get()[:1])
|
proxmox_node = self.proxmox_api.nodes(vm['node'])
|
||||||
return False
|
taskid = proxmox_node.qemu(vmid).status.start.post()
|
||||||
return True
|
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 stop_vm(self, vm, force):
|
||||||
def start_vm(module, proxmox, vm):
|
vmid = vm['vmid']
|
||||||
vmid = vm[0]['vmid']
|
proxmox_node = self.proxmox_api.nodes(vm['node'])
|
||||||
proxmox_node = proxmox.nodes(vm[0]['node'])
|
taskid = proxmox_node.qemu(vmid).status.shutdown.post(forceStop=(1 if force else 0))
|
||||||
taskid = proxmox_node.qemu(vmid).status.start.post()
|
if not self.wait_for_task(vm['node'], taskid):
|
||||||
if not wait_for_task(module, proxmox, vm[0]['node'], taskid):
|
self.module.fail_json(msg='Reached timeout while waiting for stopping VM. Last line in task before timeout: %s' %
|
||||||
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])
|
||||||
proxmox_node.tasks(taskid).log.get()[:1])
|
return False
|
||||||
return False
|
return True
|
||||||
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 main():
|
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(
|
module = AnsibleModule(
|
||||||
argument_spec=dict(
|
argument_spec=module_args,
|
||||||
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']),
|
|
||||||
),
|
|
||||||
mutually_exclusive=[('delete', 'revert'), ('delete', 'update'), ('revert', 'update'), ('clone', 'update'), ('clone', 'delete'), ('clone', 'revert')],
|
mutually_exclusive=[('delete', 'revert'), ('delete', 'update'), ('revert', 'update'), ('clone', 'update'), ('clone', 'delete'), ('clone', 'revert')],
|
||||||
required_together=[('api_token_id', 'api_token_secret')],
|
required_together=[('api_token_id', 'api_token_secret')],
|
||||||
required_one_of=[('name', 'vmid'), ('api_password', 'api_token_id')],
|
required_one_of=[('name', 'vmid'), ('api_password', 'api_token_id')],
|
||||||
required_if=[('state', 'present', ['node'])],
|
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']
|
clone = module.params['clone']
|
||||||
cpu = module.params['cpu']
|
cpu = module.params['cpu']
|
||||||
cores = module.params['cores']
|
cores = module.params['cores']
|
||||||
|
@ -1112,153 +1070,136 @@ def main():
|
||||||
if module.params['format'] == 'unspecified':
|
if module.params['format'] == 'unspecified':
|
||||||
module.params['format'] = None
|
module.params['format'] = None
|
||||||
|
|
||||||
auth_args = {'user': api_user}
|
proxmox = ProxmoxKvmAnsible(module)
|
||||||
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)
|
|
||||||
|
|
||||||
# If vmid is not defined then retrieve its value from the vm name,
|
# 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.
|
# the cloned vm name or retrieve the next free VM id from ProxmoxAPI.
|
||||||
if not vmid:
|
if not vmid:
|
||||||
if state == 'present' and not update and not clone and not delete and not revert:
|
if state == 'present' and not update and not clone and not delete and not revert:
|
||||||
try:
|
try:
|
||||||
vmid = get_nextvmid(module, proxmox)
|
vmid = proxmox.get_nextvmid()
|
||||||
except Exception:
|
except Exception:
|
||||||
module.fail_json(msg="Can't get the next vmid for VM {0} automatically. Ensure your cluster state is good".format(name))
|
module.fail_json(msg="Can't get the next vmid for VM {0} automatically. Ensure your cluster state is good".format(name))
|
||||||
else:
|
else:
|
||||||
clone_target = clone or name
|
clone_target = clone or name
|
||||||
try:
|
vmid = proxmox.get_vmid(clone_target, ignore_missing=True, choose_first_if_multiple=True)
|
||||||
vmid = get_vmid(proxmox, clone_target)[0]
|
|
||||||
except Exception:
|
|
||||||
vmid = -1
|
|
||||||
|
|
||||||
if clone is not None:
|
if clone is not None:
|
||||||
# If newid is not defined then retrieve the next free id from ProxmoxAPI
|
# If newid is not defined then retrieve the next free id from ProxmoxAPI
|
||||||
if not newid:
|
if not newid:
|
||||||
try:
|
try:
|
||||||
newid = get_nextvmid(module, proxmox)
|
newid = proxmox.get_nextvmid()
|
||||||
except Exception:
|
except Exception:
|
||||||
module.fail_json(msg="Can't get the next vmid for VM {0} automatically. Ensure your cluster state is good".format(name))
|
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
|
# 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)
|
module.fail_json(msg='VM with name = %s does not exist in cluster' % clone)
|
||||||
|
|
||||||
# Ensure source VM id exists when cloning
|
# Ensure source VM id exists when cloning
|
||||||
if not get_vm(proxmox, vmid):
|
proxmox.get_vm(vmid)
|
||||||
module.fail_json(vmid=vmid, msg='VM with vmid = %s does not exist in cluster' % vmid)
|
|
||||||
|
|
||||||
# Ensure the choosen VM name doesn't already exist when cloning
|
# 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:
|
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
|
# 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))
|
module.exit_json(changed=False, vmid=vmid, msg="vmid %s with VM name %s already exists" % (newid, name))
|
||||||
|
|
||||||
if delete is not None:
|
if delete is not None:
|
||||||
try:
|
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))
|
module.exit_json(changed=True, vmid=vmid, msg="Settings has deleted on VM {0} with vmid {1}".format(name, vmid))
|
||||||
except Exception as e:
|
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))
|
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:
|
if revert is not None:
|
||||||
try:
|
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))
|
module.exit_json(changed=True, vmid=vmid, msg="Settings has reverted on VM {0} with vmid {1}".format(name, vmid))
|
||||||
except Exception as e:
|
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))
|
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':
|
if state == 'present':
|
||||||
try:
|
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)
|
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):
|
elif proxmox.get_vmid(name, ignore_missing=True, choose_first_if_multiple=True) and not (update or clone):
|
||||||
module.exit_json(changed=False, vmid=get_vmid(proxmox, name)[0], msg="VM with name <%s> already exists" % name)
|
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):
|
elif not (node, name):
|
||||||
module.fail_json(msg='node, name is mandatory for creating/updating vm')
|
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)
|
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,
|
proxmox.create_vm(vmid, newid, node, name, memory, cpu, cores, sockets, update,
|
||||||
acpi=module.params['acpi'],
|
acpi=module.params['acpi'],
|
||||||
agent=module.params['agent'],
|
agent=module.params['agent'],
|
||||||
autostart=module.params['autostart'],
|
autostart=module.params['autostart'],
|
||||||
balloon=module.params['balloon'],
|
balloon=module.params['balloon'],
|
||||||
bios=module.params['bios'],
|
bios=module.params['bios'],
|
||||||
boot=module.params['boot'],
|
boot=module.params['boot'],
|
||||||
bootdisk=module.params['bootdisk'],
|
bootdisk=module.params['bootdisk'],
|
||||||
cicustom=module.params['cicustom'],
|
cicustom=module.params['cicustom'],
|
||||||
cipassword=module.params['cipassword'],
|
cipassword=module.params['cipassword'],
|
||||||
citype=module.params['citype'],
|
citype=module.params['citype'],
|
||||||
ciuser=module.params['ciuser'],
|
ciuser=module.params['ciuser'],
|
||||||
cpulimit=module.params['cpulimit'],
|
cpulimit=module.params['cpulimit'],
|
||||||
cpuunits=module.params['cpuunits'],
|
cpuunits=module.params['cpuunits'],
|
||||||
description=module.params['description'],
|
description=module.params['description'],
|
||||||
digest=module.params['digest'],
|
digest=module.params['digest'],
|
||||||
force=module.params['force'],
|
force=module.params['force'],
|
||||||
freeze=module.params['freeze'],
|
freeze=module.params['freeze'],
|
||||||
hostpci=module.params['hostpci'],
|
hostpci=module.params['hostpci'],
|
||||||
hotplug=module.params['hotplug'],
|
hotplug=module.params['hotplug'],
|
||||||
hugepages=module.params['hugepages'],
|
hugepages=module.params['hugepages'],
|
||||||
ide=module.params['ide'],
|
ide=module.params['ide'],
|
||||||
ipconfig=module.params['ipconfig'],
|
ipconfig=module.params['ipconfig'],
|
||||||
keyboard=module.params['keyboard'],
|
keyboard=module.params['keyboard'],
|
||||||
kvm=module.params['kvm'],
|
kvm=module.params['kvm'],
|
||||||
localtime=module.params['localtime'],
|
localtime=module.params['localtime'],
|
||||||
lock=module.params['lock'],
|
lock=module.params['lock'],
|
||||||
machine=module.params['machine'],
|
machine=module.params['machine'],
|
||||||
migrate_downtime=module.params['migrate_downtime'],
|
migrate_downtime=module.params['migrate_downtime'],
|
||||||
migrate_speed=module.params['migrate_speed'],
|
migrate_speed=module.params['migrate_speed'],
|
||||||
net=module.params['net'],
|
net=module.params['net'],
|
||||||
numa=module.params['numa'],
|
numa=module.params['numa'],
|
||||||
numa_enabled=module.params['numa_enabled'],
|
numa_enabled=module.params['numa_enabled'],
|
||||||
onboot=module.params['onboot'],
|
onboot=module.params['onboot'],
|
||||||
ostype=module.params['ostype'],
|
ostype=module.params['ostype'],
|
||||||
parallel=module.params['parallel'],
|
parallel=module.params['parallel'],
|
||||||
pool=module.params['pool'],
|
pool=module.params['pool'],
|
||||||
protection=module.params['protection'],
|
protection=module.params['protection'],
|
||||||
reboot=module.params['reboot'],
|
reboot=module.params['reboot'],
|
||||||
sata=module.params['sata'],
|
sata=module.params['sata'],
|
||||||
scsi=module.params['scsi'],
|
scsi=module.params['scsi'],
|
||||||
scsihw=module.params['scsihw'],
|
scsihw=module.params['scsihw'],
|
||||||
serial=module.params['serial'],
|
serial=module.params['serial'],
|
||||||
shares=module.params['shares'],
|
shares=module.params['shares'],
|
||||||
skiplock=module.params['skiplock'],
|
skiplock=module.params['skiplock'],
|
||||||
smbios1=module.params['smbios'],
|
smbios1=module.params['smbios'],
|
||||||
snapname=module.params['snapname'],
|
snapname=module.params['snapname'],
|
||||||
sshkeys=module.params['sshkeys'],
|
sshkeys=module.params['sshkeys'],
|
||||||
startdate=module.params['startdate'],
|
startdate=module.params['startdate'],
|
||||||
startup=module.params['startup'],
|
startup=module.params['startup'],
|
||||||
tablet=module.params['tablet'],
|
tablet=module.params['tablet'],
|
||||||
tags=module.params['tags'],
|
tags=module.params['tags'],
|
||||||
target=module.params['target'],
|
target=module.params['target'],
|
||||||
tdf=module.params['tdf'],
|
tdf=module.params['tdf'],
|
||||||
template=module.params['template'],
|
template=module.params['template'],
|
||||||
vcpus=module.params['vcpus'],
|
vcpus=module.params['vcpus'],
|
||||||
vga=module.params['vga'],
|
vga=module.params['vga'],
|
||||||
virtio=module.params['virtio'],
|
virtio=module.params['virtio'],
|
||||||
watchdog=module.params['watchdog'])
|
watchdog=module.params['watchdog'])
|
||||||
|
|
||||||
if not clone:
|
if not clone:
|
||||||
get_vminfo(module, proxmox, node, vmid,
|
proxmox.get_vminfo(node, vmid,
|
||||||
ide=module.params['ide'],
|
ide=module.params['ide'],
|
||||||
net=module.params['net'],
|
net=module.params['net'],
|
||||||
sata=module.params['sata'],
|
sata=module.params['sata'],
|
||||||
scsi=module.params['scsi'],
|
scsi=module.params['scsi'],
|
||||||
virtio=module.params['virtio'])
|
virtio=module.params['virtio'])
|
||||||
if update:
|
if update:
|
||||||
module.exit_json(changed=True, vmid=vmid, msg="VM %s with vmid %s updated" % (name, vmid))
|
module.exit_json(changed=True, vmid=vmid, msg="VM %s with vmid %s updated" % (name, vmid))
|
||||||
elif clone is not None:
|
elif clone is not None:
|
||||||
|
@ -1276,16 +1217,14 @@ def main():
|
||||||
elif state == 'started':
|
elif state == 'started':
|
||||||
status = {}
|
status = {}
|
||||||
try:
|
try:
|
||||||
if -1 == vmid:
|
if not vmid:
|
||||||
module.fail_json(msg='VM with name = %s does not exist in cluster' % name)
|
module.fail_json(msg='VM with name = %s does not exist in cluster' % name)
|
||||||
vm = get_vm(proxmox, vmid)
|
vm = proxmox.get_vm(vmid)
|
||||||
if not vm:
|
status['status'] = vm['status']
|
||||||
module.fail_json(vmid=vmid, msg='VM with vmid <%s> does not exist in cluster' % vmid)
|
if vm['status'] == 'running':
|
||||||
status['status'] = vm[0]['status']
|
|
||||||
if vm[0]['status'] == 'running':
|
|
||||||
module.exit_json(changed=False, vmid=vmid, msg="VM %s is already running" % vmid, **status)
|
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)
|
module.exit_json(changed=True, vmid=vmid, msg="VM %s started" % vmid, **status)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
module.fail_json(vmid=vmid, msg="starting of VM %s failed with exception: %s" % (vmid, e), **status)
|
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':
|
elif state == 'stopped':
|
||||||
status = {}
|
status = {}
|
||||||
try:
|
try:
|
||||||
if -1 == vmid:
|
if not vmid:
|
||||||
module.fail_json(msg='VM with name = %s does not exist in cluster' % name)
|
module.fail_json(msg='VM with name = %s does not exist in cluster' % name)
|
||||||
|
|
||||||
vm = get_vm(proxmox, vmid)
|
vm = proxmox.get_vm(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']
|
status['status'] = vm['status']
|
||||||
if vm[0]['status'] == 'stopped':
|
if vm['status'] == 'stopped':
|
||||||
module.exit_json(changed=False, vmid=vmid, msg="VM %s is already stopped" % vmid, **status)
|
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)
|
module.exit_json(changed=True, vmid=vmid, msg="VM %s is shutting down" % vmid, **status)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
module.fail_json(vmid=vmid, msg="stopping of VM %s failed with exception: %s" % (vmid, e), **status)
|
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':
|
elif state == 'restarted':
|
||||||
status = {}
|
status = {}
|
||||||
try:
|
try:
|
||||||
if -1 == vmid:
|
if not vmid:
|
||||||
module.fail_json(msg='VM with name = %s does not exist in cluster' % name)
|
module.fail_json(msg='VM with name = %s does not exist in cluster' % name)
|
||||||
|
|
||||||
vm = get_vm(proxmox, vmid)
|
vm = proxmox.get_vm(vmid)
|
||||||
if not vm:
|
status['status'] = vm['status']
|
||||||
module.fail_json(vmid=vmid, msg='VM with vmid = %s does not exist in cluster' % vmid)
|
if vm['status'] == 'stopped':
|
||||||
status['status'] = vm[0]['status']
|
|
||||||
if vm[0]['status'] == 'stopped':
|
|
||||||
module.exit_json(changed=False, vmid=vmid, msg="VM %s is not running" % vmid, **status)
|
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)
|
module.exit_json(changed=True, vmid=vmid, msg="VM %s is restarted" % vmid, **status)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
module.fail_json(vmid=vmid, msg="restarting of VM %s failed with exception: %s" % (vmid, e), **status)
|
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':
|
elif state == 'absent':
|
||||||
status = {}
|
status = {}
|
||||||
try:
|
try:
|
||||||
vm = get_vm(proxmox, vmid)
|
vm = proxmox.get_vm(vmid, ignore_missing=True)
|
||||||
if not vm:
|
if not vm:
|
||||||
module.exit_json(changed=False, vmid=vmid)
|
module.exit_json(changed=False, vmid=vmid)
|
||||||
|
|
||||||
proxmox_node = proxmox.nodes(vm[0]['node'])
|
proxmox_node = proxmox.proxmox_api.nodes(vm['node'])
|
||||||
status['status'] = vm[0]['status']
|
status['status'] = vm['status']
|
||||||
if vm[0]['status'] == 'running':
|
if vm['status'] == 'running':
|
||||||
if module.params['force']:
|
if module.params['force']:
|
||||||
stop_vm(module, proxmox, vm, True)
|
proxmox.stop_vm(vm, True)
|
||||||
else:
|
else:
|
||||||
module.exit_json(changed=False, vmid=vmid, msg="VM %s is running. Stop it before deletion or use force=yes." % vmid)
|
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)
|
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' %
|
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])
|
proxmox_node.tasks(taskid).log.get()[:1])
|
||||||
else:
|
else:
|
||||||
|
@ -1352,14 +1287,12 @@ def main():
|
||||||
|
|
||||||
elif state == 'current':
|
elif state == 'current':
|
||||||
status = {}
|
status = {}
|
||||||
if -1 == vmid:
|
if not vmid:
|
||||||
module.fail_json(msg='VM with name = %s does not exist in cluster' % name)
|
module.fail_json(msg='VM with name = %s does not exist in cluster' % name)
|
||||||
vm = get_vm(proxmox, vmid)
|
vm = proxmox.get_vm(vmid)
|
||||||
if not vm:
|
|
||||||
module.fail_json(msg='VM with vmid = %s does not exist in cluster' % vmid)
|
|
||||||
if not name:
|
if not name:
|
||||||
name = vm[0]['name']
|
name = vm['name']
|
||||||
current = proxmox.nodes(vm[0]['node']).qemu(vmid).status.current.get()['status']
|
current = proxmox.proxmox_api.nodes(vm['node']).qemu(vmid).status.current.get()['status']
|
||||||
status['status'] = current
|
status['status'] = current
|
||||||
if status:
|
if status:
|
||||||
module.exit_json(changed=False, vmid=vmid, msg="VM %s with vmid = %s is %s" % (name, vmid, current), **status)
|
module.exit_json(changed=False, vmid=vmid, msg="VM %s with vmid = %s is %s" % (name, vmid, current), **status)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
# Copyright: (c) 2021, Lammert Hellinga (@Kogelvis) <lammert@hellinga.it>
|
# Copyright: (c) 2021, Lammert Hellinga (@Kogelvis) <lammert@hellinga.it>
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
# 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"
|
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.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):
|
class ProxmoxNicAnsible(ProxmoxAnsible):
|
||||||
try:
|
def update_nic(self, vmid, interface, model, **kwargs):
|
||||||
vms = [vm['vmid'] for vm in proxmox.cluster.resources.get(type='vm') if vm.get('name') == name]
|
vm = self.get_vm(vmid)
|
||||||
except Exception as e:
|
|
||||||
module.fail_json(msg='Error: %s occurred while retrieving VM with name = %s' % (e, name))
|
|
||||||
|
|
||||||
if not vms:
|
try:
|
||||||
module.fail_json(msg='No VM found with name: %s' % name)
|
vminfo = self.proxmox_api.nodes(vm['node']).qemu(vmid).config.get()
|
||||||
elif len(vms) > 1:
|
except Exception as e:
|
||||||
module.fail_json(msg='Multiple VMs found with name: %s, provide vmid instead' % name)
|
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):
|
for i in config:
|
||||||
return [vm for vm in proxmox.cluster.resources.get(type='vm') if vm['vmid'] == int(vmid)]
|
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):
|
# build nic config string
|
||||||
vm = get_vm(proxmox, vmid)
|
config_provided = "{0}={1}".format(model, current_mac)
|
||||||
|
else:
|
||||||
|
config_provided = model
|
||||||
|
|
||||||
try:
|
if kwargs['mac']:
|
||||||
vminfo = proxmox.nodes(vm[0]['node']).qemu(vmid).config.get()
|
config_provided = "{0}={1}".format(model, kwargs['mac'])
|
||||||
except Exception as e:
|
|
||||||
module.fail_json(msg='Getting information for VM with vmid = %s failed with exception: %s' % (vmid, e))
|
|
||||||
|
|
||||||
if interface in vminfo:
|
if kwargs['bridge']:
|
||||||
# Convert the current config to a dictionary
|
config_provided += ",bridge={0}".format(kwargs['bridge'])
|
||||||
config = vminfo[interface].split(',')
|
|
||||||
config.sort()
|
|
||||||
|
|
||||||
config_current = {}
|
if kwargs['firewall']:
|
||||||
|
config_provided += ",firewall=1"
|
||||||
|
|
||||||
for i in config:
|
if kwargs['link_down']:
|
||||||
kv = i.split('=')
|
config_provided += ',link_down=1'
|
||||||
try:
|
|
||||||
config_current[kv[0]] = kv[1]
|
|
||||||
except IndexError:
|
|
||||||
config_current[kv[0]] = ''
|
|
||||||
|
|
||||||
# determine the current model nic and mac-address
|
if kwargs['mtu']:
|
||||||
models = ['e1000', 'e1000-82540em', 'e1000-82544gc', 'e1000-82545em', 'i82551', 'i82557b',
|
config_provided += ",mtu={0}".format(kwargs['mtu'])
|
||||||
'i82559er', 'ne2k_isa', 'ne2k_pci', 'pcnet', 'rtl8139', 'virtio', 'vmxnet3']
|
if model != 'virtio':
|
||||||
current_model = set(models) & set(config_current.keys())
|
self.module.warn(
|
||||||
current_model = current_model.pop()
|
'Ignoring MTU for nic {0} on VM with vmid {1}, '
|
||||||
current_mac = config_current[current_model]
|
'model should be set to \'virtio\': '.format(interface, vmid))
|
||||||
|
|
||||||
# build nic config string
|
if kwargs['queues']:
|
||||||
config_provided = "{0}={1}".format(model, current_mac)
|
config_provided += ",queues={0}".format(kwargs['queues'])
|
||||||
else:
|
|
||||||
config_provided = model
|
|
||||||
|
|
||||||
if kwargs['mac']:
|
if kwargs['rate']:
|
||||||
config_provided = "{0}={1}".format(model, kwargs['mac'])
|
config_provided += ",rate={0}".format(kwargs['rate'])
|
||||||
|
|
||||||
if kwargs['bridge']:
|
if kwargs['tag']:
|
||||||
config_provided += ",bridge={0}".format(kwargs['bridge'])
|
config_provided += ",tag={0}".format(kwargs['tag'])
|
||||||
|
|
||||||
if kwargs['firewall']:
|
if kwargs['trunks']:
|
||||||
config_provided += ",firewall=1"
|
config_provided += ",trunks={0}".format(';'.join(str(x) for x in kwargs['trunks']))
|
||||||
|
|
||||||
if kwargs['link_down']:
|
net = {interface: config_provided}
|
||||||
config_provided += ',link_down=1'
|
vm = self.get_vm(vmid)
|
||||||
|
|
||||||
if kwargs['mtu']:
|
if ((interface not in vminfo) or (vminfo[interface] != config_provided)):
|
||||||
config_provided += ",mtu={0}".format(kwargs['mtu'])
|
if not self.module.check_mode:
|
||||||
if model != 'virtio':
|
self.proxmox_api.nodes(vm['node']).qemu(vmid).config.set(**net)
|
||||||
module.warn(
|
return True
|
||||||
'Ignoring MTU for nic {0} on VM with vmid {1}, '
|
|
||||||
'model should be set to \'virtio\': '.format(interface, vmid))
|
|
||||||
|
|
||||||
if kwargs['queues']:
|
return False
|
||||||
config_provided += ",queues={0}".format(kwargs['queues'])
|
|
||||||
|
|
||||||
if kwargs['rate']:
|
def delete_nic(self, vmid, interface):
|
||||||
config_provided += ",rate={0}".format(kwargs['rate'])
|
vm = self.get_vm(vmid)
|
||||||
|
vminfo = self.proxmox_api.nodes(vm['node']).qemu(vmid).config.get()
|
||||||
|
|
||||||
if kwargs['tag']:
|
if interface in vminfo:
|
||||||
config_provided += ",tag={0}".format(kwargs['tag'])
|
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']:
|
return False
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
@ -281,53 +257,33 @@ def main():
|
||||||
supports_check_mode=True,
|
supports_check_mode=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not HAS_PROXMOXER:
|
proxmox = ProxmoxNicAnsible(module)
|
||||||
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']
|
|
||||||
interface = module.params['interface']
|
interface = module.params['interface']
|
||||||
model = module.params['model']
|
model = module.params['model']
|
||||||
name = module.params['name']
|
name = module.params['name']
|
||||||
state = module.params['state']
|
state = module.params['state']
|
||||||
validate_certs = module.params['validate_certs']
|
|
||||||
vmid = module.params['vmid']
|
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 vmid is not defined then retrieve its value from the vm name,
|
||||||
if not vmid:
|
if not vmid:
|
||||||
vmid = get_vmid(module, proxmox, name)
|
vmid = proxmox.get_vmid(name)
|
||||||
|
|
||||||
# Ensure VM id exists
|
# Ensure VM id exists
|
||||||
if not get_vm(proxmox, vmid):
|
proxmox.get_vm(vmid)
|
||||||
module.fail_json(vmid=vmid, msg='VM with vmid = %s does not exist in cluster' % vmid)
|
|
||||||
|
|
||||||
if state == 'present':
|
if state == 'present':
|
||||||
try:
|
try:
|
||||||
if update_nic(module, proxmox, vmid, interface, model,
|
if proxmox.update_nic(vmid, interface, model,
|
||||||
bridge=module.params['bridge'],
|
bridge=module.params['bridge'],
|
||||||
firewall=module.params['firewall'],
|
firewall=module.params['firewall'],
|
||||||
link_down=module.params['link_down'],
|
link_down=module.params['link_down'],
|
||||||
mac=module.params['mac'],
|
mac=module.params['mac'],
|
||||||
mtu=module.params['mtu'],
|
mtu=module.params['mtu'],
|
||||||
queues=module.params['queues'],
|
queues=module.params['queues'],
|
||||||
rate=module.params['rate'],
|
rate=module.params['rate'],
|
||||||
tag=module.params['tag'],
|
tag=module.params['tag'],
|
||||||
trunks=module.params['trunks']):
|
trunks=module.params['trunks']):
|
||||||
module.exit_json(changed=True, vmid=vmid, msg="Nic {0} updated on VM with vmid {1}".format(interface, vmid))
|
module.exit_json(changed=True, vmid=vmid, msg="Nic {0} updated on VM with vmid {1}".format(interface, vmid))
|
||||||
else:
|
else:
|
||||||
module.exit_json(vmid=vmid, msg="Nic {0} unchanged on VM with vmid {1}".format(interface, vmid))
|
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':
|
elif state == 'absent':
|
||||||
try:
|
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))
|
module.exit_json(changed=True, vmid=vmid, msg="Nic {0} deleted on VM with vmid {1}".format(interface, vmid))
|
||||||
else:
|
else:
|
||||||
module.exit_json(vmid=vmid, msg="Nic {0} does not exist on VM with vmid {1}".format(interface, vmid))
|
module.exit_json(vmid=vmid, msg="Nic {0} does not exist on VM with vmid {1}".format(interface, vmid))
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
# Copyright: (c) 2020, Jeffrey van Pelt (@Thulium-Drake) <jeff@vanpelt.one>
|
# Copyright: (c) 2020, Jeffrey van Pelt (@Thulium-Drake) <jeff@vanpelt.one>
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
# 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.
|
- 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.
|
- Supports both KVM and LXC, OpenVZ has not been tested, as it is no longer supported on Proxmox VE.
|
||||||
options:
|
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:
|
hostname:
|
||||||
description:
|
description:
|
||||||
- The instance name.
|
- The instance name.
|
||||||
|
@ -41,11 +25,6 @@ options:
|
||||||
- The instance id.
|
- The instance id.
|
||||||
- If not set, will be fetched from PromoxAPI based on the hostname.
|
- If not set, will be fetched from PromoxAPI based on the hostname.
|
||||||
type: str
|
type: str
|
||||||
validate_certs:
|
|
||||||
description:
|
|
||||||
- Enable / disable https certificate verification.
|
|
||||||
type: bool
|
|
||||||
default: no
|
|
||||||
state:
|
state:
|
||||||
description:
|
description:
|
||||||
- Indicate desired state of the instance snapshot.
|
- Indicate desired state of the instance snapshot.
|
||||||
|
@ -83,6 +62,8 @@ notes:
|
||||||
- Supports C(check_mode).
|
- Supports C(check_mode).
|
||||||
requirements: [ "proxmoxer", "python >= 2.7", "requests" ]
|
requirements: [ "proxmoxer", "python >= 2.7", "requests" ]
|
||||||
author: Jeffrey van Pelt (@Thulium-Drake)
|
author: Jeffrey van Pelt (@Thulium-Drake)
|
||||||
|
extends_documentation_fragment:
|
||||||
|
- community.general.proxmox.documentation
|
||||||
'''
|
'''
|
||||||
|
|
||||||
EXAMPLES = r'''
|
EXAMPLES = r'''
|
||||||
|
@ -110,102 +91,76 @@ RETURN = r'''#'''
|
||||||
import time
|
import time
|
||||||
import traceback
|
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.basic import AnsibleModule, missing_required_lib, env_fallback
|
||||||
from ansible.module_utils.common.text.converters import to_native
|
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 snapshot_create(self, vm, vmid, timeout, snapname, description, vmstate):
|
||||||
def get_vmid(proxmox, hostname):
|
if self.module.check_mode:
|
||||||
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'):
|
|
||||||
return True
|
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)
|
if vm['type'] == 'lxc':
|
||||||
return False
|
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):
|
def snapshot_remove(self, vm, vmid, timeout, snapname, force):
|
||||||
if module.check_mode:
|
if self.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'):
|
|
||||||
return True
|
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)
|
taskid = self.snapshot(vm, vmid).delete(snapname, force=int(force))
|
||||||
return False
|
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])
|
||||||
|
|
||||||
|
time.sleep(1)
|
||||||
def setup_api(api_host, api_user, api_password, validate_certs):
|
return False
|
||||||
api = ProxmoxAPI(api_host, user=api_user, password=api_password, verify_ssl=validate_certs)
|
|
||||||
return api
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
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(
|
module = AnsibleModule(
|
||||||
argument_spec=dict(
|
argument_spec=module_args,
|
||||||
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'),
|
|
||||||
),
|
|
||||||
supports_check_mode=True
|
supports_check_mode=True
|
||||||
)
|
)
|
||||||
|
|
||||||
if not HAS_PROXMOXER:
|
proxmox = ProxmoxSnapAnsible(module)
|
||||||
module.fail_json(msg=missing_required_lib('proxmoxer'),
|
|
||||||
exception=PROXMOXER_IMP_ERR)
|
|
||||||
|
|
||||||
state = module.params['state']
|
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']
|
vmid = module.params['vmid']
|
||||||
validate_certs = module.params['validate_certs']
|
|
||||||
hostname = module.params['hostname']
|
hostname = module.params['hostname']
|
||||||
description = module.params['description']
|
description = module.params['description']
|
||||||
snapname = module.params['snapname']
|
snapname = module.params['snapname']
|
||||||
|
@ -213,37 +168,21 @@ def main():
|
||||||
force = module.params['force']
|
force = module.params['force']
|
||||||
vmstate = module.params['vmstate']
|
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 hostname is set get the VM id from ProxmoxAPI
|
||||||
if not vmid and hostname:
|
if not vmid and hostname:
|
||||||
hosts = get_vmid(proxmox, hostname)
|
vmid = proxmox.get_vmid(hostname, choose_first_if_multiple=True)
|
||||||
if len(hosts) == 0:
|
|
||||||
module.fail_json(msg="Vmid could not be fetched => Hostname does not exist (action: %s)" % state)
|
|
||||||
vmid = hosts[0]
|
|
||||||
elif not vmid:
|
elif not vmid:
|
||||||
module.exit_json(changed=False, msg="Vmid could not be fetched for the following action: %s" % state)
|
module.exit_json(changed=False, msg="Vmid could not be fetched for the following action: %s" % state)
|
||||||
|
|
||||||
vm = get_instance(proxmox, vmid)
|
vm = proxmox.get_vm(vmid)
|
||||||
|
|
||||||
global VZ_TYPE
|
|
||||||
VZ_TYPE = vm[0]['type']
|
|
||||||
|
|
||||||
if state == 'present':
|
if state == 'present':
|
||||||
try:
|
try:
|
||||||
vm = get_instance(proxmox, vmid)
|
for i in proxmox.snapshot(vm, vmid).get():
|
||||||
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():
|
|
||||||
if i['name'] == snapname:
|
if i['name'] == snapname:
|
||||||
module.exit_json(changed=False, msg="Snapshot %s is already present" % 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:
|
if module.check_mode:
|
||||||
module.exit_json(changed=False, msg="Snapshot %s would be created" % snapname)
|
module.exit_json(changed=False, msg="Snapshot %s would be created" % snapname)
|
||||||
else:
|
else:
|
||||||
|
@ -254,13 +193,9 @@ def main():
|
||||||
|
|
||||||
elif state == 'absent':
|
elif state == 'absent':
|
||||||
try:
|
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
|
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:
|
if i['name'] == snapname:
|
||||||
snap_exist = True
|
snap_exist = True
|
||||||
continue
|
continue
|
||||||
|
@ -268,7 +203,7 @@ def main():
|
||||||
if not snap_exist:
|
if not snap_exist:
|
||||||
module.exit_json(changed=False, msg="Snapshot %s does not exist" % snapname)
|
module.exit_json(changed=False, msg="Snapshot %s does not exist" % snapname)
|
||||||
else:
|
else:
|
||||||
if snapshot_remove(module, proxmox, vm, vmid, timeout, snapname, force):
|
if proxmox.snapshot_remove(vm, vmid, timeout, snapname, force):
|
||||||
if module.check_mode:
|
if module.check_mode:
|
||||||
module.exit_json(changed=False, msg="Snapshot %s would be removed" % snapname)
|
module.exit_json(changed=False, msg="Snapshot %s would be removed" % snapname)
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright: Ansible Project
|
# Copyright: Ansible Project
|
||||||
#
|
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
# 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
|
from __future__ import absolute_import, division, print_function
|
||||||
|
@ -117,112 +116,81 @@ EXAMPLES = '''
|
||||||
import os
|
import os
|
||||||
import time
|
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.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):
|
class ProxmoxTemplateAnsible(ProxmoxAnsible):
|
||||||
return [True for tmpl in proxmox.nodes(node).storage(storage).content.get()
|
def get_template(self, node, storage, content_type, template):
|
||||||
if tmpl['volid'] == '%s:%s/%s' % (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):
|
time.sleep(1)
|
||||||
"""
|
return False
|
||||||
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)
|
def upload_template(self, node, storage, content_type, realpath, timeout):
|
||||||
return False
|
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):
|
def delete_template(self, node, storage, content_type, template, timeout):
|
||||||
taskid = proxmox.nodes(node).storage(storage).upload.post(content=content_type, filename=open(realpath, 'rb'))
|
volid = '%s:%s/%s' % (storage, content_type, template)
|
||||||
return task_status(module, proxmox, node, taskid, timeout)
|
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.')
|
||||||
|
|
||||||
|
time.sleep(1)
|
||||||
def download_template(module, proxmox, node, storage, template, timeout):
|
return False
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
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(
|
module = AnsibleModule(
|
||||||
argument_spec=dict(
|
argument_spec=module_args,
|
||||||
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']),
|
|
||||||
),
|
|
||||||
required_together=[('api_token_id', 'api_token_secret')],
|
required_together=[('api_token_id', 'api_token_secret')],
|
||||||
required_one_of=[('api_password', 'api_token_id')],
|
required_one_of=[('api_password', 'api_token_id')],
|
||||||
required_if=[('state', 'absent', ['template'])]
|
required_if=[('state', 'absent', ['template'])]
|
||||||
)
|
)
|
||||||
|
|
||||||
if not HAS_PROXMOXER:
|
proxmox = ProxmoxTemplateAnsible(module)
|
||||||
module.fail_json(msg='proxmoxer required for this module')
|
|
||||||
|
|
||||||
state = module.params['state']
|
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']
|
node = module.params['node']
|
||||||
storage = module.params['storage']
|
storage = module.params['storage']
|
||||||
timeout = module.params['timeout']
|
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':
|
if state == 'present':
|
||||||
try:
|
try:
|
||||||
content_type = module.params['content_type']
|
content_type = module.params['content_type']
|
||||||
|
@ -235,21 +203,21 @@ def main():
|
||||||
if not template:
|
if not template:
|
||||||
module.fail_json(msg='template param for downloading appliance template is mandatory')
|
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))
|
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))
|
module.exit_json(changed=True, msg='template with volid=%s:%s/%s downloaded' % (storage, content_type, template))
|
||||||
|
|
||||||
template = os.path.basename(src)
|
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))
|
module.exit_json(changed=False, msg='template with volid=%s:%s/%s is already exists' % (storage, content_type, template))
|
||||||
elif not src:
|
elif not src:
|
||||||
module.fail_json(msg='src param to uploading template file is mandatory')
|
module.fail_json(msg='src param to uploading template file is mandatory')
|
||||||
elif not (os.path.exists(src) and os.path.isfile(src)):
|
elif not (os.path.exists(src) and os.path.isfile(src)):
|
||||||
module.fail_json(msg='template file on path %s not exists' % 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))
|
module.exit_json(changed=True, msg='template with volid=%s:%s/%s uploaded' % (storage, content_type, template))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
module.fail_json(msg="uploading/downloading of template %s failed with exception: %s" % (template, 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']
|
content_type = module.params['content_type']
|
||||||
template = module.params['template']
|
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))
|
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))
|
module.exit_json(changed=True, msg='template with volid=%s:%s/%s deleted' % (storage, content_type, template))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
module.fail_json(msg="deleting of template %s failed with exception: %s" % (template, e))
|
module.fail_json(msg="deleting of template %s failed with exception: %s" % (template, e))
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
# Copyright: (c) 2021, Ansible Project
|
# Copyright: (c) 2021, Ansible Project
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
# Copyright: (c) 2019, Ansible Project
|
# Copyright: (c) 2019, Ansible Project
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
# 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)
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
@ -6,8 +8,9 @@ __metaclass__ = type
|
||||||
import json
|
import json
|
||||||
import pytest
|
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
|
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
|
from ansible_collections.community.general.tests.unit.plugins.modules.utils import set_module_args
|
||||||
|
|
||||||
|
|
||||||
|
@ -32,8 +35,8 @@ def get_resources(type):
|
||||||
"status": "running"}]
|
"status": "running"}]
|
||||||
|
|
||||||
|
|
||||||
def fake_api(api_host, api_user, api_password, validate_certs):
|
def fake_api(mocker):
|
||||||
r = MagicMock()
|
r = mocker.MagicMock()
|
||||||
r.cluster.resources.get = MagicMock(side_effect=get_resources)
|
r.cluster.resources.get = MagicMock(side_effect=get_resources)
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
@ -48,7 +51,8 @@ def test_proxmox_snap_without_argument(capfd):
|
||||||
assert json.loads(out)['failed']
|
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",
|
set_module_args({"hostname": "test-lxc",
|
||||||
"api_user": "root@pam",
|
"api_user": "root@pam",
|
||||||
"api_password": "secret",
|
"api_password": "secret",
|
||||||
|
@ -58,8 +62,8 @@ def test_create_snapshot_check_mode(capfd, mocker):
|
||||||
"timeout": "1",
|
"timeout": "1",
|
||||||
"force": True,
|
"force": True,
|
||||||
"_ansible_check_mode": True})
|
"_ansible_check_mode": True})
|
||||||
proxmox_snap.HAS_PROXMOXER = True
|
proxmox_utils.HAS_PROXMOXER = True
|
||||||
proxmox_snap.setup_api = mocker.MagicMock(side_effect=fake_api)
|
connect_mock.side_effect = lambda: fake_api(mocker)
|
||||||
with pytest.raises(SystemExit) as results:
|
with pytest.raises(SystemExit) as results:
|
||||||
proxmox_snap.main()
|
proxmox_snap.main()
|
||||||
|
|
||||||
|
@ -68,7 +72,8 @@ def test_create_snapshot_check_mode(capfd, mocker):
|
||||||
assert not json.loads(out)['changed']
|
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",
|
set_module_args({"hostname": "test-lxc",
|
||||||
"api_user": "root@pam",
|
"api_user": "root@pam",
|
||||||
"api_password": "secret",
|
"api_password": "secret",
|
||||||
|
@ -78,8 +83,8 @@ def test_remove_snapshot_check_mode(capfd, mocker):
|
||||||
"timeout": "1",
|
"timeout": "1",
|
||||||
"force": True,
|
"force": True,
|
||||||
"_ansible_check_mode": True})
|
"_ansible_check_mode": True})
|
||||||
proxmox_snap.HAS_PROXMOXER = True
|
proxmox_utils.HAS_PROXMOXER = True
|
||||||
proxmox_snap.setup_api = mocker.MagicMock(side_effect=fake_api)
|
connect_mock.side_effect = lambda: fake_api(mocker)
|
||||||
with pytest.raises(SystemExit) as results:
|
with pytest.raises(SystemExit) as results:
|
||||||
proxmox_snap.main()
|
proxmox_snap.main()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
# Copyright: (c) 2021, Andreas Botzner (@paginabianca) <andreas at botzner dot com>
|
# Copyright: (c) 2021, Andreas Botzner (@paginabianca) <andreas at botzner dot com>
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
# 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']
|
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')
|
@patch('ansible_collections.community.general.plugins.module_utils.proxmox.ProxmoxAnsible._connect')
|
||||||
def test_get_tasks(connect_mock, capfd, mocker):
|
def test_get_tasks(connect_mock, capfd, mocker):
|
||||||
set_module_args({'api_host': 'proxmoxhost',
|
set_module_args({'api_host': 'proxmoxhost',
|
||||||
|
@ -137,14 +146,7 @@ def test_get_tasks(connect_mock, capfd, mocker):
|
||||||
'api_password': 'supersecret',
|
'api_password': 'supersecret',
|
||||||
'node': NODE})
|
'node': NODE})
|
||||||
|
|
||||||
def f():
|
connect_mock.side_effect = lambda: 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
|
|
||||||
|
|
||||||
connect_mock.side_effect = f
|
|
||||||
proxmox_utils.HAS_PROXMOXER = True
|
proxmox_utils.HAS_PROXMOXER = True
|
||||||
|
|
||||||
with pytest.raises(SystemExit):
|
with pytest.raises(SystemExit):
|
||||||
|
@ -163,14 +165,7 @@ def test_get_single_task(connect_mock, capfd, mocker):
|
||||||
'node': NODE,
|
'node': NODE,
|
||||||
'task': TASK_UPID})
|
'task': TASK_UPID})
|
||||||
|
|
||||||
def f():
|
connect_mock.side_effect = lambda: 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
|
|
||||||
|
|
||||||
connect_mock.side_effect = f
|
|
||||||
proxmox_utils.HAS_PROXMOXER = True
|
proxmox_utils.HAS_PROXMOXER = True
|
||||||
|
|
||||||
with pytest.raises(SystemExit):
|
with pytest.raises(SystemExit):
|
||||||
|
@ -190,14 +185,7 @@ def test_get_non_existent_task(connect_mock, capfd, mocker):
|
||||||
'node': NODE,
|
'node': NODE,
|
||||||
'task': 'UPID:nonexistent'})
|
'task': 'UPID:nonexistent'})
|
||||||
|
|
||||||
def f():
|
connect_mock.side_effect = lambda: 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
|
|
||||||
|
|
||||||
connect_mock.side_effect = f
|
|
||||||
proxmox_utils.HAS_PROXMOXER = True
|
proxmox_utils.HAS_PROXMOXER = True
|
||||||
|
|
||||||
with pytest.raises(SystemExit):
|
with pytest.raises(SystemExit):
|
||||||
|
|
Loading…
Reference in a new issue