Remove proxmox content (#10110)
Some checks failed
EOL CI / EOL Sanity (Ⓐ2.16) (push) Has been cancelled
EOL CI / EOL Units (Ⓐ2.16+py2.7) (push) Has been cancelled
EOL CI / EOL Units (Ⓐ2.16+py3.11) (push) Has been cancelled
EOL CI / EOL Units (Ⓐ2.16+py3.6) (push) Has been cancelled
EOL CI / EOL I (Ⓐ2.16+alpine3+py:azp/posix/1/) (push) Has been cancelled
EOL CI / EOL I (Ⓐ2.16+alpine3+py:azp/posix/2/) (push) Has been cancelled
EOL CI / EOL I (Ⓐ2.16+alpine3+py:azp/posix/3/) (push) Has been cancelled
EOL CI / EOL I (Ⓐ2.16+fedora38+py:azp/posix/1/) (push) Has been cancelled
EOL CI / EOL I (Ⓐ2.16+fedora38+py:azp/posix/2/) (push) Has been cancelled
EOL CI / EOL I (Ⓐ2.16+fedora38+py:azp/posix/3/) (push) Has been cancelled
EOL CI / EOL I (Ⓐ2.16+opensuse15+py:azp/posix/1/) (push) Has been cancelled
EOL CI / EOL I (Ⓐ2.16+opensuse15+py:azp/posix/2/) (push) Has been cancelled
EOL CI / EOL I (Ⓐ2.16+opensuse15+py:azp/posix/3/) (push) Has been cancelled
nox / Run extra sanity tests (push) Has been cancelled

Remove proxmox content.
This commit is contained in:
Felix Fontein 2025-06-08 16:18:16 +02:00 committed by GitHub
commit f2b7bdf293
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
76 changed files with 124 additions and 14629 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,570 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2024, IamLunchbox <r.grieger@hotmail.com>
# 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
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r"""
module: proxmox_backup
author: "Raphael Grieger (@IamLunchbox) <r.grieger@hotmail.com>"
short_description: Start a VM backup in Proxmox VE cluster
version_added: 10.1.0
description:
- Allows you to create backups of KVM and LXC guests in Proxmox VE cluster.
- Offers the GUI functionality of creating a single backup as well as using the run-now functionality from the cluster backup
schedule.
- The mininum required privileges to use this module are C(VM.Backup) and C(Datastore.AllocateSpace) for the respective
VMs and storage.
- Most options are optional and if unspecified will be chosen by the Cluster and its default values.
- Note that this module B(is not idempotent). It always starts a new backup (when not in check mode).
attributes:
check_mode:
support: full
diff_mode:
support: none
options:
backup_mode:
description:
- The mode how Proxmox performs backups. The default is, to create a runtime snapshot including memory.
- Check U(https://pve.proxmox.com/pve-docs/chapter-vzdump.html#_backup_modes) for an explanation of the differences.
type: str
choices: ["snapshot", "suspend", "stop"]
default: snapshot
bandwidth:
description:
- Limit the I/O bandwidth (in KiB/s) to write backup. V(0) is unlimited.
type: int
change_detection_mode:
description:
- Set the change detection mode (available from Proxmox VE 8.3).
- It is only used when backing up containers, Proxmox silently ignores this option when applied to kvm guests.
type: str
choices: ["legacy", "data", "metadata"]
compress:
description:
- Enable additional compression of the backup archive.
- V(0) will use the Proxmox recommended value, depending on your storage target.
type: str
choices: ["0", "1", "gzip", "lzo", "zstd"]
compression_threads:
description:
- The number of threads zstd will use to compress the backup.
- V(0) uses 50% of the available cores, anything larger than V(0) will use exactly as many threads.
- Is ignored if you specify O(compress=gzip) or O(compress=lzo).
type: int
description:
description:
- Specify the description of the backup.
- Needs to be a single line, newline and backslash need to be escaped as V(\\n) and V(\\\\) respectively.
- If you need variable interpolation, you can set the content as usual through ansible jinja templating and/or let Proxmox
substitute templates.
- Proxmox currently supports V({{cluster}}), V({{guestname}}), V({{node}}), and V({{vmid}}) as templating variables.
Since this is also a jinja delimiter, you need to set these values as raw jinja.
default: "{{guestname}}"
type: str
fleecing:
description:
- Enable backup fleecing. Works only for virtual machines and their disks.
- Must be entered as a string, containing key-value pairs in a list.
type: str
mode:
description:
- Specifices the mode to select backup targets.
choices: ["include", "all", "pool"]
required: true
type: str
node:
description:
- Only execute the backup job for the given node.
- This option is usually used if O(mode=all).
- If you specify a node ID and your vmids or pool do not reside there, they will not be backed up!
type: str
notification_mode:
description:
- Determine which notification system to use.
type: str
choices: ["auto", "legacy-sendmail", "notification-system"]
default: auto
performance_tweaks:
description:
- Enable other performance-related settings.
- Must be entered as a string, containing comma separated key-value pairs.
- 'For example: V(max-workers=2,pbs-entries-max=2).'
type: str
pool:
description:
- Specify a pool name to limit backups to guests to the given pool.
- Required, when O(mode=pool).
- Also required, when your user only has VM.Backup permission for this single pool.
type: str
protected:
description:
- Marks backups as protected.
- '"Might fail, when the PBS backend has verify enabled due to this bug: U(https://bugzilla.proxmox.com/show_bug.cgi?id=4289)".'
type: bool
retention:
description:
- Use custom retention options instead of those from the default cluster configuration (which is usually V("keep-all=1")).
- Always requires Datastore.Allocate permission at the storage endpoint.
- Specifying a retention time other than V(keep-all=1) might trigger pruning on the datastore, if an existing backup
should be deleted due to your specified timeframe.
- Deleting requires C(Datastore.Modify) or C(Datastore.Prune) permissions on the backup storage.
type: str
storage:
description:
- Store the backup archive on this storage.
type: str
required: true
vmids:
description:
- The instance IDs to be backed up.
- Only valid, if O(mode=include).
type: list
elements: int
wait:
description:
- Wait for the backup to be finished.
- Fails, if job does not succeed successfully within the given timeout.
type: bool
default: false
wait_timeout:
description:
- Seconds to wait for the backup to be finished.
- Will only be evaluated, if O(wait=true).
type: int
default: 10
requirements: ["proxmoxer", "requests"]
extends_documentation_fragment:
- community.general.proxmox.actiongroup_proxmox
- community.general.proxmox.documentation
- community.general.attributes
"""
EXAMPLES = r"""
- name: Backup all vms in the Proxmox cluster to storage mypbs
community.general.proxmox_backup:
api_user: root@pam
api_password: secret
api_host: node1
storage: mypbs
mode: all
- name: Backup VMID 100 by stopping it and set an individual retention
community.general.proxmox_backup:
api_user: root@pam
api_password: secret
api_host: node1
backup-mode: stop
mode: include
retention: keep-daily=5, keep-last=14, keep-monthly=4, keep-weekly=4, keep-yearly=0
storage: mypbs
vmid: [100]
- name: Backup all vms on node node2 to storage mypbs and wait for the task to finish
community.general.proxmox_backup:
api_user: test@pve
api_password: 1q2w3e
api_host: node2
storage: mypbs
mode: all
node: node2
wait: true
wait_timeout: 30
- name: Use all the options
community.general.proxmox_backup:
api_user: root@pam
api_password: secret
api_host: node1
bandwidth: 1000
backup_mode: suspend
compress: zstd
compression_threads: 0
description: A single backup for {% raw %}{{ guestname }}{% endraw %}
mode: include
notification_mode: notification-system
protected: true
retention: keep-monthly=1, keep-weekly=1
storage: mypbs
vmids:
- 100
- 101
"""
RETURN = r"""
backups:
description: List of nodes and their task IDs.
returned: on success
type: list
elements: dict
contains:
node:
description: Node ID.
returned: on success
type: str
status:
description: Last known task status. Will be unknown, if O(wait=false).
returned: on success
type: str
choices: ["unknown", "success", "failed"]
upid:
description: >-
Proxmox cluster UPID, which is needed to lookup task info. Returns OK, when a cluster node did not create a task after
being called, for example due to no matching targets.
returned: on success
type: str
"""
import time
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.common.text.converters import to_native
from ansible_collections.community.general.plugins.module_utils.proxmox import ProxmoxAnsible, proxmox_auth_argument_spec
def has_permission(permission_tree, permission, search_scopes, default=0, expected=1):
return any(permission_tree.get(scope, {}).get(permission, default) == expected for scope in search_scopes)
class ProxmoxBackupAnsible(ProxmoxAnsible):
def _get_permissions(self):
return self.proxmox_api.access.permissions.get()
def _get_resources(self, resource_type=None):
return self.proxmox_api.cluster.resources.get(type=resource_type)
def _get_tasklog(self, node, upid):
return self.proxmox_api.nodes(node).tasks(upid).log.get()
def _get_taskok(self, node, upid):
return self.proxmox_api.nodes(node).tasks(upid).status.get()
def _post_vzdump(self, node, request_body):
return self.proxmox_api.nodes(node).vzdump.post(**request_body)
def request_backup(
self,
request_body,
node_endpoints):
task_ids = []
for node in node_endpoints:
upid = self._post_vzdump(node, request_body)
if upid != "OK":
tasklog = ", ".join(logentry["t"] for logentry in self._get_tasklog(node, upid))
else:
tasklog = ""
task_ids.extend([{"node": node, "upid": upid, "status": "unknown", "log": "%s" % tasklog}])
return task_ids
def check_relevant_nodes(self, node):
nodes = [
item["node"]
for item in self._get_resources("node")
if item["status"] == "online"
]
if node and node not in nodes:
self.module.fail_json(msg="Node %s was specified, but does not exist on the cluster" % node)
elif node:
return [node]
return nodes
def check_storage_permissions(
self,
permissions,
storage,
bandwidth,
performance,
retention):
# Check for Datastore.AllocateSpace in the permission tree
if not has_permission(permissions, "Datastore.AllocateSpace", search_scopes=["/", "/storage/", "/storage/" + storage]):
self.module.fail_json(changed=False, msg="Insufficient permission: Datastore.AllocateSpace is missing")
if (bandwidth or performance) and has_permission(permissions, "Sys.Modify", search_scopes=["/"], expected=0):
self.module.fail_json(changed=False, msg="Insufficient permission: Performance_tweaks and bandwidth require 'Sys.Modify' permission for '/'")
if retention:
if not has_permission(permissions, "Datastore.Allocate", search_scopes=["/", "/storage", "/storage/" + storage]):
self.module.fail_json(changed=False, msg="Insufficient permissions: Custom retention was requested, but Datastore.Allocate is missing")
def check_vmid_backup_permission(self, permissions, vmids, pool):
sufficient_permissions = has_permission(permissions, "VM.Backup", search_scopes=["/", "/vms"])
if pool and not sufficient_permissions:
sufficient_permissions = has_permission(permissions, "VM.Backup", search_scopes=["/pool/" + pool, "/pool/" + pool + "/vms"])
if not sufficient_permissions:
# Since VM.Backup can be given for each vmid at a time, iterate through all of them
# and check, if the permission is set
failed_vmids = []
for vm in vmids:
vm_path = "/vms/" + str(vm)
if has_permission(permissions, "VM.Backup", search_scopes=[vm_path], default=1, expected=0):
failed_vmids.append(str(vm))
if failed_vmids:
self.module.fail_json(
changed=False, msg="Insufficient permissions: "
"You dont have the VM.Backup permission for VMID %s" %
", ".join(failed_vmids))
sufficient_permissions = True
# Finally, when no check succeeded, fail
if not sufficient_permissions:
self.module.fail_json(changed=False, msg="Insufficient permissions: You do not have the VM.Backup permission")
def check_general_backup_permission(self, permissions, pool):
if not has_permission(permissions, "VM.Backup", search_scopes=["/", "/vms"] + (["/pool/" + pool] if pool else [])):
self.module.fail_json(changed=False, msg="Insufficient permissions: You dont have the VM.Backup permission")
def check_if_storage_exists(self, storage, node):
storages = self.get_storages(type=None)
# Loop through all cluster storages and get all matching storages
validated_storagepath = [storageentry for storageentry in storages if storageentry["storage"] == storage]
if not validated_storagepath:
self.module.fail_json(
changed=False,
msg="Storage %s does not exist in the cluster" %
storage)
def check_vmids(self, vmids):
cluster_vmids = [vm["vmid"] for vm in self._get_resources("vm")]
if not cluster_vmids:
self.module.warn(
"VM.Audit permission is missing or there are no VMs. This task might fail if one VMID does not exist")
return
vmids_not_found = [str(vm) for vm in vmids if vm not in cluster_vmids]
if vmids_not_found:
self.module.warn(
"VMIDs %s not found. This task will fail if one VMID does not exist" %
", ".join(vmids_not_found))
def wait_for_timeout(self, timeout, raw_tasks):
# filter all entries, which did not get a task id from the Cluster
tasks = []
ok_tasks = []
for node in raw_tasks:
if node["upid"] != "OK":
tasks.append(node)
else:
ok_tasks.append(node)
start_time = time.time()
# iterate through the task ids and check their values
while True:
for node in tasks:
if node["status"] == "unknown":
try:
# proxmox.api_task_ok does not suffice, since it only
# is true at `stopped` and `ok`
status = self._get_taskok(node["node"], node["upid"])
if status["status"] == "stopped" and status["exitstatus"] == "OK":
node["status"] = "success"
if status["status"] == "stopped" and status["exitstatus"] == "job errors":
node["status"] = "failed"
except Exception as e:
self.module.fail_json(msg="Unable to retrieve API task ID from node %s: %s" % (node["node"], e))
if len([item for item in tasks if item["status"] != "unknown"]) == len(tasks):
break
if time.time() > start_time + timeout:
timeouted_nodes = [
node["node"]
for node in tasks
if node["status"] == "unknown"
]
failed_nodes = [node["node"] for node in tasks if node["status"] == "failed"]
if failed_nodes:
self.module.fail_json(
msg="Reached timeout while waiting for backup task. "
"Nodes, who reached the timeout: %s. "
"Nodes, which failed: %s" %
(", ".join(timeouted_nodes), ", ".join(failed_nodes)))
self.module.fail_json(
msg="Reached timeout while waiting for creating VM snapshot. "
"Nodes who reached the timeout: %s" %
", ".join(timeouted_nodes))
time.sleep(1)
error_logs = []
for node in tasks:
if node["status"] == "failed":
tasklog = ", ".join([logentry["t"] for logentry in self._get_tasklog(node["node"], node["upid"])])
error_logs.append("%s: %s" % (node, tasklog))
if error_logs:
self.module.fail_json(
msg="An error occured creating the backups. "
"These are the last log lines from the failed nodes: %s" %
", ".join(error_logs))
for node in tasks:
tasklog = ", ".join([logentry["t"] for logentry in self._get_tasklog(node["node"], node["upid"])])
node["log"] = tasklog
# Finally, reattach ok tasks to show, that all nodes were contacted
tasks.extend(ok_tasks)
return tasks
def permission_check(
self,
storage,
mode,
node,
bandwidth,
performance_tweaks,
retention,
pool,
vmids):
permissions = self._get_permissions()
self.check_if_storage_exists(storage, node)
self.check_storage_permissions(
permissions, storage, bandwidth, performance_tweaks, retention)
if mode == "include":
self.check_vmid_backup_permission(permissions, vmids, pool)
else:
self.check_general_backup_permission(permissions, pool)
def prepare_request_parameters(self, module_arguments):
# ensure only valid post parameters are passed to proxmox
# list of dict items to replace with (new_val, old_val)
post_params = [("bwlimit", "bandwidth"),
("compress", "compress"),
("fleecing", "fleecing"),
("mode", "backup_mode"),
("notes-template", "description"),
("notification-mode", "notification_mode"),
("pbs-change-detection-mode", "change_detection_mode"),
("performance", "performance_tweaks"),
("pool", "pool"),
("protected", "protected"),
("prune-backups", "retention"),
("storage", "storage"),
("zstd", "compression_threads"),
("vmid", "vmids")]
request_body = {}
for new, old in post_params:
if module_arguments.get(old):
request_body.update({new: module_arguments[old]})
# Set mode specific values
if module_arguments["mode"] == "include":
request_body.pop("pool", None)
request_body["all"] = 0
elif module_arguments["mode"] == "all":
request_body.pop("vmid", None)
request_body.pop("pool", None)
request_body["all"] = 1
elif module_arguments["mode"] == "pool":
request_body.pop("vmid", None)
request_body["all"] = 0
# Create comma separated list from vmids, the API expects so
if request_body.get("vmid"):
request_body.update({"vmid": ",".join(str(vmid) for vmid in request_body["vmid"])})
# remove whitespaces from option strings
for key in ("prune-backups", "performance"):
if request_body.get(key):
request_body[key] = request_body[key].replace(" ", "")
# convert booleans to 0/1
for key in ("protected",):
if request_body.get(key):
request_body[key] = 1
return request_body
def backup_create(
self,
module_arguments,
check_mode,
node_endpoints):
request_body = self.prepare_request_parameters(module_arguments)
# stop here, before anything gets changed
if check_mode:
return []
task_ids = self.request_backup(request_body, node_endpoints)
updated_task_ids = []
if module_arguments["wait"]:
updated_task_ids = self.wait_for_timeout(
module_arguments["wait_timeout"], task_ids)
return updated_task_ids if updated_task_ids else task_ids
def main():
module_args = proxmox_auth_argument_spec()
backup_args = {
"backup_mode": {"type": "str", "default": "snapshot", "choices": ["snapshot", "suspend", "stop"]},
"bandwidth": {"type": "int"},
"change_detection_mode": {"type": "str", "choices": ["legacy", "data", "metadata"]},
"compress": {"type": "str", "choices": ["0", "1", "gzip", "lzo", "zstd"]},
"compression_threads": {"type": "int"},
"description": {"type": "str", "default": "{{guestname}}"},
"fleecing": {"type": "str"},
"mode": {"type": "str", "required": True, "choices": ["include", "all", "pool"]},
"node": {"type": "str"},
"notification_mode": {"type": "str", "default": "auto", "choices": ["auto", "legacy-sendmail", "notification-system"]},
"performance_tweaks": {"type": "str"},
"pool": {"type": "str"},
"protected": {"type": "bool"},
"retention": {"type": "str"},
"storage": {"type": "str", "required": True},
"vmids": {"type": "list", "elements": "int"},
"wait": {"type": "bool", "default": False},
"wait_timeout": {"type": "int", "default": 10}}
module_args.update(backup_args)
module = AnsibleModule(
argument_spec=module_args,
supports_check_mode=True,
required_if=[
("mode", "include", ("vmids",), True),
("mode", "pool", ("pool",))
]
)
proxmox = ProxmoxBackupAnsible(module)
bandwidth = module.params["bandwidth"]
mode = module.params["mode"]
node = module.params["node"]
performance_tweaks = module.params["performance_tweaks"]
pool = module.params["pool"]
retention = module.params["retention"]
storage = module.params["storage"]
vmids = module.params["vmids"]
proxmox.permission_check(
storage,
mode,
node,
bandwidth,
performance_tweaks,
retention,
pool,
vmids)
if module.params["mode"] == "include":
proxmox.check_vmids(module.params["vmids"])
node_endpoints = proxmox.check_relevant_nodes(module.params["node"])
try:
result = proxmox.backup_create(module.params, module.check_mode, node_endpoints)
except Exception as e:
module.fail_json(msg="Creating backups failed with exception: %s" % to_native(e))
if module.check_mode:
module.exit_json(backups=result, changed=True, msg="Backups would be created")
elif len([entry for entry in result if entry["upid"] == "OK"]) == len(result):
module.exit_json(backups=result, changed=False, msg="Backup request sent to proxmox, no tasks created")
elif module.params["wait"]:
module.exit_json(backups=result, changed=True, msg="Backups succeeded")
else:
module.exit_json(backups=result, changed=True,
msg="Backup tasks created")
if __name__ == "__main__":
main()

View file

@ -1,244 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2024 Marzieh Raoufnezhad <raoufnezhad at gmail.com>
# Copyright (c) 2024 Maryam Mayabi <mayabi.ahm at gmail.com>
# 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
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = """
---
module: proxmox_backup_info
short_description: Retrieve information on Proxmox scheduled backups
version_added: 10.3.0
description:
- Retrieve information such as backup times, VM name, VM ID, mode, backup type, and backup schedule using the Proxmox Server API.
author:
- "Marzieh Raoufnezhad (@raoufnezhad) <raoufnezhad@gmail.com>"
- "Maryam Mayabi (@mmayabi) <mayabi.ahm@gmail.com>"
options:
vm_name:
description:
- The name of the Proxmox VM.
- If defined, the returned list will contain backup jobs that have been parsed and filtered based on O(vm_name) value.
- Mutually exclusive with O(vm_id) and O(backup_jobs).
type: str
vm_id:
description:
- The ID of the Proxmox VM.
- If defined, the returned list will contain backup jobs that have been parsed and filtered based on O(vm_id) value.
- Mutually exclusive with O(vm_name) and O(backup_jobs).
type: str
backup_jobs:
description:
- If V(true), the module will return all backup jobs information.
- If V(false), the module will parse all backup jobs based on VM IDs and return a list of VMs' backup information.
- Mutually exclusive with O(vm_id) and O(vm_name).
default: false
type: bool
extends_documentation_fragment:
- community.general.proxmox.documentation
- community.general.attributes
- community.general.attributes.info_module
- community.general.proxmox.actiongroup_proxmox
"""
EXAMPLES = """
- name: Print all backup information by VM ID and VM name
community.general.proxmox_backup_info:
api_user: 'myUser@pam'
api_password: '*******'
api_host: '192.168.20.20'
- name: Print Proxmox backup information for a specific VM based on its name
community.general.proxmox_backup_info:
api_user: 'myUser@pam'
api_password: '*******'
api_host: '192.168.20.20'
vm_name: 'mailsrv'
- name: Print Proxmox backup information for a specific VM based on its VM ID
community.general.proxmox_backup_info:
api_user: 'myUser@pam'
api_password: '*******'
api_host: '192.168.20.20'
vm_id: '150'
- name: Print Proxmox all backup job information
community.general.proxmox_backup_info:
api_user: 'myUser@pam'
api_password: '*******'
api_host: '192.168.20.20'
backup_jobs: true
"""
RETURN = """
---
backup_info:
description: The return value provides backup job information based on VM ID or VM name, or total backup job information.
returned: on success, but can be empty
type: list
elements: dict
contains:
bktype:
description: The type of the backup.
returned: on success
type: str
sample: vzdump
enabled:
description: V(1) if backup is enabled else V(0).
returned: on success
type: int
sample: 1
id:
description: The backup job ID.
returned: on success
type: str
sample: backup-83831498-c631
mode:
description: The backup job mode such as snapshot.
returned: on success
type: str
sample: snapshot
next-run:
description: The next backup time.
returned: on success
type: str
sample: "2024-12-28 11:30:00"
schedule:
description: The backup job schedule.
returned: on success
type: str
sample: "sat 15:00"
storage:
description: The backup storage location.
returned: on success
type: str
sample: local
vm_name:
description: The VM name.
returned: on success
type: str
sample: test01
vmid:
description: The VM ID.
returned: on success
type: str
sample: "100"
"""
from datetime import datetime
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible_collections.community.general.plugins.module_utils.proxmox import (
proxmox_auth_argument_spec, ProxmoxAnsible, HAS_PROXMOXER, PROXMOXER_IMP_ERR)
class ProxmoxBackupInfoAnsible(ProxmoxAnsible):
# Get all backup information
def get_jobs_list(self):
try:
backupJobs = self.proxmox_api.cluster.backup.get()
except Exception as e:
self.module.fail_json(msg="Getting backup jobs failed: %s" % e)
return backupJobs
# Get VM information
def get_vms_list(self):
try:
vms = self.proxmox_api.cluster.resources.get(type='vm')
except Exception as e:
self.module.fail_json(msg="Getting VMs info from cluster failed: %s" % e)
return vms
# Get all backup information by VM ID and VM name
def vms_backup_info(self):
backupList = self.get_jobs_list()
vmInfo = self.get_vms_list()
bkInfo = []
for backupItem in backupList:
nextrun = datetime.fromtimestamp(backupItem['next-run'])
vmids = backupItem['vmid'].split(',')
for vmid in vmids:
for vm in vmInfo:
if vm['vmid'] == int(vmid):
vmName = vm['name']
break
bkInfoData = {'id': backupItem['id'],
'schedule': backupItem['schedule'],
'storage': backupItem['storage'],
'mode': backupItem['mode'],
'next-run': nextrun.strftime("%Y-%m-%d %H:%M:%S"),
'enabled': backupItem['enabled'],
'bktype': backupItem['type'],
'vmid': vmid,
'vm_name': vmName}
bkInfo.append(bkInfoData)
return bkInfo
# Get proxmox backup information for a specific VM based on its VM ID or VM name
def specific_vmbackup_info(self, vm_name_id):
fullBackupInfo = self.vms_backup_info()
vmBackupJobs = []
for vm in fullBackupInfo:
if (vm["vm_name"] == vm_name_id or vm["vmid"] == vm_name_id):
vmBackupJobs.append(vm)
return vmBackupJobs
def main():
# Define module args
args = proxmox_auth_argument_spec()
backup_info_args = dict(
vm_id=dict(type='str'),
vm_name=dict(type='str'),
backup_jobs=dict(type='bool', default=False)
)
args.update(backup_info_args)
module = AnsibleModule(
argument_spec=args,
mutually_exclusive=[('backup_jobs', 'vm_id', 'vm_name')],
supports_check_mode=True
)
# Define (init) result value
result = dict(
changed=False
)
# Check if proxmoxer exist
if not HAS_PROXMOXER:
module.fail_json(msg=missing_required_lib('proxmoxer'), exception=PROXMOXER_IMP_ERR)
# Start to connect to proxmox to get backup data
proxmox = ProxmoxBackupInfoAnsible(module)
vm_id = module.params['vm_id']
vm_name = module.params['vm_name']
backup_jobs = module.params['backup_jobs']
# Update result value based on what requested (module args)
if backup_jobs:
result['backup_info'] = proxmox.get_jobs_list()
elif vm_id:
result['backup_info'] = proxmox.specific_vmbackup_info(vm_id)
elif vm_name:
result['backup_info'] = proxmox.specific_vmbackup_info(vm_name)
else:
result['backup_info'] = proxmox.vms_backup_info()
# Return result value
module.exit_json(**result)
if __name__ == '__main__':
main()

View file

@ -1,877 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2022, Castor Sky (@castorsky) <csky57@gmail.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r"""
module: proxmox_disk
short_description: Management of a disk of a Qemu(KVM) VM in a Proxmox VE cluster
version_added: 5.7.0
description:
- Allows you to perform some supported operations on a disk in Qemu(KVM) Virtual Machines in a Proxmox VE cluster.
author: "Castor Sky (@castorsky) <csky57@gmail.com>"
attributes:
check_mode:
support: none
diff_mode:
support: none
action_group:
version_added: 9.0.0
options:
name:
description:
- The unique name of the VM.
- You can specify either O(name) or O(vmid) or both of them.
type: str
vmid:
description:
- The unique ID of the VM.
- You can specify either O(vmid) or O(name) or both of them.
type: int
disk:
description:
- The disk key (V(unused[n]), V(ide[n]), V(sata[n]), V(scsi[n]) or V(virtio[n])) you want to operate on.
- Disk buses (IDE, SATA and so on) have fixed ranges of V(n) that accepted by Proxmox API.
- 'For IDE: 0-3; for SCSI: 0-30; for SATA: 0-5; for VirtIO: 0-15; for Unused: 0-255.'
type: str
required: true
state:
description:
- Indicates desired state of the disk.
- O(state=present) can be used to create, replace disk or update options in existing disk. It will create missing disk
or update options in existing one by default. See the O(create) parameter description to control behavior of this
option.
- Some updates on options (like O(cache)) are not being applied instantly and require VM restart.
- Use O(state=detached) to detach existing disk from VM but do not remove it entirely. When O(state=detached) and disk
is V(unused[n]) it will be left in same state (not removed).
- O(state=moved) may be used to change backing storage for the disk in bounds of the same VM or to send the disk to
another VM (using the same backing storage).
- O(state=resized) intended to change the disk size. As of Proxmox 7.2 you can only increase the disk size because shrinking
disks is not supported by the PVE API and has to be done manually.
- To entirely remove the disk from backing storage use O(state=absent).
type: str
choices: ['present', 'resized', 'detached', 'moved', 'absent']
default: present
create:
description:
- With O(create) flag you can control behavior of O(state=present).
- When O(create=disabled) it will not create new disk (if not exists) but will update options in existing disk.
- When O(create=regular) it will either create new disk (if not exists) or update options in existing disk.
- When O(create=forced) it will always create new disk (if disk exists it will be detached and left unused).
type: str
choices: ['disabled', 'regular', 'forced']
default: regular
storage:
description:
- The drive's backing storage.
- Used only when O(state) is V(present).
type: str
size:
description:
- Desired volume size in GB to allocate when O(state=present) (specify O(size) without suffix).
- New (or additional) size of volume when O(state=resized). With the V(+) sign the value is added to the actual size
of the volume and without it, the value is taken as an absolute one.
type: str
bwlimit:
description:
- Override I/O bandwidth limit (in KB/s).
- Used only when O(state=moved).
type: int
delete_moved:
description:
- Delete the original disk after successful copy.
- By default the original disk is kept as unused disk.
- Used only when O(state=moved).
type: bool
target_disk:
description:
- The config key the disk will be moved to on the target VM (for example, V(ide0) or V(scsi1)).
- Default is the source disk key.
- Used only when O(state=moved).
type: str
target_storage:
description:
- Move the disk to this storage when O(state=moved).
- You can move between storages only in scope of one VM.
- Mutually exclusive with O(target_vmid).
- Consider increasing O(timeout) in case of large disk images or slow storage backend.
type: str
target_vmid:
description:
- The (unique) ID of the VM where disk will be placed when O(state=moved).
- You can move disk between VMs only when the same storage is used.
- Mutually exclusive with O(target_vmid).
type: int
timeout:
description:
- Timeout in seconds to wait for slow operations such as importing disk or moving disk between storages.
- Used only when O(state) is V(present) or V(moved).
type: int
default: 600
aio:
description:
- AIO type to use.
type: str
choices: ['native', 'threads', 'io_uring']
backup:
description:
- Whether the drive should be included when making backups.
type: bool
bps_max_length:
description:
- Maximum length of total r/w I/O bursts in seconds.
type: int
bps_rd_max_length:
description:
- Maximum length of read I/O bursts in seconds.
type: int
bps_wr_max_length:
description:
- Maximum length of write I/O bursts in seconds.
type: int
cache:
description:
- The drive's cache mode.
type: str
choices: ['none', 'writethrough', 'writeback', 'unsafe', 'directsync']
cyls:
description:
- Force the drive's physical geometry to have a specific cylinder count.
type: int
detect_zeroes:
description:
- Control whether to detect and try to optimize writes of zeroes.
type: bool
discard:
description:
- Control whether to pass discard/trim requests to the underlying storage.
type: str
choices: ['ignore', 'on']
format:
description:
- The drive's backing file's data format.
type: str
choices: ['raw', 'cow', 'qcow', 'qed', 'qcow2', 'vmdk', 'cloop']
heads:
description:
- Force the drive's physical geometry to have a specific head count.
type: int
import_from:
description:
- Import volume from this existing one.
- Volume string format.
- V(<STORAGE>:<VMID>/<FULL_NAME>) or V(<ABSOLUTE_PATH>/<FULL_NAME>).
- Attention! Only root can use absolute paths.
- This parameter is mutually exclusive with O(size).
- Increase O(timeout) parameter when importing large disk images or using slow storage.
type: str
iops:
description:
- Maximum total r/w I/O in operations per second.
- You can specify either total limit or per operation (mutually exclusive with O(iops_rd) and O(iops_wr)).
type: int
iops_max:
description:
- Maximum unthrottled total r/w I/O pool in operations per second.
type: int
iops_max_length:
description:
- Maximum length of total r/w I/O bursts in seconds.
type: int
iops_rd:
description:
- Maximum read I/O in operations per second.
- You can specify either read or total limit (mutually exclusive with O(iops)).
type: int
iops_rd_max:
description:
- Maximum unthrottled read I/O pool in operations per second.
type: int
iops_rd_max_length:
description:
- Maximum length of read I/O bursts in seconds.
type: int
iops_wr:
description:
- Maximum write I/O in operations per second.
- You can specify either write or total limit (mutually exclusive with O(iops)).
type: int
iops_wr_max:
description:
- Maximum unthrottled write I/O pool in operations per second.
type: int
iops_wr_max_length:
description:
- Maximum length of write I/O bursts in seconds.
type: int
iothread:
description:
- Whether to use iothreads for this drive (only for SCSI and VirtIO).
type: bool
mbps:
description:
- Maximum total r/w speed in megabytes per second.
- Can be fractional but use with caution - fractionals less than 1 are not supported officially.
- You can specify either total limit or per operation (mutually exclusive with O(mbps_rd) and O(mbps_wr)).
type: float
mbps_max:
description:
- Maximum unthrottled total r/w pool in megabytes per second.
type: float
mbps_rd:
description:
- Maximum read speed in megabytes per second.
- You can specify either read or total limit (mutually exclusive with O(mbps)).
type: float
mbps_rd_max:
description:
- Maximum unthrottled read pool in megabytes per second.
type: float
mbps_wr:
description:
- Maximum write speed in megabytes per second.
- You can specify either write or total limit (mutually exclusive with O(mbps)).
type: float
mbps_wr_max:
description:
- Maximum unthrottled write pool in megabytes per second.
type: float
media:
description:
- The drive's media type.
type: str
choices: ['cdrom', 'disk']
iso_image:
description:
- The ISO image to be mounted on the specified in O(disk) CD-ROM.
- O(media=cdrom) needs to be specified for this option to work.
- Use V(<STORAGE>:iso/<ISO_NAME>) to mount ISO.
- Use V(cdrom) to access the physical CD/DVD drive.
- Use V(none) to unmount image from existent CD-ROM or create empty CD-ROM drive.
type: str
version_added: 8.1.0
queues:
description:
- Number of queues (SCSI only).
type: int
replicate:
description:
- Whether the drive should considered for replication jobs.
type: bool
rerror:
description:
- Read error action.
type: str
choices: ['ignore', 'report', 'stop']
ro:
description:
- Whether the drive is read-only.
type: bool
scsiblock:
description:
- Whether to use scsi-block for full passthrough of host block device.
- Can lead to I/O errors in combination with low memory or high memory fragmentation on host.
type: bool
secs:
description:
- Force the drive's physical geometry to have a specific sector count.
type: int
serial:
description:
- The drive's reported serial number, url-encoded, up to 20 bytes long.
type: str
shared:
description:
- Mark this locally-managed volume as available on all nodes.
- This option does not share the volume automatically, it assumes it is shared already!
type: bool
snapshot:
description:
- Control qemu's snapshot mode feature.
- If activated, changes made to the disk are temporary and will be discarded when the VM is shutdown.
type: bool
ssd:
description:
- Whether to expose this drive as an SSD, rather than a rotational hard disk.
type: bool
trans:
description:
- Force disk geometry bios translation mode.
type: str
choices: ['auto', 'lba', 'none']
werror:
description:
- Write error action.
type: str
choices: ['enospc', 'ignore', 'report', 'stop']
wwn:
description:
- The drive's worldwide name, encoded as 16 bytes hex string, prefixed by V(0x).
type: str
extends_documentation_fragment:
- community.general.proxmox.actiongroup_proxmox
- community.general.proxmox.documentation
- community.general.attributes
"""
EXAMPLES = r"""
- name: Create new disk in VM (do not rewrite in case it exists already)
community.general.proxmox_disk:
api_host: node1
api_user: root@pam
api_token_id: token1
api_token_secret: some-token-data
name: vm-name
disk: scsi3
backup: true
cache: none
storage: local-zfs
size: 5
state: present
- name: Create new disk in VM (force rewrite in case it exists already)
community.general.proxmox_disk:
api_host: node1
api_user: root@pam
api_token_id: token1
api_token_secret: some-token-data
vmid: 101
disk: scsi3
format: qcow2
storage: local
size: 16
create: forced
state: present
- name: Update existing disk
community.general.proxmox_disk:
api_host: node1
api_user: root@pam
api_token_id: token1
api_token_secret: some-token-data
vmid: 101
disk: ide0
backup: false
ro: true
aio: native
state: present
- name: Grow existing disk
community.general.proxmox_disk:
api_host: node1
api_user: root@pam
api_token_id: token1
api_token_secret: some-token-data
vmid: 101
disk: sata4
size: +5G
state: resized
- name: Detach disk (leave it unused)
community.general.proxmox_disk:
api_host: node1
api_user: root@pam
api_token_id: token1
api_token_secret: some-token-data
name: vm-name
disk: virtio0
state: detached
- name: Move disk to another storage
community.general.proxmox_disk:
api_host: node1
api_user: root@pam
api_password: secret
vmid: 101
disk: scsi7
target_storage: local
format: qcow2
state: moved
- name: Move disk from one VM to another
community.general.proxmox_disk:
api_host: node1
api_user: root@pam
api_token_id: token1
api_token_secret: some-token-data
vmid: 101
disk: scsi7
target_vmid: 201
state: moved
- name: Remove disk permanently
community.general.proxmox_disk:
api_host: node1
api_user: root@pam
api_password: secret
vmid: 101
disk: scsi4
state: absent
- name: Mount ISO image on CD-ROM (create drive if missing)
community.general.proxmox_disk:
api_host: node1
api_user: root@pam
api_token_id: token1
api_token_secret: some-token-data
vmid: 101
disk: ide2
media: cdrom
iso_image: local:iso/favorite_distro_amd64.iso
state: present
"""
RETURN = r"""
vmid:
description: The VM vmid.
returned: success
type: int
sample: 101
msg:
description: A short message on what the module did.
returned: always
type: str
sample: "Disk scsi3 created in VM 101"
"""
from ansible.module_utils.basic import AnsibleModule
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)
from re import compile, match, sub
def disk_conf_str_to_dict(config_string):
"""
Transform Proxmox configuration string for disk element into dictionary which has
volume option parsed in '{ storage }:{ volume }' format and other options parsed
in '{ option }={ value }' format. This dictionary will be compared afterward with
attributes that user passed to this module in playbook.\n
config_string examples:
- local-lvm:vm-100-disk-0,ssd=1,discard=on,size=25G
- local:iso/new-vm-ignition.iso,media=cdrom,size=70k
- none,media=cdrom
:param config_string: Retrieved from Proxmox API configuration string
:return: Dictionary with volume option divided into parts ('volume_name', 'storage_name', 'volume') \n
and other options as key:value.
"""
config = config_string.split(',')
# When empty CD-ROM drive present, the volume part of config string is "none".
storage_volume = config.pop(0)
if storage_volume in ["none", "cdrom"]:
config_current = dict(
volume=storage_volume,
storage_name=None,
volume_name=None,
size=None,
)
else:
storage_volume = storage_volume.split(':')
storage_name = storage_volume[0]
volume_name = storage_volume[1]
config_current = dict(
volume='%s:%s' % (storage_name, volume_name),
storage_name=storage_name,
volume_name=volume_name,
)
config.sort()
for option in config:
k, v = option.split('=')
config_current[k] = v
return config_current
class ProxmoxDiskAnsible(ProxmoxAnsible):
create_update_fields = [
'aio', 'backup', 'bps_max_length', 'bps_rd_max_length', 'bps_wr_max_length',
'cache', 'cyls', 'detect_zeroes', 'discard', 'format', 'heads', 'import_from', 'iops', 'iops_max',
'iops_max_length', 'iops_rd', 'iops_rd_max', 'iops_rd_max_length', 'iops_wr', 'iops_wr_max',
'iops_wr_max_length', 'iothread', 'mbps', 'mbps_max', 'mbps_rd', 'mbps_rd_max', 'mbps_wr', 'mbps_wr_max',
'media', 'queues', 'replicate', 'rerror', 'ro', 'scsiblock', 'secs', 'serial', 'shared', 'snapshot',
'ssd', 'trans', 'werror', 'wwn'
]
supported_bus_num_ranges = dict(
ide=range(0, 4),
scsi=range(0, 31),
sata=range(0, 6),
virtio=range(0, 16),
unused=range(0, 256)
)
def get_create_attributes(self):
# Sanitize parameters dictionary:
# - Remove not defined args
# - Ensure True and False converted to int.
# - Remove unnecessary parameters
params = {
k: int(v) if isinstance(v, bool) else v
for k, v in self.module.params.items()
if v is not None and k in self.create_update_fields
}
return params
def create_disk(self, disk, vmid, vm, vm_config):
"""Create a disk in the specified virtual machine. Check if creation is required,
and if so, compile the disk configuration and create it by updating the virtual
machine configuration. After calling the API function, wait for the result.
:param disk: ID of the disk in format "<bus><number>".
:param vmid: ID of the virtual machine where the disk will be created.
:param vm: Name of the virtual machine where the disk will be created.
:param vm_config: Configuration of the virtual machine.
:return: (bool, string) Whether the task was successful or not
and the message to return to Ansible.
"""
create = self.module.params['create']
if create == 'disabled' and disk not in vm_config:
# NOOP
return False, "Disk %s not found in VM %s and creation was disabled in parameters." % (disk, vmid)
timeout_str = "Reached timeout. Last line in task before timeout: %s"
if (create == 'regular' and disk not in vm_config) or (create == 'forced'):
# CREATE
playbook_config = self.get_create_attributes()
import_string = playbook_config.pop('import_from', None)
iso_image = self.module.params.get('iso_image', None)
if import_string:
# When 'import_from' option is present in task options.
config_str = "%s:%s,import-from=%s" % (self.module.params["storage"], "0", import_string)
timeout_str = "Reached timeout while importing VM disk. Last line in task before timeout: %s"
ok_str = "Disk %s imported into VM %s"
elif iso_image is not None:
# disk=<busN>, media=cdrom, iso_image=<ISO_NAME>
config_str = iso_image
ok_str = "CD-ROM was created on %s bus in VM %s"
else:
config_str = self.module.params["storage"]
if not config_str:
self.module.fail_json(msg="The storage option must be specified.")
if self.module.params.get("media") != "cdrom":
config_str += ":%s" % (self.module.params["size"])
ok_str = "Disk %s created in VM %s"
timeout_str = "Reached timeout while creating VM disk. Last line in task before timeout: %s"
for k, v in playbook_config.items():
config_str += ',%s=%s' % (k, v)
disk_config_to_apply = {self.module.params["disk"]: config_str}
if create in ['disabled', 'regular'] and disk in vm_config:
# UPDATE
ok_str = "Disk %s updated in VM %s"
iso_image = self.module.params.get('iso_image', None)
proxmox_config = disk_conf_str_to_dict(vm_config[disk])
# 'import_from' fails on disk updates
playbook_config = self.get_create_attributes()
playbook_config.pop('import_from', None)
# Begin composing configuration string
if iso_image is not None:
config_str = iso_image
else:
config_str = proxmox_config["volume"]
# Append all mandatory fields from playbook_config
for k, v in playbook_config.items():
config_str += ',%s=%s' % (k, v)
# Append to playbook_config fields which are constants for disk images
for option in ['size', 'storage_name', 'volume', 'volume_name']:
playbook_config.update({option: proxmox_config[option]})
# CD-ROM is special disk device and its disk image is subject to change
if iso_image is not None:
playbook_config['volume'] = iso_image
# Values in params are numbers, but strings are needed to compare with disk_config
playbook_config = {k: str(v) for k, v in playbook_config.items()}
# Now compare old and new config to detect if changes are needed
if proxmox_config == playbook_config:
return False, "Disk %s is up to date in VM %s" % (disk, vmid)
disk_config_to_apply = {self.module.params["disk"]: config_str}
current_task_id = self.proxmox_api.nodes(vm['node']).qemu(vmid).config.post(**disk_config_to_apply)
task_success, fail_reason = self.api_task_complete(vm['node'], current_task_id, self.module.params['timeout'])
if task_success:
return True, ok_str % (disk, vmid)
else:
if fail_reason == ProxmoxAnsible.TASK_TIMED_OUT:
self.module.fail_json(
msg=timeout_str % self.proxmox_api.nodes(vm['node']).tasks(current_task_id).log.get()[:1]
)
else:
self.module.fail_json(msg="Error occurred on task execution: %s" % fail_reason)
def move_disk(self, disk, vmid, vm, vm_config):
"""Call the `move_disk` API function that moves the disk to another storage and wait for the result.
:param disk: ID of disk in format "<bus><number>".
:param vmid: ID of virtual machine which disk will be moved.
:param vm: Name of virtual machine which disk will be moved.
:param vm_config: Virtual machine configuration.
:return: (bool, string) Whether the task was successful or not
and the message to return to Ansible.
"""
disk_config = disk_conf_str_to_dict(vm_config[disk])
disk_storage = disk_config["storage_name"]
params = dict()
params['disk'] = disk
params['vmid'] = vmid
params['bwlimit'] = self.module.params['bwlimit']
params['storage'] = self.module.params['target_storage']
params['target-disk'] = self.module.params['target_disk']
params['target-vmid'] = self.module.params['target_vmid']
params['format'] = self.module.params['format']
params['delete'] = 1 if self.module.params.get('delete_moved', False) else 0
# Remove not defined args
params = {k: v for k, v in params.items() if v is not None}
if params.get('storage', False):
# Check if the disk is already in the target storage.
disk_config = disk_conf_str_to_dict(vm_config[disk])
if params['storage'] == disk_config['storage_name']:
return False, "Disk %s already at %s storage" % (disk, disk_storage)
current_task_id = self.proxmox_api.nodes(vm['node']).qemu(vmid).move_disk.post(**params)
task_success, fail_reason = self.api_task_complete(vm['node'], current_task_id, self.module.params['timeout'])
if task_success:
return True, "Disk %s moved from VM %s storage %s" % (disk, vmid, disk_storage)
else:
if fail_reason == ProxmoxAnsible.TASK_TIMED_OUT:
self.module.fail_json(
msg='Reached timeout while waiting for moving VM disk. Last line in task before timeout: %s' %
self.proxmox_api.nodes(vm['node']).tasks(current_task_id).log.get()[:1]
)
else:
self.module.fail_json(msg="Error occurred on task execution: %s" % fail_reason)
def resize_disk(self, disk, vmid, vm, vm_config):
"""Call the `resize` API function to change the disk size and wait for the result.
:param disk: ID of disk in format "<bus><number>".
:param vmid: ID of virtual machine which disk will be resized.
:param vm: Name of virtual machine which disk will be resized.
:param vm_config: Virtual machine configuration.
:return: (Bool, string) Whether the task was successful or not
and the message to return to Ansible.
"""
size = self.module.params['size']
if not match(r'^\+?\d+(\.\d+)?[KMGT]?$', size):
self.module.fail_json(msg="Unrecognized size pattern for disk %s: %s" % (disk, size))
disk_config = disk_conf_str_to_dict(vm_config[disk])
actual_size = disk_config['size']
if size == actual_size:
return False, "Disk %s is already %s size" % (disk, size)
# Resize disk API endpoint has changed at v8.0: PUT method become async.
version = self.version()
pve_major_version = 3 if version < LooseVersion('4.0') else version.version[0]
if pve_major_version >= 8:
current_task_id = self.proxmox_api.nodes(vm['node']).qemu(vmid).resize.set(disk=disk, size=size)
task_success, fail_reason = self.api_task_complete(vm['node'], current_task_id, self.module.params['timeout'])
if task_success:
return True, "Disk %s resized in VM %s" % (disk, vmid)
else:
if fail_reason == ProxmoxAnsible.TASK_TIMED_OUT:
self.module.fail_json(
msg="Reached timeout while resizing disk. Last line in task before timeout: %s" %
self.proxmox_api.nodes(vm['node']).tasks(current_task_id).log.get()[:1]
)
else:
self.module.fail_json(msg="Error occurred on task execution: %s" % fail_reason)
else:
self.proxmox_api.nodes(vm['node']).qemu(vmid).resize.set(disk=disk, size=size)
return True, "Disk %s resized in VM %s" % (disk, vmid)
def main():
module_args = proxmox_auth_argument_spec()
disk_args = dict(
# Proxmox native parameters
aio=dict(type='str', choices=['native', 'threads', 'io_uring']),
backup=dict(type='bool'),
bps_max_length=dict(type='int'),
bps_rd_max_length=dict(type='int'),
bps_wr_max_length=dict(type='int'),
cache=dict(type='str', choices=['none', 'writethrough', 'writeback', 'unsafe', 'directsync']),
cyls=dict(type='int'),
detect_zeroes=dict(type='bool'),
discard=dict(type='str', choices=['ignore', 'on']),
format=dict(type='str', choices=['raw', 'cow', 'qcow', 'qed', 'qcow2', 'vmdk', 'cloop']),
heads=dict(type='int'),
import_from=dict(type='str'),
iops=dict(type='int'),
iops_max=dict(type='int'),
iops_max_length=dict(type='int'),
iops_rd=dict(type='int'),
iops_rd_max=dict(type='int'),
iops_rd_max_length=dict(type='int'),
iops_wr=dict(type='int'),
iops_wr_max=dict(type='int'),
iops_wr_max_length=dict(type='int'),
iothread=dict(type='bool'),
iso_image=dict(type='str'),
mbps=dict(type='float'),
mbps_max=dict(type='float'),
mbps_rd=dict(type='float'),
mbps_rd_max=dict(type='float'),
mbps_wr=dict(type='float'),
mbps_wr_max=dict(type='float'),
media=dict(type='str', choices=['cdrom', 'disk']),
queues=dict(type='int'),
replicate=dict(type='bool'),
rerror=dict(type='str', choices=['ignore', 'report', 'stop']),
ro=dict(type='bool'),
scsiblock=dict(type='bool'),
secs=dict(type='int'),
serial=dict(type='str'),
shared=dict(type='bool'),
snapshot=dict(type='bool'),
ssd=dict(type='bool'),
trans=dict(type='str', choices=['auto', 'lba', 'none']),
werror=dict(type='str', choices=['enospc', 'ignore', 'report', 'stop']),
wwn=dict(type='str'),
# Disk moving relates parameters
bwlimit=dict(type='int'),
target_storage=dict(type='str'),
target_disk=dict(type='str'),
target_vmid=dict(type='int'),
delete_moved=dict(type='bool'),
timeout=dict(type='int', default='600'),
# Module related parameters
name=dict(type='str'),
vmid=dict(type='int'),
disk=dict(type='str', required=True),
storage=dict(type='str'),
size=dict(type='str'),
state=dict(type='str', choices=['present', 'resized', 'detached', 'moved', 'absent'],
default='present'),
create=dict(type='str', choices=['disabled', 'regular', 'forced'], default='regular'),
)
module_args.update(disk_args)
module = AnsibleModule(
argument_spec=module_args,
required_together=[('api_token_id', 'api_token_secret')],
required_one_of=[('name', 'vmid'), ('api_password', 'api_token_id')],
required_if=[
('create', 'forced', ['storage']),
('state', 'resized', ['size']),
],
required_by={
'target_disk': 'target_vmid',
'mbps_max': 'mbps',
'mbps_rd_max': 'mbps_rd',
'mbps_wr_max': 'mbps_wr',
'bps_max_length': 'mbps_max',
'bps_rd_max_length': 'mbps_rd_max',
'bps_wr_max_length': 'mbps_wr_max',
'iops_max': 'iops',
'iops_rd_max': 'iops_rd',
'iops_wr_max': 'iops_wr',
'iops_max_length': 'iops_max',
'iops_rd_max_length': 'iops_rd_max',
'iops_wr_max_length': 'iops_wr_max',
'iso_image': 'media',
},
supports_check_mode=False,
mutually_exclusive=[
('target_vmid', 'target_storage'),
('mbps', 'mbps_rd'),
('mbps', 'mbps_wr'),
('iops', 'iops_rd'),
('iops', 'iops_wr'),
('import_from', 'size'),
]
)
proxmox = ProxmoxDiskAnsible(module)
disk = module.params['disk']
# Verify disk name has appropriate name
disk_regex = compile(r'^([a-z]+)([0-9]+)$')
disk_bus = sub(disk_regex, r'\1', disk)
disk_number = int(sub(disk_regex, r'\2', disk))
if disk_bus not in proxmox.supported_bus_num_ranges:
proxmox.module.fail_json(msg='Unsupported disk bus: %s' % disk_bus)
elif disk_number not in proxmox.supported_bus_num_ranges[disk_bus]:
bus_range = proxmox.supported_bus_num_ranges[disk_bus]
proxmox.module.fail_json(msg='Disk %s number not in range %s..%s ' % (disk, bus_range[0], bus_range[-1]))
name = module.params['name']
state = module.params['state']
vmid = module.params['vmid'] or proxmox.get_vmid(name)
# Ensure VM id exists and retrieve its config
vm = None
vm_config = None
try:
vm = proxmox.get_vm(vmid)
vm_config = proxmox.proxmox_api.nodes(vm['node']).qemu(vmid).config.get()
except Exception as e:
proxmox.module.fail_json(msg='Getting information for VM %s failed with exception: %s' % (vmid, str(e)))
# Do not try to perform actions on missing disk
if disk not in vm_config and state in ['resized', 'moved']:
module.fail_json(vmid=vmid, msg='Unable to process missing disk %s in VM %s' % (disk, vmid))
if state == 'present':
try:
changed, message = proxmox.create_disk(disk, vmid, vm, vm_config)
module.exit_json(changed=changed, vmid=vmid, msg=message)
except Exception as e:
module.fail_json(vmid=vmid, msg='Unable to create/update disk %s in VM %s: %s' % (disk, vmid, str(e)))
elif state == 'detached':
try:
if disk_bus == 'unused':
module.exit_json(changed=False, vmid=vmid, msg='Disk %s already detached in VM %s' % (disk, vmid))
if disk not in vm_config:
module.exit_json(changed=False, vmid=vmid, msg="Disk %s not present in VM %s config" % (disk, vmid))
proxmox.proxmox_api.nodes(vm['node']).qemu(vmid).unlink.put(idlist=disk, force=0)
module.exit_json(changed=True, vmid=vmid, msg="Disk %s detached from VM %s" % (disk, vmid))
except Exception as e:
module.fail_json(msg="Failed to detach disk %s from VM %s with exception: %s" % (disk, vmid, str(e)))
elif state == 'moved':
try:
changed, message = proxmox.move_disk(disk, vmid, vm, vm_config)
module.exit_json(changed=changed, vmid=vmid, msg=message)
except Exception as e:
module.fail_json(msg="Failed to move disk %s in VM %s with exception: %s" % (disk, vmid, str(e)))
elif state == 'resized':
try:
changed, message = proxmox.resize_disk(disk, vmid, vm, vm_config)
module.exit_json(changed=changed, vmid=vmid, msg=message)
except Exception as e:
module.fail_json(msg="Failed to resize disk %s in VM %s with exception: %s" % (disk, vmid, str(e)))
elif state == 'absent':
try:
if disk not in vm_config:
module.exit_json(changed=False, vmid=vmid, msg="Disk %s is already absent in VM %s" % (disk, vmid))
proxmox.proxmox_api.nodes(vm['node']).qemu(vmid).unlink.put(idlist=disk, force=1)
module.exit_json(changed=True, vmid=vmid, msg="Disk %s removed from VM %s" % (disk, vmid))
except Exception as e:
module.fail_json(vmid=vmid, msg='Unable to remove disk %s from VM %s: %s' % (disk, vmid, str(e)))
if __name__ == '__main__':
main()

View file

@ -1,137 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright Tristan Le Guern (@tleguern) <tleguern at bouledef.eu>
# 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
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r"""
module: proxmox_domain_info
short_description: Retrieve information about one or more Proxmox VE domains
version_added: 1.3.0
description:
- Retrieve information about one or more Proxmox VE domains.
attributes:
action_group:
version_added: 9.0.0
options:
domain:
description:
- Restrict results to a specific authentication realm.
aliases: ['realm', 'name']
type: str
author: Tristan Le Guern (@tleguern)
extends_documentation_fragment:
- community.general.proxmox.actiongroup_proxmox
- community.general.proxmox.documentation
- community.general.attributes
- community.general.attributes.info_module
"""
EXAMPLES = r"""
- name: List existing domains
community.general.proxmox_domain_info:
api_host: helldorado
api_user: root@pam
api_password: "{{ password | default(omit) }}"
api_token_id: "{{ token_id | default(omit) }}"
api_token_secret: "{{ token_secret | default(omit) }}"
register: proxmox_domains
- name: Retrieve information about the pve domain
community.general.proxmox_domain_info:
api_host: helldorado
api_user: root@pam
api_password: "{{ password | default(omit) }}"
api_token_id: "{{ token_id | default(omit) }}"
api_token_secret: "{{ token_secret | default(omit) }}"
domain: pve
register: proxmox_domain_pve
"""
RETURN = r"""
proxmox_domains:
description: List of authentication domains.
returned: always, but can be empty
type: list
elements: dict
contains:
comment:
description: Short description of the realm.
returned: on success
type: str
realm:
description: Realm name.
returned: on success
type: str
type:
description: Realm type.
returned: on success
type: str
digest:
description: Realm hash.
returned: on success, can be absent
type: str
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.proxmox import (
proxmox_auth_argument_spec, ProxmoxAnsible)
class ProxmoxDomainInfoAnsible(ProxmoxAnsible):
def get_domain(self, realm):
try:
domain = self.proxmox_api.access.domains.get(realm)
except Exception:
self.module.fail_json(msg="Domain '%s' does not exist" % realm)
domain['realm'] = realm
return domain
def get_domains(self):
domains = self.proxmox_api.access.domains.get()
return domains
def proxmox_domain_info_argument_spec():
return dict(
domain=dict(type='str', aliases=['realm', 'name']),
)
def main():
module_args = proxmox_auth_argument_spec()
domain_info_args = proxmox_domain_info_argument_spec()
module_args.update(domain_info_args)
module = AnsibleModule(
argument_spec=module_args,
required_one_of=[('api_password', 'api_token_id')],
required_together=[('api_token_id', 'api_token_secret')],
supports_check_mode=True
)
result = dict(
changed=False
)
proxmox = ProxmoxDomainInfoAnsible(module)
domain = module.params['domain']
if domain:
domains = [proxmox.get_domain(realm=domain)]
else:
domains = proxmox.get_domains()
result['proxmox_domains'] = domains
module.exit_json(**result)
if __name__ == '__main__':
main()

View file

@ -1,147 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright Tristan Le Guern <tleguern at bouledef.eu>
# 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
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r"""
module: proxmox_group_info
short_description: Retrieve information about one or more Proxmox VE groups
version_added: 1.3.0
description:
- Retrieve information about one or more Proxmox VE groups.
attributes:
action_group:
version_added: 9.0.0
options:
group:
description:
- Restrict results to a specific group.
aliases: ['groupid', 'name']
type: str
author: Tristan Le Guern (@tleguern)
extends_documentation_fragment:
- community.general.proxmox.actiongroup_proxmox
- community.general.proxmox.documentation
- community.general.attributes
- community.general.attributes.info_module
"""
EXAMPLES = r"""
- name: List existing groups
community.general.proxmox_group_info:
api_host: helldorado
api_user: root@pam
api_password: "{{ password | default(omit) }}"
api_token_id: "{{ token_id | default(omit) }}"
api_token_secret: "{{ token_secret | default(omit) }}"
register: proxmox_groups
- name: Retrieve information about the admin group
community.general.proxmox_group_info:
api_host: helldorado
api_user: root@pam
api_password: "{{ password | default(omit) }}"
api_token_id: "{{ token_id | default(omit) }}"
api_token_secret: "{{ token_secret | default(omit) }}"
group: admin
register: proxmox_group_admin
"""
RETURN = r"""
proxmox_groups:
description: List of groups.
returned: always, but can be empty
type: list
elements: dict
contains:
comment:
description: Short description of the group.
returned: on success, can be absent
type: str
groupid:
description: Group name.
returned: on success
type: str
users:
description: List of users in the group.
returned: on success
type: list
elements: str
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.proxmox import (
proxmox_auth_argument_spec, ProxmoxAnsible)
class ProxmoxGroupInfoAnsible(ProxmoxAnsible):
def get_group(self, groupid):
try:
group = self.proxmox_api.access.groups.get(groupid)
except Exception:
self.module.fail_json(msg="Group '%s' does not exist" % groupid)
group['groupid'] = groupid
return ProxmoxGroup(group)
def get_groups(self):
groups = self.proxmox_api.access.groups.get()
return [ProxmoxGroup(group) for group in groups]
class ProxmoxGroup:
def __init__(self, group):
self.group = dict()
# Data representation is not the same depending on API calls
for k, v in group.items():
if k == 'users' and isinstance(v, str):
self.group['users'] = v.split(',')
elif k == 'members':
self.group['users'] = group['members']
else:
self.group[k] = v
def proxmox_group_info_argument_spec():
return dict(
group=dict(type='str', aliases=['groupid', 'name']),
)
def main():
module_args = proxmox_auth_argument_spec()
group_info_args = proxmox_group_info_argument_spec()
module_args.update(group_info_args)
module = AnsibleModule(
argument_spec=module_args,
required_one_of=[('api_password', 'api_token_id')],
required_together=[('api_token_id', 'api_token_secret')],
supports_check_mode=True
)
result = dict(
changed=False
)
proxmox = ProxmoxGroupInfoAnsible(module)
group = module.params['group']
if group:
groups = [proxmox.get_group(groupid=group)]
else:
groups = proxmox.get_groups()
result['proxmox_groups'] = [group.group for group in groups]
module.exit_json(**result)
if __name__ == '__main__':
main()

File diff suppressed because it is too large Load diff

View file

@ -1,313 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2021, Lammert Hellinga (@Kogelvis) <lammert@hellinga.it>
# 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
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r"""
module: proxmox_nic
short_description: Management of a NIC of a Qemu(KVM) VM in a Proxmox VE cluster
version_added: 3.1.0
description:
- Allows you to create/update/delete a NIC on Qemu(KVM) Virtual Machines in a Proxmox VE cluster.
author: "Lammert Hellinga (@Kogelvis) <lammert@hellinga.it>"
attributes:
check_mode:
support: full
diff_mode:
support: none
action_group:
version_added: 9.0.0
options:
bridge:
description:
- Add this interface to the specified bridge device. The Proxmox VE default bridge is called V(vmbr0).
type: str
firewall:
description:
- Whether this interface should be protected by the firewall.
type: bool
default: false
interface:
description:
- Name of the interface, should be V(net[n]) where C(1 n 31).
type: str
required: true
link_down:
description:
- Whether this interface should be disconnected (like pulling the plug).
type: bool
default: false
mac:
description:
- V(XX:XX:XX:XX:XX:XX) should be a unique MAC address. This is automatically generated if not specified.
- When not specified this module will keep the MAC address the same when changing an existing interface.
type: str
model:
description:
- The NIC emulator model.
type: str
choices: ['e1000', 'e1000-82540em', 'e1000-82544gc', 'e1000-82545em', 'i82551', 'i82557b', 'i82559er', 'ne2k_isa', 'ne2k_pci',
'pcnet', 'rtl8139', 'virtio', 'vmxnet3']
default: virtio
mtu:
description:
- Force MTU, for C(virtio) model only, setting will be ignored otherwise.
- Set to V(1) to use the bridge MTU.
- Value should be C(1 n 65520).
type: int
name:
description:
- Specifies the VM name. Only used on the configuration web interface.
- Required only for O(state=present).
type: str
queues:
description:
- Number of packet queues to be used on the device.
- Value should be C(0 n 16).
type: int
rate:
description:
- Rate limit in MBps (MegaBytes per second) as floating point number.
type: float
state:
description:
- Indicates desired state of the NIC.
type: str
choices: ['present', 'absent']
default: present
tag:
description:
- VLAN tag to apply to packets on this interface.
- Value should be C(1 n 4094).
type: int
trunks:
description:
- List of VLAN trunks to pass through this interface.
type: list
elements: int
vmid:
description:
- Specifies the instance ID.
type: int
extends_documentation_fragment:
- community.general.proxmox.actiongroup_proxmox
- community.general.proxmox.documentation
- community.general.attributes
"""
EXAMPLES = r"""
- name: Create NIC net0 targeting the vm by name
community.general.proxmox_nic:
api_user: root@pam
api_password: secret
api_host: proxmoxhost
name: my_vm
interface: net0
bridge: vmbr0
tag: 3
- name: Create NIC net0 targeting the vm by id
community.general.proxmox_nic:
api_user: root@pam
api_password: secret
api_host: proxmoxhost
vmid: 103
interface: net0
bridge: vmbr0
mac: "12:34:56:C0:FF:EE"
firewall: true
- name: Delete NIC net0 targeting the vm by name
community.general.proxmox_nic:
api_user: root@pam
api_password: secret
api_host: proxmoxhost
name: my_vm
interface: net0
state: absent
"""
RETURN = r"""
vmid:
description: The VM vmid.
returned: success
type: int
sample: 115
msg:
description: A short message.
returned: always
type: str
sample: "Nic net0 unchanged on VM with vmid 103"
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.proxmox import (proxmox_auth_argument_spec, ProxmoxAnsible)
class ProxmoxNicAnsible(ProxmoxAnsible):
def update_nic(self, vmid, interface, model, **kwargs):
vm = self.get_vm(vmid)
try:
vminfo = self.proxmox_api.nodes(vm['node']).qemu(vmid).config.get()
except Exception as e:
self.module.fail_json(msg='Getting information for VM with vmid = %s failed with exception: %s' % (vmid, e))
if interface in vminfo:
# Convert the current config to a dictionary
config = vminfo[interface].split(',')
config.sort()
config_current = {}
for i in config:
kv = i.split('=')
try:
config_current[kv[0]] = kv[1]
except IndexError:
config_current[kv[0]] = ''
# determine the current model nic and mac-address
models = ['e1000', 'e1000-82540em', 'e1000-82544gc', 'e1000-82545em', 'i82551', 'i82557b',
'i82559er', 'ne2k_isa', 'ne2k_pci', 'pcnet', 'rtl8139', 'virtio', 'vmxnet3']
current_model = set(models) & set(config_current.keys())
current_model = current_model.pop()
current_mac = config_current[current_model]
# build nic config string
config_provided = "{0}={1}".format(model, current_mac)
else:
config_provided = model
if kwargs['mac']:
config_provided = "{0}={1}".format(model, kwargs['mac'])
if kwargs['bridge']:
config_provided += ",bridge={0}".format(kwargs['bridge'])
if kwargs['firewall']:
config_provided += ",firewall=1"
if kwargs['link_down']:
config_provided += ',link_down=1'
if kwargs['mtu']:
config_provided += ",mtu={0}".format(kwargs['mtu'])
if model != 'virtio':
self.module.warn(
'Ignoring MTU for nic {0} on VM with vmid {1}, '
'model should be set to \'virtio\': '.format(interface, vmid))
if kwargs['queues']:
config_provided += ",queues={0}".format(kwargs['queues'])
if kwargs['rate']:
config_provided += ",rate={0}".format(kwargs['rate'])
if kwargs['tag']:
config_provided += ",tag={0}".format(kwargs['tag'])
if kwargs['trunks']:
config_provided += ",trunks={0}".format(';'.join(str(x) for x in kwargs['trunks']))
net = {interface: config_provided}
vm = self.get_vm(vmid)
if ((interface not in vminfo) or (vminfo[interface] != config_provided)):
if not self.module.check_mode:
self.proxmox_api.nodes(vm['node']).qemu(vmid).config.set(**net)
return True
return False
def delete_nic(self, vmid, interface):
vm = self.get_vm(vmid)
vminfo = self.proxmox_api.nodes(vm['node']).qemu(vmid).config.get()
if interface in vminfo:
if not self.module.check_mode:
self.proxmox_api.nodes(vm['node']).qemu(vmid).config.set(delete=interface)
return True
return False
def main():
module_args = proxmox_auth_argument_spec()
nic_args = dict(
bridge=dict(type='str'),
firewall=dict(type='bool', default=False),
interface=dict(type='str', required=True),
link_down=dict(type='bool', default=False),
mac=dict(type='str'),
model=dict(choices=['e1000', 'e1000-82540em', 'e1000-82544gc', 'e1000-82545em',
'i82551', 'i82557b', 'i82559er', 'ne2k_isa', 'ne2k_pci', 'pcnet',
'rtl8139', 'virtio', 'vmxnet3'], default='virtio'),
mtu=dict(type='int'),
name=dict(type='str'),
queues=dict(type='int'),
rate=dict(type='float'),
state=dict(default='present', choices=['present', 'absent']),
tag=dict(type='int'),
trunks=dict(type='list', elements='int'),
vmid=dict(type='int'),
)
module_args.update(nic_args)
module = AnsibleModule(
argument_spec=module_args,
required_together=[('api_token_id', 'api_token_secret')],
required_one_of=[('name', 'vmid'), ('api_password', 'api_token_id')],
supports_check_mode=True,
)
proxmox = ProxmoxNicAnsible(module)
interface = module.params['interface']
model = module.params['model']
name = module.params['name']
state = module.params['state']
vmid = module.params['vmid']
# If vmid is not defined then retrieve its value from the vm name,
if not vmid:
vmid = proxmox.get_vmid(name)
# Ensure VM id exists
proxmox.get_vm(vmid)
if state == 'present':
try:
if proxmox.update_nic(vmid, interface, model,
bridge=module.params['bridge'],
firewall=module.params['firewall'],
link_down=module.params['link_down'],
mac=module.params['mac'],
mtu=module.params['mtu'],
queues=module.params['queues'],
rate=module.params['rate'],
tag=module.params['tag'],
trunks=module.params['trunks']):
module.exit_json(changed=True, vmid=vmid, msg="Nic {0} updated on VM with vmid {1}".format(interface, vmid))
else:
module.exit_json(vmid=vmid, msg="Nic {0} unchanged on VM with vmid {1}".format(interface, vmid))
except Exception as e:
module.fail_json(vmid=vmid, msg='Unable to change nic {0} on VM with vmid {1}: '.format(interface, vmid) + str(e))
elif state == 'absent':
try:
if proxmox.delete_nic(vmid, interface):
module.exit_json(changed=True, vmid=vmid, msg="Nic {0} deleted on VM with vmid {1}".format(interface, vmid))
else:
module.exit_json(vmid=vmid, msg="Nic {0} does not exist on VM with vmid {1}".format(interface, vmid))
except Exception as e:
module.fail_json(vmid=vmid, msg='Unable to delete nic {0} on VM with vmid {1}: '.format(interface, vmid) + str(e))
if __name__ == '__main__':
main()

View file

@ -1,143 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright John Berninger (@jberning) <john.berninger at gmail.com>
# 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
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r"""
module: proxmox_node_info
short_description: Retrieve information about one or more Proxmox VE nodes
version_added: 8.2.0
description:
- Retrieve information about one or more Proxmox VE nodes.
author: John Berninger (@jwbernin)
attributes:
action_group:
version_added: 9.0.0
extends_documentation_fragment:
- community.general.proxmox.actiongroup_proxmox
- community.general.proxmox.documentation
- community.general.attributes
- community.general.attributes.info_module
"""
EXAMPLES = r"""
- name: List existing nodes
community.general.proxmox_node_info:
api_host: proxmox1
api_user: root@pam
api_password: "{{ password | default(omit) }}"
api_token_id: "{{ token_id | default(omit) }}"
api_token_secret: "{{ token_secret | default(omit) }}"
register: proxmox_nodes
"""
RETURN = r"""
proxmox_nodes:
description: List of Proxmox VE nodes.
returned: always, but can be empty
type: list
elements: dict
contains:
cpu:
description: Current CPU usage in fractional shares of this host's total available CPU.
returned: on success
type: float
disk:
description: Current local disk usage of this host.
returned: on success
type: int
id:
description: Identity of the node.
returned: on success
type: str
level:
description: Support level. Can be blank if not under a paid support contract.
returned: on success
type: str
maxcpu:
description: Total number of available CPUs on this host.
returned: on success
type: int
maxdisk:
description: Size of local disk in bytes.
returned: on success
type: int
maxmem:
description: Memory size in bytes.
returned: on success
type: int
mem:
description: Used memory in bytes.
returned: on success
type: int
node:
description: Short hostname of this node.
returned: on success
type: str
ssl_fingerprint:
description: SSL fingerprint of the node certificate.
returned: on success
type: str
status:
description: Node status.
returned: on success
type: str
type:
description: Object type being returned.
returned: on success
type: str
uptime:
description: Node uptime in seconds.
returned: on success
type: int
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.proxmox import (
proxmox_auth_argument_spec, ProxmoxAnsible)
class ProxmoxNodeInfoAnsible(ProxmoxAnsible):
def get_nodes(self):
nodes = self.proxmox_api.nodes.get()
return nodes
def proxmox_node_info_argument_spec():
return dict()
def main():
module_args = proxmox_auth_argument_spec()
node_info_args = proxmox_node_info_argument_spec()
module_args.update(node_info_args)
module = AnsibleModule(
argument_spec=module_args,
required_one_of=[('api_password', 'api_token_id')],
required_together=[('api_token_id', 'api_token_secret')],
supports_check_mode=True,
)
result = dict(
changed=False
)
proxmox = ProxmoxNodeInfoAnsible(module)
nodes = proxmox.get_nodes()
result['proxmox_nodes'] = nodes
module.exit_json(**result)
if __name__ == '__main__':
main()

View file

@ -1,182 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2023, Sergei Antipov (UnderGreen) <greendayonfire@gmail.com>
# 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
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = r"""
module: proxmox_pool
short_description: Pool management for Proxmox VE cluster
description:
- Create or delete a pool for Proxmox VE clusters.
- For pool members management please consult M(community.general.proxmox_pool_member) module.
version_added: 7.1.0
author: "Sergei Antipov (@UnderGreen) <greendayonfire@gmail.com>"
attributes:
check_mode:
support: full
diff_mode:
support: none
action_group:
version_added: 9.0.0
options:
poolid:
description:
- The pool ID.
type: str
aliases: ["name"]
required: true
state:
description:
- Indicate desired state of the pool.
- The pool must be empty prior deleting it with O(state=absent).
choices: ['present', 'absent']
default: present
type: str
comment:
description:
- Specify the description for the pool.
- Parameter is ignored when pool already exists or O(state=absent).
type: str
extends_documentation_fragment:
- community.general.proxmox.actiongroup_proxmox
- community.general.proxmox.documentation
- community.general.attributes
"""
EXAMPLES = r"""
- name: Create new Proxmox VE pool
community.general.proxmox_pool:
api_host: node1
api_user: root@pam
api_password: password
poolid: test
comment: 'New pool'
- name: Delete the Proxmox VE pool
community.general.proxmox_pool:
api_host: node1
api_user: root@pam
api_password: password
poolid: test
state: absent
"""
RETURN = r"""
poolid:
description: The pool ID.
returned: success
type: str
sample: test
msg:
description: A short message on what the module did.
returned: always
type: str
sample: "Pool test successfully created"
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.proxmox import (proxmox_auth_argument_spec, ProxmoxAnsible)
class ProxmoxPoolAnsible(ProxmoxAnsible):
def is_pool_existing(self, poolid):
"""Check whether pool already exist
:param poolid: str - name of the pool
:return: bool - is pool exists?
"""
try:
pools = self.proxmox_api.pools.get()
for pool in pools:
if pool['poolid'] == poolid:
return True
return False
except Exception as e:
self.module.fail_json(msg="Unable to retrieve pools: {0}".format(e))
def is_pool_empty(self, poolid):
"""Check whether pool has members
:param poolid: str - name of the pool
:return: bool - is pool empty?
"""
return True if not self.get_pool(poolid)['members'] else False
def create_pool(self, poolid, comment=None):
"""Create Proxmox VE pool
:param poolid: str - name of the pool
:param comment: str, optional - Description of a pool
:return: None
"""
if self.is_pool_existing(poolid):
self.module.exit_json(changed=False, poolid=poolid, msg="Pool {0} already exists".format(poolid))
if self.module.check_mode:
return
try:
self.proxmox_api.pools.post(poolid=poolid, comment=comment)
except Exception as e:
self.module.fail_json(msg="Failed to create pool with ID {0}: {1}".format(poolid, e))
def delete_pool(self, poolid):
"""Delete Proxmox VE pool
:param poolid: str - name of the pool
:return: None
"""
if not self.is_pool_existing(poolid):
self.module.exit_json(changed=False, poolid=poolid, msg="Pool {0} doesn't exist".format(poolid))
if self.is_pool_empty(poolid):
if self.module.check_mode:
return
try:
self.proxmox_api.pools(poolid).delete()
except Exception as e:
self.module.fail_json(msg="Failed to delete pool with ID {0}: {1}".format(poolid, e))
else:
self.module.fail_json(msg="Can't delete pool {0} with members. Please remove members from pool first.".format(poolid))
def main():
module_args = proxmox_auth_argument_spec()
pools_args = dict(
poolid=dict(type="str", aliases=["name"], required=True),
comment=dict(type="str"),
state=dict(default="present", choices=["present", "absent"]),
)
module_args.update(pools_args)
module = AnsibleModule(
argument_spec=module_args,
required_together=[("api_token_id", "api_token_secret")],
required_one_of=[("api_password", "api_token_id")],
supports_check_mode=True
)
poolid = module.params["poolid"]
comment = module.params["comment"]
state = module.params["state"]
proxmox = ProxmoxPoolAnsible(module)
if state == "present":
proxmox.create_pool(poolid, comment)
module.exit_json(changed=True, poolid=poolid, msg="Pool {0} successfully created".format(poolid))
else:
proxmox.delete_pool(poolid)
module.exit_json(changed=True, poolid=poolid, msg="Pool {0} successfully deleted".format(poolid))
if __name__ == "__main__":
main()

View file

@ -1,240 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2023, Sergei Antipov (UnderGreen) <greendayonfire@gmail.com>
# 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
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = r"""
module: proxmox_pool_member
short_description: Add or delete members from Proxmox VE cluster pools
description:
- Create or delete a pool member in Proxmox VE clusters.
version_added: 7.1.0
author: "Sergei Antipov (@UnderGreen) <greendayonfire@gmail.com>"
attributes:
check_mode:
support: full
diff_mode:
support: full
action_group:
version_added: 9.0.0
options:
poolid:
description:
- The pool ID.
type: str
aliases: ["name"]
required: true
member:
description:
- Specify the member name.
- For O(type=storage) it is a storage name.
- For O(type=vm) either vmid or vm name could be used.
type: str
required: true
type:
description:
- Member type to add/remove from the pool.
choices: ["vm", "storage"]
default: vm
type: str
state:
description:
- Indicate desired state of the pool member.
choices: ['present', 'absent']
default: present
type: str
extends_documentation_fragment:
- community.general.proxmox.actiongroup_proxmox
- community.general.proxmox.documentation
- community.general.attributes
"""
EXAMPLES = r"""
- name: Add new VM to Proxmox VE pool
community.general.proxmox_pool_member:
api_host: node1
api_user: root@pam
api_password: password
poolid: test
member: 101
- name: Add new storage to Proxmox VE pool
community.general.proxmox_pool_member:
api_host: node1
api_user: root@pam
api_password: password
poolid: test
member: zfs-data
type: storage
- name: Remove VM from the Proxmox VE pool using VM name
community.general.proxmox_pool_member:
api_host: node1
api_user: root@pam
api_password: password
poolid: test
member: pxe.home.arpa
state: absent
- name: Remove storage from the Proxmox VE pool
community.general.proxmox_pool_member:
api_host: node1
api_user: root@pam
api_password: password
poolid: test
member: zfs-storage
type: storage
state: absent
"""
RETURN = r"""
poolid:
description: The pool ID.
returned: success
type: str
sample: test
member:
description: Member name.
returned: success
type: str
sample: 101
msg:
description: A short message on what the module did.
returned: always
type: str
sample: "Member 101 deleted from the pool test"
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.proxmox import (proxmox_auth_argument_spec, ProxmoxAnsible)
class ProxmoxPoolMemberAnsible(ProxmoxAnsible):
def pool_members(self, poolid):
vms = []
storage = []
for member in self.get_pool(poolid)["members"]:
if member["type"] == "storage":
storage.append(member["storage"])
else:
vms.append(member["vmid"])
return (vms, storage)
def add_pool_member(self, poolid, member, member_type):
current_vms_members, current_storage_members = self.pool_members(poolid)
all_members_before = current_storage_members + current_vms_members
all_members_after = all_members_before.copy()
diff = {"before": {"members": all_members_before}, "after": {"members": all_members_after}}
try:
if member_type == "storage":
storages = self.get_storages(type=None)
if member not in [storage["storage"] for storage in storages]:
self.module.fail_json(msg="Storage {0} doesn't exist in the cluster".format(member))
if member in current_storage_members:
self.module.exit_json(changed=False, poolid=poolid, member=member,
diff=diff, msg="Member {0} is already part of the pool {1}".format(member, poolid))
all_members_after.append(member)
if self.module.check_mode:
return diff
self.proxmox_api.pools(poolid).put(storage=[member])
return diff
else:
try:
vmid = int(member)
except ValueError:
vmid = self.get_vmid(member)
if vmid in current_vms_members:
self.module.exit_json(changed=False, poolid=poolid, member=member,
diff=diff, msg="VM {0} is already part of the pool {1}".format(member, poolid))
all_members_after.append(member)
if not self.module.check_mode:
self.proxmox_api.pools(poolid).put(vms=[vmid])
return diff
except Exception as e:
self.module.fail_json(msg="Failed to add a new member ({0}) to the pool {1}: {2}".format(member, poolid, e))
def delete_pool_member(self, poolid, member, member_type):
current_vms_members, current_storage_members = self.pool_members(poolid)
all_members_before = current_storage_members + current_vms_members
all_members_after = all_members_before.copy()
diff = {"before": {"members": all_members_before}, "after": {"members": all_members_after}}
try:
if member_type == "storage":
if member not in current_storage_members:
self.module.exit_json(changed=False, poolid=poolid, member=member,
diff=diff, msg="Member {0} is not part of the pool {1}".format(member, poolid))
all_members_after.remove(member)
if self.module.check_mode:
return diff
self.proxmox_api.pools(poolid).put(storage=[member], delete=1)
return diff
else:
try:
vmid = int(member)
except ValueError:
vmid = self.get_vmid(member)
if vmid not in current_vms_members:
self.module.exit_json(changed=False, poolid=poolid, member=member,
diff=diff, msg="VM {0} is not part of the pool {1}".format(member, poolid))
all_members_after.remove(vmid)
if not self.module.check_mode:
self.proxmox_api.pools(poolid).put(vms=[vmid], delete=1)
return diff
except Exception as e:
self.module.fail_json(msg="Failed to delete a member ({0}) from the pool {1}: {2}".format(member, poolid, e))
def main():
module_args = proxmox_auth_argument_spec()
pool_members_args = dict(
poolid=dict(type="str", aliases=["name"], required=True),
member=dict(type="str", required=True),
type=dict(default="vm", choices=["vm", "storage"]),
state=dict(default="present", choices=["present", "absent"]),
)
module_args.update(pool_members_args)
module = AnsibleModule(
argument_spec=module_args,
required_together=[("api_token_id", "api_token_secret")],
required_one_of=[("api_password", "api_token_id")],
supports_check_mode=True
)
poolid = module.params["poolid"]
member = module.params["member"]
member_type = module.params["type"]
state = module.params["state"]
proxmox = ProxmoxPoolMemberAnsible(module)
if state == "present":
diff = proxmox.add_pool_member(poolid, member, member_type)
module.exit_json(changed=True, poolid=poolid, member=member, diff=diff, msg="New member {0} added to the pool {1}".format(member, poolid))
else:
diff = proxmox.delete_pool_member(poolid, member, member_type)
module.exit_json(changed=True, poolid=poolid, member=member, diff=diff, msg="Member {0} deleted from the pool {1}".format(member, poolid))
if __name__ == "__main__":
main()

View file

@ -1,395 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2020, Jeffrey van Pelt (@Thulium-Drake) <jeff@vanpelt.one>
# 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
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r"""
module: proxmox_snap
short_description: Snapshot management of instances in Proxmox VE cluster
version_added: 2.0.0
description:
- Allows you to create/delete/restore 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.
attributes:
check_mode:
support: full
diff_mode:
support: none
action_group:
version_added: 9.0.0
options:
hostname:
description:
- The instance name.
type: str
vmid:
description:
- The instance ID.
- If not set, will be fetched from PromoxAPI based on the hostname.
type: str
state:
description:
- Indicate desired state of the instance snapshot.
- The V(rollback) value was added in community.general 4.8.0.
choices: ['present', 'absent', 'rollback']
default: present
type: str
force:
description:
- For removal from config file, even if removing disk snapshot fails.
default: false
type: bool
unbind:
description:
- This option only applies to LXC containers.
- Allows to snapshot a container even if it has configured mountpoints.
- Temporarily disables all configured mountpoints, takes snapshot, and finally restores original configuration.
- If running, the container will be stopped and restarted to apply config changes.
- Due to restrictions in the Proxmox API this option can only be used authenticating as V(root@pam) with O(api_password),
API tokens do not work either.
- See U(https://pve.proxmox.com/pve-docs/api-viewer/#/nodes/{node}/lxc/{vmid}/config) (PUT tab) for more details.
default: false
type: bool
version_added: 5.7.0
vmstate:
description:
- Snapshot includes RAM.
default: false
type: bool
description:
description:
- Specify the description for the snapshot. Only used on the configuration web interface.
- This is saved as a comment inside the configuration file.
type: str
timeout:
description:
- Timeout for operations.
default: 30
type: int
snapname:
description:
- Name of the snapshot that has to be created/deleted/restored.
default: 'ansible_snap'
type: str
retention:
description:
- Remove old snapshots if there are more than O(retention) snapshots.
- If O(retention) is set to V(0), all snapshots will be kept.
- This is only used when O(state=present) and when an actual snapshot is created. If no snapshot is created, all existing
snapshots will be kept.
default: 0
type: int
version_added: 7.1.0
notes:
- Requires proxmoxer and requests modules on host. These modules can be installed with pip.
requirements: ["proxmoxer", "requests"]
author: Jeffrey van Pelt (@Thulium-Drake)
extends_documentation_fragment:
- community.general.proxmox.actiongroup_proxmox
- community.general.proxmox.documentation
- community.general.attributes
"""
EXAMPLES = r"""
- name: Create new container snapshot
community.general.proxmox_snap:
api_user: root@pam
api_password: 1q2w3e
api_host: node1
vmid: 100
state: present
snapname: pre-updates
- name: Create new container snapshot and keep only the 2 newest snapshots
community.general.proxmox_snap:
api_user: root@pam
api_password: 1q2w3e
api_host: node1
vmid: 100
state: present
snapname: snapshot-42
retention: 2
- name: Create new snapshot for a container with configured mountpoints
community.general.proxmox_snap:
api_user: root@pam
api_password: 1q2w3e
api_host: node1
vmid: 100
state: present
unbind: true # requires root@pam+password auth, API tokens are not supported
snapname: pre-updates
- name: Remove container snapshot
community.general.proxmox_snap:
api_user: root@pam
api_password: 1q2w3e
api_host: node1
vmid: 100
state: absent
snapname: pre-updates
- name: Rollback container snapshot
community.general.proxmox_snap:
api_user: root@pam
api_password: 1q2w3e
api_host: node1
vmid: 100
state: rollback
snapname: pre-updates
"""
RETURN = r"""#"""
import time
from ansible.module_utils.basic import AnsibleModule
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)
class ProxmoxSnapAnsible(ProxmoxAnsible):
def snapshot(self, vm, vmid):
return getattr(self.proxmox_api.nodes(vm['node']), vm['type'])(vmid).snapshot
def vmconfig(self, vm, vmid):
return getattr(self.proxmox_api.nodes(vm['node']), vm['type'])(vmid).config
def vmstatus(self, vm, vmid):
return getattr(self.proxmox_api.nodes(vm['node']), vm['type'])(vmid).status
def _container_mp_get(self, vm, vmid):
cfg = self.vmconfig(vm, vmid).get()
mountpoints = {}
for key, value in cfg.items():
if key.startswith('mp'):
mountpoints[key] = value
return mountpoints
def _container_mp_disable(self, vm, vmid, timeout, unbind, mountpoints, vmstatus):
# shutdown container if running
if vmstatus == 'running':
self.shutdown_instance(vm, vmid, timeout)
# delete all mountpoints configs
self.vmconfig(vm, vmid).put(delete=' '.join(mountpoints))
def _container_mp_restore(self, vm, vmid, timeout, unbind, mountpoints, vmstatus):
# NOTE: requires auth as `root@pam`, API tokens are not supported
# see https://pve.proxmox.com/pve-docs/api-viewer/#/nodes/{node}/lxc/{vmid}/config
# restore original config
self.vmconfig(vm, vmid).put(**mountpoints)
# start container (if was running before snap)
if vmstatus == 'running':
self.start_instance(vm, vmid, timeout)
def start_instance(self, vm, vmid, timeout):
taskid = self.vmstatus(vm, vmid).start.post()
while timeout >= 0:
if self.api_task_ok(vm['node'], taskid):
return True
timeout -= 1
if timeout == 0:
self.module.fail_json(msg='Reached timeout while waiting for VM to start. Last line in task before timeout: %s' %
self.proxmox_api.nodes(vm['node']).tasks(taskid).log.get()[:1])
time.sleep(1)
return False
def shutdown_instance(self, vm, vmid, timeout):
taskid = self.vmstatus(vm, vmid).shutdown.post()
while timeout >= 0:
if self.api_task_ok(vm['node'], taskid):
return True
timeout -= 1
if timeout == 0:
self.module.fail_json(msg='Reached timeout while waiting for VM to stop. 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_retention(self, vm, vmid, retention):
# ignore the last snapshot, which is the current state
snapshots = self.snapshot(vm, vmid).get()[:-1]
if retention > 0 and len(snapshots) > retention:
# sort by age, oldest first
for snap in sorted(snapshots, key=lambda x: x['snaptime'])[:len(snapshots) - retention]:
self.snapshot(vm, vmid)(snap['name']).delete()
def snapshot_create(self, vm, vmid, timeout, snapname, description, vmstate, unbind, retention):
if self.module.check_mode:
return True
if vm['type'] == 'lxc':
if unbind is True:
# check if credentials will work
# WARN: it is crucial this check runs here!
# The correct permissions are required only to reconfig mounts.
# Not checking now would allow to remove the configuration BUT
# fail later, leaving the container in a misconfigured state.
if (
self.module.params['api_user'] != 'root@pam'
or not self.module.params['api_password']
):
self.module.fail_json(msg='`unbind=True` requires authentication as `root@pam` with `api_password`, API tokens are not supported.')
return False
mountpoints = self._container_mp_get(vm, vmid)
vmstatus = self.vmstatus(vm, vmid).current().get()['status']
if mountpoints:
self._container_mp_disable(vm, vmid, timeout, unbind, mountpoints, vmstatus)
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 >= 0:
if self.api_task_ok(vm['node'], taskid):
break
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)
timeout -= 1
if vm['type'] == 'lxc' and unbind is True and mountpoints:
self._container_mp_restore(vm, vmid, timeout, unbind, mountpoints, vmstatus)
self.snapshot_retention(vm, vmid, retention)
return timeout > 0
def snapshot_remove(self, vm, vmid, timeout, snapname, force):
if self.module.check_mode:
return True
taskid = self.snapshot(vm, vmid).delete(snapname, force=int(force))
while timeout >= 0:
if self.api_task_ok(vm['node'], taskid):
return True
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)
timeout -= 1
return False
def snapshot_rollback(self, vm, vmid, timeout, snapname):
if self.module.check_mode:
return True
taskid = self.snapshot(vm, vmid)(snapname).post("rollback")
while timeout >= 0:
if self.api_task_ok(vm['node'], taskid):
return True
if timeout == 0:
self.module.fail_json(msg='Reached timeout while waiting for rolling back VM snapshot. Last line in task before timeout: %s' %
self.proxmox_api.nodes(vm['node']).tasks(taskid).log.get()[:1])
time.sleep(1)
timeout -= 1
return False
def main():
module_args = proxmox_auth_argument_spec()
snap_args = dict(
vmid=dict(required=False),
hostname=dict(),
timeout=dict(type='int', default=30),
state=dict(default='present', choices=['present', 'absent', 'rollback']),
description=dict(type='str'),
snapname=dict(type='str', default='ansible_snap'),
force=dict(type='bool', default=False),
unbind=dict(type='bool', default=False),
vmstate=dict(type='bool', default=False),
retention=dict(type='int', default=0),
)
module_args.update(snap_args)
module = AnsibleModule(
argument_spec=module_args,
supports_check_mode=True
)
proxmox = ProxmoxSnapAnsible(module)
state = module.params['state']
vmid = module.params['vmid']
hostname = module.params['hostname']
description = module.params['description']
snapname = module.params['snapname']
timeout = module.params['timeout']
force = module.params['force']
unbind = module.params['unbind']
vmstate = module.params['vmstate']
retention = module.params['retention']
# If hostname is set get the VM id from ProxmoxAPI
if not vmid and hostname:
vmid = proxmox.get_vmid(hostname)
elif not vmid:
module.exit_json(changed=False, msg="Vmid could not be fetched for the following action: %s" % state)
vm = proxmox.get_vm(vmid)
if state == 'present':
try:
for i in proxmox.snapshot(vm, vmid).get():
if i['name'] == snapname:
module.exit_json(changed=False, msg="Snapshot %s is already present" % snapname)
if proxmox.snapshot_create(vm, vmid, timeout, snapname, description, vmstate, unbind, retention):
if module.check_mode:
module.exit_json(changed=False, msg="Snapshot %s would be created" % snapname)
else:
module.exit_json(changed=True, msg="Snapshot %s created" % snapname)
except Exception as e:
module.fail_json(msg="Creating snapshot %s of VM %s failed with exception: %s" % (snapname, vmid, to_native(e)))
elif state == 'absent':
try:
snap_exist = False
for i in proxmox.snapshot(vm, vmid).get():
if i['name'] == snapname:
snap_exist = True
continue
if not snap_exist:
module.exit_json(changed=False, msg="Snapshot %s does not exist" % snapname)
else:
if proxmox.snapshot_remove(vm, vmid, timeout, snapname, force):
if module.check_mode:
module.exit_json(changed=False, msg="Snapshot %s would be removed" % snapname)
else:
module.exit_json(changed=True, msg="Snapshot %s removed" % snapname)
except Exception as e:
module.fail_json(msg="Removing snapshot %s of VM %s failed with exception: %s" % (snapname, vmid, to_native(e)))
elif state == 'rollback':
try:
snap_exist = False
for i in proxmox.snapshot(vm, vmid).get():
if i['name'] == snapname:
snap_exist = True
continue
if not snap_exist:
module.exit_json(changed=False, msg="Snapshot %s does not exist" % snapname)
if proxmox.snapshot_rollback(vm, vmid, timeout, snapname):
if module.check_mode:
module.exit_json(changed=True, msg="Snapshot %s would be rolled back" % snapname)
else:
module.exit_json(changed=True, msg="Snapshot %s rolled back" % snapname)
except Exception as e:
module.fail_json(msg="Rollback of snapshot %s of VM %s failed with exception: %s" % (snapname, vmid, to_native(e)))
if __name__ == '__main__':
main()

View file

@ -1,147 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright Julian Vanden Broeck (@l00ptr) <julian.vandenbroeck at dalibo.com>
# 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
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r"""
module: proxmox_storage_contents_info
short_description: List content from a Proxmox VE storage
version_added: 8.2.0
description:
- Retrieves information about stored objects on a specific storage attached to a node.
attributes:
action_group:
version_added: 9.0.0
options:
storage:
description:
- Only return content stored on that specific storage.
aliases: ['name']
type: str
required: true
node:
description:
- Proxmox node to which the storage is attached.
type: str
required: true
content:
description:
- Filter on a specific content type.
type: str
choices: ["all", "backup", "rootdir", "images", "iso"]
default: "all"
vmid:
description:
- Filter on a specific VMID.
type: int
author: Julian Vanden Broeck (@l00ptr)
extends_documentation_fragment:
- community.general.proxmox.actiongroup_proxmox
- community.general.proxmox.documentation
- community.general.attributes
- community.general.attributes.info_module
"""
EXAMPLES = r"""
- name: List existing storages
community.general.proxmox_storage_contents_info:
api_host: helldorado
api_user: root@pam
api_password: "{{ password | default(omit) }}"
api_token_id: "{{ token_id | default(omit) }}"
api_token_secret: "{{ token_secret | default(omit) }}"
storage: lvm2
content: backup
vmid: 130
"""
RETURN = r"""
proxmox_storage_content:
description: Content of of storage attached to a node.
type: list
returned: success
elements: dict
contains:
content:
description: Proxmox content of listed objects on this storage.
type: str
returned: success
ctime:
description: Creation time of the listed objects.
type: str
returned: success
format:
description: Format of the listed objects (can be V(raw), V(pbs-vm), V(iso),...).
type: str
returned: success
size:
description: Size of the listed objects.
type: int
returned: success
subtype:
description: Subtype of the listed objects (can be V(qemu) or V(lxc)).
type: str
returned: When storage is dedicated to backup, typically on PBS storage.
verification:
description: Backup verification status of the listed objects.
type: dict
returned: When storage is dedicated to backup, typically on PBS storage.
sample: {
"state": "ok",
"upid": "UPID:backup-srv:00130F49:1A12D8375:00001CD7:657A2258:verificationjob:daily\\x3av\\x2dd0cc18c5\\x2d8707:root@pam:"
}
volid:
description: Volume identifier of the listed objects.
type: str
returned: success
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.proxmox import (
ProxmoxAnsible, proxmox_auth_argument_spec)
def proxmox_storage_info_argument_spec():
return dict(
storage=dict(type="str", required=True, aliases=["name"]),
content=dict(type="str", required=False, default="all", choices=["all", "backup", "rootdir", "images", "iso"]),
vmid=dict(type="int"),
node=dict(required=True, type="str"),
)
def main():
module_args = proxmox_auth_argument_spec()
storage_info_args = proxmox_storage_info_argument_spec()
module_args.update(storage_info_args)
module = AnsibleModule(
argument_spec=module_args,
required_one_of=[("api_password", "api_token_id")],
required_together=[("api_token_id", "api_token_secret")],
supports_check_mode=True,
)
result = dict(changed=False)
proxmox = ProxmoxAnsible(module)
res = proxmox.get_storage_content(
node=module.params["node"],
storage=module.params["storage"],
content=None if module.params["content"] == "all" else module.params["content"],
vmid=module.params["vmid"],
)
result["proxmox_storage_content"] = res
module.exit_json(**result)
if __name__ == "__main__":
main()

View file

@ -1,194 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright Tristan Le Guern (@tleguern) <tleguern at bouledef.eu>
# 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
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r"""
module: proxmox_storage_info
short_description: Retrieve information about one or more Proxmox VE storages
version_added: 2.2.0
description:
- Retrieve information about one or more Proxmox VE storages.
attributes:
action_group:
version_added: 9.0.0
options:
storage:
description:
- Only return information on a specific storage.
aliases: ['name']
type: str
type:
description:
- Filter on a specific storage type.
type: str
author: Tristan Le Guern (@tleguern)
extends_documentation_fragment:
- community.general.proxmox.actiongroup_proxmox
- community.general.proxmox.documentation
- community.general.attributes
- community.general.attributes.info_module
notes:
- Storage specific options can be returned by this module, please look at the documentation at U(https://pve.proxmox.com/wiki/Storage).
"""
EXAMPLES = r"""
- name: List existing storages
community.general.proxmox_storage_info:
api_host: helldorado
api_user: root@pam
api_password: "{{ password | default(omit) }}"
api_token_id: "{{ token_id | default(omit) }}"
api_token_secret: "{{ token_secret | default(omit) }}"
register: proxmox_storages
- name: List NFS storages only
community.general.proxmox_storage_info:
api_host: helldorado
api_user: root@pam
api_password: "{{ password | default(omit) }}"
api_token_id: "{{ token_id | default(omit) }}"
api_token_secret: "{{ token_secret | default(omit) }}"
type: nfs
register: proxmox_storages_nfs
- name: Retrieve information about the lvm2 storage
community.general.proxmox_storage_info:
api_host: helldorado
api_user: root@pam
api_password: "{{ password | default(omit) }}"
api_token_id: "{{ token_id | default(omit) }}"
api_token_secret: "{{ token_secret | default(omit) }}"
storage: lvm2
register: proxmox_storage_lvm
"""
RETURN = r"""
proxmox_storages:
description: List of storage pools.
returned: on success
type: list
elements: dict
contains:
content:
description: Proxmox content types available in this storage.
returned: on success
type: list
elements: str
digest:
description: Storage's digest.
returned: on success
type: str
nodes:
description: List of nodes associated to this storage.
returned: on success, if storage is not local
type: list
elements: str
path:
description: Physical path to this storage.
returned: on success
type: str
prune-backups:
description: Backup retention options.
returned: on success
type: list
elements: dict
shared:
description: Is this storage shared.
returned: on success
type: bool
storage:
description: Storage name.
returned: on success
type: str
type:
description: Storage type.
returned: on success
type: str
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.proxmox import (
proxmox_auth_argument_spec, ProxmoxAnsible, proxmox_to_ansible_bool)
class ProxmoxStorageInfoAnsible(ProxmoxAnsible):
def get_storage(self, storage):
try:
storage = self.proxmox_api.storage.get(storage)
except Exception:
self.module.fail_json(msg="Storage '%s' does not exist" % storage)
return ProxmoxStorage(storage)
def get_storages(self, type=None):
storages = self.proxmox_api.storage.get(type=type)
storages = [ProxmoxStorage(storage) for storage in storages]
return storages
class ProxmoxStorage:
def __init__(self, storage):
self.storage = storage
# Convert proxmox representation of lists, dicts and boolean for easier
# manipulation within ansible.
if 'shared' in self.storage:
self.storage['shared'] = proxmox_to_ansible_bool(self.storage['shared'])
if 'content' in self.storage:
self.storage['content'] = self.storage['content'].split(',')
if 'nodes' in self.storage:
self.storage['nodes'] = self.storage['nodes'].split(',')
if 'prune-backups' in storage:
options = storage['prune-backups'].split(',')
self.storage['prune-backups'] = dict()
for option in options:
k, v = option.split('=')
self.storage['prune-backups'][k] = v
def proxmox_storage_info_argument_spec():
return dict(
storage=dict(type='str', aliases=['name']),
type=dict(type='str'),
)
def main():
module_args = proxmox_auth_argument_spec()
storage_info_args = proxmox_storage_info_argument_spec()
module_args.update(storage_info_args)
module = AnsibleModule(
argument_spec=module_args,
required_one_of=[('api_password', 'api_token_id')],
required_together=[('api_token_id', 'api_token_secret')],
mutually_exclusive=[('storage', 'type')],
supports_check_mode=True
)
result = dict(
changed=False
)
proxmox = ProxmoxStorageInfoAnsible(module)
storage = module.params['storage']
storagetype = module.params['type']
if storage:
storages = [proxmox.get_storage(storage)]
else:
storages = proxmox.get_storages(type=storagetype)
result['proxmox_storages'] = [storage.storage for storage in storages]
module.exit_json(**result)
if __name__ == '__main__':
main()

View file

@ -1,188 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2021, Andreas Botzner (@paginabianca) <andreas at botzner dot com>
# 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
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r"""
module: proxmox_tasks_info
short_description: Retrieve information about one or more Proxmox VE tasks
version_added: 3.8.0
description:
- Retrieve information about one or more Proxmox VE tasks.
author: 'Andreas Botzner (@paginabianca) <andreas at botzner dot com>'
attributes:
action_group:
version_added: 9.0.0
options:
node:
description:
- Node where to get tasks.
required: true
type: str
task:
description:
- Return specific task.
aliases: ['upid', 'name']
type: str
extends_documentation_fragment:
- community.general.proxmox.actiongroup_proxmox
- community.general.proxmox.documentation
- community.general.attributes
- community.general.attributes.info_module
"""
EXAMPLES = r"""
- name: List tasks on node01
community.general.proxmox_tasks_info:
api_host: proxmoxhost
api_user: root@pam
api_password: '{{ password | default(omit) }}'
api_token_id: '{{ token_id | default(omit) }}'
api_token_secret: '{{ token_secret | default(omit) }}'
node: node01
register: result
- name: Retrieve information about specific tasks on node01
community.general.proxmox_tasks_info:
api_host: proxmoxhost
api_user: root@pam
api_password: '{{ password | default(omit) }}'
api_token_id: '{{ token_id | default(omit) }}'
api_token_secret: '{{ token_secret | default(omit) }}'
task: 'UPID:node01:00003263:16167ACE:621EE230:srvreload:networking:root@pam:'
node: node01
register: proxmox_tasks
"""
RETURN = r"""
proxmox_tasks:
description: List of tasks.
returned: on success
type: list
elements: dict
contains:
id:
description: ID of the task.
returned: on success
type: str
node:
description: Node name.
returned: on success
type: str
pid:
description: PID of the task.
returned: on success
type: int
pstart:
description: Pastart of the task.
returned: on success
type: int
starttime:
description: Starting time of the task.
returned: on success
type: int
type:
description: Type of the task.
returned: on success
type: str
upid:
description: UPID of the task.
returned: on success
type: str
user:
description: User that owns the task.
returned: on success
type: str
endtime:
description: Endtime of the task.
returned: on success, can be absent
type: int
status:
description: Status of the task.
returned: on success, can be absent
type: str
failed:
description: If the task failed.
returned: when status is defined
type: bool
msg:
description: Short message.
returned: on failure
type: str
sample: 'Task: UPID:xyz:xyz does not exist on node: proxmoxnode'
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.proxmox import (
proxmox_auth_argument_spec, ProxmoxAnsible)
class ProxmoxTaskInfoAnsible(ProxmoxAnsible):
def get_task(self, upid, node):
tasks = self.get_tasks(node)
for task in tasks:
if task.info['upid'] == upid:
return [task]
def get_tasks(self, node):
tasks = self.proxmox_api.nodes(node).tasks.get()
return [ProxmoxTask(task) for task in tasks]
class ProxmoxTask:
def __init__(self, task):
self.info = dict()
for k, v in task.items():
if k == 'status' and isinstance(v, str):
self.info[k] = v
if v != 'OK':
self.info['failed'] = True
else:
self.info[k] = v
def proxmox_task_info_argument_spec():
return dict(
task=dict(type='str', aliases=['upid', 'name'], required=False),
node=dict(type='str', required=True),
)
def main():
module_args = proxmox_auth_argument_spec()
task_info_args = proxmox_task_info_argument_spec()
module_args.update(task_info_args)
module = AnsibleModule(
argument_spec=module_args,
required_together=[('api_token_id', 'api_token_secret')],
required_one_of=[('api_password', 'api_token_id')],
supports_check_mode=True)
result = dict(changed=False)
proxmox = ProxmoxTaskInfoAnsible(module)
upid = module.params['task']
node = module.params['node']
if upid:
tasks = proxmox.get_task(upid=upid, node=node)
else:
tasks = proxmox.get_tasks(node=node)
if tasks is not None:
result['proxmox_tasks'] = [task.info for task in tasks]
module.exit_json(**result)
else:
result['msg'] = 'Task: {0} does not exist on node: {1}.'.format(
upid, node)
module.fail_json(**result)
if __name__ == '__main__':
main()

View file

@ -1,374 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# 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
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r"""
module: proxmox_template
short_description: Management of OS templates in Proxmox VE cluster
description:
- Allows you to upload/delete templates in Proxmox VE cluster.
attributes:
check_mode:
support: none
diff_mode:
support: none
action_group:
version_added: 9.0.0
options:
node:
description:
- Proxmox VE node on which to operate.
type: str
src:
description:
- Path to uploaded file.
- Exactly one of O(src) or O(url) is required for O(state=present).
type: path
url:
description:
- URL to file to download.
- Exactly one of O(src) or O(url) is required for O(state=present).
type: str
version_added: 10.1.0
template:
description:
- The template name.
- Required for O(state=absent) to delete a template.
- Required for O(state=present) to download an appliance container template (pveam).
type: str
content_type:
description:
- Content type.
- Required only for O(state=present).
type: str
default: 'vztmpl'
choices: ['vztmpl', 'iso']
storage:
description:
- Target storage.
type: str
default: 'local'
timeout:
description:
- Timeout for operations.
type: int
default: 30
force:
description:
- It can only be used with O(state=present), existing template will be overwritten.
type: bool
default: false
state:
description:
- Indicate desired state of the template.
type: str
choices: ['present', 'absent']
default: present
checksum_algorithm:
description:
- Algorithm used to verify the checksum.
- If specified, O(checksum) must also be specified.
type: str
choices: ['md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512']
version_added: 10.3.0
checksum:
description:
- The checksum to validate against.
- Checksums are often provided by software distributors to verify that a download is not corrupted.
- Checksums can usually be found on the distributors download page in the form of a file or string.
- If specified, O(checksum_algorithm) must also be specified.
type: str
version_added: 10.3.0
notes:
- Requires C(proxmoxer) and C(requests) modules on host. Those modules can be installed with M(ansible.builtin.pip).
- C(proxmoxer) >= 1.2.0 requires C(requests_toolbelt) to upload files larger than 256 MB.
author: Sergei Antipov (@UnderGreen)
extends_documentation_fragment:
- community.general.proxmox.actiongroup_proxmox
- community.general.proxmox.documentation
- community.general.attributes
"""
EXAMPLES = r"""
---
- name: Upload new openvz template with minimal options
community.general.proxmox_template:
node: uk-mc02
api_user: root@pam
api_password: 1q2w3e
api_host: node1
src: ~/ubuntu-14.04-x86_64.tar.gz
- name: Pull new openvz template with minimal options
community.general.proxmox_template:
node: uk-mc02
api_user: root@pam
api_password: 1q2w3e
api_host: node1
url: https://ubuntu-mirror/ubuntu-14.04-x86_64.tar.gz
- name: >
Upload new openvz template with minimal options use environment
PROXMOX_PASSWORD variable(you should export it before)
community.general.proxmox_template:
node: uk-mc02
api_user: root@pam
api_host: node1
src: ~/ubuntu-14.04-x86_64.tar.gz
- name: Upload new openvz template with all options and force overwrite
community.general.proxmox_template:
node: uk-mc02
api_user: root@pam
api_password: 1q2w3e
api_host: node1
storage: local
content_type: vztmpl
src: ~/ubuntu-14.04-x86_64.tar.gz
force: true
- name: Pull new openvz template with all options and force overwrite
community.general.proxmox_template:
node: uk-mc02
api_user: root@pam
api_password: 1q2w3e
api_host: node1
storage: local
content_type: vztmpl
url: https://ubuntu-mirror/ubuntu-14.04-x86_64.tar.gz
force: true
- name: Delete template with minimal options
community.general.proxmox_template:
node: uk-mc02
api_user: root@pam
api_password: 1q2w3e
api_host: node1
template: ubuntu-14.04-x86_64.tar.gz
state: absent
- name: Download proxmox appliance container template
community.general.proxmox_template:
node: uk-mc02
api_user: root@pam
api_password: 1q2w3e
api_host: node1
storage: local
content_type: vztmpl
template: ubuntu-20.04-standard_20.04-1_amd64.tar.gz
- name: Download and verify a template's checksum
community.general.proxmox_template:
node: uk-mc02
api_user: root@pam
api_password: 1q2w3e
api_host: node1
url: ubuntu-20.04-standard_20.04-1_amd64.tar.gz
checksum_algorithm: sha256
checksum: 65d860160bdc9b98abf72407e14ca40b609417de7939897d3b58d55787aaef69
"""
import os
import time
import traceback
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible_collections.community.general.plugins.module_utils.proxmox import (proxmox_auth_argument_spec, ProxmoxAnsible)
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
from ansible.module_utils.six.moves.urllib.parse import urlparse, urlencode
REQUESTS_TOOLBELT_ERR = None
try:
# requests_toolbelt is used internally by proxmoxer module
import requests_toolbelt # noqa: F401, pylint: disable=unused-import
HAS_REQUESTS_TOOLBELT = True
except ImportError:
HAS_REQUESTS_TOOLBELT = False
REQUESTS_TOOLBELT_ERR = traceback.format_exc()
class ProxmoxTemplateAnsible(ProxmoxAnsible):
def has_template(self, node, storage, content_type, template):
volid = '%s:%s/%s' % (storage, content_type, template)
try:
return any(tmpl['volid'] == volid for tmpl in self.proxmox_api.nodes(node).storage(storage).content.get())
except Exception as e:
self.module.fail_json(msg="Failed to retrieve template '%s': %s" % (volid, e))
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:
if self.api_task_ok(node, taskid):
return True
elif self.api_task_failed(node, taskid):
self.module.fail_json(msg="Task error: %s" % self.proxmox_api.nodes(node).tasks(taskid).status.get()['exitstatus'])
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.nodes(node).tasks(taskid).log.get()[:1])
time.sleep(1)
return False
def upload_template(self, node, storage, content_type, realpath, timeout):
stats = os.stat(realpath)
if (LooseVersion(self.proxmoxer_version) >= LooseVersion('1.2.0') and
stats.st_size > 268435456 and not HAS_REQUESTS_TOOLBELT):
self.module.fail_json(msg="'requests_toolbelt' module is required to upload files larger than 256MB",
exception=missing_required_lib('requests_toolbelt'))
try:
taskid = self.proxmox_api.nodes(node).storage(storage).upload.post(content=content_type, filename=open(realpath, 'rb'))
return self.task_status(node, taskid, timeout)
except Exception as e:
self.module.fail_json(msg="Uploading template %s failed with error: %s" % (realpath, e))
def fetch_template(self, node, storage, content_type, url, timeout):
"""Fetch a template from a web url source using the proxmox download-url endpoint
"""
try:
taskid = self.proxmox_api.nodes(node).storage(storage)("download-url").post(
url=url, content=content_type, filename=os.path.basename(url)
)
return self.task_status(node, taskid, timeout)
except Exception as e:
self.module.fail_json(msg="Fetching template from url %s failed with error: %s" % (url, e))
def download_template(self, node, storage, template, timeout):
try:
taskid = self.proxmox_api.nodes(node).aplinfo.post(storage=storage, template=template)
return self.task_status(node, taskid, timeout)
except Exception as e:
self.module.fail_json(msg="Downloading template %s failed with error: %s" % (template, e))
def delete_template(self, node, storage, content_type, template, timeout):
volid = '%s:%s/%s' % (storage, content_type, template)
self.proxmox_api.nodes(node).storage(storage).content.delete(volid)
while timeout:
if not self.has_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)
return False
def fetch_and_verify(self, node, storage, url, content_type, timeout, checksum, checksum_algorithm):
""" Fetch a template from a web url, then verify it using a checksum.
"""
data = {
'url': url,
'content': content_type,
'filename': os.path.basename(url),
'checksum': checksum,
'checksum-algorithm': checksum_algorithm}
try:
taskid = self.proxmox_api.nodes(node).storage(storage).post("download-url?{}".format(urlencode(data)))
return self.task_status(node, taskid, timeout)
except Exception as e:
self.module.fail_json(msg="Checksum mismatch: %s" % (e))
def main():
module_args = proxmox_auth_argument_spec()
template_args = dict(
node=dict(),
src=dict(type='path'),
url=dict(),
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']),
checksum_algorithm=dict(choices=['md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512']),
checksum=dict(type='str'),
)
module_args.update(template_args)
module = AnsibleModule(
argument_spec=module_args,
required_together=[('api_token_id', 'api_token_secret'), ('checksum', 'checksum_algorithm')],
required_one_of=[('api_password', 'api_token_id')],
required_if=[('state', 'absent', ['template'])],
mutually_exclusive=[("src", "url")],
)
proxmox = ProxmoxTemplateAnsible(module)
state = module.params['state']
node = module.params['node']
storage = module.params['storage']
timeout = module.params['timeout']
checksum = module.params['checksum']
checksum_algorithm = module.params['checksum_algorithm']
if state == 'present':
content_type = module.params['content_type']
src = module.params['src']
url = module.params['url']
# download appliance template
if content_type == 'vztmpl' and not (src or url):
template = module.params['template']
if not template:
module.fail_json(msg='template param for downloading appliance template is mandatory')
if proxmox.has_template(node, storage, content_type, template) and not module.params['force']:
module.exit_json(changed=False, msg='template with volid=%s:%s/%s already exists' % (storage, content_type, template))
if proxmox.download_template(node, storage, template, timeout):
module.exit_json(changed=True, msg='template with volid=%s:%s/%s downloaded' % (storage, content_type, template))
if not src and not url:
module.fail_json(msg='src or url param for uploading template file is mandatory')
elif not url:
template = os.path.basename(src)
if proxmox.has_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))
elif not (os.path.exists(src) and os.path.isfile(src)):
module.fail_json(msg='template file on path %s not exists' % src)
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))
elif not src:
template = os.path.basename(urlparse(url).path)
if proxmox.has_template(node, storage, content_type, template):
if not module.params['force']:
module.exit_json(changed=False, msg='template with volid=%s:%s/%s already exists' % (storage, content_type, template))
elif not proxmox.delete_template(node, storage, content_type, template, timeout):
module.fail_json(changed=False, msg='failed to delete template with volid=%s:%s/%s' % (storage, content_type, template))
if checksum:
if proxmox.fetch_and_verify(node, storage, url, content_type, timeout, checksum, checksum_algorithm):
module.exit_json(changed=True, msg="Checksum verified, template with volid=%s:%s/%s uploaded" % (storage, content_type, template))
if proxmox.fetch_template(node, storage, content_type, url, timeout):
module.exit_json(changed=True, msg='template with volid=%s:%s/%s uploaded' % (storage, content_type, template))
elif state == 'absent':
try:
content_type = module.params['content_type']
template = module.params['template']
if not proxmox.has_template(node, storage, content_type, template):
module.exit_json(changed=False, msg='template with volid=%s:%s/%s is already deleted' % (storage, content_type, template))
if proxmox.delete_template(node, storage, content_type, template, timeout):
module.exit_json(changed=True, msg='template with volid=%s:%s/%s deleted' % (storage, content_type, template))
except Exception as e:
module.fail_json(msg="deleting of template %s failed with exception: %s" % (template, e))
if __name__ == '__main__':
main()

View file

@ -1,260 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright Tristan Le Guern <tleguern at bouledef.eu>
# 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
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r"""
module: proxmox_user_info
short_description: Retrieve information about one or more Proxmox VE users
version_added: 1.3.0
description:
- Retrieve information about one or more Proxmox VE users.
attributes:
action_group:
version_added: 9.0.0
options:
domain:
description:
- Restrict results to a specific authentication realm.
aliases: ['realm']
type: str
user:
description:
- Restrict results to a specific user.
aliases: ['name']
type: str
userid:
description:
- Restrict results to a specific user ID, which is a concatenation of a user and domain parts.
type: str
author: Tristan Le Guern (@tleguern)
extends_documentation_fragment:
- community.general.proxmox.actiongroup_proxmox
- community.general.proxmox.documentation
- community.general.attributes
- community.general.attributes.info_module
"""
EXAMPLES = r"""
- name: List existing users
community.general.proxmox_user_info:
api_host: helldorado
api_user: root@pam
api_password: "{{ password | default(omit) }}"
api_token_id: "{{ token_id | default(omit) }}"
api_token_secret: "{{ token_secret | default(omit) }}"
register: proxmox_users
- name: List existing users in the pve authentication realm
community.general.proxmox_user_info:
api_host: helldorado
api_user: root@pam
api_password: "{{ password | default(omit) }}"
api_token_id: "{{ token_id | default(omit) }}"
api_token_secret: "{{ token_secret | default(omit) }}"
domain: pve
register: proxmox_users_pve
- name: Retrieve information about admin@pve
community.general.proxmox_user_info:
api_host: helldorado
api_user: root@pam
api_password: "{{ password | default(omit) }}"
api_token_id: "{{ token_id | default(omit) }}"
api_token_secret: "{{ token_secret | default(omit) }}"
userid: admin@pve
register: proxmox_user_admin
- name: Alternative way to retrieve information about admin@pve
community.general.proxmox_user_info:
api_host: helldorado
api_user: root@pam
api_password: "{{ password | default(omit) }}"
api_token_id: "{{ token_id | default(omit) }}"
api_token_secret: "{{ token_secret | default(omit) }}"
user: admin
domain: pve
register: proxmox_user_admin
"""
RETURN = r"""
proxmox_users:
description: List of users.
returned: always, but can be empty
type: list
elements: dict
contains:
comment:
description: Short description of the user.
returned: on success
type: str
domain:
description: User's authentication realm, also the right part of the user ID.
returned: on success
type: str
email:
description: User's email address.
returned: on success
type: str
enabled:
description: User's account state.
returned: on success
type: bool
expire:
description: Expiration date in seconds since EPOCH. Zero means no expiration.
returned: on success
type: int
firstname:
description: User's first name.
returned: on success
type: str
groups:
description: List of groups which the user is a member of.
returned: on success
type: list
elements: str
keys:
description: User's two factor authentication keys.
returned: on success
type: str
lastname:
description: User's last name.
returned: on success
type: str
tokens:
description: List of API tokens associated to the user.
returned: on success
type: list
elements: dict
contains:
comment:
description: Short description of the token.
returned: on success
type: str
expire:
description: Expiration date in seconds since EPOCH. Zero means no expiration.
returned: on success
type: int
privsep:
description: Describe if the API token is further restricted with ACLs or is fully privileged.
returned: on success
type: bool
tokenid:
description: Token name.
returned: on success
type: str
user:
description: User's login name, also the left part of the user ID.
returned: on success
type: str
userid:
description: Proxmox user ID, represented as user@realm.
returned: on success
type: str
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.proxmox import (
proxmox_auth_argument_spec, ProxmoxAnsible, proxmox_to_ansible_bool)
class ProxmoxUserInfoAnsible(ProxmoxAnsible):
def get_user(self, userid):
try:
user = self.proxmox_api.access.users.get(userid)
except Exception:
self.module.fail_json(msg="User '%s' does not exist" % userid)
user['userid'] = userid
return ProxmoxUser(user)
def get_users(self, domain=None):
users = self.proxmox_api.access.users.get(full=1)
users = [ProxmoxUser(user) for user in users]
if domain:
return [user for user in users if user.user['domain'] == domain]
return users
class ProxmoxUser:
def __init__(self, user):
self.user = dict()
# Data representation is not the same depending on API calls
for k, v in user.items():
if k == 'enable':
self.user['enabled'] = proxmox_to_ansible_bool(user['enable'])
elif k == 'userid':
self.user['user'] = user['userid'].split('@')[0]
self.user['domain'] = user['userid'].split('@')[1]
self.user[k] = v
elif k in ['groups', 'tokens'] and (v == '' or v is None):
self.user[k] = []
elif k == 'groups' and isinstance(v, str):
self.user['groups'] = v.split(',')
elif k == 'tokens' and isinstance(v, list):
for token in v:
if 'privsep' in token:
token['privsep'] = proxmox_to_ansible_bool(token['privsep'])
self.user['tokens'] = v
elif k == 'tokens' and isinstance(v, dict):
self.user['tokens'] = list()
for tokenid, tokenvalues in v.items():
t = tokenvalues
t['tokenid'] = tokenid
if 'privsep' in tokenvalues:
t['privsep'] = proxmox_to_ansible_bool(tokenvalues['privsep'])
self.user['tokens'].append(t)
else:
self.user[k] = v
def proxmox_user_info_argument_spec():
return dict(
domain=dict(type='str', aliases=['realm']),
user=dict(type='str', aliases=['name']),
userid=dict(type='str'),
)
def main():
module_args = proxmox_auth_argument_spec()
user_info_args = proxmox_user_info_argument_spec()
module_args.update(user_info_args)
module = AnsibleModule(
argument_spec=module_args,
required_one_of=[('api_password', 'api_token_id')],
required_together=[('api_token_id', 'api_token_secret')],
mutually_exclusive=[('user', 'userid'), ('domain', 'userid')],
supports_check_mode=True
)
result = dict(
changed=False
)
proxmox = ProxmoxUserInfoAnsible(module)
domain = module.params['domain']
user = module.params['user']
if user and domain:
userid = user + '@' + domain
else:
userid = module.params['userid']
if userid:
users = [proxmox.get_user(userid=userid)]
else:
users = proxmox.get_users(domain=domain)
result['proxmox_users'] = [user.user for user in users]
module.exit_json(**result)
if __name__ == '__main__':
main()

View file

@ -1,285 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2023, Sergei Antipov <greendayonfire at gmail.com>
# 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
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r"""
module: proxmox_vm_info
short_description: Retrieve information about one or more Proxmox VE virtual machines
version_added: 7.2.0
description:
- Retrieve information about one or more Proxmox VE virtual machines.
author: 'Sergei Antipov (@UnderGreen) <greendayonfire at gmail dot com>'
attributes:
action_group:
version_added: 9.0.0
options:
node:
description:
- Restrict results to a specific Proxmox VE node.
type: str
type:
description:
- Restrict results to a specific virtual machine(s) type.
type: str
choices:
- all
- qemu
- lxc
default: all
vmid:
description:
- Restrict results to a specific virtual machine by using its ID.
- If VM with the specified vmid does not exist in a cluster then resulting list will be empty.
type: int
name:
description:
- Restrict results to a specific virtual machine(s) by using their name.
- If VM(s) with the specified name do not exist in a cluster then the resulting list will be empty.
type: str
config:
description:
- Whether to retrieve the VM configuration along with VM status.
- If set to V(none) (default), no configuration will be returned.
- If set to V(current), the current running configuration will be returned.
- If set to V(pending), the configuration with pending changes applied will be returned.
type: str
choices:
- none
- current
- pending
default: none
version_added: 8.1.0
network:
description:
- Whether to retrieve the current network status.
- Requires enabled/running qemu-guest-agent on qemu VMs.
type: bool
default: false
version_added: 9.1.0
extends_documentation_fragment:
- community.general.proxmox.actiongroup_proxmox
- community.general.proxmox.documentation
- community.general.attributes
- community.general.attributes.info_module
"""
EXAMPLES = r"""
- name: List all existing virtual machines on node
community.general.proxmox_vm_info:
api_host: proxmoxhost
api_user: root@pam
api_token_id: '{{ token_id | default(omit) }}'
api_token_secret: '{{ token_secret | default(omit) }}'
node: node01
- name: List all QEMU virtual machines on node
community.general.proxmox_vm_info:
api_host: proxmoxhost
api_user: root@pam
api_password: '{{ password | default(omit) }}'
node: node01
type: qemu
- name: Retrieve information about specific VM by ID
community.general.proxmox_vm_info:
api_host: proxmoxhost
api_user: root@pam
api_password: '{{ password | default(omit) }}'
node: node01
type: qemu
vmid: 101
- name: Retrieve information about specific VM by name and get current configuration
community.general.proxmox_vm_info:
api_host: proxmoxhost
api_user: root@pam
api_password: '{{ password | default(omit) }}'
node: node01
type: lxc
name: lxc05.home.arpa
config: current
"""
RETURN = r"""
proxmox_vms:
description: List of virtual machines.
returned: on success
type: list
elements: dict
sample:
[
{
"cpu": 0.258944410905281,
"cpus": 1,
"disk": 0,
"diskread": 0,
"diskwrite": 0,
"id": "qemu/100",
"maxcpu": 1,
"maxdisk": 34359738368,
"maxmem": 4294967296,
"mem": 35158379,
"name": "pxe.home.arpa",
"netin": 99715803,
"netout": 14237835,
"node": "pve",
"pid": 1947197,
"status": "running",
"template": False,
"type": "qemu",
"uptime": 135530,
"vmid": 100
},
{
"cpu": 0,
"cpus": 1,
"disk": 0,
"diskread": 0,
"diskwrite": 0,
"id": "qemu/101",
"maxcpu": 1,
"maxdisk": 0,
"maxmem": 536870912,
"mem": 0,
"name": "test1",
"netin": 0,
"netout": 0,
"node": "pve",
"status": "stopped",
"template": False,
"type": "qemu",
"uptime": 0,
"vmid": 101
}
]
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.proxmox import (
proxmox_auth_argument_spec,
ProxmoxAnsible,
proxmox_to_ansible_bool,
)
class ProxmoxVmInfoAnsible(ProxmoxAnsible):
def get_vms_from_cluster_resources(self):
try:
return self.proxmox_api.cluster().resources().get(type="vm")
except Exception as e:
self.module.fail_json(
msg="Failed to retrieve VMs information from cluster resources: %s" % e
)
def get_vms_from_nodes(self, cluster_machines, type, vmid=None, name=None, node=None, config=None, network=False):
# Leave in dict only machines that user wants to know about
filtered_vms = {
vm: info for vm, info in cluster_machines.items() if not (
type != info["type"]
or (node and info["node"] != node)
or (vmid and int(info["vmid"]) != vmid)
or (name is not None and info["name"] != name)
)
}
# Get list of unique node names and loop through it to get info about machines.
nodes = frozenset([info["node"] for vm, info in filtered_vms.items()])
for this_node in nodes:
# "type" is mandatory and can have only values of "qemu" or "lxc". Seems that use of reflection is safe.
call_vm_getter = getattr(self.proxmox_api.nodes(this_node), type)
vms_from_this_node = call_vm_getter().get()
for detected_vm in vms_from_this_node:
this_vm_id = int(detected_vm["vmid"])
desired_vm = filtered_vms.get(this_vm_id, None)
if desired_vm:
desired_vm.update(detected_vm)
desired_vm["vmid"] = this_vm_id
desired_vm["template"] = proxmox_to_ansible_bool(desired_vm.get("template", 0))
# When user wants to retrieve the VM configuration
if config != "none":
# pending = 0, current = 1
config_type = 0 if config == "pending" else 1
# GET /nodes/{node}/qemu/{vmid}/config current=[0/1]
desired_vm["config"] = call_vm_getter(this_vm_id).config().get(current=config_type)
if network:
if type == "qemu":
desired_vm["network"] = call_vm_getter(this_vm_id).agent("network-get-interfaces").get()['result']
elif type == "lxc":
desired_vm["network"] = call_vm_getter(this_vm_id).interfaces.get()
return filtered_vms
def get_qemu_vms(self, cluster_machines, vmid=None, name=None, node=None, config=None, network=False):
try:
return self.get_vms_from_nodes(cluster_machines, "qemu", vmid, name, node, config, network)
except Exception as e:
self.module.fail_json(msg="Failed to retrieve QEMU VMs information: %s" % e)
def get_lxc_vms(self, cluster_machines, vmid=None, name=None, node=None, config=None, network=False):
try:
return self.get_vms_from_nodes(cluster_machines, "lxc", vmid, name, node, config, network)
except Exception as e:
self.module.fail_json(msg="Failed to retrieve LXC VMs information: %s" % e)
def main():
module_args = proxmox_auth_argument_spec()
vm_info_args = dict(
node=dict(type="str", required=False),
type=dict(
type="str", choices=["lxc", "qemu", "all"], default="all", required=False
),
vmid=dict(type="int", required=False),
name=dict(type="str", required=False),
config=dict(
type="str", choices=["none", "current", "pending"],
default="none", required=False
),
network=dict(type="bool", default=False, required=False),
)
module_args.update(vm_info_args)
module = AnsibleModule(
argument_spec=module_args,
required_together=[("api_token_id", "api_token_secret")],
required_one_of=[("api_password", "api_token_id")],
supports_check_mode=True,
)
proxmox = ProxmoxVmInfoAnsible(module)
node = module.params["node"]
type = module.params["type"]
vmid = module.params["vmid"]
name = module.params["name"]
config = module.params["config"]
network = module.params["network"]
result = dict(changed=False)
if node and proxmox.get_node(node) is None:
module.fail_json(msg="Node %s doesn't exist in PVE cluster" % node)
vms_cluster_resources = proxmox.get_vms_from_cluster_resources()
cluster_machines = {int(machine["vmid"]): machine for machine in vms_cluster_resources}
vms = {}
if type == "lxc":
vms = proxmox.get_lxc_vms(cluster_machines, vmid, name, node, config, network)
elif type == "qemu":
vms = proxmox.get_qemu_vms(cluster_machines, vmid, name, node, config, network)
else:
vms = proxmox.get_qemu_vms(cluster_machines, vmid, name, node, config, network)
vms.update(proxmox.get_lxc_vms(cluster_machines, vmid, name, node, config, network))
result["proxmox_vms"] = [info for vm, info in sorted(vms.items())]
module.exit_json(**result)
if __name__ == "__main__":
main()