mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-09-30 21:43:22 -07:00
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
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:
parent
f63fdceb23
commit
f2b7bdf293
76 changed files with 124 additions and 14629 deletions
File diff suppressed because it is too large
Load diff
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
Loading…
Add table
Add a link
Reference in a new issue