mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
lxd: dynamic inventory (#2026)
* lxd dynamic inventory and test data * added ``merge_profile`` parameter to merge configurations from the play to an existing profile * cosmetic changes * added ``merge_profile`` parameter to merge configurations from the play to an existing profile * cosmetic changes * fix pylint errors * fix flake8 warnings * fix pep8 errors without "line to long" * fix ansible tests * fix typo * fix version added * fix lost of suggestions from felixfontein * fix filter fix ansible test errors * delete test config * delete 'notes:' and copy content to description * move testdata load testdata by path from config * updated documentation * fix test data and remove inventory branch * fix spellings and rename lxd to community.general.lxd * fix documentation * remove selftest * strip example data * add unit test * switch to ansible.module_utils.common.dict_transformations * documentation cleanup * move lxd_inventory.atd from files to fixtures * update documentation move lxd_inventory.atd * rename self.groups to self dispose remove dumpdata * cleanup * fix unittests comment out dump_data, it breaks the unit tests * fix pep8 * Apply suggestions from code review * Update plugins/inventory/lxd.py * add test if no groupby is selected * rename disposed to groupby remove unused constant other suggested cleanups * Use bundled ipaddress instead of own code. * Update plugins/inventory/lxd.py * Exceptions should not be eaten. * Improve error handling for network range/address parsing. * Fix typo. * Make network range valid. * Do not error when groupby is not a dict. Co-authored-by: Frank Dornheim <“dornheim@posteo.de@users.noreply.github.com”> Co-authored-by: Felix Fontein <felix@fontein.de>
This commit is contained in:
parent
99c564398a
commit
2cc3ce0230
3 changed files with 1224 additions and 0 deletions
950
plugins/inventory/lxd.py
Normal file
950
plugins/inventory/lxd.py
Normal file
|
@ -0,0 +1,950 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright: (c) 2021, Frank Dornheim <dornheim@posteo.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 = r'''
|
||||
name: community.general.lxd
|
||||
short_description: Returns Ansible inventory from lxd host
|
||||
description:
|
||||
- Get inventory from the lxd.
|
||||
- Uses a YAML configuration file that ends with 'lxd.(yml|yaml)'.
|
||||
version_added: "3.0.0"
|
||||
author: "Frank Dornheim (@conloos)"
|
||||
options:
|
||||
plugin:
|
||||
description: Token that ensures this is a source file for the 'lxd' plugin.
|
||||
required: true
|
||||
choices: [ 'community.general.lxd' ]
|
||||
url:
|
||||
description:
|
||||
- The unix domain socket path or the https URL for the lxd server.
|
||||
- Sockets in filesystem have to start with C(unix:).
|
||||
- Mostly C(unix:/var/lib/lxd/unix.socket) or C(unix:/var/snap/lxd/common/lxd/unix.socket).
|
||||
default: unix:/var/snap/lxd/common/lxd/unix.socket
|
||||
type: str
|
||||
client_key:
|
||||
description:
|
||||
- The client certificate key file path.
|
||||
aliases: [ key_file ]
|
||||
default: $HOME/.config/lxc/client.key
|
||||
type: path
|
||||
client_cert:
|
||||
description:
|
||||
- The client certificate file path.
|
||||
aliases: [ cert_file ]
|
||||
default: $HOME/.config/lxc/client.crt
|
||||
type: path
|
||||
trust_password:
|
||||
description:
|
||||
- The client trusted password.
|
||||
- You need to set this password on the lxd server before
|
||||
running this module using the following command
|
||||
C(lxc config set core.trust_password <some random password>)
|
||||
See U(https://www.stgraber.org/2016/04/18/lxd-api-direct-interaction/).
|
||||
- If I(trust_password) is set, this module send a request for authentication before sending any requests.
|
||||
type: str
|
||||
state:
|
||||
description: Filter the container according to the current status.
|
||||
type: str
|
||||
default: none
|
||||
choices: [ 'STOPPED', 'STARTING', 'RUNNING', 'none' ]
|
||||
prefered_container_network_interface:
|
||||
description:
|
||||
- If a container has multiple network interfaces, select which one is the prefered as pattern.
|
||||
- Combined with the first number that can be found e.g. 'eth' + 0.
|
||||
type: str
|
||||
default: eth
|
||||
prefered_container_network_family:
|
||||
description:
|
||||
- If a container has multiple network interfaces, which one is the prefered by family.
|
||||
- Specify C(inet) for IPv4 and C(inet6) for IPv6.
|
||||
type: str
|
||||
default: inet
|
||||
choices: [ 'inet', 'inet6' ]
|
||||
groupby:
|
||||
description:
|
||||
- Create groups by the following keywords C(location), C(pattern), C(network_range), C(os), C(release), C(profile), C(vlanid).
|
||||
- See example for syntax.
|
||||
type: json
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# simple lxd.yml
|
||||
plugin: community.general.lxd
|
||||
url: unix:/var/snap/lxd/common/lxd/unix.socket
|
||||
|
||||
# simple lxd.yml including filter
|
||||
plugin: community.general.lxd
|
||||
url: unix:/var/snap/lxd/common/lxd/unix.socket
|
||||
state: RUNNING
|
||||
|
||||
# grouping lxd.yml
|
||||
groupby:
|
||||
testpattern:
|
||||
type: pattern
|
||||
attribute: test
|
||||
vlan666:
|
||||
type: vlanid
|
||||
attribute: 666
|
||||
locationBerlin:
|
||||
type: location
|
||||
attribute: Berlin
|
||||
osUbuntu:
|
||||
type: os
|
||||
attribute: ubuntu
|
||||
releaseFocal:
|
||||
type: release
|
||||
attribute: focal
|
||||
releaseBionic:
|
||||
type: release
|
||||
attribute: bionic
|
||||
profileDefault:
|
||||
type: profile
|
||||
attribute: default
|
||||
profileX11:
|
||||
type: profile
|
||||
attribute: x11
|
||||
netRangeIPv4:
|
||||
type: network_range
|
||||
attribute: 10.98.143.0/24
|
||||
netRangeIPv6:
|
||||
type: network_range
|
||||
attribute: fd42:bd00:7b11:2167:216:3eff::/24
|
||||
'''
|
||||
|
||||
import binascii
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
import os
|
||||
import socket
|
||||
from ansible.plugins.inventory import BaseInventoryPlugin
|
||||
from ansible.module_utils._text import to_native, to_text
|
||||
from ansible.module_utils.common.dict_transformations import dict_merge
|
||||
from ansible.errors import AnsibleError, AnsibleParserError
|
||||
from ansible_collections.community.general.plugins.module_utils.compat import ipaddress
|
||||
from ansible_collections.community.general.plugins.module_utils.lxd import LXDClient, LXDClientException
|
||||
|
||||
|
||||
class InventoryModule(BaseInventoryPlugin):
|
||||
DEBUG = 4
|
||||
NAME = 'community.general.lxd'
|
||||
SNAP_SOCKET_URL = 'unix:/var/snap/lxd/common/lxd/unix.socket'
|
||||
SOCKET_URL = 'unix:/var/lib/lxd/unix.socket'
|
||||
|
||||
@staticmethod
|
||||
def load_json_data(path):
|
||||
"""Load json data
|
||||
|
||||
Load json data from file
|
||||
|
||||
Args:
|
||||
list(path): Path elements
|
||||
str(file_name): Filename of data
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
dict(json_data): json data"""
|
||||
try:
|
||||
with open(path, 'r') as json_file:
|
||||
return json.load(json_file)
|
||||
except (IOError, json.decoder.JSONDecodeError) as err:
|
||||
raise AnsibleParserError('Could not load the test data from {0}: {1}'.format(to_native(path), to_native(err)))
|
||||
|
||||
def save_json_data(self, path, file_name=None):
|
||||
"""save data as json
|
||||
|
||||
Save data as json file
|
||||
|
||||
Args:
|
||||
list(path): Path elements
|
||||
str(file_name): Filename of data
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
None"""
|
||||
|
||||
if file_name:
|
||||
path.append(file_name)
|
||||
else:
|
||||
prefix = 'lxd_data-'
|
||||
time_stamp = time.strftime('%Y%m%d-%H%M%S')
|
||||
suffix = '.atd'
|
||||
path.append(prefix + time_stamp + suffix)
|
||||
|
||||
try:
|
||||
cwd = os.path.abspath(os.path.dirname(__file__))
|
||||
with open(os.path.abspath(os.path.join(cwd, *path)), 'w') as json_file:
|
||||
json.dump(self.data, json_file)
|
||||
except IOError as err:
|
||||
raise AnsibleParserError('Could not save data: {0}'.format(to_native(err)))
|
||||
|
||||
def verify_file(self, path):
|
||||
"""Check the config
|
||||
|
||||
Return true/false if the config-file is valid for this plugin
|
||||
|
||||
Args:
|
||||
str(path): path to the config
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
bool(valid): is valid"""
|
||||
valid = False
|
||||
if super(InventoryModule, self).verify_file(path):
|
||||
if path.endswith(('lxd.yaml', 'lxd.yml')):
|
||||
valid = True
|
||||
else:
|
||||
self.display.vvv('Inventory source not ending in "lxd.yaml" or "lxd.yml"')
|
||||
return valid
|
||||
|
||||
@staticmethod
|
||||
def validate_url(url):
|
||||
"""validate url
|
||||
|
||||
check whether the url is correctly formatted
|
||||
|
||||
Args:
|
||||
url
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
AnsibleError
|
||||
Returns:
|
||||
bool"""
|
||||
if not isinstance(url, str):
|
||||
return False
|
||||
if not url.startswith(('unix:', 'https:')):
|
||||
raise AnsibleError('URL is malformed: {0}'.format(to_native(url)))
|
||||
return True
|
||||
|
||||
def _connect_to_socket(self):
|
||||
"""connect to lxd socket
|
||||
|
||||
Connect to lxd socket by provided url or defaults
|
||||
|
||||
Args:
|
||||
None
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
AnsibleError
|
||||
Returns:
|
||||
None"""
|
||||
error_storage = {}
|
||||
url_list = [self.get_option('url'), self.SNAP_SOCKET_URL, self.SOCKET_URL]
|
||||
urls = (url for url in url_list if self.validate_url(url))
|
||||
for url in urls:
|
||||
try:
|
||||
socket_connection = LXDClient(url, self.client_key, self.client_cert, self.debug)
|
||||
return socket_connection
|
||||
except LXDClientException as err:
|
||||
error_storage[url] = err
|
||||
raise AnsibleError('No connection to the socket: {0}'.format(to_native(error_storage)))
|
||||
|
||||
def _get_networks(self):
|
||||
"""Get Networknames
|
||||
|
||||
Returns all network config names
|
||||
|
||||
Args:
|
||||
None
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
list(names): names of all network_configs"""
|
||||
# e.g. {'type': 'sync',
|
||||
# 'status': 'Success',
|
||||
# 'status_code': 200,
|
||||
# 'operation': '',
|
||||
# 'error_code': 0,
|
||||
# 'error': '',
|
||||
# 'metadata': ['/1.0/networks/lxdbr0']}
|
||||
network_configs = self.socket.do('GET', '/1.0/networks')
|
||||
return [m.split('/')[3] for m in network_configs['metadata']]
|
||||
|
||||
def _get_containers(self):
|
||||
"""Get Containernames
|
||||
|
||||
Returns all containernames
|
||||
|
||||
Args:
|
||||
None
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
list(names): names of all containers"""
|
||||
# e.g. {'type': 'sync',
|
||||
# 'status': 'Success',
|
||||
# 'status_code': 200,
|
||||
# 'operation': '',
|
||||
# 'error_code': 0,
|
||||
# 'error': '',
|
||||
# 'metadata': ['/1.0/containers/udemy-ansible-ubuntu-2004']}
|
||||
containers = self.socket.do('GET', '/1.0/containers')
|
||||
return [m.split('/')[3] for m in containers['metadata']]
|
||||
|
||||
def _get_config(self, branch, name):
|
||||
"""Get inventory of container
|
||||
|
||||
Get config of container
|
||||
|
||||
Args:
|
||||
str(branch): Name oft the API-Branch
|
||||
str(name): Name of Container
|
||||
Kwargs:
|
||||
None
|
||||
Source:
|
||||
https://github.com/lxc/lxd/blob/master/doc/rest-api.md
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
dict(config): Config of the container"""
|
||||
config = {}
|
||||
if isinstance(branch, (tuple, list)):
|
||||
config[name] = {branch[1]: self.socket.do('GET', '/1.0/{0}/{1}/{2}'.format(to_native(branch[0]), to_native(name), to_native(branch[1])))}
|
||||
else:
|
||||
config[name] = {branch: self.socket.do('GET', '/1.0/{0}/{1}'.format(to_native(branch), to_native(name)))}
|
||||
return config
|
||||
|
||||
def get_container_data(self, names):
|
||||
"""Create Inventory of the container
|
||||
|
||||
Iterate through the different branches of the containers and collect Informations.
|
||||
|
||||
Args:
|
||||
list(names): List of container names
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
None"""
|
||||
# tuple(('instances','metadata/templates')) to get section in branch
|
||||
# e.g. /1.0/instances/<name>/metadata/templates
|
||||
branches = ['containers', ('instances', 'state')]
|
||||
container_config = {}
|
||||
for branch in branches:
|
||||
for name in names:
|
||||
container_config['containers'] = self._get_config(branch, name)
|
||||
self.data = dict_merge(container_config, self.data)
|
||||
|
||||
def get_network_data(self, names):
|
||||
"""Create Inventory of the container
|
||||
|
||||
Iterate through the different branches of the containers and collect Informations.
|
||||
|
||||
Args:
|
||||
list(names): List of container names
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
None"""
|
||||
# tuple(('instances','metadata/templates')) to get section in branch
|
||||
# e.g. /1.0/instances/<name>/metadata/templates
|
||||
branches = [('networks', 'state')]
|
||||
network_config = {}
|
||||
for branch in branches:
|
||||
for name in names:
|
||||
try:
|
||||
network_config['networks'] = self._get_config(branch, name)
|
||||
except LXDClientException:
|
||||
network_config['networks'] = {name: None}
|
||||
self.data = dict_merge(network_config, self.data)
|
||||
|
||||
def extract_network_information_from_container_config(self, container_name):
|
||||
"""Returns the network interface configuration
|
||||
|
||||
Returns the network ipv4 and ipv6 config of the container without local-link
|
||||
|
||||
Args:
|
||||
str(container_name): Name oft he container
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
dict(network_configuration): network config"""
|
||||
container_network_interfaces = self._get_data_entry('containers/{0}/state/metadata/network'.format(container_name))
|
||||
network_configuration = None
|
||||
if container_network_interfaces:
|
||||
network_configuration = {}
|
||||
gen_interface_names = [interface_name for interface_name in container_network_interfaces if interface_name != 'lo']
|
||||
for interface_name in gen_interface_names:
|
||||
gen_address = [address for address in container_network_interfaces[interface_name]['addresses'] if address.get('scope') != 'link']
|
||||
network_configuration[interface_name] = []
|
||||
for address in gen_address:
|
||||
address_set = {}
|
||||
address_set['family'] = address.get('family')
|
||||
address_set['address'] = address.get('address')
|
||||
address_set['netmask'] = address.get('netmask')
|
||||
address_set['combined'] = address.get('address') + '/' + address.get('netmask')
|
||||
network_configuration[interface_name].append(address_set)
|
||||
return network_configuration
|
||||
|
||||
def get_prefered_container_network_interface(self, container_name):
|
||||
"""Helper to get the prefered interface of thr container
|
||||
|
||||
Helper to get the prefered interface provide by neme pattern from 'prefered_container_network_interface'.
|
||||
|
||||
Args:
|
||||
str(containe_name): name of container
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
str(prefered_interface): None or interface name"""
|
||||
container_network_interfaces = self._get_data_entry('inventory/{0}/network_interfaces'.format(container_name))
|
||||
prefered_interface = None # init
|
||||
if container_network_interfaces: # container have network interfaces
|
||||
# generator if interfaces which start with the desired pattern
|
||||
net_generator = [interface for interface in container_network_interfaces if interface.startswith(self.prefered_container_network_interface)]
|
||||
selected_interfaces = [] # init
|
||||
for interface in net_generator:
|
||||
selected_interfaces.append(interface)
|
||||
if len(selected_interfaces) > 0:
|
||||
prefered_interface = sorted(selected_interfaces)[0]
|
||||
return prefered_interface
|
||||
|
||||
def get_container_vlans(self, container_name):
|
||||
"""Get VLAN(s) from container
|
||||
|
||||
Helper to get the VLAN_ID from the container
|
||||
|
||||
Args:
|
||||
str(containe_name): name of container
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
None"""
|
||||
# get network device configuration and store {network: vlan_id}
|
||||
network_vlans = {}
|
||||
for network in self._get_data_entry('networks'):
|
||||
if self._get_data_entry('state/metadata/vlan/vid', data=self.data['networks'].get(network)):
|
||||
network_vlans[network] = self._get_data_entry('state/metadata/vlan/vid', data=self.data['networks'].get(network))
|
||||
|
||||
# get networkdevices of container and return
|
||||
# e.g.
|
||||
# "eth0":{ "name":"eth0",
|
||||
# "network":"lxdbr0",
|
||||
# "type":"nic"},
|
||||
vlan_ids = {}
|
||||
devices = self._get_data_entry('containers/{0}/containers/metadata/expanded_devices'.format(to_native(container_name)))
|
||||
for device in devices:
|
||||
if 'network' in devices[device]:
|
||||
if devices[device]['network'] in network_vlans:
|
||||
vlan_ids[devices[device].get('network')] = network_vlans[devices[device].get('network')]
|
||||
return vlan_ids if vlan_ids else None
|
||||
|
||||
def _get_data_entry(self, path, data=None, delimiter='/'):
|
||||
"""Helper to get data
|
||||
|
||||
Helper to get data from self.data by a path like 'path/to/target'
|
||||
Attention: Escaping of the delimiter is not (yet) provided.
|
||||
|
||||
Args:
|
||||
str(path): path to nested dict
|
||||
Kwargs:
|
||||
dict(data): datastore
|
||||
str(delimiter): delimiter in Path.
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
*(value)"""
|
||||
try:
|
||||
if not data:
|
||||
data = self.data
|
||||
if delimiter in path:
|
||||
path = path.split(delimiter)
|
||||
|
||||
if isinstance(path, list) and len(path) > 1:
|
||||
data = data[path.pop(0)]
|
||||
path = delimiter.join(path)
|
||||
return self._get_data_entry(path, data, delimiter) # recursion
|
||||
return data[path]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
def _set_data_entry(self, container_name, key, value, path=None):
|
||||
"""Helper to save data
|
||||
|
||||
Helper to save the data in self.data
|
||||
Detect if data is allready in branch and use dict_merge() to prevent that branch is overwritten.
|
||||
|
||||
Args:
|
||||
str(container_name): name of container
|
||||
str(key): same as dict
|
||||
*(value): same as dict
|
||||
Kwargs:
|
||||
str(path): path to branch-part
|
||||
Raises:
|
||||
AnsibleParserError
|
||||
Returns:
|
||||
None"""
|
||||
if not path:
|
||||
path = self.data['inventory']
|
||||
if container_name not in path:
|
||||
path[container_name] = {}
|
||||
|
||||
try:
|
||||
if isinstance(value, dict) and key in path[container_name]:
|
||||
path[container_name] = dict_merge(value, path[container_name][key])
|
||||
else:
|
||||
path[container_name][key] = value
|
||||
except KeyError as err:
|
||||
raise AnsibleParserError("Unable to store Informations: {0}".format(to_native(err)))
|
||||
|
||||
def extract_information_from_container_configs(self):
|
||||
"""Process configuration information
|
||||
|
||||
Preparation of the data
|
||||
|
||||
Args:
|
||||
dict(configs): Container configurations
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
None"""
|
||||
# create branch "inventory"
|
||||
if 'inventory' not in self.data:
|
||||
self.data['inventory'] = {}
|
||||
|
||||
for container_name in self.data['containers']:
|
||||
self._set_data_entry(container_name, 'os', self._get_data_entry(
|
||||
'containers/{0}/containers/metadata/config/image.os'.format(container_name)))
|
||||
self._set_data_entry(container_name, 'release', self._get_data_entry(
|
||||
'containers/{0}/containers/metadata/config/image.release'.format(container_name)))
|
||||
self._set_data_entry(container_name, 'version', self._get_data_entry(
|
||||
'containers/{0}/containers/metadata/config/image.version'.format(container_name)))
|
||||
self._set_data_entry(container_name, 'profile', self._get_data_entry(
|
||||
'containers/{0}/containers/metadata/profiles'.format(container_name)))
|
||||
self._set_data_entry(container_name, 'location', self._get_data_entry(
|
||||
'containers/{0}/containers/metadata/location'.format(container_name)))
|
||||
self._set_data_entry(container_name, 'state', self._get_data_entry(
|
||||
'containers/{0}/containers/metadata/config/volatile.last_state.power'.format(container_name)))
|
||||
self._set_data_entry(container_name, 'network_interfaces', self.extract_network_information_from_container_config(container_name))
|
||||
self._set_data_entry(container_name, 'preferred_interface', self.get_prefered_container_network_interface(container_name))
|
||||
self._set_data_entry(container_name, 'vlan_ids', self.get_container_vlans(container_name))
|
||||
|
||||
def build_inventory_network(self, container_name):
|
||||
"""Add the network interfaces of the container to the inventory
|
||||
|
||||
Logic:
|
||||
- if the container have no interface -> 'ansible_connection: local'
|
||||
- get preferred_interface & prefered_container_network_family -> 'ansible_connection: ssh' & 'ansible_host: <IP>'
|
||||
- first Interface from: network_interfaces prefered_container_network_family -> 'ansible_connection: ssh' & 'ansible_host: <IP>'
|
||||
|
||||
Args:
|
||||
str(container_name): name of container
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
None"""
|
||||
|
||||
def interface_selection(container_name):
|
||||
"""Select container Interface for inventory
|
||||
|
||||
Logic:
|
||||
- get preferred_interface & prefered_container_network_family -> str(IP)
|
||||
- first Interface from: network_interfaces prefered_container_network_family -> str(IP)
|
||||
|
||||
Args:
|
||||
str(container_name): name of container
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
dict(interface_name: ip)"""
|
||||
prefered_interface = self._get_data_entry('inventory/{0}/preferred_interface'.format(container_name)) # name or None
|
||||
prefered_container_network_family = self.prefered_container_network_family
|
||||
|
||||
ip_address = ''
|
||||
if prefered_interface:
|
||||
interface = self._get_data_entry('inventory/{0}/network_interfaces/{1}'.format(container_name, prefered_interface))
|
||||
for config in interface:
|
||||
if config['family'] == prefered_container_network_family:
|
||||
ip_address = config['address']
|
||||
break
|
||||
else:
|
||||
interface = self._get_data_entry('inventory/{0}/network_interfaces'.format(container_name))
|
||||
for config in interface:
|
||||
if config['family'] == prefered_container_network_family:
|
||||
ip_address = config['address']
|
||||
break
|
||||
return ip_address
|
||||
|
||||
if self._get_data_entry('inventory/{0}/network_interfaces'.format(container_name)): # container have network interfaces
|
||||
if self._get_data_entry('inventory/{0}/preferred_interface'.format(container_name)): # container have a preferred interface
|
||||
self.inventory.set_variable(container_name, 'ansible_connection', 'ssh')
|
||||
self.inventory.set_variable(container_name, 'ansible_host', interface_selection(container_name))
|
||||
else:
|
||||
self.inventory.set_variable(container_name, 'ansible_connection', 'local')
|
||||
|
||||
def build_inventory_hosts(self):
|
||||
"""Build host-part dynamic inventory
|
||||
|
||||
Build the host-part of the dynamic inventory.
|
||||
Add Hosts and host_vars to the inventory.
|
||||
|
||||
Args:
|
||||
None
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
None"""
|
||||
for container_name in self.data['inventory']:
|
||||
# Only consider containers that match the "state" filter, if self.state is not None
|
||||
if self.filter:
|
||||
if self.filter.lower() != self._get_data_entry('inventory/{0}/state'.format(container_name)).lower():
|
||||
continue
|
||||
# add container
|
||||
self.inventory.add_host(container_name)
|
||||
# add network informations
|
||||
self.build_inventory_network(container_name)
|
||||
# add os
|
||||
self.inventory.set_variable(container_name, 'ansible_lxd_os', self._get_data_entry('inventory/{0}/os'.format(container_name)).lower())
|
||||
# add release
|
||||
self.inventory.set_variable(container_name, 'ansible_lxd_release', self._get_data_entry('inventory/{0}/release'.format(container_name)).lower())
|
||||
# add profile
|
||||
self.inventory.set_variable(container_name, 'ansible_lxd_profile', self._get_data_entry('inventory/{0}/profile'.format(container_name)))
|
||||
# add state
|
||||
self.inventory.set_variable(container_name, 'ansible_lxd_state', self._get_data_entry('inventory/{0}/state'.format(container_name)).lower())
|
||||
# add location information
|
||||
if self._get_data_entry('inventory/{0}/location'.format(container_name)) != "none": # wrong type by lxd 'none' != 'None'
|
||||
self.inventory.set_variable(container_name, 'ansible_lxd_location', self._get_data_entry('inventory/{0}/location'.format(container_name)))
|
||||
# add VLAN_ID information
|
||||
if self._get_data_entry('inventory/{0}/vlan_ids'.format(container_name)):
|
||||
self.inventory.set_variable(container_name, 'ansible_lxd_vlan_ids', self._get_data_entry('inventory/{0}/vlan_ids'.format(container_name)))
|
||||
|
||||
def build_inventory_groups_location(self, group_name):
|
||||
"""create group by attribute: location
|
||||
|
||||
Args:
|
||||
str(group_name): Group name
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
None"""
|
||||
# maybe we just want to expand one group
|
||||
if group_name not in self.inventory.groups:
|
||||
self.inventory.add_group(group_name)
|
||||
|
||||
for container_name in self.inventory.hosts:
|
||||
if 'ansible_lxd_location' in self.inventory.get_host(container_name).get_vars():
|
||||
self.inventory.add_child(group_name, container_name)
|
||||
|
||||
def build_inventory_groups_pattern(self, group_name):
|
||||
"""create group by name pattern
|
||||
|
||||
Args:
|
||||
str(group_name): Group name
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
None"""
|
||||
# maybe we just want to expand one group
|
||||
if group_name not in self.inventory.groups:
|
||||
self.inventory.add_group(group_name)
|
||||
|
||||
regex_pattern = self.groupby[group_name].get('attribute')
|
||||
|
||||
for container_name in self.inventory.hosts:
|
||||
result = re.search(regex_pattern, container_name)
|
||||
if result:
|
||||
self.inventory.add_child(group_name, container_name)
|
||||
|
||||
def build_inventory_groups_network_range(self, group_name):
|
||||
"""check if IP is in network-class
|
||||
|
||||
Args:
|
||||
str(group_name): Group name
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
None"""
|
||||
# maybe we just want to expand one group
|
||||
if group_name not in self.inventory.groups:
|
||||
self.inventory.add_group(group_name)
|
||||
|
||||
try:
|
||||
network = ipaddress.ip_network(to_text(self.groupby[group_name].get('attribute')))
|
||||
except ValueError as err:
|
||||
raise AnsibleParserError(
|
||||
'Error while parsing network range {0}: {1}'.format(self.groupby[group_name].get('attribute'), to_native(err)))
|
||||
|
||||
for container_name in self.inventory.hosts:
|
||||
if self.data['inventory'][container_name].get('network_interfaces') is not None:
|
||||
for interface in self.data['inventory'][container_name].get('network_interfaces'):
|
||||
for interface_family in self.data['inventory'][container_name].get('network_interfaces')[interface]:
|
||||
try:
|
||||
address = ipaddress.ip_address(to_text(interface_family['address']))
|
||||
if address.version == network.version and address in network:
|
||||
self.inventory.add_child(group_name, container_name)
|
||||
except ValueError:
|
||||
# Ignore invalid IP addresses returned by lxd
|
||||
pass
|
||||
|
||||
def build_inventory_groups_os(self, group_name):
|
||||
"""create group by attribute: os
|
||||
|
||||
Args:
|
||||
str(group_name): Group name
|
||||
Kwargs:
|
||||
Noneself.data['inventory'][container_name][interface]
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
None"""
|
||||
# maybe we just want to expand one group
|
||||
if group_name not in self.inventory.groups:
|
||||
self.inventory.add_group(group_name)
|
||||
|
||||
gen_containers = [
|
||||
container_name for container_name in self.inventory.hosts
|
||||
if 'ansible_lxd_os' in self.inventory.get_host(container_name).get_vars()]
|
||||
for container_name in gen_containers:
|
||||
if self.groupby[group_name].get('attribute').lower() == self.inventory.get_host(container_name).get_vars().get('ansible_lxd_os'):
|
||||
self.inventory.add_child(group_name, container_name)
|
||||
|
||||
def build_inventory_groups_release(self, group_name):
|
||||
"""create group by attribute: release
|
||||
|
||||
Args:
|
||||
str(group_name): Group name
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
None"""
|
||||
# maybe we just want to expand one group
|
||||
if group_name not in self.inventory.groups:
|
||||
self.inventory.add_group(group_name)
|
||||
|
||||
gen_containers = [
|
||||
container_name for container_name in self.inventory.hosts
|
||||
if 'ansible_lxd_release' in self.inventory.get_host(container_name).get_vars()]
|
||||
for container_name in gen_containers:
|
||||
if self.groupby[group_name].get('attribute').lower() == self.inventory.get_host(container_name).get_vars().get('ansible_lxd_release'):
|
||||
self.inventory.add_child(group_name, container_name)
|
||||
|
||||
def build_inventory_groups_profile(self, group_name):
|
||||
"""create group by attribute: profile
|
||||
|
||||
Args:
|
||||
str(group_name): Group name
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
None"""
|
||||
# maybe we just want to expand one group
|
||||
if group_name not in self.inventory.groups:
|
||||
self.inventory.add_group(group_name)
|
||||
|
||||
gen_containers = [
|
||||
container_name for container_name in self.inventory.hosts.keys()
|
||||
if 'ansible_lxd_profile' in self.inventory.get_host(container_name).get_vars().keys()]
|
||||
for container_name in gen_containers:
|
||||
if self.groupby[group_name].get('attribute').lower() in self.inventory.get_host(container_name).get_vars().get('ansible_lxd_profile'):
|
||||
self.inventory.add_child(group_name, container_name)
|
||||
|
||||
def build_inventory_groups_vlanid(self, group_name):
|
||||
"""create group by attribute: vlanid
|
||||
|
||||
Args:
|
||||
str(group_name): Group name
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
None"""
|
||||
# maybe we just want to expand one group
|
||||
if group_name not in self.inventory.groups:
|
||||
self.inventory.add_group(group_name)
|
||||
|
||||
gen_containers = [
|
||||
container_name for container_name in self.inventory.hosts.keys()
|
||||
if 'ansible_lxd_vlan_ids' in self.inventory.get_host(container_name).get_vars().keys()]
|
||||
for container_name in gen_containers:
|
||||
if self.groupby[group_name].get('attribute') in self.inventory.get_host(container_name).get_vars().get('ansible_lxd_vlan_ids').values():
|
||||
self.inventory.add_child(group_name, container_name)
|
||||
|
||||
def build_inventory_groups(self):
|
||||
"""Build group-part dynamic inventory
|
||||
|
||||
Build the group-part of the dynamic inventory.
|
||||
Add groups to the inventory.
|
||||
|
||||
Args:
|
||||
None
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
None"""
|
||||
|
||||
def group_type(group_name):
|
||||
"""create groups defined by lxd.yml or defaultvalues
|
||||
|
||||
create groups defined by lxd.yml or defaultvalues
|
||||
supportetd:
|
||||
* 'location'
|
||||
* 'pattern'
|
||||
* 'network_range'
|
||||
* 'os'
|
||||
* 'release'
|
||||
* 'profile'
|
||||
* 'vlanid'
|
||||
|
||||
Args:
|
||||
str(group_name): Group name
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
None"""
|
||||
|
||||
# Due to the compatibility with python 2 no use of map
|
||||
if self.groupby[group_name].get('type') == 'location':
|
||||
self.build_inventory_groups_location(group_name)
|
||||
elif self.groupby[group_name].get('type') == 'pattern':
|
||||
self.build_inventory_groups_pattern(group_name)
|
||||
elif self.groupby[group_name].get('type') == 'network_range':
|
||||
self.build_inventory_groups_network_range(group_name)
|
||||
elif self.groupby[group_name].get('type') == 'os':
|
||||
self.build_inventory_groups_os(group_name)
|
||||
elif self.groupby[group_name].get('type') == 'release':
|
||||
self.build_inventory_groups_release(group_name)
|
||||
elif self.groupby[group_name].get('type') == 'profile':
|
||||
self.build_inventory_groups_profile(group_name)
|
||||
elif self.groupby[group_name].get('type') == 'vlanid':
|
||||
self.build_inventory_groups_vlanid(group_name)
|
||||
else:
|
||||
raise AnsibleParserError('Unknown group type: {0}'.format(to_native(group_name)))
|
||||
|
||||
if self.groupby:
|
||||
for group_name in self.groupby:
|
||||
if not group_name.isalnum():
|
||||
raise AnsibleParserError('Invalid character(s) in groupname: {0}'.format(to_native(group_name)))
|
||||
group_type(group_name)
|
||||
|
||||
def build_inventory(self):
|
||||
"""Build dynamic inventory
|
||||
|
||||
Build the dynamic inventory.
|
||||
|
||||
Args:
|
||||
None
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
None"""
|
||||
|
||||
self.build_inventory_hosts()
|
||||
self.build_inventory_groups()
|
||||
|
||||
def _populate(self):
|
||||
"""Return the hosts and groups
|
||||
|
||||
Returns the processed container configurations from the lxd import
|
||||
|
||||
Args:
|
||||
None
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
None"""
|
||||
|
||||
if len(self.data) == 0: # If no data is injected by unittests open socket
|
||||
self.socket = self._connect_to_socket()
|
||||
self.get_container_data(self._get_containers())
|
||||
self.get_network_data(self._get_networks())
|
||||
|
||||
self.extract_information_from_container_configs()
|
||||
|
||||
# self.display.vvv(self.save_json_data([os.path.abspath(__file__)]))
|
||||
|
||||
self.build_inventory()
|
||||
|
||||
def parse(self, inventory, loader, path, cache):
|
||||
"""Return dynamic inventory from source
|
||||
|
||||
Returns the processed inventory from the lxd import
|
||||
|
||||
Args:
|
||||
str(inventory): inventory object with existing data and
|
||||
the methods to add hosts/groups/variables
|
||||
to inventory
|
||||
str(loader): Ansible's DataLoader
|
||||
str(path): path to the config
|
||||
bool(cache): use or avoid caches
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
AnsibleParserError
|
||||
Returns:
|
||||
None"""
|
||||
|
||||
super(InventoryModule, self).parse(inventory, loader, path, cache=False)
|
||||
# Read the inventory YAML file
|
||||
self._read_config_data(path)
|
||||
try:
|
||||
self.client_key = self.get_option('client_key')
|
||||
self.client_cert = self.get_option('client_cert')
|
||||
self.debug = self.DEBUG
|
||||
self.data = {} # store for inventory-data
|
||||
self.groupby = self.get_option('groupby')
|
||||
self.plugin = self.get_option('plugin')
|
||||
self.prefered_container_network_family = self.get_option('prefered_container_network_family')
|
||||
self.prefered_container_network_interface = self.get_option('prefered_container_network_interface')
|
||||
if self.get_option('state').lower() == 'none': # none in config is str()
|
||||
self.filter = None
|
||||
else:
|
||||
self.filter = self.get_option('state').lower()
|
||||
self.trust_password = self.get_option('trust_password')
|
||||
self.url = self.get_option('url')
|
||||
except Exception as err:
|
||||
raise AnsibleParserError(
|
||||
'All correct options required: {0}'.format(to_native(err)))
|
||||
# Call our internal helper to populate the dynamic inventory
|
||||
self._populate()
|
174
tests/unit/plugins/inventory/fixtures/lxd_inventory.atd
Normal file
174
tests/unit/plugins/inventory/fixtures/lxd_inventory.atd
Normal file
|
@ -0,0 +1,174 @@
|
|||
{
|
||||
"containers":{
|
||||
"vlantest":{
|
||||
"containers":{
|
||||
"metadata":{
|
||||
"config":{
|
||||
"image.os":"ubuntu",
|
||||
"image.release":"focal",
|
||||
"image.version":"20.04",
|
||||
"volatile.last_state.power":"RUNNING"
|
||||
},
|
||||
"devices":{
|
||||
"eth0":{
|
||||
"name":"eth0",
|
||||
"network":"my-macvlan",
|
||||
"type":"nic"
|
||||
}
|
||||
},
|
||||
"profiles":[
|
||||
"default"
|
||||
],
|
||||
"expanded_devices":{
|
||||
"eth0":{
|
||||
"name":"eth0",
|
||||
"network":"my-macvlan",
|
||||
"type":"nic"
|
||||
}
|
||||
},
|
||||
"name":"vlantest",
|
||||
"status":"Running",
|
||||
"location":"Berlin"
|
||||
}
|
||||
},
|
||||
"state":{
|
||||
"metadata":{
|
||||
"status":"Running",
|
||||
"network":{
|
||||
"eth0":{
|
||||
"addresses":[
|
||||
{
|
||||
"family":"inet",
|
||||
"address":"10.98.143.199",
|
||||
"netmask":"24",
|
||||
"scope":"global"
|
||||
},
|
||||
{
|
||||
"family":"inet6",
|
||||
"address":"fd42:bd00:7b11:2167:216:3eff:fe78:2ef3",
|
||||
"netmask":"64",
|
||||
"scope":"global"
|
||||
},
|
||||
{
|
||||
"family":"inet6",
|
||||
"address":"fe80::216:3eff:fed3:7af3",
|
||||
"netmask":"64",
|
||||
"scope":"link"
|
||||
}
|
||||
]
|
||||
},
|
||||
"lo":{
|
||||
"addresses":[
|
||||
{
|
||||
"family":"inet",
|
||||
"address":"127.0.0.1",
|
||||
"netmask":"8",
|
||||
"scope":"local"
|
||||
},
|
||||
{
|
||||
"family":"inet6",
|
||||
"address":"::1",
|
||||
"netmask":"128",
|
||||
"scope":"local"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"networks":{
|
||||
"my-macvlan":{
|
||||
"state":{
|
||||
"metadata":{
|
||||
"addresses":[
|
||||
{
|
||||
"family":"inet",
|
||||
"address":"192.168.178.199",
|
||||
"netmask":"24",
|
||||
"scope":"global"
|
||||
},
|
||||
{
|
||||
"family":"inet6",
|
||||
"address":"fd42:bd00:7b11:2167:216:3eff:fe78:2ef3",
|
||||
"netmask":"64",
|
||||
"scope":"global"
|
||||
},
|
||||
{
|
||||
"family":"inet6",
|
||||
"address":"fe80::216:3eff:fed3:7af3",
|
||||
"netmask":"64",
|
||||
"scope":"link"
|
||||
}
|
||||
],
|
||||
"vlan":{
|
||||
"lower_device":"eno1",
|
||||
"vid":666
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"lo":{
|
||||
"state":{
|
||||
"metadata":{
|
||||
"addresses":[
|
||||
{
|
||||
"family":"inet",
|
||||
"address":"127.0.0.1",
|
||||
"netmask":"8",
|
||||
"scope":"local"
|
||||
},
|
||||
{
|
||||
"family":"inet6",
|
||||
"address":"::1",
|
||||
"netmask":"128",
|
||||
"scope":"local"
|
||||
}
|
||||
],
|
||||
"vlan":null
|
||||
}
|
||||
}
|
||||
},
|
||||
"eno1":{
|
||||
"state":{
|
||||
"metadata":{
|
||||
"addresses":[
|
||||
{
|
||||
"family":"inet",
|
||||
"address":"192.168.178.126",
|
||||
"netmask":"24",
|
||||
"scope":"global"
|
||||
},
|
||||
{
|
||||
"family":"inet6",
|
||||
"address":"fe80::3c0b:7da9:3cc7:9e40",
|
||||
"netmask":"64",
|
||||
"scope":"link"
|
||||
}
|
||||
],
|
||||
"vlan":null
|
||||
}
|
||||
}
|
||||
},
|
||||
"eno1.666":{
|
||||
"state":{
|
||||
"metadata":{
|
||||
"addresses":[
|
||||
{
|
||||
"family":"inet6",
|
||||
"address":"fe80::de4a:3eff:fe8d:f356",
|
||||
"netmask":"64",
|
||||
"scope":"link"
|
||||
}
|
||||
],
|
||||
"vlan":{
|
||||
"lower_device":"eno1",
|
||||
"vid":666
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
100
tests/unit/plugins/inventory/test_lxd.py
Normal file
100
tests/unit/plugins/inventory/test_lxd.py
Normal file
|
@ -0,0 +1,100 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright: (c) 2021, Frank Dornheim <dornheim@posteo.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
|
||||
|
||||
import pytest
|
||||
import os
|
||||
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.inventory.data import InventoryData
|
||||
from ansible_collections.community.general.plugins.inventory.lxd import InventoryModule
|
||||
|
||||
|
||||
HOST_COMPARATIVE_DATA = {
|
||||
'ansible_connection': 'ssh', 'ansible_host': '10.98.143.199', 'ansible_lxd_os': 'ubuntu', 'ansible_lxd_release': 'focal',
|
||||
'ansible_lxd_profile': ['default'], 'ansible_lxd_state': 'running', 'ansible_lxd_location': 'Berlin',
|
||||
'ansible_lxd_vlan_ids': {'my-macvlan': 666}, 'inventory_hostname': 'vlantest', 'inventory_hostname_short': 'vlantest'}
|
||||
GROUP_COMPARATIVE_DATA = {
|
||||
'all': [], 'ungrouped': [], 'testpattern': ['vlantest'], 'vlan666': ['vlantest'], 'locationBerlin': ['vlantest'],
|
||||
'osUbuntu': ['vlantest'], 'releaseFocal': ['vlantest'], 'releaseBionic': [], 'profileDefault': ['vlantest'],
|
||||
'profileX11': [], 'netRangeIPv4': ['vlantest'], 'netRangeIPv6': ['vlantest']}
|
||||
GROUP_Config = {
|
||||
'testpattern': {'type': 'pattern', 'attribute': 'test'},
|
||||
'vlan666': {'type': 'vlanid', 'attribute': 666},
|
||||
'locationBerlin': {'type': 'location', 'attribute': 'Berlin'},
|
||||
'osUbuntu': {'type': 'os', 'attribute': 'ubuntu'},
|
||||
'releaseFocal': {'type': 'release', 'attribute': 'focal'},
|
||||
'releaseBionic': {'type': 'release', 'attribute': 'bionic'},
|
||||
'profileDefault': {'type': 'profile', 'attribute': 'default'},
|
||||
'profileX11': {'type': 'profile', 'attribute': 'x11'},
|
||||
'netRangeIPv4': {'type': 'network_range', 'attribute': '10.98.143.0/24'},
|
||||
'netRangeIPv6': {'type': 'network_range', 'attribute': 'fd42:bd00:7b11:2167:216:3eff::/96'}}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def inventory():
|
||||
inv = InventoryModule()
|
||||
inv.inventory = InventoryData()
|
||||
|
||||
# Test Values
|
||||
inv.data = inv.load_json_data('tests/unit/plugins/inventory/fixtures/lxd_inventory.atd') # Load Test Data
|
||||
inv.groupby = GROUP_Config
|
||||
inv.prefered_container_network_interface = 'eth'
|
||||
inv.prefered_container_network_family = 'inet'
|
||||
inv.filter = 'running'
|
||||
inv.dump_data = False
|
||||
|
||||
return inv
|
||||
|
||||
|
||||
def test_verify_file_bad_config(inventory):
|
||||
assert inventory.verify_file('foobar.lxd.yml') is False
|
||||
|
||||
|
||||
def test_build_inventory_hosts(inventory):
|
||||
"""Load example data and start the inventoryto test the host generation.
|
||||
|
||||
After the inventory plugin has run with the test data, the result of the host is checked."""
|
||||
inventory._populate()
|
||||
generated_data = inventory.inventory.get_host('vlantest').get_vars()
|
||||
|
||||
eq = True
|
||||
for key, value in HOST_COMPARATIVE_DATA.items():
|
||||
if generated_data[key] != value:
|
||||
eq = False
|
||||
assert eq
|
||||
|
||||
|
||||
def test_build_inventory_groups(inventory):
|
||||
"""Load example data and start the inventory to test the group generation.
|
||||
|
||||
After the inventory plugin has run with the test data, the result of the host is checked."""
|
||||
inventory._populate()
|
||||
generated_data = inventory.inventory.get_groups_dict()
|
||||
|
||||
eq = True
|
||||
for key, value in GROUP_COMPARATIVE_DATA.items():
|
||||
if generated_data[key] != value:
|
||||
eq = False
|
||||
assert eq
|
||||
|
||||
|
||||
def test_build_inventory_groups_with_no_groupselection(inventory):
|
||||
"""Load example data and start the inventory to test the group generation with groupby is none.
|
||||
|
||||
After the inventory plugin has run with the test data, the result of the host is checked."""
|
||||
inventory.groupby = None
|
||||
inventory._populate()
|
||||
generated_data = inventory.inventory.get_groups_dict()
|
||||
group_comparative_data = {'all': [], 'ungrouped': []}
|
||||
|
||||
eq = True
|
||||
print("data: {0}".format(generated_data))
|
||||
for key, value in group_comparative_data.items():
|
||||
if generated_data[key] != value:
|
||||
eq = False
|
||||
assert eq
|
Loading…
Reference in a new issue