mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
proxmox: Add clone parameter (#3930)
* proxmox: Add clone parameter * Add changelog fragment * Add version_added Co-authored-by: Felix Fontein <felix@fontein.de> * Add PR URL to changelog fragment Co-authored-by: Felix Fontein <felix@fontein.de> * Clarify what content_check does * Split up try/except block to give a separate error message when creation pre-checks fail * Create seperate case for cloning * Prevent 'clone' argument from being removed * Fix double argument, add todo's * Check if to be cloned container actually exists * Adjust module options dependencies * Require 'storage' parameter when cloned container is not a template and ignore otherwise * Don't only create linked clones of template containers * Fix pylint errors * Add extra example * Minor language fix * Add clone_type parameter to specify cloning behaviour * I can't find if openvz nodes have this clone API, so just don't support it * Remove unrelated changes * Don't pass unused kwargs * Revert more unrelated changes * Remove required_together clone and clone_type because clone_type has a default choice * Fix clone_type reference Co-authored-by: Felix Fontein <felix@fontein.de> * Fix missing period Co-authored-by: Felix Fontein <felix@fontein.de> * Fix redundant period Co-authored-by: Felix Fontein <felix@fontein.de> * Fix redundant period Co-authored-by: Felix Fontein <felix@fontein.de> Co-authored-by: Felix Fontein <felix@fontein.de>
This commit is contained in:
parent
11205eefee
commit
b0c27f7a68
2 changed files with 136 additions and 8 deletions
2
changelogs/fragments/3930-proxmox-add-clone.yaml
Normal file
2
changelogs/fragments/3930-proxmox-add-clone.yaml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
minor_changes:
|
||||||
|
- proxmox - add ``clone`` parameter (https://github.com/ansible-collections/community.general/pull/3930).
|
|
@ -167,6 +167,25 @@ options:
|
||||||
- compatibility
|
- compatibility
|
||||||
- no_defaults
|
- no_defaults
|
||||||
version_added: "1.3.0"
|
version_added: "1.3.0"
|
||||||
|
clone:
|
||||||
|
description:
|
||||||
|
- ID of the container to be cloned.
|
||||||
|
- I(description), I(hostname), and I(pool) will be copied from the cloned container if not specified.
|
||||||
|
- The type of clone created is defined by the I(clone_type) parameter.
|
||||||
|
- This operator is only supported for Proxmox clusters that use LXC containerization (PVE version >= 4).
|
||||||
|
type: int
|
||||||
|
version_added: 4.3.0
|
||||||
|
clone_type:
|
||||||
|
description:
|
||||||
|
- Type of the clone created.
|
||||||
|
- C(full) creates a full clone, and I(storage) must be specified.
|
||||||
|
- C(linked) creates a linked clone, and the cloned container must be a template container.
|
||||||
|
- C(opportunistic) creates a linked clone if the cloned container is a template container, and a full clone if not.
|
||||||
|
I(storage) may be specified, if not it will fall back to the default.
|
||||||
|
type: str
|
||||||
|
choices: ['full', 'linked', 'opportunistic']
|
||||||
|
default: opportunistic
|
||||||
|
version_added: 4.3.0
|
||||||
author: Sergei Antipov (@UnderGreen)
|
author: Sergei Antipov (@UnderGreen)
|
||||||
extends_documentation_fragment:
|
extends_documentation_fragment:
|
||||||
- community.general.proxmox.documentation
|
- community.general.proxmox.documentation
|
||||||
|
@ -292,6 +311,28 @@ EXAMPLES = r'''
|
||||||
- nesting=1
|
- nesting=1
|
||||||
- mount=cifs,nfs
|
- mount=cifs,nfs
|
||||||
|
|
||||||
|
- name: >
|
||||||
|
Create a linked clone of the template container with id 100. The newly created container with be a
|
||||||
|
linked clone, because no storage parameter is defined
|
||||||
|
community.general.proxmox:
|
||||||
|
vmid: 201
|
||||||
|
node: uk-mc02
|
||||||
|
api_user: root@pam
|
||||||
|
api_password: 1q2w3e
|
||||||
|
api_host: node1
|
||||||
|
clone: 100
|
||||||
|
hostname: clone.example.org
|
||||||
|
|
||||||
|
- name: Create a full clone of the container with id 100
|
||||||
|
community.general.proxmox:
|
||||||
|
vmid: 201
|
||||||
|
node: uk-mc02
|
||||||
|
api_user: root@pam
|
||||||
|
api_password: 1q2w3e
|
||||||
|
api_host: node1
|
||||||
|
clone: 100
|
||||||
|
hostname: clone.example.org
|
||||||
|
storage: local
|
||||||
|
|
||||||
- name: Start container
|
- name: Start container
|
||||||
community.general.proxmox:
|
community.general.proxmox:
|
||||||
|
@ -389,6 +430,13 @@ def content_check(proxmox, node, ostemplate, template_store):
|
||||||
return [True for cnt in proxmox.nodes(node).storage(template_store).content.get() if cnt['volid'] == ostemplate]
|
return [True for cnt in proxmox.nodes(node).storage(template_store).content.get() if cnt['volid'] == ostemplate]
|
||||||
|
|
||||||
|
|
||||||
|
def is_template_container(proxmox, node, vmid):
|
||||||
|
"""Check if the specified container is a template."""
|
||||||
|
proxmox_node = proxmox.nodes(node)
|
||||||
|
config = getattr(proxmox_node, VZ_TYPE)(vmid).config.get()
|
||||||
|
return config['template']
|
||||||
|
|
||||||
|
|
||||||
def node_check(proxmox, node):
|
def node_check(proxmox, node):
|
||||||
return [True for nd in proxmox.nodes.get() if nd['node'] == node]
|
return [True for nd in proxmox.nodes.get() if nd['node'] == node]
|
||||||
|
|
||||||
|
@ -398,8 +446,10 @@ def proxmox_version(proxmox):
|
||||||
return LooseVersion(apireturn['version'])
|
return LooseVersion(apireturn['version'])
|
||||||
|
|
||||||
|
|
||||||
def create_instance(module, proxmox, vmid, node, disk, storage, cpus, memory, swap, timeout, **kwargs):
|
def create_instance(module, proxmox, vmid, node, disk, storage, cpus, memory, swap, timeout, clone, **kwargs):
|
||||||
proxmox_node = proxmox.nodes(node)
|
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)
|
kwargs = dict((k, v) for k, v in kwargs.items() if v is not None)
|
||||||
|
|
||||||
if VZ_TYPE == 'lxc':
|
if VZ_TYPE == 'lxc':
|
||||||
|
@ -419,7 +469,49 @@ def create_instance(module, proxmox, vmid, node, disk, storage, cpus, memory, sw
|
||||||
kwargs['cpus'] = cpus
|
kwargs['cpus'] = cpus
|
||||||
kwargs['disk'] = disk
|
kwargs['disk'] = disk
|
||||||
|
|
||||||
taskid = getattr(proxmox_node, VZ_TYPE).create(vmid=vmid, storage=storage, memory=memory, swap=swap, **kwargs)
|
if clone is not None:
|
||||||
|
if VZ_TYPE != 'lxc':
|
||||||
|
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:
|
||||||
|
clone_parameters['full'] = '0'
|
||||||
|
for param in valid_clone_parameters:
|
||||||
|
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)
|
||||||
|
else:
|
||||||
|
taskid = getattr(proxmox_node, VZ_TYPE).create(vmid=vmid, storage=storage, memory=memory, swap=swap, **kwargs)
|
||||||
|
|
||||||
while timeout:
|
while timeout:
|
||||||
if (proxmox_node.tasks(taskid).status.get()['status'] == 'stopped' and
|
if (proxmox_node.tasks(taskid).status.get()['status'] == 'stopped' and
|
||||||
|
@ -520,10 +612,19 @@ def main():
|
||||||
description=dict(type='str'),
|
description=dict(type='str'),
|
||||||
hookscript=dict(type='str'),
|
hookscript=dict(type='str'),
|
||||||
proxmox_default_behavior=dict(type='str', default='no_defaults', choices=['compatibility', 'no_defaults']),
|
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=[('state', 'present', ['node', 'hostname', 'ostemplate'])],
|
required_if=[
|
||||||
required_together=[('api_token_id', 'api_token_secret')],
|
('state', 'present', ['node', 'hostname']),
|
||||||
|
('state', 'present', ('clone', 'ostemplate'), True), # Require one of clone and ostemplate. Together with mutually_exclusive this ensures that we
|
||||||
|
# either clone a container or create a new one from a template file.
|
||||||
|
],
|
||||||
|
required_together=[
|
||||||
|
('api_token_id', 'api_token_secret')
|
||||||
|
],
|
||||||
required_one_of=[('api_password', 'api_token_id')],
|
required_one_of=[('api_password', 'api_token_id')],
|
||||||
|
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:
|
if not HAS_PROXMOXER:
|
||||||
|
@ -547,6 +648,7 @@ def main():
|
||||||
if module.params['ostemplate'] is not None:
|
if module.params['ostemplate'] is not None:
|
||||||
template_store = module.params['ostemplate'].split(":")[0]
|
template_store = module.params['ostemplate'].split(":")[0]
|
||||||
timeout = module.params['timeout']
|
timeout = module.params['timeout']
|
||||||
|
clone = module.params['clone']
|
||||||
|
|
||||||
if module.params['proxmox_default_behavior'] == 'compatibility':
|
if module.params['proxmox_default_behavior'] == 'compatibility':
|
||||||
old_default_values = dict(
|
old_default_values = dict(
|
||||||
|
@ -588,7 +690,8 @@ def main():
|
||||||
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)
|
||||||
|
|
||||||
if state == 'present':
|
# Create a new container
|
||||||
|
if state == 'present' and clone is None:
|
||||||
try:
|
try:
|
||||||
if get_instance(proxmox, vmid) and not module.params['force']:
|
if get_instance(proxmox, vmid) 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)
|
||||||
|
@ -600,8 +703,11 @@ def main():
|
||||||
elif not content_check(proxmox, node, module.params['ostemplate'], template_store):
|
elif not content_check(proxmox, 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:
|
||||||
|
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))
|
||||||
|
|
||||||
create_instance(module, proxmox, vmid, node, disk, storage, cpus, memory, swap, timeout,
|
try:
|
||||||
|
create_instance(module, proxmox, 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'],
|
||||||
|
@ -621,9 +727,29 @@ def main():
|
||||||
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:
|
||||||
module.fail_json(msg="creation of %s VM %s failed with exception: %s" % (VZ_TYPE, vmid, e))
|
module.fail_json(msg="Creation of %s VM %s failed with exception: %s" % (VZ_TYPE, vmid, e))
|
||||||
|
|
||||||
|
# Clone a container
|
||||||
|
elif state == 'present' and clone is not None:
|
||||||
|
try:
|
||||||
|
if get_instance(proxmox, vmid) and not module.params['force']:
|
||||||
|
module.exit_json(changed=False, msg="VM with vmid = %s is already exists" % vmid)
|
||||||
|
# If no vmid was passed, there cannot be another VM named 'hostname'
|
||||||
|
if not module.params['vmid'] and get_vmid(proxmox, hostname) and not module.params['force']:
|
||||||
|
module.exit_json(changed=False, msg="VM with hostname %s already exists and has ID number %s" % (hostname, get_vmid(proxmox, hostname)[0]))
|
||||||
|
if not get_instance(proxmox, clone):
|
||||||
|
module.exit_json(changed=False, msg="Container to be cloned does not exist")
|
||||||
|
except Exception as e:
|
||||||
|
module.fail_json(msg="Pre-clone checks of {VZ_TYPE} VM {vmid} failed with exception: {e}".format(VZ_TYPE=VZ_TYPE, vmid=vmid, e=e))
|
||||||
|
|
||||||
|
try:
|
||||||
|
create_instance(module, proxmox, vmid, node, disk, storage, cpus, memory, swap, timeout, clone)
|
||||||
|
|
||||||
|
module.exit_json(changed=True, msg="Cloned VM %s from %s" % (vmid, clone))
|
||||||
|
except Exception as e:
|
||||||
|
module.fail_json(msg="Cloning %s VM %s failed with exception: %s" % (VZ_TYPE, vmid, e))
|
||||||
|
|
||||||
elif state == 'started':
|
elif state == 'started':
|
||||||
try:
|
try:
|
||||||
|
|
Loading…
Reference in a new issue