2020-08-18 13:10:09 +00:00
# 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)
2020-08-18 21:44:49 +00:00
""" Dynamic inventory plugin for Proxmox VE. """
2020-08-18 13:10:09 +00:00
2023-01-30 13:20:45 +00:00
from __future__ import ( absolute_import , division , print_function )
2020-08-18 13:10:09 +00:00
__metaclass__ = type
DOCUMENTATION = """
2023-01-30 20:03:11 +00:00
- - -
name : proxmox
plugin_type : inventory
short_description : Proxmox VE inventory source
version_added : 1.1 .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: xoxys.general.proxmox) entry. "
extends_documentation_fragment :
- inventory_cache
options :
plugin :
description : The name of this plugin , it should always be set to C ( xoxys . general . proxmox ) for this plugin to recognize it as it ' s own.
required : yes
choices : [ " xoxys.general.proxmox " ]
api_host :
2020-08-18 13:10:09 +00:00
description :
2023-01-30 20:03:11 +00:00
- Specify the target host of the Proxmox VE cluster .
type : str
required : true
api_user :
description :
- Specify the user to authenticate with .
type : str
required : true
api_password :
description :
- Specify the password to authenticate with .
- You can use C ( PROXMOX_PASSWORD ) environment variable .
type : str
api_token_id :
description :
- Specify the token ID .
type : str
api_token_secret :
description :
- Specify the token secret .
type : str
verify_ssl :
description :
- If C ( false ) , SSL certificates will not be validated .
- This should only be used on personally controlled sites using self - signed certificates .
type : bool
default : True
auth_timeout :
description : Proxmox VE authentication timeout .
type : int
default : 5
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 .
type : string
default : proxmox
want_facts :
description : Toggle , if C ( true ) the plugin will retrieve host facts from the server
type : boolean
default : True
2023-01-30 13:20:45 +00:00
""" # noqa
2020-08-18 13:10:09 +00:00
EXAMPLES = """
# proxmox.yml
2020-08-18 15:52:43 +00:00
plugin : xoxys . general . proxmox
2023-01-30 20:03:11 +00:00
api_user : root @pam
api_password : secret
api_host : helldorado
2020-08-18 13:10:09 +00:00
"""
import json
import re
import socket
2023-01-30 13:20:45 +00:00
from collections import defaultdict
from distutils . version import LooseVersion
2020-08-18 13:10:09 +00:00
from ansible . errors import AnsibleError
from ansible . module_utils . _text import to_native
2020-09-04 20:57:25 +00:00
from ansible . module_utils . parsing . convert_bool import boolean
2020-08-18 13:10:09 +00:00
from ansible . module_utils . six import iteritems
from ansible . plugins . inventory import BaseInventoryPlugin
try :
from proxmoxer import ProxmoxAPI
HAS_PROXMOXER = True
except ImportError :
HAS_PROXMOXER = False
2023-01-30 13:20:45 +00:00
try :
from requests . packages import urllib3
HAS_URLLIB3 = True
except ImportError :
try :
import urllib3
HAS_URLLIB3 = True
except ImportError :
HAS_URLLIB3 = False
2020-08-18 13:10:09 +00:00
class InventoryModule ( BaseInventoryPlugin ) :
2023-01-30 13:20:45 +00:00
""" Provide Proxmox VE inventory. """
2020-08-18 15:52:43 +00:00
NAME = " xoxys.general.proxmox "
2020-08-18 13:10:09 +00:00
def _auth ( self ) :
2023-01-30 20:03:11 +00:00
auth_args = { " user " : self . get_option ( " api_user " ) }
if not ( self . get_option ( " api_token_id " ) and self . get_option ( " api_token_secret " ) ) :
auth_args [ " password " ] = self . get_option ( " api_password " )
else :
auth_args [ " token_name " ] = self . get_option ( " api_token_id " )
auth_args [ " token_value " ] = self . get_option ( " api_token_secret " )
2023-01-30 13:20:45 +00:00
2023-01-30 20:03:11 +00:00
verify_ssl = boolean ( self . get_option ( " verify_ssl " ) , strict = False )
2023-01-30 13:20:45 +00:00
if not verify_ssl and HAS_URLLIB3 :
urllib3 . disable_warnings ( urllib3 . exceptions . InsecureRequestWarning )
2020-08-18 13:10:09 +00:00
return ProxmoxAPI (
2023-01-30 20:03:11 +00:00
self . get_option ( " api_host " ) ,
2023-01-30 13:20:45 +00:00
verify_ssl = verify_ssl ,
2023-01-30 20:03:11 +00:00
timeout = self . get_option ( " auth_timeout " ) ,
* * auth_args
2020-08-18 13:10:09 +00:00
)
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 ) :
if pve_type == " node " :
2023-01-30 13:20:45 +00:00
return [ node [ " node " ] for node in pve_list ]
if pve_type == " pool " :
return [ pool [ " poolid " ] for pool in pve_list ]
2020-08-18 13:10:09 +00:00
2023-01-30 13:20:45 +00:00
return [ ]
2020-08-18 13:10:09 +00:00
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
2023-01-30 13:20:45 +00:00
if socket . inet_aton ( address ) and address != " 127.0.0.1 " :
return address
except OSError :
2020-08-18 13:10:09 +00:00
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 " ]
2023-01-30 13:20:45 +00:00
except Exception : # noqa
2020-08-18 13:10:09 +00:00
pass
2023-01-30 13:20:45 +00:00
if networks and type ( networks ) is list :
for network in networks :
for ip_address in network [ " ip-addresses " ] :
address = validate ( ip_address [ " ip-address " ] )
2020-08-18 13:10:09 +00:00
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 )
2023-01-30 13:20:45 +00:00
except Exception : # noqa
2020-08-18 13:10:09 +00:00
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 ( ) )
2023-01-30 13:20:45 +00:00
except Exception as e : # noqa
raise AnsibleError ( f " Proxmoxer API error: { to_native ( e ) } " ) from e
2020-08-18 13:10:09 +00:00
# Merge QEMU and Containers lists from this node
2020-08-18 15:52:43 +00:00
instances = self . _get_variables ( qemu_list , " qemu " ) . copy ( )
2020-08-18 13:10:09 +00:00
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
2023-01-30 13:20:45 +00:00
except Exception as e : # noqa
raise AnsibleError ( f " Proxmoxer API error: { to_native ( e ) } " ) from e
2020-08-18 13:10:09 +00:00
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 " ] )
2023-01-30 13:20:45 +00:00
except Exception as e : # noqa
raise AnsibleError ( f " Proxmoxer API error: { to_native ( e ) } " ) from e
2020-08-18 13:10:09 +00:00
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. """
2023-01-30 13:20:45 +00:00
if super ( ) . verify_file ( path ) :
2020-08-18 13:10:09 +00:00
endings = ( " proxmox.yaml " , " proxmox.yml " )
2023-01-30 13:20:45 +00:00
if any ( path . endswith ( ending ) for ending in endings ) :
2020-08-18 13:10:09 +00:00
return True
return False
2023-01-30 13:20:45 +00:00
def parse ( self , inventory , loader , path , cache = True ) : # noqa
2020-08-18 13:10:09 +00:00
""" 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/ "
)
2023-01-30 13:20:45 +00:00
super ( ) . parse ( inventory , loader , path )
2020-08-18 13:10:09 +00:00
self . _read_config_data ( path )
self . client = self . _auth ( )
self . _propagate ( )