xoxys.general/plugins/inventory/proxmox.py

274 lines
9.3 KiB
Python

"""Dynamic inventory plugin for Proxmox VE."""
# -*- coding: utf-8 -*-
# Copyright (c) 2014, Mathieu GAUTHIER-LAFAYE <gauthierl@lapth.cnrs.fr>
# Copyright (c) 2016, Matt Harris <matthaeus.harris@gmail.com>
# Copyright (c) 2020, Robert Kaussow <mail@thegeeklab.de>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = """
name: proxmox
plugin_type: inventory
short_description: Proxmox VE inventory source
version_added: 1.0.0
description:
- Get inventory hosts from the proxmox service.
- "Uses a configuration file as an inventory source, it must end in C(.proxmox.yml) or C(.proxmox.yaml) and has a C(plugin: proxmox) entry."
extends_documentation_fragment:
- inventory_cache
options:
plugin:
description: The name of this plugin, it should always be set to C(proxmox) for this plugin to recognize it as it"s own.
required: yes
choices: ["xoxys.general.proxmox"]
server:
description: Proxmox VE server url.
default: "pve.example.com"
required: yes
env:
- name: PROXMOX_SERVER
user:
description: Proxmox VE authentication user.
required: yes
env:
- name: PROXMOX_USER
password:
description: Proxmox VE authentication password
required: yes
env:
- name: PROXMOX_PASSWORD
exclude_vmid:
description: VMID"s to exclude from inventory
type: list
default: []
elements: str
exclude_state:
description: VM states to exclude from inventory
type: list
default: []
elements: str
group:
description: Group to place all hosts into
default: proxmox
want_facts:
description: Toggle, if C(true) the plugin will retrieve host facts from the server
type: boolean
default: yes
""" # noqa
EXAMPLES = """
# proxmox.yml
plugin: community.general.proxmox
server: pve.example.com
user: admin@pve
password: secure
"""
import json
import re
import socket
from ansible.errors import AnsibleError
from ansible.module_utils._text import to_native
from ansible.module_utils.six import iteritems
from ansible.plugins.inventory import BaseInventoryPlugin
from collections import defaultdict
from distutils.version import LooseVersion
try:
from proxmoxer import ProxmoxAPI
HAS_PROXMOXER = True
except ImportError:
HAS_PROXMOXER = False
class InventoryModule(BaseInventoryPlugin):
NAME = "community.general.proxmox"
def _auth(self):
return ProxmoxAPI(
self.get_option("server"),
user=self.get_option("user"),
password=self.get_option("password"),
verify_ssl=False
)
def _get_version(self):
return LooseVersion(self.client.version.get()["version"])
def _get_major(self):
return LooseVersion(self.client.version.get()["release"])
def _get_names(self, pve_list, pve_type):
names = []
if pve_type == "node":
names = [node["node"] for node in pve_list]
elif pve_type == "pool":
names = [pool["poolid"] for pool in pve_list]
return names
def _get_variables(self, pve_list, pve_type):
variables = {}
if pve_type in ["qemu", "container"]:
for vm in pve_list:
nested = {}
for key, value in iteritems(vm):
nested["proxmox_" + key] = value
variables[vm["name"]] = nested
return variables
def _get_ip_address(self, pve_type, pve_node, vmid):
def validate(address):
try:
# IP address validation
if socket.inet_aton(address):
# Ignore localhost
if address != "127.0.0.1":
return address
except socket.error:
return False
address = False
networks = False
if pve_type == "qemu":
# If qemu agent is enabled, try to gather the IP address
try:
if self.client.nodes(pve_node).get(pve_type, vmid, "agent", "info") is not None:
networks = self.client.nodes(pve_node).get(
"qemu", vmid, "agent", "network-get-interfaces"
)["result"]
except Exception:
pass
if networks:
if type(networks) is list:
for network in networks:
for ip_address in network["ip-addresses"]:
address = validate(ip_address["ip-address"])
else:
try:
config = self.client.nodes(pve_node).get(pve_type, vmid, "config")
address = re.search(r"ip=(\d*\.\d*\.\d*\.\d*)", config["net0"]).group(1)
except Exception:
pass
return address
def _exclude(self, pve_list):
filtered = []
for item in pve_list:
obj = defaultdict(dict, item)
if obj["template"] == 1:
continue
if obj["status"] in self.get_option("exclude_state"):
continue
if obj["vmid"] in self.get_option("exclude_vmid"):
continue
filtered.append(item.copy())
return filtered
def _propagate(self):
for node in self._get_names(self.client.nodes.get(), "node"):
try:
qemu_list = self._exclude(self.client.nodes(node).qemu.get())
container_list = self._exclude(self.client.nodes(node).lxc.get())
except Exception as e:
raise AnsibleError("Proxmoxer API error: {0}".format(to_native(e)))
# Merge QEMU and Containers lists from this node
instances = self._get_variables(qemu_list.copy(), "qemu")
instances.update(self._get_variables(container_list, "container"))
for host in instances:
vmid = instances[host]["proxmox_vmid"]
try:
pve_type = instances[host]["proxmox_type"]
except KeyError:
pve_type = "qemu"
try:
description = self.client.nodes(node).get(pve_type, vmid,
"config")["description"]
except KeyError:
description = None
except Exception as e:
raise AnsibleError("Proxmoxer API error: {0}".format(to_native(e)))
try:
metadata = json.loads(description)
except TypeError:
metadata = {}
except ValueError:
metadata = {"notes": description}
# Add hosts to default group
self.inventory.add_group(group=self.get_option("group"))
self.inventory.add_host(group=self.get_option("group"), host=host)
# Group hosts by status
self.inventory.add_group(group=instances[host]["proxmox_status"])
self.inventory.add_host(group=instances[host]["proxmox_status"], host=host)
if "groups" in metadata:
for group in metadata["groups"]:
self.inventory.add_group(group=group)
self.inventory.add_host(group=group, host=host)
if self.get_option("want_facts"):
for attr in instances[host]:
if attr not in ["proxmox_template"]:
self.inventory.set_variable(host, attr, instances[host][attr])
address = self._get_ip_address(pve_type, node, vmid)
if address:
self.inventory.set_variable(host, "ansible_host", address)
for pool in self._get_names(self.client.pools.get(), "pool"):
try:
pool_list = self._exclude(self.client.pool(pool).get()["members"])
except Exception as e:
raise AnsibleError("Proxmoxer API error: {0}".format(to_native(e)))
members = [
member["name"]
for member in pool_list
if (member["type"] == "qemu" or member["type"] == "lxc")
]
for member in members:
self.inventory.add_host(group=pool, host=member)
def verify_file(self, path):
"""Verify the Proxmox VE configuration file."""
if super(InventoryModule, self).verify_file(path):
endings = ("proxmox.yaml", "proxmox.yml")
if any((path.endswith(ending) for ending in endings)):
return True
return False
def parse(self, inventory, loader, path, cache=True):
"""Dynamically parse the Proxmox VE cloud inventory."""
if not HAS_PROXMOXER:
raise AnsibleError(
"The Proxmox VE dynamic inventory plugin requires proxmoxer: "
"https://pypi.org/project/proxmoxer/"
)
super(InventoryModule, self).parse(inventory, loader, path)
self._read_config_data(path)
self.client = self._auth()
self._propagate()