2020-03-09 09:11:07 +00:00
|
|
|
#!/usr/bin/python
|
2021-08-08 08:40:22 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
2022-02-07 05:21:24 +00:00
|
|
|
#
|
2022-08-05 10:28:29 +00:00
|
|
|
# Copyright Ansible Project
|
|
|
|
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
2020-03-09 09:11:07 +00:00
|
|
|
|
|
|
|
from __future__ import absolute_import, division, print_function
|
2025-01-26 15:06:14 +00:00
|
|
|
|
2020-03-09 09:11:07 +00:00
|
|
|
__metaclass__ = type
|
|
|
|
|
2024-12-26 07:24:16 +00:00
|
|
|
DOCUMENTATION = r"""
|
2020-03-09 09:11:07 +00:00
|
|
|
module: proxmox
|
2022-11-09 12:57:41 +00:00
|
|
|
short_description: Management of instances in Proxmox VE cluster
|
2020-03-09 09:11:07 +00:00
|
|
|
description:
|
2023-11-22 21:45:28 +00:00
|
|
|
- Allows you to create/delete/stop instances in Proxmox VE cluster.
|
|
|
|
- The module automatically detects containerization type (lxc for PVE 4, openvz for older).
|
2024-04-22 16:28:22 +00:00
|
|
|
- Since community.general 4.0.0 on, there are no more default values.
|
Add attributes to atomic, memset, one, oneview, packet, proxmox, and xenserver modules (#5958)
Add attributes to atomic, memset, one, oneview, packet, proxmox, and xenserver modules.
2023-02-20 16:27:38 +00:00
|
|
|
attributes:
|
|
|
|
check_mode:
|
|
|
|
support: none
|
|
|
|
diff_mode:
|
|
|
|
support: none
|
2024-05-12 08:03:06 +00:00
|
|
|
action_group:
|
|
|
|
version_added: 9.0.0
|
2020-03-09 09:11:07 +00:00
|
|
|
options:
|
|
|
|
password:
|
|
|
|
description:
|
2024-12-26 07:24:16 +00:00
|
|
|
- The instance root password.
|
2020-09-01 11:44:04 +00:00
|
|
|
type: str
|
2020-03-09 09:11:07 +00:00
|
|
|
hostname:
|
|
|
|
description:
|
2024-12-26 07:24:16 +00:00
|
|
|
- The instance hostname.
|
|
|
|
- Required only for O(state=present).
|
|
|
|
- Must be unique if vmid is not passed.
|
2020-09-01 11:44:04 +00:00
|
|
|
type: str
|
2020-03-09 09:11:07 +00:00
|
|
|
ostemplate:
|
|
|
|
description:
|
2024-12-26 07:24:16 +00:00
|
|
|
- The template for VM creating.
|
|
|
|
- Required only for O(state=present).
|
2020-09-01 11:44:04 +00:00
|
|
|
type: str
|
2020-03-09 09:11:07 +00:00
|
|
|
disk:
|
|
|
|
description:
|
2025-01-08 19:41:03 +00:00
|
|
|
- This option was previously described as "hard disk size in GB for instance" however several formats describing a lxc
|
|
|
|
mount are permitted.
|
|
|
|
- Older versions of Proxmox will accept a numeric value for size using the O(storage) parameter to automatically choose
|
|
|
|
which storage to allocate from, however new versions enforce the C(<STORAGE>:<SIZE>) syntax.
|
|
|
|
- Additional options are available by using some combination of the following key-value pairs as a comma-delimited list
|
2025-01-26 15:06:14 +00:00
|
|
|
C([volume=]<volume>
|
|
|
|
[,acl=<1|0>] [,mountoptions=<opt[;opt...] [,quota=<1|0>] [,replicate=<1|0>] [,ro=<1|0>] [,shared=<1|0>]
|
2025-01-08 19:41:03 +00:00
|
|
|
[,size=<DiskSize>]).
|
2021-08-31 05:10:10 +00:00
|
|
|
- See U(https://pve.proxmox.com/wiki/Linux_Container) for a full description.
|
2025-01-26 15:06:14 +00:00
|
|
|
- This option is mutually exclusive with O(disk_volume).
|
2020-09-01 11:44:04 +00:00
|
|
|
type: str
|
2024-07-14 10:07:05 +00:00
|
|
|
disk_volume:
|
|
|
|
description:
|
|
|
|
- Specify a hash/dictionary of the C(rootfs) disk.
|
|
|
|
- See U(https://pve.proxmox.com/wiki/Linux_Container#pct_mount_points) for a full description.
|
|
|
|
- This option is mutually exclusive with O(storage) and O(disk).
|
|
|
|
type: dict
|
|
|
|
version_added: 9.2.0
|
|
|
|
suboptions:
|
|
|
|
storage:
|
|
|
|
description:
|
|
|
|
- O(disk_volume.storage) is the storage identifier of the storage to use for the C(rootfs).
|
|
|
|
- Mutually exclusive with O(disk_volume.host_path).
|
|
|
|
type: str
|
|
|
|
volume:
|
|
|
|
description:
|
|
|
|
- O(disk_volume.volume) is the name of an existing volume.
|
|
|
|
- If not defined, the module will check if one exists. If not, a new volume will be created.
|
|
|
|
- If defined, the volume must exist under that name.
|
2025-01-26 15:06:14 +00:00
|
|
|
- Required only if O(disk_volume.storage) is defined, and mutually exclusive with O(disk_volume.host_path).
|
2024-07-14 10:07:05 +00:00
|
|
|
type: str
|
|
|
|
size:
|
|
|
|
description:
|
|
|
|
- O(disk_volume.size) is the size of the storage to use.
|
2025-01-26 15:06:14 +00:00
|
|
|
- The size is given in GiB.
|
|
|
|
- Required only if O(disk_volume.storage) is defined, and mutually exclusive with O(disk_volume.host_path).
|
2024-07-14 10:07:05 +00:00
|
|
|
type: int
|
|
|
|
host_path:
|
|
|
|
description:
|
|
|
|
- O(disk_volume.host_path) defines a bind or device path on the PVE host to use for the C(rootfs).
|
|
|
|
- Mutually exclusive with O(disk_volume.storage), O(disk_volume.volume), and O(disk_volume.size).
|
|
|
|
type: path
|
|
|
|
options:
|
|
|
|
description:
|
|
|
|
- O(disk_volume.options) is a dict of extra options.
|
|
|
|
- The value of any given option must be a string, for example V("1").
|
|
|
|
type: dict
|
2020-03-09 09:11:07 +00:00
|
|
|
cores:
|
|
|
|
description:
|
|
|
|
- Specify number of cores per socket.
|
2020-09-01 11:44:04 +00:00
|
|
|
type: int
|
2020-03-09 09:11:07 +00:00
|
|
|
cpus:
|
|
|
|
description:
|
2024-12-26 07:24:16 +00:00
|
|
|
- Number of allocated cpus for instance.
|
2020-09-01 11:44:04 +00:00
|
|
|
type: int
|
2020-03-09 09:11:07 +00:00
|
|
|
memory:
|
|
|
|
description:
|
2024-12-26 07:24:16 +00:00
|
|
|
- Memory size in MB for instance.
|
2020-09-01 11:44:04 +00:00
|
|
|
type: int
|
2020-03-09 09:11:07 +00:00
|
|
|
swap:
|
|
|
|
description:
|
2024-12-26 07:24:16 +00:00
|
|
|
- Swap memory size in MB for instance.
|
2020-09-01 11:44:04 +00:00
|
|
|
type: int
|
2020-03-09 09:11:07 +00:00
|
|
|
netif:
|
|
|
|
description:
|
2024-12-26 07:24:16 +00:00
|
|
|
- Specifies network interfaces for the container. As a hash/dictionary defining interfaces.
|
2020-09-01 11:44:04 +00:00
|
|
|
type: dict
|
2020-12-27 13:07:10 +00:00
|
|
|
features:
|
|
|
|
description:
|
|
|
|
- Specifies a list of features to be enabled. For valid options, see U(https://pve.proxmox.com/wiki/Linux_Container#pct_options).
|
|
|
|
- Some features require the use of a privileged container.
|
|
|
|
type: list
|
|
|
|
elements: str
|
|
|
|
version_added: 2.0.0
|
2024-03-24 21:22:10 +00:00
|
|
|
startup:
|
|
|
|
description:
|
|
|
|
- Specifies the startup order of the container.
|
2025-01-08 19:41:03 +00:00
|
|
|
- Use C(order=#) where C(#) is a non-negative number to define the general startup order. Shutdown in done with reverse
|
|
|
|
ordering.
|
2024-03-24 21:22:10 +00:00
|
|
|
- Use C(up=#) where C(#) is in seconds, to specify a delay to wait before the next VM is started.
|
|
|
|
- Use C(down=#) where C(#) is in seconds, to specify a delay to wait before the next VM is stopped.
|
|
|
|
type: list
|
|
|
|
elements: str
|
|
|
|
version_added: 8.5.0
|
2020-03-09 09:11:07 +00:00
|
|
|
mounts:
|
|
|
|
description:
|
2024-07-14 10:07:05 +00:00
|
|
|
- Specifies additional mounts (separate disks) for the container. As a hash/dictionary defining mount points as strings.
|
|
|
|
- This Option is mutually exclusive with O(mount_volumes).
|
2020-09-01 11:44:04 +00:00
|
|
|
type: dict
|
2024-07-14 10:07:05 +00:00
|
|
|
mount_volumes:
|
|
|
|
description:
|
|
|
|
- Specify additional mounts (separate disks) for the container. As a hash/dictionary defining mount points.
|
|
|
|
- See U(https://pve.proxmox.com/wiki/Linux_Container#pct_mount_points) for a full description.
|
|
|
|
- This Option is mutually exclusive with O(mounts).
|
|
|
|
type: list
|
|
|
|
elements: dict
|
|
|
|
version_added: 9.2.0
|
|
|
|
suboptions:
|
|
|
|
id:
|
|
|
|
description:
|
|
|
|
- O(mount_volumes[].id) is the identifier of the mount point written as C(mp[n]).
|
|
|
|
type: str
|
|
|
|
required: true
|
|
|
|
storage:
|
|
|
|
description:
|
|
|
|
- O(mount_volumes[].storage) is the storage identifier of the storage to use.
|
|
|
|
- Mutually exclusive with O(mount_volumes[].host_path).
|
|
|
|
type: str
|
|
|
|
volume:
|
|
|
|
description:
|
|
|
|
- O(mount_volumes[].volume) is the name of an existing volume.
|
|
|
|
- If not defined, the module will check if one exists. If not, a new volume will be created.
|
|
|
|
- If defined, the volume must exist under that name.
|
|
|
|
- Required only if O(mount_volumes[].storage) is defined and mutually exclusive with O(mount_volumes[].host_path).
|
|
|
|
type: str
|
|
|
|
size:
|
|
|
|
description:
|
|
|
|
- O(mount_volumes[].size) is the size of the storage to use.
|
2025-01-26 15:06:14 +00:00
|
|
|
- The size is given in GiB.
|
2024-07-14 10:07:05 +00:00
|
|
|
- Required only if O(mount_volumes[].storage) is defined and mutually exclusive with O(mount_volumes[].host_path).
|
|
|
|
type: int
|
|
|
|
host_path:
|
|
|
|
description:
|
|
|
|
- O(mount_volumes[].host_path) defines a bind or device path on the PVE host to use for the C(rootfs).
|
|
|
|
- Mutually exclusive with O(mount_volumes[].storage), O(mount_volumes[].volume), and O(mount_volumes[].size).
|
|
|
|
type: path
|
|
|
|
mountpoint:
|
|
|
|
description:
|
|
|
|
- O(mount_volumes[].mountpoint) is the mount point of the volume.
|
|
|
|
type: path
|
|
|
|
required: true
|
|
|
|
options:
|
|
|
|
description:
|
|
|
|
- O(mount_volumes[].options) is a dict of extra options.
|
|
|
|
- The value of any given option must be a string, for example V("1").
|
|
|
|
type: dict
|
2020-03-09 09:11:07 +00:00
|
|
|
ip_address:
|
|
|
|
description:
|
2024-12-26 07:24:16 +00:00
|
|
|
- Specifies the address the container will be assigned.
|
2020-09-01 11:44:04 +00:00
|
|
|
type: str
|
2020-03-09 09:11:07 +00:00
|
|
|
onboot:
|
|
|
|
description:
|
2024-12-26 07:24:16 +00:00
|
|
|
- Specifies whether a VM will be started during system bootup.
|
2020-03-09 09:11:07 +00:00
|
|
|
type: bool
|
|
|
|
storage:
|
|
|
|
description:
|
2024-07-14 10:07:05 +00:00
|
|
|
- Target storage.
|
2025-01-26 15:06:14 +00:00
|
|
|
- This option is mutually exclusive with O(disk_volume) and O(mount_volumes).
|
2020-09-01 11:44:04 +00:00
|
|
|
type: str
|
2020-03-09 09:11:07 +00:00
|
|
|
default: 'local'
|
2023-11-11 11:02:53 +00:00
|
|
|
ostype:
|
|
|
|
description:
|
|
|
|
- Specifies the C(ostype) of the LXC container.
|
|
|
|
- If set to V(auto), no C(ostype) will be provided on instance creation.
|
2025-01-08 19:41:03 +00:00
|
|
|
choices: ['auto', 'debian', 'devuan', 'ubuntu', 'centos', 'fedora', 'opensuse', 'archlinux', 'alpine', 'gentoo', 'nixos',
|
|
|
|
'unmanaged']
|
2023-11-11 11:02:53 +00:00
|
|
|
type: str
|
|
|
|
default: 'auto'
|
|
|
|
version_added: 8.1.0
|
2020-03-09 09:11:07 +00:00
|
|
|
cpuunits:
|
|
|
|
description:
|
2024-12-26 07:24:16 +00:00
|
|
|
- CPU weight for a VM.
|
2020-09-01 11:44:04 +00:00
|
|
|
type: int
|
2020-03-09 09:11:07 +00:00
|
|
|
nameserver:
|
|
|
|
description:
|
2024-12-26 07:24:16 +00:00
|
|
|
- Sets DNS server IP address for a container.
|
2020-09-01 11:44:04 +00:00
|
|
|
type: str
|
2020-03-09 09:11:07 +00:00
|
|
|
searchdomain:
|
|
|
|
description:
|
2024-12-26 07:24:16 +00:00
|
|
|
- Sets DNS search domain for a container.
|
2020-09-01 11:44:04 +00:00
|
|
|
type: str
|
2022-12-30 21:09:00 +00:00
|
|
|
tags:
|
|
|
|
description:
|
|
|
|
- List of tags to apply to the container.
|
2023-06-15 13:48:17 +00:00
|
|
|
- Tags must start with V([a-z0-9_]) followed by zero or more of the following characters V([a-z0-9_-+.]).
|
2022-12-30 21:09:00 +00:00
|
|
|
- Tags are only available in Proxmox 7+.
|
|
|
|
type: list
|
|
|
|
elements: str
|
|
|
|
version_added: 6.2.0
|
2020-03-09 09:11:07 +00:00
|
|
|
timeout:
|
|
|
|
description:
|
2024-12-26 07:24:16 +00:00
|
|
|
- Timeout for operations.
|
2020-09-01 11:44:04 +00:00
|
|
|
type: int
|
2020-03-09 09:11:07 +00:00
|
|
|
default: 30
|
2023-12-01 06:33:02 +00:00
|
|
|
update:
|
|
|
|
description:
|
|
|
|
- If V(true), the container will be updated with new values.
|
2025-01-26 15:06:14 +00:00
|
|
|
- The current default value of V(false) is deprecated and should will change to V(true) in community.general 11.0.0.
|
|
|
|
Please set O(update) explicitly to V(false) or V(true) to avoid surprises and get rid of the deprecation warning.
|
2023-12-01 06:33:02 +00:00
|
|
|
type: bool
|
|
|
|
version_added: 8.1.0
|
2020-03-09 09:11:07 +00:00
|
|
|
force:
|
|
|
|
description:
|
2023-06-15 13:48:17 +00:00
|
|
|
- Forcing operations.
|
|
|
|
- Can be used only with states V(present), V(stopped), V(restarted).
|
2024-12-26 07:24:16 +00:00
|
|
|
- With O(state=present) force option allow to overwrite existing container.
|
|
|
|
- With states V(stopped), V(restarted) allow to force stop instance.
|
2020-03-09 09:11:07 +00:00
|
|
|
type: bool
|
2022-08-24 18:16:25 +00:00
|
|
|
default: false
|
2021-03-19 18:18:05 +00:00
|
|
|
purge:
|
|
|
|
description:
|
|
|
|
- Remove container from all related configurations.
|
|
|
|
- For example backup jobs, replication jobs, or HA.
|
|
|
|
- Related ACLs and Firewall entries will always be removed.
|
2023-06-15 13:48:17 +00:00
|
|
|
- Used with O(state=absent).
|
2021-03-19 18:18:05 +00:00
|
|
|
type: bool
|
|
|
|
default: false
|
|
|
|
version_added: 2.3.0
|
2020-03-09 09:11:07 +00:00
|
|
|
state:
|
|
|
|
description:
|
2024-12-26 07:24:16 +00:00
|
|
|
- Indicate desired state of the instance.
|
|
|
|
- V(template) was added in community.general 8.1.0.
|
2020-09-01 11:44:04 +00:00
|
|
|
type: str
|
2023-11-05 14:57:16 +00:00
|
|
|
choices: ['present', 'started', 'absent', 'stopped', 'restarted', 'template']
|
2020-03-09 09:11:07 +00:00
|
|
|
default: present
|
|
|
|
pubkey:
|
|
|
|
description:
|
2024-12-26 07:24:16 +00:00
|
|
|
- Public key to add to /root/.ssh/authorized_keys. This was added on Proxmox 4.2, it is ignored for earlier versions.
|
2020-09-01 11:44:04 +00:00
|
|
|
type: str
|
2020-03-09 09:11:07 +00:00
|
|
|
unprivileged:
|
|
|
|
description:
|
2022-09-10 08:53:08 +00:00
|
|
|
- Indicate if the container should be unprivileged.
|
2023-06-15 13:48:17 +00:00
|
|
|
- The default change to V(true) in community.general 7.0.0. It used to be V(false) before.
|
2020-03-09 09:11:07 +00:00
|
|
|
type: bool
|
2023-04-26 05:32:00 +00:00
|
|
|
default: true
|
2020-05-18 05:17:11 +00:00
|
|
|
description:
|
|
|
|
description:
|
|
|
|
- Specify the description for the container. Only used on the configuration web interface.
|
|
|
|
- This is saved as a comment inside the configuration file.
|
|
|
|
type: str
|
2020-06-13 13:01:19 +00:00
|
|
|
version_added: '0.2.0'
|
2020-05-18 05:17:11 +00:00
|
|
|
hookscript:
|
|
|
|
description:
|
|
|
|
- Script that will be executed during various steps in the containers lifetime.
|
|
|
|
type: str
|
2020-06-13 13:01:19 +00:00
|
|
|
version_added: '0.2.0'
|
2023-05-31 06:01:47 +00:00
|
|
|
timezone:
|
|
|
|
description:
|
2023-06-15 13:48:17 +00:00
|
|
|
- Timezone used by the container, accepts values like V(Europe/Paris).
|
|
|
|
- The special value V(host) configures the same timezone used by Proxmox host.
|
2023-05-31 06:01:47 +00:00
|
|
|
type: str
|
|
|
|
version_added: '7.1.0'
|
2022-01-06 06:01:25 +00:00
|
|
|
clone:
|
|
|
|
description:
|
|
|
|
- ID of the container to be cloned.
|
2023-06-15 13:48:17 +00:00
|
|
|
- O(description), O(hostname), and O(pool) will be copied from the cloned container if not specified.
|
|
|
|
- The type of clone created is defined by the O(clone_type) parameter.
|
2022-01-06 06:01:25 +00:00
|
|
|
- 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.
|
2023-06-15 13:48:17 +00:00
|
|
|
- V(full) creates a full clone, and O(storage) must be specified.
|
|
|
|
- V(linked) creates a linked clone, and the cloned container must be a template container.
|
2025-01-08 19:41:03 +00:00
|
|
|
- V(opportunistic) creates a linked clone if the cloned container is a template container, and a full clone if not.
|
|
|
|
O(storage) may be specified, if not it will fall back to the default.
|
2022-01-06 06:01:25 +00:00
|
|
|
type: str
|
|
|
|
choices: ['full', 'linked', 'opportunistic']
|
|
|
|
default: opportunistic
|
|
|
|
version_added: 4.3.0
|
2020-03-09 09:11:07 +00:00
|
|
|
author: Sergei Antipov (@UnderGreen)
|
2023-07-08 08:15:04 +00:00
|
|
|
seealso:
|
|
|
|
- module: community.general.proxmox_vm_info
|
2020-12-02 15:08:07 +00:00
|
|
|
extends_documentation_fragment:
|
2024-05-12 08:03:06 +00:00
|
|
|
- community.general.proxmox.actiongroup_proxmox
|
2020-12-02 15:08:07 +00:00
|
|
|
- community.general.proxmox.documentation
|
|
|
|
- community.general.proxmox.selection
|
Add attributes to atomic, memset, one, oneview, packet, proxmox, and xenserver modules (#5958)
Add attributes to atomic, memset, one, oneview, packet, proxmox, and xenserver modules.
2023-02-20 16:27:38 +00:00
|
|
|
- community.general.attributes
|
2024-12-26 07:24:16 +00:00
|
|
|
"""
|
2020-03-09 09:11:07 +00:00
|
|
|
|
2024-12-26 07:24:16 +00:00
|
|
|
EXAMPLES = r"""
|
2020-04-28 14:22:02 +00:00
|
|
|
- name: Create new container with minimal options
|
2020-07-13 19:50:31 +00:00
|
|
|
community.general.proxmox:
|
2020-03-09 09:11:07 +00:00
|
|
|
vmid: 100
|
|
|
|
node: uk-mc02
|
|
|
|
api_user: root@pam
|
|
|
|
api_password: 1q2w3e
|
|
|
|
api_host: node1
|
|
|
|
password: 123456
|
|
|
|
hostname: example.org
|
|
|
|
ostemplate: 'local:vztmpl/ubuntu-14.04-x86_64.tar.gz'
|
|
|
|
|
2023-12-20 06:30:17 +00:00
|
|
|
- name: Create new container with minimal options specifying disk storage location and size
|
|
|
|
community.general.proxmox:
|
|
|
|
vmid: 100
|
|
|
|
node: uk-mc02
|
|
|
|
api_user: root@pam
|
|
|
|
api_password: 1q2w3e
|
|
|
|
api_host: node1
|
|
|
|
password: 123456
|
|
|
|
hostname: example.org
|
|
|
|
ostemplate: 'local:vztmpl/ubuntu-14.04-x86_64.tar.gz'
|
|
|
|
disk: 'local-lvm:20'
|
|
|
|
|
2024-07-14 10:07:05 +00:00
|
|
|
- name: Create new container with minimal options specifying disk storage location and size via disk_volume
|
|
|
|
community.general.proxmox:
|
|
|
|
vmid: 100
|
|
|
|
node: uk-mc02
|
|
|
|
api_user: root@pam
|
|
|
|
api_password: 1q2w3e
|
|
|
|
api_host: node1
|
|
|
|
password: 123456
|
|
|
|
hostname: example.org
|
|
|
|
ostemplate: 'local:vztmpl/ubuntu-14.04-x86_64.tar.gz'
|
|
|
|
disk_volume:
|
|
|
|
storage: local
|
|
|
|
size: 20
|
|
|
|
|
2020-05-18 05:17:11 +00:00
|
|
|
- name: Create new container with hookscript and description
|
2020-07-13 19:50:31 +00:00
|
|
|
community.general.proxmox:
|
2020-05-18 05:17:11 +00:00
|
|
|
vmid: 100
|
|
|
|
node: uk-mc02
|
|
|
|
api_user: root@pam
|
|
|
|
api_password: 1q2w3e
|
|
|
|
api_host: node1
|
|
|
|
password: 123456
|
|
|
|
hostname: example.org
|
|
|
|
ostemplate: 'local:vztmpl/ubuntu-14.04-x86_64.tar.gz'
|
|
|
|
hookscript: 'local:snippets/vm_hook.sh'
|
|
|
|
description: created with ansible
|
|
|
|
|
2020-04-28 14:22:02 +00:00
|
|
|
- name: Create new container automatically selecting the next available vmid.
|
2020-07-13 19:50:31 +00:00
|
|
|
community.general.proxmox:
|
2020-03-09 09:11:07 +00:00
|
|
|
node: 'uk-mc02'
|
|
|
|
api_user: 'root@pam'
|
|
|
|
api_password: '1q2w3e'
|
|
|
|
api_host: 'node1'
|
|
|
|
password: '123456'
|
|
|
|
hostname: 'example.org'
|
|
|
|
ostemplate: 'local:vztmpl/ubuntu-14.04-x86_64.tar.gz'
|
|
|
|
|
2020-04-28 14:22:02 +00:00
|
|
|
- name: Create new container with minimal options with force(it will rewrite existing container)
|
2020-07-13 19:50:31 +00:00
|
|
|
community.general.proxmox:
|
2020-03-09 09:11:07 +00:00
|
|
|
vmid: 100
|
|
|
|
node: uk-mc02
|
|
|
|
api_user: root@pam
|
|
|
|
api_password: 1q2w3e
|
|
|
|
api_host: node1
|
|
|
|
password: 123456
|
|
|
|
hostname: example.org
|
|
|
|
ostemplate: 'local:vztmpl/ubuntu-14.04-x86_64.tar.gz'
|
2022-08-24 18:16:25 +00:00
|
|
|
force: true
|
2020-03-09 09:11:07 +00:00
|
|
|
|
2020-04-28 14:22:02 +00:00
|
|
|
- name: Create new container with minimal options use environment PROXMOX_PASSWORD variable(you should export it before)
|
2020-07-13 19:50:31 +00:00
|
|
|
community.general.proxmox:
|
2020-03-09 09:11:07 +00:00
|
|
|
vmid: 100
|
|
|
|
node: uk-mc02
|
|
|
|
api_user: root@pam
|
|
|
|
api_host: node1
|
|
|
|
password: 123456
|
|
|
|
hostname: example.org
|
|
|
|
ostemplate: 'local:vztmpl/ubuntu-14.04-x86_64.tar.gz'
|
|
|
|
|
2020-04-28 14:22:02 +00:00
|
|
|
- name: Create new container with minimal options defining network interface with dhcp
|
2020-07-13 19:50:31 +00:00
|
|
|
community.general.proxmox:
|
2020-03-09 09:11:07 +00:00
|
|
|
vmid: 100
|
|
|
|
node: uk-mc02
|
|
|
|
api_user: root@pam
|
|
|
|
api_password: 1q2w3e
|
|
|
|
api_host: node1
|
|
|
|
password: 123456
|
|
|
|
hostname: example.org
|
|
|
|
ostemplate: 'local:vztmpl/ubuntu-14.04-x86_64.tar.gz'
|
2024-08-01 15:11:52 +00:00
|
|
|
netif:
|
|
|
|
net0: "name=eth0,ip=dhcp,ip6=dhcp,bridge=vmbr0"
|
2020-03-09 09:11:07 +00:00
|
|
|
|
2020-04-28 14:22:02 +00:00
|
|
|
- name: Create new container with minimal options defining network interface with static ip
|
2020-07-13 19:50:31 +00:00
|
|
|
community.general.proxmox:
|
2020-03-09 09:11:07 +00:00
|
|
|
vmid: 100
|
|
|
|
node: uk-mc02
|
|
|
|
api_user: root@pam
|
|
|
|
api_password: 1q2w3e
|
|
|
|
api_host: node1
|
|
|
|
password: 123456
|
|
|
|
hostname: example.org
|
|
|
|
ostemplate: 'local:vztmpl/ubuntu-14.04-x86_64.tar.gz'
|
2024-08-01 15:11:52 +00:00
|
|
|
netif:
|
|
|
|
net0: "name=eth0,gw=192.168.0.1,ip=192.168.0.2/24,bridge=vmbr0"
|
|
|
|
|
|
|
|
- name: Create new container with more options defining network interface with static ip4 and ip6 with vlan-tag and mtu
|
|
|
|
community.general.proxmox:
|
|
|
|
vmid: 100
|
|
|
|
node: uk-mc02
|
|
|
|
api_user: root@pam
|
|
|
|
api_password: 1q2w3e
|
|
|
|
api_host: node1
|
|
|
|
password: 123456
|
|
|
|
hostname: example.org
|
|
|
|
ostemplate: 'local:vztmpl/ubuntu-14.04-x86_64.tar.gz'
|
|
|
|
netif:
|
|
|
|
net0: "name=eth0,gw=192.168.0.1,ip=192.168.0.2/24,ip6=fe80::1227/64,gw6=fe80::1,bridge=vmbr0,firewall=1,tag=934,mtu=1500"
|
2020-03-09 09:11:07 +00:00
|
|
|
|
2020-04-28 14:22:02 +00:00
|
|
|
- name: Create new container with minimal options defining a mount with 8GB
|
2020-07-13 19:50:31 +00:00
|
|
|
community.general.proxmox:
|
2020-03-09 09:11:07 +00:00
|
|
|
vmid: 100
|
|
|
|
node: uk-mc02
|
|
|
|
api_user: root@pam
|
|
|
|
api_password: 1q2w3e
|
|
|
|
api_host: node1
|
|
|
|
password: 123456
|
|
|
|
hostname: example.org
|
2024-02-28 07:03:15 +00:00
|
|
|
ostemplate: 'local:vztmpl/ubuntu-14.04-x86_64.tar.gz'
|
2024-08-01 15:11:52 +00:00
|
|
|
mounts:
|
|
|
|
mp0: "local:8,mp=/mnt/test/"
|
2020-03-09 09:11:07 +00:00
|
|
|
|
2024-07-14 10:07:05 +00:00
|
|
|
- name: Create new container with minimal options defining a mount with 8GB using mount_volumes
|
|
|
|
community.general.proxmox:
|
|
|
|
vmid: 100
|
|
|
|
node: uk-mc02
|
|
|
|
api_user: root@pam
|
|
|
|
api_password: 1q2w3e
|
|
|
|
api_host: node1
|
|
|
|
password: 123456
|
|
|
|
hostname: example.org
|
|
|
|
ostemplate: 'local:vztmpl/ubuntu-14.04-x86_64.tar.gz'
|
|
|
|
mount_volumes:
|
|
|
|
- id: mp0
|
|
|
|
storage: local
|
|
|
|
size: 8
|
|
|
|
mountpoint: /mnt/test
|
|
|
|
|
2020-04-28 14:22:02 +00:00
|
|
|
- name: Create new container with minimal options defining a cpu core limit
|
2020-07-13 19:50:31 +00:00
|
|
|
community.general.proxmox:
|
2020-03-09 09:11:07 +00:00
|
|
|
vmid: 100
|
|
|
|
node: uk-mc02
|
|
|
|
api_user: root@pam
|
|
|
|
api_password: 1q2w3e
|
|
|
|
api_host: node1
|
|
|
|
password: 123456
|
|
|
|
hostname: example.org
|
2024-02-28 07:03:15 +00:00
|
|
|
ostemplate: 'local:vztmpl/ubuntu-14.04-x86_64.tar.gz'
|
2020-03-09 09:11:07 +00:00
|
|
|
cores: 2
|
|
|
|
|
2023-05-31 06:01:47 +00:00
|
|
|
- name: Create new container with minimal options and same timezone as proxmox host
|
|
|
|
community.general.proxmox:
|
|
|
|
vmid: 100
|
|
|
|
node: uk-mc02
|
|
|
|
api_user: root@pam
|
|
|
|
api_password: 1q2w3e
|
|
|
|
api_host: node1
|
|
|
|
password: 123456
|
|
|
|
hostname: example.org
|
|
|
|
ostemplate: 'local:vztmpl/ubuntu-14.04-x86_64.tar.gz'
|
|
|
|
timezone: host
|
|
|
|
|
2020-12-27 13:07:10 +00:00
|
|
|
- name: Create a new container with nesting enabled and allows the use of CIFS/NFS inside the container.
|
|
|
|
community.general.proxmox:
|
|
|
|
vmid: 100
|
|
|
|
node: uk-mc02
|
|
|
|
api_user: root@pam
|
|
|
|
api_password: 1q2w3e
|
|
|
|
api_host: node1
|
|
|
|
password: 123456
|
|
|
|
hostname: example.org
|
2024-02-28 07:03:15 +00:00
|
|
|
ostemplate: 'local:vztmpl/ubuntu-14.04-x86_64.tar.gz'
|
2020-12-27 13:07:10 +00:00
|
|
|
features:
|
2024-12-26 07:24:16 +00:00
|
|
|
- nesting=1
|
|
|
|
- mount=cifs,nfs
|
2020-12-27 13:07:10 +00:00
|
|
|
|
2022-01-06 06:01:25 +00:00
|
|
|
- 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
|
2020-12-27 13:07:10 +00:00
|
|
|
|
2023-12-01 06:33:02 +00:00
|
|
|
- name: Update container configuration
|
|
|
|
community.general.proxmox:
|
|
|
|
vmid: 100
|
|
|
|
node: uk-mc02
|
|
|
|
api_user: root@pam
|
|
|
|
api_password: 1q2w3e
|
|
|
|
api_host: node1
|
2024-08-01 15:11:52 +00:00
|
|
|
netif:
|
|
|
|
net0: "name=eth0,gw=192.168.0.1,ip=192.168.0.3/24,bridge=vmbr0"
|
2023-12-01 06:33:02 +00:00
|
|
|
update: true
|
|
|
|
|
2020-04-28 14:22:02 +00:00
|
|
|
- name: Start container
|
2020-07-13 19:50:31 +00:00
|
|
|
community.general.proxmox:
|
2020-03-09 09:11:07 +00:00
|
|
|
vmid: 100
|
|
|
|
api_user: root@pam
|
|
|
|
api_password: 1q2w3e
|
|
|
|
api_host: node1
|
|
|
|
state: started
|
|
|
|
|
2020-04-28 14:22:02 +00:00
|
|
|
- name: >
|
|
|
|
Start container with mount. You should enter a 90-second timeout because servers
|
|
|
|
with additional disks take longer to boot
|
2020-07-13 19:50:31 +00:00
|
|
|
community.general.proxmox:
|
2020-03-09 09:11:07 +00:00
|
|
|
vmid: 100
|
|
|
|
api_user: root@pam
|
|
|
|
api_password: 1q2w3e
|
|
|
|
api_host: node1
|
|
|
|
state: started
|
|
|
|
timeout: 90
|
|
|
|
|
2020-04-28 14:22:02 +00:00
|
|
|
- name: Stop container
|
2020-07-13 19:50:31 +00:00
|
|
|
community.general.proxmox:
|
2020-03-09 09:11:07 +00:00
|
|
|
vmid: 100
|
|
|
|
api_user: root@pam
|
|
|
|
api_password: 1q2w3e
|
|
|
|
api_host: node1
|
|
|
|
state: stopped
|
|
|
|
|
2020-04-28 14:22:02 +00:00
|
|
|
- name: Stop container with force
|
2020-07-13 19:50:31 +00:00
|
|
|
community.general.proxmox:
|
2020-03-09 09:11:07 +00:00
|
|
|
vmid: 100
|
|
|
|
api_user: root@pam
|
|
|
|
api_password: 1q2w3e
|
|
|
|
api_host: node1
|
2022-08-24 18:16:25 +00:00
|
|
|
force: true
|
2020-03-09 09:11:07 +00:00
|
|
|
state: stopped
|
|
|
|
|
2020-04-28 14:22:02 +00:00
|
|
|
- name: Restart container(stopped or mounted container you can't restart)
|
2020-07-13 19:50:31 +00:00
|
|
|
community.general.proxmox:
|
2020-03-09 09:11:07 +00:00
|
|
|
vmid: 100
|
|
|
|
api_user: root@pam
|
|
|
|
api_password: 1q2w3e
|
|
|
|
api_host: node1
|
|
|
|
state: restarted
|
|
|
|
|
2023-11-05 14:57:16 +00:00
|
|
|
- name: Convert container to template
|
|
|
|
community.general.proxmox:
|
|
|
|
vmid: 100
|
|
|
|
api_user: root@pam
|
|
|
|
api_password: 1q2w3e
|
|
|
|
api_host: node1
|
|
|
|
state: template
|
|
|
|
|
|
|
|
- name: Convert container to template (stop container if running)
|
|
|
|
community.general.proxmox:
|
|
|
|
vmid: 100
|
|
|
|
api_user: root@pam
|
|
|
|
api_password: 1q2w3e
|
|
|
|
api_host: node1
|
|
|
|
state: template
|
|
|
|
force: true
|
|
|
|
|
2020-04-28 14:22:02 +00:00
|
|
|
- name: Remove container
|
2020-07-13 19:50:31 +00:00
|
|
|
community.general.proxmox:
|
2020-03-09 09:11:07 +00:00
|
|
|
vmid: 100
|
|
|
|
api_user: root@pam
|
|
|
|
api_password: 1q2w3e
|
|
|
|
api_host: node1
|
|
|
|
state: absent
|
2024-12-26 07:24:16 +00:00
|
|
|
"""
|
2020-03-09 09:11:07 +00:00
|
|
|
|
2022-12-30 21:09:00 +00:00
|
|
|
import re
|
2020-03-09 09:11:07 +00:00
|
|
|
import time
|
2021-12-24 17:34:48 +00:00
|
|
|
|
2022-09-09 20:23:48 +00:00
|
|
|
from ansible.module_utils.basic import AnsibleModule
|
2024-07-23 15:58:54 +00:00
|
|
|
from ansible.module_utils.common.text.converters import to_native
|
2021-12-14 05:46:49 +00:00
|
|
|
from ansible_collections.community.general.plugins.module_utils.proxmox import (
|
2025-01-26 15:06:14 +00:00
|
|
|
ProxmoxAnsible,
|
|
|
|
ansible_to_proxmox_bool,
|
|
|
|
proxmox_auth_argument_spec,
|
|
|
|
)
|
|
|
|
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
|
2020-03-09 09:11:07 +00:00
|
|
|
|
|
|
|
|
2025-01-26 15:06:14 +00:00
|
|
|
def get_proxmox_args():
|
|
|
|
return dict(
|
|
|
|
vmid=dict(type="int", required=False),
|
2022-02-07 05:21:24 +00:00
|
|
|
node=dict(),
|
|
|
|
pool=dict(),
|
|
|
|
password=dict(no_log=True),
|
|
|
|
hostname=dict(),
|
|
|
|
ostemplate=dict(),
|
2025-01-26 15:06:14 +00:00
|
|
|
disk=dict(type="str"),
|
2024-07-14 10:07:05 +00:00
|
|
|
disk_volume=dict(
|
|
|
|
type="dict",
|
|
|
|
options=dict(
|
|
|
|
storage=dict(type="str"),
|
|
|
|
volume=dict(type="str"),
|
|
|
|
size=dict(type="int"),
|
|
|
|
host_path=dict(type="path"),
|
|
|
|
options=dict(type="dict"),
|
|
|
|
),
|
|
|
|
required_together=[("storage", "size")],
|
|
|
|
required_by={
|
|
|
|
"volume": ("storage", "size"),
|
|
|
|
},
|
|
|
|
mutually_exclusive=[
|
|
|
|
("host_path", "storage"),
|
|
|
|
("host_path", "volume"),
|
|
|
|
("host_path", "size"),
|
|
|
|
],
|
|
|
|
),
|
2025-01-26 15:06:14 +00:00
|
|
|
cores=dict(type="int"),
|
|
|
|
cpus=dict(type="int"),
|
|
|
|
memory=dict(type="int"),
|
|
|
|
swap=dict(type="int"),
|
|
|
|
netif=dict(type="dict"),
|
|
|
|
mounts=dict(type="dict"),
|
2024-07-14 10:07:05 +00:00
|
|
|
mount_volumes=dict(
|
|
|
|
type="list",
|
|
|
|
elements="dict",
|
|
|
|
options=dict(
|
|
|
|
id=(dict(type="str", required=True)),
|
|
|
|
storage=dict(type="str"),
|
|
|
|
volume=dict(type="str"),
|
|
|
|
size=dict(type="int"),
|
|
|
|
host_path=dict(type="path"),
|
|
|
|
mountpoint=dict(type="path", required=True),
|
|
|
|
options=dict(type="dict"),
|
|
|
|
),
|
|
|
|
required_together=[("storage", "size")],
|
|
|
|
required_by={
|
|
|
|
"volume": ("storage", "size"),
|
|
|
|
},
|
|
|
|
mutually_exclusive=[
|
|
|
|
("host_path", "storage"),
|
|
|
|
("host_path", "volume"),
|
|
|
|
("host_path", "size"),
|
|
|
|
],
|
|
|
|
),
|
2022-02-07 05:21:24 +00:00
|
|
|
ip_address=dict(),
|
2025-01-26 15:06:14 +00:00
|
|
|
ostype=dict(
|
|
|
|
default="auto",
|
|
|
|
choices=[
|
|
|
|
"auto",
|
|
|
|
"debian",
|
|
|
|
"devuan",
|
|
|
|
"ubuntu",
|
|
|
|
"centos",
|
|
|
|
"fedora",
|
|
|
|
"opensuse",
|
|
|
|
"archlinux",
|
|
|
|
"alpine",
|
|
|
|
"gentoo",
|
|
|
|
"nixos",
|
|
|
|
"unmanaged",
|
|
|
|
],
|
|
|
|
),
|
|
|
|
onboot=dict(type="bool"),
|
|
|
|
features=dict(type="list", elements="str"),
|
|
|
|
startup=dict(type="list", elements="str"),
|
|
|
|
storage=dict(default="local"),
|
|
|
|
cpuunits=dict(type="int"),
|
2022-02-07 05:21:24 +00:00
|
|
|
nameserver=dict(),
|
|
|
|
searchdomain=dict(),
|
2025-01-26 15:06:14 +00:00
|
|
|
timeout=dict(type="int", default=30),
|
|
|
|
update=dict(type="bool"),
|
|
|
|
force=dict(type="bool", default=False),
|
|
|
|
purge=dict(type="bool", default=False),
|
|
|
|
state=dict(
|
|
|
|
default="present",
|
|
|
|
choices=[
|
|
|
|
"present",
|
|
|
|
"absent",
|
|
|
|
"stopped",
|
|
|
|
"started",
|
|
|
|
"restarted",
|
|
|
|
"template",
|
|
|
|
],
|
|
|
|
),
|
|
|
|
pubkey=dict(type="str"),
|
|
|
|
unprivileged=dict(type="bool", default=True),
|
|
|
|
description=dict(type="str"),
|
|
|
|
hookscript=dict(type="str"),
|
|
|
|
timezone=dict(type="str"),
|
|
|
|
clone=dict(type="int"),
|
|
|
|
clone_type=dict(
|
|
|
|
default="opportunistic", choices=["full", "linked", "opportunistic"]
|
|
|
|
),
|
|
|
|
tags=dict(type="list", elements="str"),
|
2022-02-07 05:21:24 +00:00
|
|
|
)
|
|
|
|
|
2025-01-26 15:06:14 +00:00
|
|
|
|
|
|
|
def get_ansible_module():
|
|
|
|
module_args = proxmox_auth_argument_spec()
|
|
|
|
module_args.update(get_proxmox_args())
|
|
|
|
|
|
|
|
return AnsibleModule(
|
2022-02-07 05:21:24 +00:00
|
|
|
argument_spec=module_args,
|
2022-01-06 06:01:25 +00:00
|
|
|
required_if=[
|
2025-01-26 15:06:14 +00:00
|
|
|
("state", "present", ["node", "hostname"]),
|
|
|
|
# Require one of clone, ostemplate, or update.
|
|
|
|
# Together with mutually_exclusive this ensures that we either
|
|
|
|
# clone a container or create a new one from a template file.
|
|
|
|
("state", "present", ("clone", "ostemplate", "update"), True),
|
2022-01-06 06:01:25 +00:00
|
|
|
],
|
2024-07-14 10:07:05 +00:00
|
|
|
required_together=[("api_token_id", "api_token_secret")],
|
2025-01-26 15:06:14 +00:00
|
|
|
required_one_of=[
|
|
|
|
("api_password", "api_token_id"),
|
|
|
|
("vmid", "hostname"),
|
|
|
|
],
|
2024-07-14 10:07:05 +00:00
|
|
|
mutually_exclusive=[
|
2025-01-26 15:06:14 +00:00
|
|
|
# Creating a new container is done either by cloning an existing one, or based on a template.
|
|
|
|
("clone", "ostemplate", "update"),
|
|
|
|
("disk", "disk_volume"),
|
|
|
|
("storage", "disk_volume"),
|
2024-07-14 10:07:05 +00:00
|
|
|
("mounts", "mount_volumes"),
|
2022-01-06 06:01:25 +00:00
|
|
|
],
|
2020-03-09 09:11:07 +00:00
|
|
|
)
|
|
|
|
|
2022-02-07 05:21:24 +00:00
|
|
|
|
2025-01-26 15:06:14 +00:00
|
|
|
class ProxmoxLxcAnsible(ProxmoxAnsible):
|
|
|
|
MINIMUM_VERSIONS = {
|
|
|
|
"disk_volume": "5.0",
|
|
|
|
"mount_volumes": "5.0",
|
|
|
|
"tags": "6.1",
|
|
|
|
"timezone": "6.3",
|
|
|
|
}
|
2020-03-09 09:11:07 +00:00
|
|
|
|
2025-01-26 15:06:14 +00:00
|
|
|
def __init__(self, module):
|
|
|
|
super(ProxmoxLxcAnsible, self).__init__(module)
|
2022-01-06 06:01:25 +00:00
|
|
|
|
2025-01-26 15:06:14 +00:00
|
|
|
self.VZ_TYPE = "openvz" if self.version() < LooseVersion("4.0") else "lxc"
|
|
|
|
self.params = self.module.params
|
2022-01-06 06:01:25 +00:00
|
|
|
|
2025-01-26 15:06:14 +00:00
|
|
|
def run(self):
|
|
|
|
self.check_supported_features()
|
2022-01-06 06:01:25 +00:00
|
|
|
|
2025-01-26 15:06:14 +00:00
|
|
|
state = self.params.get("state")
|
|
|
|
|
|
|
|
vmid = self.params.get("vmid")
|
|
|
|
hostname = self.params.get("hostname")
|
|
|
|
|
|
|
|
if not vmid and not hostname:
|
|
|
|
self.module.fail_json(msg="Either VMID or hostname must be provided.")
|
|
|
|
|
|
|
|
if state == "present":
|
|
|
|
self.lxc_present(
|
|
|
|
vmid,
|
|
|
|
hostname,
|
|
|
|
node=self.params.get("node"),
|
|
|
|
update=self.params.get("update"),
|
|
|
|
force=self.params.get("force"),
|
|
|
|
)
|
|
|
|
elif state == "absent":
|
|
|
|
self.lxc_absent(
|
|
|
|
vmid,
|
|
|
|
hostname,
|
|
|
|
node=self.params.get("node"),
|
|
|
|
timeout=self.params.get("timeout"),
|
|
|
|
purge=self.params.get("purge"),
|
|
|
|
)
|
|
|
|
elif state == "started":
|
|
|
|
self.lxc_started(
|
|
|
|
vmid,
|
|
|
|
hostname,
|
|
|
|
node=self.params.get("node"),
|
|
|
|
timeout=self.params.get("timeout"),
|
|
|
|
)
|
|
|
|
elif state == "stopped":
|
|
|
|
self.lxc_stopped(
|
|
|
|
vmid,
|
|
|
|
hostname,
|
|
|
|
node=self.params.get("node"),
|
|
|
|
timeout=self.params.get("timeout"),
|
|
|
|
force=self.params.get("force"),
|
|
|
|
)
|
|
|
|
elif state == "restarted":
|
|
|
|
self.lxc_restarted(
|
|
|
|
vmid,
|
|
|
|
hostname,
|
|
|
|
node=self.params.get("node"),
|
|
|
|
timeout=self.params.get("timeout"),
|
|
|
|
force=self.params.get("force"),
|
|
|
|
)
|
|
|
|
elif state == "template":
|
|
|
|
self.lxc_to_template(
|
|
|
|
vmid,
|
|
|
|
hostname,
|
|
|
|
node=self.params.get("node"),
|
|
|
|
timeout=self.params.get("timeout"),
|
|
|
|
force=self.params.get("force"),
|
|
|
|
)
|
2020-03-09 09:11:07 +00:00
|
|
|
|
2025-01-26 15:06:14 +00:00
|
|
|
def lxc_present(self, vmid, hostname, node, update, force):
|
2020-03-09 09:11:07 +00:00
|
|
|
try:
|
2025-01-26 15:06:14 +00:00
|
|
|
lxc = self.get_lxc_resource(vmid, hostname)
|
|
|
|
vmid = vmid or lxc["id"].split("/")[-1]
|
|
|
|
node = node or lxc["node"]
|
|
|
|
except LookupError:
|
|
|
|
lxc = None
|
|
|
|
vmid = vmid or self.get_nextvmid()
|
|
|
|
|
|
|
|
if node is None:
|
|
|
|
raise ValueError(
|
|
|
|
"Argument 'node' is None, but should be found from VMID/hostname or provided."
|
|
|
|
)
|
2020-03-09 09:11:07 +00:00
|
|
|
|
2025-01-26 15:06:14 +00:00
|
|
|
# check if the container exists already
|
|
|
|
if lxc is not None:
|
|
|
|
if update is None:
|
|
|
|
# TODO: Remove deprecation warning in version 11.0.0
|
|
|
|
self.module.deprecate(
|
|
|
|
msg="The default value of false for 'update' has been deprecated and will be changed to true in version 11.0.0.",
|
|
|
|
version="11.0.0",
|
|
|
|
collection_name="community.general",
|
|
|
|
)
|
|
|
|
update = False
|
|
|
|
|
|
|
|
if update:
|
|
|
|
# Update it if we should
|
|
|
|
identifier = self.format_vm_identifier(vmid, hostname)
|
|
|
|
self.update_lxc_instance(
|
|
|
|
vmid,
|
|
|
|
node,
|
|
|
|
cores=self.params.get("cores"),
|
|
|
|
cpus=self.params.get("cpus"),
|
|
|
|
cpuunits=self.params.get("cpuunits"),
|
|
|
|
description=self.params.get("description"),
|
|
|
|
disk=self.params.get("disk"),
|
|
|
|
disk_volume=self.params.get("disk_volume"),
|
|
|
|
features=self.params.get("features"),
|
|
|
|
hookscript=self.params.get("hookscript"),
|
|
|
|
hostname=self.params.get("hostname"),
|
|
|
|
ip_address=self.params.get("ip_address"),
|
|
|
|
memory=self.params.get("memory"),
|
|
|
|
mounts=self.params.get("mounts"),
|
|
|
|
mount_volumes=self.params.get("mount_volumes"),
|
|
|
|
nameserver=self.params.get("nameserver"),
|
|
|
|
netif=self.params.get("netif"),
|
|
|
|
onboot=ansible_to_proxmox_bool(self.params.get("onboot")),
|
|
|
|
searchdomain=self.params.get("searchdomain"),
|
|
|
|
startup=self.params.get("startup"),
|
|
|
|
swap=self.params.get("swap"),
|
|
|
|
tags=self.params.get("tags"),
|
|
|
|
timezone=self.params.get("timezone"),
|
|
|
|
)
|
|
|
|
self.module.exit_json(
|
|
|
|
changed=True, vmid=vmid, msg="VM %s has been updated." % identifier
|
|
|
|
)
|
|
|
|
elif not force:
|
|
|
|
# We're done if it shouldn't be forcefully created
|
|
|
|
identifier = self.format_vm_identifier(vmid, lxc["name"])
|
|
|
|
self.module.exit_json(
|
|
|
|
changed=False, vmid=vmid, msg="VM %s already exists." % identifier
|
|
|
|
)
|
|
|
|
self.module.debug(
|
|
|
|
"VM %s already exists, but we don't update and instead forcefully recreate it."
|
|
|
|
% identifier
|
|
|
|
)
|
|
|
|
|
|
|
|
self.new_lxc_instance(
|
|
|
|
vmid,
|
|
|
|
hostname,
|
|
|
|
node=self.params.get("node"),
|
|
|
|
clone_from=self.params.get("clone"),
|
|
|
|
ostemplate=self.params.get("ostemplate"),
|
|
|
|
force=force,
|
|
|
|
)
|
2020-03-09 09:11:07 +00:00
|
|
|
|
2025-01-26 15:06:14 +00:00
|
|
|
def lxc_absent(self, vmid, hostname, node, timeout, purge):
|
2020-03-09 09:11:07 +00:00
|
|
|
try:
|
2025-01-26 15:06:14 +00:00
|
|
|
lxc = self.get_lxc_resource(vmid, hostname)
|
|
|
|
except LookupError:
|
|
|
|
identifier = self.format_vm_identifier(vmid, hostname)
|
|
|
|
self.module.exit_json(
|
|
|
|
changed=False, vmid=vmid, msg="VM %s is already absent." % (identifier)
|
|
|
|
)
|
|
|
|
|
|
|
|
vmid = vmid or lxc["id"].split("/")[-1]
|
|
|
|
node = node or lxc["node"]
|
|
|
|
|
|
|
|
lxc_status = self.get_lxc_status(vmid, node)
|
|
|
|
identifier = self.format_vm_identifier(vmid, hostname)
|
|
|
|
|
|
|
|
if lxc_status == "running":
|
|
|
|
self.module.exit_json(
|
|
|
|
changed=False,
|
|
|
|
vmid=vmid,
|
|
|
|
msg="VM %s is running. Stop it before deletion." % identifier,
|
|
|
|
)
|
|
|
|
if lxc_status == "mounted":
|
|
|
|
self.module.exit_json(
|
|
|
|
changed=False,
|
|
|
|
vmid=vmid,
|
|
|
|
msg="VM %s is mounted. Stop it with force option before deletion."
|
|
|
|
% identifier,
|
|
|
|
)
|
|
|
|
|
|
|
|
self.remove_lxc_instance(vmid, node, timeout, purge)
|
|
|
|
self.module.exit_json(
|
|
|
|
changed=True, vmid=vmid, msg="VM %s removed." % identifier
|
|
|
|
)
|
|
|
|
|
|
|
|
def lxc_started(self, vmid, hostname, node, timeout):
|
|
|
|
lxc = self.get_lxc_resource(vmid, hostname)
|
|
|
|
vmid = vmid or lxc["id"].split("/")[-1]
|
|
|
|
hostname = hostname or lxc["name"]
|
|
|
|
identifier = self.format_vm_identifier(vmid, hostname)
|
|
|
|
node = node or lxc["node"]
|
|
|
|
lxc_status = self.get_lxc_status(vmid, lxc["node"])
|
|
|
|
|
|
|
|
if lxc_status == "running":
|
|
|
|
self.module.exit_json(
|
|
|
|
changed=False, vmid=vmid, msg="VM %s is already running." % identifier
|
|
|
|
)
|
|
|
|
|
|
|
|
self.start_lxc_instance(vmid, node, timeout)
|
|
|
|
self.module.exit_json(
|
|
|
|
changed=True, vmid=vmid, msg="VM %s started." % identifier
|
|
|
|
)
|
|
|
|
|
|
|
|
def lxc_stopped(self, vmid, hostname, node, timeout, force):
|
|
|
|
lxc = self.get_lxc_resource(vmid, hostname)
|
|
|
|
vmid = vmid or lxc["id"].split("/")[-1]
|
|
|
|
hostname = hostname or lxc["name"]
|
|
|
|
identifier = self.format_vm_identifier(vmid, hostname)
|
|
|
|
node = node or lxc["node"]
|
|
|
|
lxc_status = self.get_lxc_status(vmid, node)
|
|
|
|
|
|
|
|
if lxc_status == "mounted":
|
|
|
|
if force:
|
|
|
|
self.umount_lxc_instance(vmid, hostname, timeout)
|
|
|
|
else:
|
|
|
|
self.module.exit_json(
|
|
|
|
changed=False,
|
|
|
|
vmid=vmid,
|
|
|
|
msg="VM %s is already stopped, but mounted. Use force option to umount it."
|
|
|
|
% identifier,
|
|
|
|
)
|
|
|
|
|
|
|
|
if lxc_status == "stopped":
|
|
|
|
self.module.exit_json(
|
|
|
|
changed=False, vmid=vmid, msg="VM %s is already stopped." % identifier
|
|
|
|
)
|
|
|
|
|
|
|
|
self.stop_lxc_instance(vmid, node, timeout, force)
|
|
|
|
self.module.exit_json(
|
|
|
|
changed=True, vmid=vmid, msg="VM %s stopped." % identifier
|
|
|
|
)
|
2020-03-09 09:11:07 +00:00
|
|
|
|
2025-01-26 15:06:14 +00:00
|
|
|
def lxc_restarted(self, vmid, hostname, node, timeout, force):
|
|
|
|
lxc = self.get_lxc_resource(vmid, hostname)
|
|
|
|
|
|
|
|
vmid = vmid or lxc["id"].split("/")[-1]
|
|
|
|
hostname = hostname or lxc["name"]
|
|
|
|
node = node or lxc["node"]
|
|
|
|
|
|
|
|
identifier = self.format_vm_identifier(vmid, hostname)
|
|
|
|
lxc_status = self.get_lxc_status(vmid, node)
|
|
|
|
|
|
|
|
if lxc_status in ["stopped", "mounted"]:
|
|
|
|
self.module.exit_json(
|
|
|
|
changed=False, vmid=vmid, msg="VM %s is not running." % identifier
|
|
|
|
)
|
|
|
|
|
|
|
|
self.stop_lxc_instance(vmid, node, timeout, force)
|
|
|
|
self.start_lxc_instance(vmid, node, timeout)
|
|
|
|
self.module.exit_json(
|
|
|
|
changed=True, vmid=vmid, msg="VM %s is restarted." % identifier
|
|
|
|
)
|
|
|
|
|
|
|
|
def lxc_to_template(self, vmid, hostname, node, timeout, force):
|
|
|
|
lxc = self.get_lxc_resource(vmid, hostname)
|
|
|
|
vmid = vmid or lxc["id"].split("/")[-1]
|
|
|
|
hostname = hostname or lxc["name"]
|
|
|
|
node = node or lxc["node"]
|
|
|
|
identifier = self.format_vm_identifier(vmid, hostname)
|
|
|
|
|
|
|
|
if self.is_template_container(node, vmid):
|
|
|
|
self.module.exit_json(
|
|
|
|
changed=False,
|
|
|
|
vmid=vmid,
|
|
|
|
msg="VM %s is already a template." % identifier,
|
|
|
|
)
|
|
|
|
|
|
|
|
lxc_status = self.get_lxc_status(vmid, node)
|
|
|
|
if lxc_status == "running" and force:
|
|
|
|
self.stop_instance(vmid, hostname, node, timeout, force)
|
|
|
|
|
|
|
|
proxmox_node = self.proxmox_api.nodes(node)
|
|
|
|
getattr(proxmox_node, self.VZ_TYPE)(vmid).template.post()
|
|
|
|
self.module.exit_json(
|
|
|
|
changed=True, vmid=vmid, msg="VM %s converted to template." % identifier
|
|
|
|
)
|
|
|
|
|
|
|
|
def update_lxc_instance(self, vmid, node, **kwargs):
|
|
|
|
if self.VZ_TYPE != "lxc":
|
|
|
|
self.module.fail_json(
|
|
|
|
msg="Updating LXC containers is only supported for LXC-enabled clusters in PVE 4.0 and above."
|
|
|
|
)
|
|
|
|
|
|
|
|
kwargs = {k: v for k, v in kwargs.items() if v is not None}
|
|
|
|
|
|
|
|
self.validate_tags(kwargs.get("tags", []))
|
|
|
|
|
|
|
|
if "features" in kwargs:
|
|
|
|
kwargs["features"] = ",".join(kwargs.pop("features"))
|
|
|
|
if "startup" in kwargs:
|
|
|
|
kwargs["startup"] = ",".join(kwargs.pop("startup"))
|
|
|
|
|
|
|
|
disk_updates = self.process_disk_keys(
|
|
|
|
vmid,
|
|
|
|
node,
|
|
|
|
kwargs.pop("disk", None),
|
|
|
|
kwargs.pop("disk_volume", None),
|
|
|
|
)
|
|
|
|
mounts_updates = self.process_mount_keys(
|
|
|
|
vmid,
|
|
|
|
node,
|
|
|
|
kwargs.pop("mounts", None),
|
|
|
|
kwargs.pop("mount_volumes", None),
|
|
|
|
)
|
|
|
|
kwargs.update(disk_updates)
|
|
|
|
kwargs.update(mounts_updates)
|
|
|
|
|
|
|
|
if "cpus" in kwargs:
|
|
|
|
kwargs["cpulimit"] = kwargs.pop("cpus")
|
|
|
|
if "netif" in kwargs:
|
|
|
|
kwargs.update(kwargs.pop("netif"))
|
|
|
|
|
|
|
|
# fetch current config
|
|
|
|
proxmox_node = self.proxmox_api.nodes(node)
|
|
|
|
current_config = getattr(proxmox_node, self.VZ_TYPE)(vmid).config.get()
|
|
|
|
|
|
|
|
# create diff between the current and requested config
|
|
|
|
diff = {}
|
|
|
|
for arg, value in kwargs.items():
|
|
|
|
# if the arg isn't in the current config, it needs to be added
|
|
|
|
if arg not in current_config:
|
|
|
|
diff[arg] = value
|
|
|
|
elif isinstance(value, str):
|
|
|
|
# compare all string values as lists as some of them may be lists separated by commas. order doesn't matter
|
|
|
|
current_values = current_config[arg].split(",")
|
|
|
|
requested_values = value.split(",")
|
|
|
|
for new_value in requested_values:
|
|
|
|
if new_value not in current_values:
|
|
|
|
diff[arg] = value
|
|
|
|
break
|
|
|
|
# if it's not a list (or string) just compare the values
|
|
|
|
# some types don't match with the API, so force a string comparison
|
|
|
|
elif str(value) != str(current_config[arg]):
|
|
|
|
diff[arg] = value
|
|
|
|
|
|
|
|
if not diff:
|
|
|
|
self.module.exit_json(
|
|
|
|
changed=False, vmid=vmid, msg="Container config is already up to date."
|
|
|
|
)
|
|
|
|
|
|
|
|
# update the config
|
|
|
|
getattr(proxmox_node, self.VZ_TYPE)(vmid).config.put(
|
|
|
|
vmid=vmid, node=node, **kwargs
|
|
|
|
)
|
|
|
|
|
|
|
|
def new_lxc_instance(self, vmid, hostname, node, clone_from, ostemplate, force):
|
|
|
|
identifier = self.format_vm_identifier(vmid, hostname)
|
|
|
|
|
|
|
|
if clone_from is not None:
|
|
|
|
self.clone_lxc_instance(
|
|
|
|
vmid,
|
|
|
|
node,
|
|
|
|
clone_from,
|
|
|
|
clone_type=self.params.get("clone_type"),
|
|
|
|
timeout=self.params.get("timeout"),
|
|
|
|
description=self.params.get("description"),
|
|
|
|
hostname=hostname,
|
|
|
|
pool=self.params.get("pool"),
|
|
|
|
storage=self.params.get("storage"),
|
|
|
|
)
|
|
|
|
self.module.exit_json(
|
|
|
|
changed=True,
|
|
|
|
vmid=vmid,
|
|
|
|
msg="Cloned VM %s from %d" % (identifier, clone_from),
|
|
|
|
)
|
|
|
|
|
|
|
|
if ostemplate is not None:
|
|
|
|
self.create_lxc_instance(
|
|
|
|
vmid,
|
|
|
|
node,
|
|
|
|
ostemplate,
|
|
|
|
timeout=self.params.get("timeout"),
|
|
|
|
cores=self.params.get("cores"),
|
|
|
|
cpus=self.params.get("cpus"),
|
|
|
|
cpuunits=self.params.get("cpuunits"),
|
|
|
|
description=self.params.get("description"),
|
|
|
|
disk=self.params.get("disk"),
|
|
|
|
disk_volume=self.params.get("disk_volume"),
|
|
|
|
features=self.params.get("features"),
|
|
|
|
force=ansible_to_proxmox_bool(force),
|
|
|
|
hookscript=self.params.get("hookscript"),
|
|
|
|
hostname=hostname,
|
|
|
|
ip_address=self.params.get("ip_address"),
|
|
|
|
memory=self.params.get("memory"),
|
|
|
|
mounts=self.params.get("mounts"),
|
|
|
|
mount_volumes=self.params.get("mount_volumes"),
|
|
|
|
nameserver=self.params.get("nameserver"),
|
|
|
|
netif=self.params.get("netif"),
|
|
|
|
onboot=ansible_to_proxmox_bool(self.params.get("onboot")),
|
|
|
|
ostype=self.params.get("ostype"),
|
|
|
|
password=self.params.get("password"),
|
|
|
|
pool=self.params.get("pool"),
|
|
|
|
pubkey=self.params.get("pubkey"),
|
|
|
|
searchdomain=self.params.get("searchdomain"),
|
|
|
|
startup=self.params.get("startup"),
|
|
|
|
storage=self.params.get("storage"),
|
|
|
|
swap=self.params.get("swap"),
|
|
|
|
tags=self.params.get("tags"),
|
|
|
|
timezone=self.params.get("timezone"),
|
|
|
|
unprivileged=ansible_to_proxmox_bool(self.params.get("unprivileged")),
|
|
|
|
)
|
|
|
|
self.module.exit_json(
|
|
|
|
changed=True,
|
|
|
|
vmid=vmid,
|
|
|
|
msg="Created VM %s from template %s" % (identifier, ostemplate),
|
|
|
|
)
|
|
|
|
|
|
|
|
self.module.fail_json(
|
|
|
|
vmid=vmid,
|
|
|
|
msg="VM %s does not exist but neither clone nor ostemplate were specified!"
|
|
|
|
% identifier,
|
|
|
|
)
|
|
|
|
|
|
|
|
def create_lxc_instance(self, vmid, node, ostemplate, timeout, **kwargs):
|
|
|
|
template_store = ostemplate.split(":")[0]
|
|
|
|
if not self.content_check(node, ostemplate, template_store):
|
|
|
|
self.module.fail_json(
|
|
|
|
vmid=vmid,
|
|
|
|
msg="ostemplate %s does not exist on node %s and storage %s."
|
|
|
|
% (ostemplate, node, template_store),
|
|
|
|
)
|
|
|
|
|
|
|
|
disk_updates = self.process_disk_keys(
|
|
|
|
vmid,
|
|
|
|
node,
|
|
|
|
kwargs.pop("disk"),
|
|
|
|
kwargs.pop("disk_volume"),
|
|
|
|
)
|
|
|
|
mounts_updates = self.process_mount_keys(
|
|
|
|
vmid,
|
|
|
|
node,
|
|
|
|
kwargs.pop("mounts"),
|
|
|
|
kwargs.pop("mount_volumes"),
|
|
|
|
)
|
|
|
|
kwargs.update(disk_updates)
|
|
|
|
kwargs.update(mounts_updates)
|
|
|
|
|
|
|
|
# Remove empty values from kwargs
|
|
|
|
kwargs = {k: v for k, v in kwargs.items() if v is not None}
|
|
|
|
|
|
|
|
if "features" in kwargs:
|
|
|
|
kwargs["features"] = ",".join(kwargs.pop("features"))
|
|
|
|
|
|
|
|
if "startup" in kwargs:
|
|
|
|
kwargs["startup"] = ",".join(kwargs.pop("startup"))
|
|
|
|
|
|
|
|
self.validate_tags(kwargs.get("tags", []))
|
|
|
|
|
|
|
|
if self.VZ_TYPE == "lxc":
|
|
|
|
if "cpus" in kwargs:
|
|
|
|
kwargs["cpuunits"] = kwargs.pop("cpus")
|
|
|
|
kwargs.update(kwargs.pop("netif", {}))
|
|
|
|
else:
|
|
|
|
if "mount_volumes" in kwargs:
|
|
|
|
kwargs.pop("mount_volumes")
|
|
|
|
self.module.warn(
|
|
|
|
"'mount_volumes' is not supported for non-LXC clusters. Ignoring keyword."
|
|
|
|
)
|
|
|
|
|
|
|
|
if "pubkey" in kwargs:
|
|
|
|
pubkey = kwargs.pop("pubkey")
|
|
|
|
if self.version() >= LooseVersion("4.2"):
|
|
|
|
kwargs["ssh-public-key"] = pubkey
|
|
|
|
else:
|
|
|
|
self.module.warn(
|
|
|
|
"'pubkey' is not supported for PVE 4.1 and below. Ignoring keyword."
|
|
|
|
)
|
|
|
|
|
|
|
|
if kwargs.get("ostype") == "auto":
|
|
|
|
kwargs.pop("ostype")
|
|
|
|
|
|
|
|
proxmox_node = self.proxmox_api.nodes(node)
|
|
|
|
taskid = getattr(proxmox_node, self.VZ_TYPE).create(
|
|
|
|
vmid=vmid, ostemplate=ostemplate, **kwargs
|
|
|
|
)
|
|
|
|
self.handle_api_timeout(
|
|
|
|
vmid,
|
|
|
|
node,
|
|
|
|
taskid,
|
|
|
|
timeout,
|
|
|
|
"Reached timeout while waiting for creation of VM %s from template %s"
|
|
|
|
% (vmid, ostemplate),
|
|
|
|
)
|
|
|
|
|
|
|
|
def clone_lxc_instance(self, vmid, node, clone_from, clone_type, timeout, **kwargs):
|
|
|
|
if self.VZ_TYPE != "lxc":
|
|
|
|
self.module.fail_json(
|
|
|
|
msg="Cloning is only supported for LXC-enabled clusters in PVE 4.0 and above."
|
|
|
|
)
|
|
|
|
|
|
|
|
# Remove empty values from kwargs
|
|
|
|
kwargs = {k: v for k, v in kwargs.items() if v is not None}
|
|
|
|
|
|
|
|
target_is_template = self.is_template_container(node, clone_from)
|
|
|
|
# By default, create a full copy only when the cloned container is not a template.
|
|
|
|
create_full_copy = not target_is_template
|
|
|
|
|
|
|
|
# Only accept parameters that are compatible with the clone endpoint.
|
|
|
|
valid_clone_parameters = ["hostname", "pool", "description"]
|
|
|
|
|
|
|
|
if "storage" not in kwargs and target_is_template:
|
|
|
|
# Cloning a template, so create a full copy instead of a linked copy
|
|
|
|
create_full_copy = True
|
|
|
|
elif "storage" not in kwargs and not target_is_template:
|
|
|
|
self.module.fail_json(
|
|
|
|
changed=False,
|
|
|
|
msg="Clone target container is not a template, storage needs to be specified.",
|
|
|
|
)
|
|
|
|
|
|
|
|
if clone_type == "linked" and not target_is_template:
|
|
|
|
self.module.fail_json(
|
|
|
|
changed=False,
|
|
|
|
msg="Cloning type 'linked' is only supported for template containers.",
|
|
|
|
)
|
|
|
|
elif clone_type == "opportunistic" and not target_is_template:
|
|
|
|
# Cloned container is not a template, so we need our 'storage' parameter
|
|
|
|
valid_clone_parameters.append("storage")
|
|
|
|
elif clone_type == "full":
|
|
|
|
create_full_copy = True
|
|
|
|
valid_clone_parameters.append("storage")
|
|
|
|
|
|
|
|
clone_parameters = {}
|
|
|
|
clone_parameters["full"] = ansible_to_proxmox_bool(create_full_copy)
|
|
|
|
|
|
|
|
for param in valid_clone_parameters:
|
|
|
|
if param in kwargs:
|
|
|
|
clone_parameters[param] = kwargs[param]
|
|
|
|
|
|
|
|
proxmox_node = self.proxmox_api.nodes(node)
|
|
|
|
taskid = getattr(proxmox_node, self.VZ_TYPE)(clone_from).clone.post(
|
|
|
|
newid=vmid, **clone_parameters
|
|
|
|
)
|
|
|
|
self.handle_api_timeout(
|
|
|
|
vmid,
|
|
|
|
node,
|
|
|
|
taskid,
|
|
|
|
timeout,
|
|
|
|
timeout_msg="Reached timeout while waiting for VM to clone.",
|
|
|
|
)
|
|
|
|
|
|
|
|
def start_lxc_instance(self, vmid, node, timeout):
|
|
|
|
proxmox_node = self.proxmox_api.nodes(node)
|
|
|
|
taskid = getattr(proxmox_node, self.VZ_TYPE)(vmid).status.start.post()
|
|
|
|
|
|
|
|
self.handle_api_timeout(
|
|
|
|
vmid,
|
|
|
|
node,
|
|
|
|
taskid,
|
|
|
|
timeout,
|
|
|
|
timeout_msg="Reached timeout while waiting for VM to start.",
|
|
|
|
)
|
|
|
|
|
|
|
|
def stop_lxc_instance(self, vmid, node, timeout, force):
|
|
|
|
stop_params = {}
|
|
|
|
if force:
|
|
|
|
stop_params["forceStop"] = 1
|
|
|
|
|
|
|
|
proxmox_node = self.proxmox_api.nodes(node)
|
|
|
|
taskid = getattr(proxmox_node, self.VZ_TYPE)(vmid).status.shutdown.post(
|
|
|
|
**stop_params
|
|
|
|
)
|
|
|
|
|
|
|
|
self.handle_api_timeout(
|
|
|
|
vmid,
|
|
|
|
node,
|
|
|
|
taskid,
|
|
|
|
timeout,
|
|
|
|
timeout_msg="Reached timeout while waiting for VM to stop.",
|
|
|
|
)
|
|
|
|
|
|
|
|
def umount_lxc_instance(self, vmid, node, timeout):
|
|
|
|
proxmox_node = self.proxmox_api.nodes(node)
|
|
|
|
taskid = getattr(proxmox_node, self.VZ_TYPE)(vmid).status.unmount.post()
|
|
|
|
|
|
|
|
self.handle_api_timeout(
|
|
|
|
vmid,
|
|
|
|
node,
|
|
|
|
taskid,
|
|
|
|
timeout,
|
|
|
|
timeout_msg="Reached timeout while waiting for VM to be unmounted.",
|
|
|
|
)
|
|
|
|
|
|
|
|
def remove_lxc_instance(self, vmid, node, timeout, purge):
|
|
|
|
delete_params = {}
|
|
|
|
if purge:
|
|
|
|
delete_params["purge"] = 1
|
|
|
|
|
|
|
|
proxmox_node = self.proxmox_api.nodes(node)
|
|
|
|
taskid = getattr(proxmox_node, self.VZ_TYPE).delete(vmid, **delete_params)
|
|
|
|
|
|
|
|
self.handle_api_timeout(
|
|
|
|
vmid,
|
|
|
|
node,
|
|
|
|
taskid,
|
|
|
|
timeout,
|
|
|
|
timeout_msg="Reached timeout while waiting for VM to be removed.",
|
|
|
|
)
|
|
|
|
|
|
|
|
def process_disk_keys(self, vmid, node, disk, disk_volume):
|
|
|
|
"""
|
|
|
|
Process disk keys and return a formatted disk volume with the `rootfs` key.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
vmid (int): VM identifier.
|
|
|
|
node (str): Node identifier.
|
|
|
|
disk (str, optional): Disk key in the format 'storage:volume'. Defaults to None.
|
|
|
|
disk_volume (Dict[str, Any], optional): Disk volume data. Defaults to None.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Dict[str, str]: Formatted disk volume with the `rootfs` or `disk` key (depending on the `VZ_TYPE`), or an empty dict if no disk volume is specified.
|
|
|
|
"""
|
|
|
|
if disk is None and disk_volume is None:
|
|
|
|
return {}
|
|
|
|
|
|
|
|
disk_dict = {}
|
|
|
|
|
|
|
|
if disk is not None:
|
|
|
|
if disk.isdigit():
|
|
|
|
disk_dict["rootfs"] = disk
|
|
|
|
else:
|
|
|
|
disk_volume = self.parse_disk_string(disk)
|
|
|
|
|
|
|
|
if disk_volume is not None:
|
|
|
|
disk_dict = self.build_volume(vmid, node, key="rootfs", **disk_volume)
|
|
|
|
|
|
|
|
if self.VZ_TYPE != "lxc":
|
|
|
|
disk_dict["disk"] = disk_dict.pop("rootfs")
|
|
|
|
|
|
|
|
return disk_dict
|
|
|
|
|
|
|
|
def process_mount_keys(self, vmid, node, mounts, mount_volumes):
|
|
|
|
"""
|
|
|
|
Process mount keys and return a formatted mount volumes with the `mp[n]` keys.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
vmid (str): VM identifier.
|
|
|
|
node (str): Node identifier.
|
|
|
|
mounts (str, optional): Mount key in the format 'pool:volume'. Defaults to None.
|
|
|
|
mount_volumes (Dict[str, Any], optional): Mount volume data. Defaults to None.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Dict[str, str]: Formatted mount volumes with the `mp[n]` keys, or an empty dict if no mount volumes are specified.
|
|
|
|
"""
|
|
|
|
if mounts is not None:
|
|
|
|
mount_volumes = []
|
|
|
|
for mount_key, mount_string in mounts.items():
|
|
|
|
mount_config = self.parse_disk_string(mount_string)
|
|
|
|
mount_volumes.append(dict(id=mount_key, **mount_config))
|
|
|
|
elif mount_volumes is None or mount_volumes == []:
|
|
|
|
return {}
|
|
|
|
|
|
|
|
mounts_dict = {}
|
|
|
|
for mount_config in mount_volumes:
|
|
|
|
mount_key = mount_config.pop("id")
|
|
|
|
mount_dict = self.build_volume(vmid, node, key=mount_key, **mount_config)
|
|
|
|
mounts_dict.update(mount_dict)
|
|
|
|
|
|
|
|
return mounts_dict
|
|
|
|
|
|
|
|
def parse_disk_string(self, disk_string):
|
|
|
|
"""
|
|
|
|
Parse a disk string and return a dictionary with the disk details.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
disk_string (str): Disk string.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Dict[str, Any]: Disk details.
|
|
|
|
|
|
|
|
Note: Below are some example disk strings that this function MUST be able to parse:
|
|
|
|
"acl=0,thin1:base-100-disk-1,size=8G"
|
|
|
|
"thin1:10,backup=0"
|
|
|
|
"local:20"
|
|
|
|
"local-lvm:0.50"
|
|
|
|
"tmp-dir:300/subvol-300-disk-0.subvol,acl=1,size=0T"
|
|
|
|
"tmplog-dir:300/vm-300-disk-0.raw,mp=/var/log,mountoptions=noatime,size=32M"
|
|
|
|
"volume=local-lvm:base-100-disk-1,size=20G"
|
|
|
|
"/mnt/bindmounts/shared,mp=/shared"
|
|
|
|
"volume=/dev/USB01,mp=/mnt/usb01"
|
|
|
|
"""
|
|
|
|
args = disk_string.split(",")
|
|
|
|
# If the volume is not explicitly defined but implicit by only passing a key,
|
|
|
|
# add the "volume=" key prefix for ease of parsing.
|
|
|
|
args = ["volume=" + arg if "=" not in arg else arg for arg in args]
|
|
|
|
# Then create a dictionary from the arguments
|
|
|
|
disk_kwargs = dict(map(lambda item: item.split("="), args))
|
|
|
|
|
|
|
|
VOLUME_PATTERN = r"""(?x)
|
|
|
|
^
|
|
|
|
(?:
|
|
|
|
(?:
|
|
|
|
(?P<storage>[\w\-.]+):
|
|
|
|
(?:
|
|
|
|
(?P<size>\d+\.?\d*)|
|
|
|
|
(?P<volume>[^,\s]+)
|
|
|
|
)
|
|
|
|
)|
|
|
|
|
(?P<host_path>[^,\s]+)
|
|
|
|
)
|
|
|
|
$
|
|
|
|
"""
|
|
|
|
# DISCLAIMER:
|
|
|
|
# There are two things called a "volume":
|
|
|
|
# 1. The "volume" key which describes the storage volume, device or directory to mount into the container.
|
|
|
|
# 2. The storage volume of a storage-backed mount point in the PVE storage sub system.
|
|
|
|
# In this section, we parse the "volume" key and check which type of mount point we are dealing with.
|
|
|
|
pattern = re.compile(VOLUME_PATTERN)
|
|
|
|
volume_string = disk_kwargs.pop("volume")
|
|
|
|
match = pattern.match(volume_string)
|
|
|
|
if match is None:
|
|
|
|
raise ValueError(("Invalid volume string: %s", volume_string))
|
|
|
|
match_dict = match.groupdict()
|
|
|
|
match_dict = {k: v for k, v in match_dict.items() if v is not None}
|
|
|
|
|
|
|
|
if "storage" in match_dict and "volume" in match_dict:
|
|
|
|
disk_kwargs["storage"] = match_dict["storage"]
|
|
|
|
disk_kwargs["volume"] = match_dict["volume"]
|
|
|
|
elif "storage" in match_dict and "size" in match_dict:
|
|
|
|
disk_kwargs["storage"] = match_dict["storage"]
|
|
|
|
disk_kwargs["size"] = match_dict["size"]
|
|
|
|
elif "host_path" in match_dict:
|
|
|
|
disk_kwargs["host_path"] = match_dict["host_path"]
|
|
|
|
|
|
|
|
# Pattern matching only available in Python 3.10+
|
|
|
|
# TODO: Uncomment the following code once only Python 3.10+ is supported
|
|
|
|
# match match_dict:
|
|
|
|
# case {"storage": storage, "volume": volume}:
|
|
|
|
# disk_kwargs["storage"] = storage
|
|
|
|
# disk_kwargs["volume"] = volume
|
|
|
|
|
|
|
|
# case {"storage": storage, "size": size}:
|
|
|
|
# disk_kwargs["storage"] = storage
|
|
|
|
# disk_kwargs["size"] = size
|
|
|
|
|
|
|
|
# case {"host_path": host_path}:
|
|
|
|
# disk_kwargs["host_path"] = host_path
|
|
|
|
|
|
|
|
return disk_kwargs
|
|
|
|
|
|
|
|
def build_volume(self, vmid, node, key, storage=None, volume=None, host_path=None, size=None, mountpoint=None, options=None, **kwargs):
|
|
|
|
"""
|
|
|
|
Build a volume string for the specified VM.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
vmid (str): The VM ID.
|
|
|
|
node (str): The node where the VM resides.
|
|
|
|
key (str): The key for the volume in the VM's config.
|
|
|
|
storage (str, optional): The storage pool where the volume resides. Defaults to None.
|
|
|
|
volume (str, optional): The name of the volume. Defaults to None.
|
|
|
|
host_path (str, optional): The host path to mount. Defaults to None.
|
|
|
|
size (str | int, optional): The size of the volume in GiB. Defaults to None.
|
|
|
|
mountpoint (str, optional): The mountpoint for the volume. Defaults to None.
|
|
|
|
options (Dict[str, Any], optional): Additional options for the volume. Defaults to None.
|
|
|
|
**kwargs: Additional keyword arguments.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Dict[str, str]: The built volume string in the format {'volume_key': 'volume_string'}.
|
|
|
|
|
|
|
|
Note: Further documentation can be found in the proxmox-api documentation: https://pve.proxmox.com/wiki/Linux_Container#pct_mount_points
|
|
|
|
Note: To build a valid volume string, we need ONE of the following:
|
|
|
|
A volume name, storage name, and size
|
|
|
|
Only a storage name and size (to create a new volume or assign the volume automatically)
|
|
|
|
A host directory to mount into the container
|
|
|
|
"""
|
|
|
|
if isinstance(size, int):
|
|
|
|
size = str(size)
|
|
|
|
if size is not None and isfloat(size):
|
|
|
|
size += "G" # default to GiB
|
|
|
|
# Handle volume checks/creation
|
|
|
|
# TODO: Change the code below to pattern matching once only Python 3.10+ is supported
|
|
|
|
# 1. Check if defined volume exists
|
|
|
|
if volume is not None:
|
|
|
|
storage_content = self.get_storage_content(node, storage, vmid=vmid)
|
|
|
|
vol_ids = [vol["volid"] for vol in storage_content]
|
|
|
|
volid = "{storage}:{volume}".format(storage=storage, volume=volume)
|
|
|
|
if volid not in vol_ids:
|
|
|
|
self.module.fail_json(
|
|
|
|
changed=False,
|
|
|
|
msg="Storage {storage} does not contain volume {volume}".format(
|
|
|
|
storage=storage,
|
|
|
|
volume=volume,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
vol_string = "{storage}:{volume},size={size}".format(
|
|
|
|
storage=storage, volume=volume, size=size
|
|
|
|
)
|
|
|
|
# 2. If volume not defined (but storage is), check if it exists
|
|
|
|
elif storage is not None:
|
|
|
|
proxmox_node = self.proxmox_api.nodes(
|
|
|
|
node
|
|
|
|
) # The node must exist, but not the LXC
|
|
|
|
try:
|
|
|
|
vol = proxmox_node.lxc(vmid).get("config").get(key)
|
|
|
|
volume = self.parse_disk_string(vol).get("volume")
|
|
|
|
vol_string = "{storage}:{volume},size={size}".format(
|
|
|
|
storage=storage, volume=volume, size=size
|
|
|
|
)
|
|
|
|
|
|
|
|
# If not, we have proxmox create one using the special syntax
|
|
|
|
except Exception:
|
|
|
|
if size is None:
|
|
|
|
raise ValueError(
|
|
|
|
"Size must be provided for storage-backed volume creation."
|
|
|
|
)
|
|
|
|
elif size.endswith("G"):
|
|
|
|
size = size.rstrip("G")
|
|
|
|
vol_string = "{storage}:{size}".format(storage=storage, size=size)
|
2020-03-09 09:11:07 +00:00
|
|
|
else:
|
2025-01-26 15:06:14 +00:00
|
|
|
raise ValueError(
|
|
|
|
"Size must be provided in GiB for storage-backed volume creation. Convert it to GiB or allocate a new storage manually."
|
|
|
|
)
|
|
|
|
# 3. If we have a host_path, we don't have storage, a volume, or a size
|
|
|
|
# Then we don't have to do anything, just build and return the vol_string
|
|
|
|
elif host_path is not None:
|
|
|
|
vol_string = ""
|
|
|
|
else:
|
|
|
|
raise ValueError(
|
|
|
|
"Could not build a valid volume string. One of volume, storage, or host_path must be provided."
|
|
|
|
)
|
2020-03-09 09:11:07 +00:00
|
|
|
|
2025-01-26 15:06:14 +00:00
|
|
|
if host_path is not None:
|
|
|
|
vol_string += "," + host_path
|
2020-03-09 09:11:07 +00:00
|
|
|
|
2025-01-26 15:06:14 +00:00
|
|
|
if mountpoint is not None:
|
|
|
|
vol_string += ",mp={}".format(mountpoint)
|
2020-03-09 09:11:07 +00:00
|
|
|
|
2025-01-26 15:06:14 +00:00
|
|
|
if options is not None:
|
|
|
|
vol_string += "," + ",".join(
|
|
|
|
["{0}={1}".format(k, v) for k, v in options.items()]
|
|
|
|
)
|
2023-11-05 14:57:16 +00:00
|
|
|
|
2025-01-26 15:06:14 +00:00
|
|
|
if kwargs:
|
|
|
|
vol_string += "," + ",".join(
|
|
|
|
["{0}={1}".format(k, v) for k, v in kwargs.items()]
|
|
|
|
)
|
|
|
|
return {key: vol_string}
|
2023-11-05 14:57:16 +00:00
|
|
|
|
2025-01-26 15:06:14 +00:00
|
|
|
def get_lxc_resource(self, vmid, hostname):
|
|
|
|
if not vmid and not hostname:
|
|
|
|
self.module.fail_json(msg="Either VMID or hostname must be provided.")
|
|
|
|
|
|
|
|
if vmid:
|
|
|
|
vm = self.get_lxc_resource_by_id(vmid)
|
|
|
|
elif hostname:
|
|
|
|
vm = self.get_lxc_resource_by_hostname(hostname)
|
|
|
|
|
|
|
|
vmid = vm["vmid"]
|
|
|
|
if vm["type"] != self.VZ_TYPE:
|
|
|
|
identifier = self.format_vm_identifier(vmid, hostname)
|
|
|
|
self.module.fail_json(
|
|
|
|
msg="The specified VM %s is not an %s." % (identifier, self.VZ_TYPE)
|
|
|
|
)
|
|
|
|
|
|
|
|
return vm
|
2022-02-07 05:21:24 +00:00
|
|
|
|
2025-01-26 15:06:14 +00:00
|
|
|
def get_lxc_resource_by_id(self, vmid):
|
|
|
|
vms = self.get_vm_resources()
|
2020-03-09 09:11:07 +00:00
|
|
|
|
2025-01-26 15:06:14 +00:00
|
|
|
vms = [vm for vm in vms if vm["vmid"] == vmid]
|
|
|
|
if len(vms) == 0:
|
|
|
|
raise LookupError("VM with VMID %d does not exist in cluster." % vmid)
|
|
|
|
|
|
|
|
return vms[0]
|
|
|
|
|
|
|
|
def get_lxc_resource_by_hostname(self, hostname):
|
|
|
|
vms = self.get_vm_resources()
|
|
|
|
|
|
|
|
vms = [vm for vm in vms if vm["name"] == hostname]
|
|
|
|
if len(vms) == 0:
|
|
|
|
raise LookupError(
|
|
|
|
"VM with hostname %s does not exist in cluster." % hostname
|
|
|
|
)
|
|
|
|
elif len(vms) > 1:
|
|
|
|
raise ValueError(
|
|
|
|
"Multiple VMs found with hostname %s. Please specify VMID." % hostname
|
|
|
|
)
|
|
|
|
|
|
|
|
return vms[0]
|
|
|
|
|
|
|
|
def get_vm_resources(self):
|
|
|
|
try:
|
|
|
|
return self.proxmox_api.cluster.resources.get(type="vm")
|
2020-03-09 09:11:07 +00:00
|
|
|
except Exception as e:
|
2025-01-26 15:06:14 +00:00
|
|
|
self.module.fail_json(
|
|
|
|
msg="Unable to retrieve list of %s VMs from cluster resources: %s"
|
|
|
|
% (self.VZ_TYPE, e)
|
|
|
|
)
|
2020-03-09 09:11:07 +00:00
|
|
|
|
2025-01-26 15:06:14 +00:00
|
|
|
def get_lxc_status(self, vmid, node_name):
|
2020-03-09 09:11:07 +00:00
|
|
|
try:
|
2025-01-26 15:06:14 +00:00
|
|
|
proxmox_node = self.proxmox_api.nodes(node_name)
|
|
|
|
except Exception as e:
|
|
|
|
self.module.fail_json(msg="Unable to retrieve node information: %s" % e)
|
|
|
|
return getattr(proxmox_node, self.VZ_TYPE)(vmid).status.current.get()
|
|
|
|
|
|
|
|
def format_vm_identifier(self, vmid, hostname):
|
|
|
|
if vmid and hostname:
|
|
|
|
return "%s (%s)" % (hostname, vmid)
|
|
|
|
elif hostname:
|
|
|
|
return hostname
|
|
|
|
else:
|
|
|
|
return to_native(vmid)
|
2020-03-09 09:11:07 +00:00
|
|
|
|
2025-01-26 15:06:14 +00:00
|
|
|
def handle_api_timeout(self, vmid, node, taskid, timeout, timeout_msg=""):
|
|
|
|
if timeout_msg != "":
|
|
|
|
timeout_msg = "%s " % timeout_msg
|
2020-03-09 09:11:07 +00:00
|
|
|
|
2025-01-26 15:06:14 +00:00
|
|
|
while timeout > 0:
|
|
|
|
if self.api_task_ok(node, taskid):
|
|
|
|
return
|
|
|
|
timeout -= 1
|
|
|
|
time.sleep(1)
|
2020-03-09 09:11:07 +00:00
|
|
|
|
2025-01-26 15:06:14 +00:00
|
|
|
self.module.fail_json(
|
|
|
|
vmid=vmid,
|
|
|
|
taskid=taskid,
|
|
|
|
msg="%sLast line in task before timeout: %s"
|
|
|
|
% (timeout_msg, self.proxmox_api.nodes(node).tasks(taskid).log.get()[:1]),
|
|
|
|
)
|
2021-03-19 18:18:05 +00:00
|
|
|
|
2025-01-26 15:06:14 +00:00
|
|
|
def is_template_container(self, node, target):
|
|
|
|
"""Check if the specified container is a template."""
|
|
|
|
proxmox_node = self.proxmox_api.nodes(node)
|
|
|
|
config = getattr(proxmox_node, self.VZ_TYPE)(target).config.get()
|
|
|
|
return config.get("template", False)
|
2021-03-19 18:18:05 +00:00
|
|
|
|
2025-01-26 15:06:14 +00:00
|
|
|
def content_check(self, node, ostemplate, template_store):
|
|
|
|
"""Check if the specified ostemplate is present in the specified storage."""
|
|
|
|
proxmox_node = self.proxmox_api.nodes(node)
|
|
|
|
storage_contents = proxmox_node.storage(template_store).content.get()
|
|
|
|
return any(content["volid"] == ostemplate for content in storage_contents)
|
|
|
|
|
|
|
|
def validate_tags(self, tags):
|
|
|
|
"""Check if the specified tags are valid."""
|
|
|
|
re_tag = re.compile(r"^[a-z0-9_][a-z0-9_\-\+\.]*$")
|
|
|
|
for tag in tags:
|
|
|
|
if not re_tag.match(tag):
|
|
|
|
self.module.fail_json(msg="%s is not a valid tag" % tag)
|
|
|
|
return False
|
|
|
|
return True
|
2021-03-19 18:18:05 +00:00
|
|
|
|
2025-01-26 15:06:14 +00:00
|
|
|
def check_supported_features(self):
|
|
|
|
for option, version in self.MINIMUM_VERSIONS.items():
|
|
|
|
if self.version() < LooseVersion(version) and option in self.module.params:
|
|
|
|
self.module.fail_json(
|
|
|
|
changed=False,
|
|
|
|
msg="Feature {option} is only supported in PVE {version}+, and you're using PVE {pve_version}".format(
|
|
|
|
option=option, version=version, pve_version=self.version()
|
|
|
|
),
|
|
|
|
)
|
2020-03-09 09:11:07 +00:00
|
|
|
|
2025-01-26 15:06:14 +00:00
|
|
|
|
|
|
|
def isfloat(value):
|
|
|
|
if value is None:
|
|
|
|
return False
|
|
|
|
try:
|
|
|
|
float(value)
|
|
|
|
return True
|
|
|
|
except ValueError:
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
module = get_ansible_module()
|
|
|
|
proxmox = ProxmoxLxcAnsible(module)
|
|
|
|
|
|
|
|
try:
|
|
|
|
proxmox.run()
|
|
|
|
except Exception as e:
|
|
|
|
module.fail_json(msg="An error occurred: %s" % to_native(e))
|
2020-03-09 09:11:07 +00:00
|
|
|
|
|
|
|
|
2025-01-26 15:06:14 +00:00
|
|
|
if __name__ == "__main__":
|
2020-03-09 09:11:07 +00:00
|
|
|
main()
|