1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00

LXD inventory: Support virtual machines (#3519) (#3900)

* LXD 4.x compatibility (Containers and VMs)

* add changelog fragment

* update fixture

* update plugin options

* backwards compatible alias

Co-authored-by: Felix Fontein <felix@fontein.de>

* Update changelogs/fragments/3519-inventory-support-lxd-4.yml

Co-authored-by: Felix Fontein <felix@fontein.de>

* add lxd 4.0 requirement

* filter for type of virtualization added. due to duplication in the namespace, "type" is not used as the keyword but "nature".

* add type filter

Since the first version of this inventory plugin only supports containers,
a filter function was added to filter between containers and
virtual machines or both.
By default only containers are displayed, as in the first version of the plugin.
This behavior will change in the future.

* rename C(nature) to C(type)

The term "nature" does not fit into the lxd namespace.
Therefore i renamed nature to type.

* update changelog fragment

* Update plugins/inventory/lxd.py

Co-authored-by: Felix Fontein <felix@fontein.de>

* Apply suggestions from code review

Co-authored-by: Felix Fontein <felix@fontein.de>

* rename typefilter to type_filter

* fix tests with type_filter

* Update plugins/inventory/lxd.py

* Update plugins/inventory/lxd.py

Co-authored-by: Felix Fontein <felix@fontein.de>
Co-authored-by: Frank Dornheim <“dornheim@posteo.de@users.noreply.github.com”>
(cherry picked from commit 8825ef4711)

Co-authored-by: Élie <elie@deloumeau.fr>
This commit is contained in:
patchback[bot] 2021-12-14 06:42:47 +01:00 committed by GitHub
parent f044a83c49
commit 5d6fcaef53
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 271 additions and 184 deletions

View file

@ -0,0 +1,2 @@
minor_changes:
- lxd inventory plugin - support virtual machines (https://github.com/ansible-collections/community.general/pull/3519).

View file

@ -15,6 +15,7 @@ DOCUMENTATION = r'''
author: "Frank Dornheim (@conloos)" author: "Frank Dornheim (@conloos)"
requirements: requirements:
- ipaddress - ipaddress
- lxd >= 4.0
options: options:
plugin: plugin:
description: Token that ensures this is a source file for the 'lxd' plugin. description: Token that ensures this is a source file for the 'lxd' plugin.
@ -49,26 +50,38 @@ DOCUMENTATION = r'''
- If I(trust_password) is set, this module send a request for authentication before sending any requests. - If I(trust_password) is set, this module send a request for authentication before sending any requests.
type: str type: str
state: state:
description: Filter the container according to the current status. description: Filter the instance according to the current status.
type: str type: str
default: none default: none
choices: [ 'STOPPED', 'STARTING', 'RUNNING', 'none' ] choices: [ 'STOPPED', 'STARTING', 'RUNNING', 'none' ]
prefered_container_network_interface: type_filter:
description: description:
- If a container has multiple network interfaces, select which one is the prefered as pattern. - Filter the instances by type C(virtual-machine), C(container) or C(both).
- The first version of the inventory only supported containers.
type: str
default: container
choices: [ 'virtual-machine', 'container', 'both' ]
version_added: 4.2.0
prefered_instance_network_interface:
description:
- If an instance 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. - Combined with the first number that can be found e.g. 'eth' + 0.
- The option has been renamed from I(prefered_container_network_interface) to I(prefered_instance_network_interface) in community.general 3.8.0.
The old name still works as an alias.
type: str type: str
default: eth default: eth
prefered_container_network_family: aliases:
- prefered_container_network_interface
prefered_instance_network_family:
description: description:
- If a container has multiple network interfaces, which one is the prefered by family. - If an instance has multiple network interfaces, which one is the prefered by family.
- Specify C(inet) for IPv4 and C(inet6) for IPv6. - Specify C(inet) for IPv4 and C(inet6) for IPv6.
type: str type: str
default: inet default: inet
choices: [ 'inet', 'inet6' ] choices: [ 'inet', 'inet6' ]
groupby: groupby:
description: description:
- Create groups by the following keywords C(location), C(pattern), C(network_range), C(os), C(release), C(profile), C(vlanid). - Create groups by the following keywords C(location), C(network_range), C(os), C(pattern), C(profile), C(release), C(type), C(vlanid).
- See example for syntax. - See example for syntax.
type: dict type: dict
''' '''
@ -83,38 +96,49 @@ plugin: community.general.lxd
url: unix:/var/snap/lxd/common/lxd/unix.socket url: unix:/var/snap/lxd/common/lxd/unix.socket
state: RUNNING state: RUNNING
# simple lxd.yml including virtual machines and containers
plugin: community.general.lxd
url: unix:/var/snap/lxd/common/lxd/unix.socket
type_filter: both
# grouping lxd.yml # grouping lxd.yml
groupby: groupby:
testpattern:
type: pattern
attribute: test
vlan666:
type: vlanid
attribute: 666
locationBerlin: locationBerlin:
type: location type: location
attribute: Berlin 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: netRangeIPv4:
type: network_range type: network_range
attribute: 10.98.143.0/24 attribute: 10.98.143.0/24
netRangeIPv6: netRangeIPv6:
type: network_range type: network_range
attribute: fd42:bd00:7b11:2167:216:3eff::/24 attribute: fd42:bd00:7b11:2167:216:3eff::/24
osUbuntu:
type: os
attribute: ubuntu
testpattern:
type: pattern
attribute: test
profileDefault:
type: profile
attribute: default
profileX11:
type: profile
attribute: x11
releaseFocal:
type: release
attribute: focal
releaseBionic:
type: release
attribute: bionic
typeVM:
type: type
attribute: virtual-machine
typeContainer:
type: type
attribute: container
vlan666:
type: vlanid
attribute: 666
''' '''
import binascii import binascii
@ -283,10 +307,10 @@ class InventoryModule(BaseInventoryPlugin):
network_configs = self.socket.do('GET', '/1.0/networks') network_configs = self.socket.do('GET', '/1.0/networks')
return [m.split('/')[3] for m in network_configs['metadata']] return [m.split('/')[3] for m in network_configs['metadata']]
def _get_containers(self): def _get_instances(self):
"""Get Containernames """Get instancenames
Returns all containernames Returns all instancenames
Args: Args:
None None
@ -295,25 +319,27 @@ class InventoryModule(BaseInventoryPlugin):
Raises: Raises:
None None
Returns: Returns:
list(names): names of all containers""" list(names): names of all instances"""
# e.g. {'type': 'sync', # e.g. {
# 'status': 'Success', # "metadata": [
# 'status_code': 200, # "/1.0/instances/foo",
# 'operation': '', # "/1.0/instances/bar"
# 'error_code': 0, # ],
# 'error': '', # "status": "Success",
# 'metadata': ['/1.0/containers/udemy-ansible-ubuntu-2004']} # "status_code": 200,
containers = self.socket.do('GET', '/1.0/containers') # "type": "sync"
return [m.split('/')[3] for m in containers['metadata']] # }
instances = self.socket.do('GET', '/1.0/instances')
return [m.split('/')[3] for m in instances['metadata']]
def _get_config(self, branch, name): def _get_config(self, branch, name):
"""Get inventory of container """Get inventory of instance
Get config of container Get config of instance
Args: Args:
str(branch): Name oft the API-Branch str(branch): Name oft the API-Branch
str(name): Name of Container str(name): Name of instance
Kwargs: Kwargs:
None None
Source: Source:
@ -321,7 +347,7 @@ class InventoryModule(BaseInventoryPlugin):
Raises: Raises:
None None
Returns: Returns:
dict(config): Config of the container""" dict(config): Config of the instance"""
config = {} config = {}
if isinstance(branch, (tuple, list)): 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])))} 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])))}
@ -329,13 +355,13 @@ class InventoryModule(BaseInventoryPlugin):
config[name] = {branch: self.socket.do('GET', '/1.0/{0}/{1}'.format(to_native(branch), to_native(name)))} config[name] = {branch: self.socket.do('GET', '/1.0/{0}/{1}'.format(to_native(branch), to_native(name)))}
return config return config
def get_container_data(self, names): def get_instance_data(self, names):
"""Create Inventory of the container """Create Inventory of the instance
Iterate through the different branches of the containers and collect Informations. Iterate through the different branches of the instances and collect Informations.
Args: Args:
list(names): List of container names list(names): List of instance names
Kwargs: Kwargs:
None None
Raises: Raises:
@ -344,20 +370,20 @@ class InventoryModule(BaseInventoryPlugin):
None""" None"""
# tuple(('instances','metadata/templates')) to get section in branch # tuple(('instances','metadata/templates')) to get section in branch
# e.g. /1.0/instances/<name>/metadata/templates # e.g. /1.0/instances/<name>/metadata/templates
branches = ['containers', ('instances', 'state')] branches = ['instances', ('instances', 'state')]
container_config = {} instance_config = {}
for branch in branches: for branch in branches:
for name in names: for name in names:
container_config['containers'] = self._get_config(branch, name) instance_config['instances'] = self._get_config(branch, name)
self.data = dict_merge(container_config, self.data) self.data = dict_merge(instance_config, self.data)
def get_network_data(self, names): def get_network_data(self, names):
"""Create Inventory of the container """Create Inventory of the instance
Iterate through the different branches of the containers and collect Informations. Iterate through the different branches of the instances and collect Informations.
Args: Args:
list(names): List of container names list(names): List of instance names
Kwargs: Kwargs:
None None
Raises: Raises:
@ -376,26 +402,26 @@ class InventoryModule(BaseInventoryPlugin):
network_config['networks'] = {name: None} network_config['networks'] = {name: None}
self.data = dict_merge(network_config, self.data) self.data = dict_merge(network_config, self.data)
def extract_network_information_from_container_config(self, container_name): def extract_network_information_from_instance_config(self, instance_name):
"""Returns the network interface configuration """Returns the network interface configuration
Returns the network ipv4 and ipv6 config of the container without local-link Returns the network ipv4 and ipv6 config of the instance without local-link
Args: Args:
str(container_name): Name oft he container str(instance_name): Name oft he instance
Kwargs: Kwargs:
None None
Raises: Raises:
None None
Returns: Returns:
dict(network_configuration): network config""" dict(network_configuration): network config"""
container_network_interfaces = self._get_data_entry('containers/{0}/state/metadata/network'.format(container_name)) instance_network_interfaces = self._get_data_entry('instances/{0}/state/metadata/network'.format(instance_name))
network_configuration = None network_configuration = None
if container_network_interfaces: if instance_network_interfaces:
network_configuration = {} network_configuration = {}
gen_interface_names = [interface_name for interface_name in container_network_interfaces if interface_name != 'lo'] gen_interface_names = [interface_name for interface_name in instance_network_interfaces if interface_name != 'lo']
for interface_name in gen_interface_names: for interface_name in gen_interface_names:
gen_address = [address for address in container_network_interfaces[interface_name]['addresses'] if address.get('scope') != 'link'] gen_address = [address for address in instance_network_interfaces[interface_name]['addresses'] if address.get('scope') != 'link']
network_configuration[interface_name] = [] network_configuration[interface_name] = []
for address in gen_address: for address in gen_address:
address_set = {} address_set = {}
@ -406,24 +432,24 @@ class InventoryModule(BaseInventoryPlugin):
network_configuration[interface_name].append(address_set) network_configuration[interface_name].append(address_set)
return network_configuration return network_configuration
def get_prefered_container_network_interface(self, container_name): def get_prefered_instance_network_interface(self, instance_name):
"""Helper to get the prefered interface of thr container """Helper to get the prefered interface of thr instance
Helper to get the prefered interface provide by neme pattern from 'prefered_container_network_interface'. Helper to get the prefered interface provide by neme pattern from 'prefered_instance_network_interface'.
Args: Args:
str(containe_name): name of container str(containe_name): name of instance
Kwargs: Kwargs:
None None
Raises: Raises:
None None
Returns: Returns:
str(prefered_interface): None or interface name""" str(prefered_interface): None or interface name"""
container_network_interfaces = self._get_data_entry('inventory/{0}/network_interfaces'.format(container_name)) instance_network_interfaces = self._get_data_entry('inventory/{0}/network_interfaces'.format(instance_name))
prefered_interface = None # init prefered_interface = None # init
if container_network_interfaces: # container have network interfaces if instance_network_interfaces: # instance have network interfaces
# generator if interfaces which start with the desired pattern # 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)] net_generator = [interface for interface in instance_network_interfaces if interface.startswith(self.prefered_instance_network_interface)]
selected_interfaces = [] # init selected_interfaces = [] # init
for interface in net_generator: for interface in net_generator:
selected_interfaces.append(interface) selected_interfaces.append(interface)
@ -431,13 +457,13 @@ class InventoryModule(BaseInventoryPlugin):
prefered_interface = sorted(selected_interfaces)[0] prefered_interface = sorted(selected_interfaces)[0]
return prefered_interface return prefered_interface
def get_container_vlans(self, container_name): def get_instance_vlans(self, instance_name):
"""Get VLAN(s) from container """Get VLAN(s) from instance
Helper to get the VLAN_ID from the container Helper to get the VLAN_ID from the instance
Args: Args:
str(containe_name): name of container str(containe_name): name of instance
Kwargs: Kwargs:
None None
Raises: Raises:
@ -450,13 +476,13 @@ class InventoryModule(BaseInventoryPlugin):
if self._get_data_entry('state/metadata/vlan/vid', data=self.data['networks'].get(network)): 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)) network_vlans[network] = self._get_data_entry('state/metadata/vlan/vid', data=self.data['networks'].get(network))
# get networkdevices of container and return # get networkdevices of instance and return
# e.g. # e.g.
# "eth0":{ "name":"eth0", # "eth0":{ "name":"eth0",
# "network":"lxdbr0", # "network":"lxdbr0",
# "type":"nic"}, # "type":"nic"},
vlan_ids = {} vlan_ids = {}
devices = self._get_data_entry('containers/{0}/containers/metadata/expanded_devices'.format(to_native(container_name))) devices = self._get_data_entry('instances/{0}/instances/metadata/expanded_devices'.format(to_native(instance_name)))
for device in devices: for device in devices:
if 'network' in devices[device]: if 'network' in devices[device]:
if devices[device]['network'] in network_vlans: if devices[device]['network'] in network_vlans:
@ -492,14 +518,14 @@ class InventoryModule(BaseInventoryPlugin):
except KeyError: except KeyError:
return None return None
def _set_data_entry(self, container_name, key, value, path=None): def _set_data_entry(self, instance_name, key, value, path=None):
"""Helper to save data """Helper to save data
Helper to save the data in self.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. Detect if data is allready in branch and use dict_merge() to prevent that branch is overwritten.
Args: Args:
str(container_name): name of container str(instance_name): name of instance
str(key): same as dict str(key): same as dict
*(value): same as dict *(value): same as dict
Kwargs: Kwargs:
@ -510,24 +536,24 @@ class InventoryModule(BaseInventoryPlugin):
None""" None"""
if not path: if not path:
path = self.data['inventory'] path = self.data['inventory']
if container_name not in path: if instance_name not in path:
path[container_name] = {} path[instance_name] = {}
try: try:
if isinstance(value, dict) and key in path[container_name]: if isinstance(value, dict) and key in path[instance_name]:
path[container_name] = dict_merge(value, path[container_name][key]) path[instance_name] = dict_merge(value, path[instance_name][key])
else: else:
path[container_name][key] = value path[instance_name][key] = value
except KeyError as err: except KeyError as err:
raise AnsibleParserError("Unable to store Informations: {0}".format(to_native(err))) raise AnsibleParserError("Unable to store Informations: {0}".format(to_native(err)))
def extract_information_from_container_configs(self): def extract_information_from_instance_configs(self):
"""Process configuration information """Process configuration information
Preparation of the data Preparation of the data
Args: Args:
dict(configs): Container configurations dict(configs): instance configurations
Kwargs: Kwargs:
None None
Raises: Raises:
@ -538,33 +564,35 @@ class InventoryModule(BaseInventoryPlugin):
if 'inventory' not in self.data: if 'inventory' not in self.data:
self.data['inventory'] = {} self.data['inventory'] = {}
for container_name in self.data['containers']: for instance_name in self.data['instances']:
self._set_data_entry(container_name, 'os', self._get_data_entry( self._set_data_entry(instance_name, 'os', self._get_data_entry(
'containers/{0}/containers/metadata/config/image.os'.format(container_name))) 'instances/{0}/instances/metadata/config/image.os'.format(instance_name)))
self._set_data_entry(container_name, 'release', self._get_data_entry( self._set_data_entry(instance_name, 'release', self._get_data_entry(
'containers/{0}/containers/metadata/config/image.release'.format(container_name))) 'instances/{0}/instances/metadata/config/image.release'.format(instance_name)))
self._set_data_entry(container_name, 'version', self._get_data_entry( self._set_data_entry(instance_name, 'version', self._get_data_entry(
'containers/{0}/containers/metadata/config/image.version'.format(container_name))) 'instances/{0}/instances/metadata/config/image.version'.format(instance_name)))
self._set_data_entry(container_name, 'profile', self._get_data_entry( self._set_data_entry(instance_name, 'profile', self._get_data_entry(
'containers/{0}/containers/metadata/profiles'.format(container_name))) 'instances/{0}/instances/metadata/profiles'.format(instance_name)))
self._set_data_entry(container_name, 'location', self._get_data_entry( self._set_data_entry(instance_name, 'location', self._get_data_entry(
'containers/{0}/containers/metadata/location'.format(container_name))) 'instances/{0}/instances/metadata/location'.format(instance_name)))
self._set_data_entry(container_name, 'state', self._get_data_entry( self._set_data_entry(instance_name, 'state', self._get_data_entry(
'containers/{0}/containers/metadata/config/volatile.last_state.power'.format(container_name))) 'instances/{0}/instances/metadata/config/volatile.last_state.power'.format(instance_name)))
self._set_data_entry(container_name, 'network_interfaces', self.extract_network_information_from_container_config(container_name)) self._set_data_entry(instance_name, 'type', self._get_data_entry(
self._set_data_entry(container_name, 'preferred_interface', self.get_prefered_container_network_interface(container_name)) 'instances/{0}/instances/metadata/type'.format(instance_name)))
self._set_data_entry(container_name, 'vlan_ids', self.get_container_vlans(container_name)) self._set_data_entry(instance_name, 'network_interfaces', self.extract_network_information_from_instance_config(instance_name))
self._set_data_entry(instance_name, 'preferred_interface', self.get_prefered_instance_network_interface(instance_name))
self._set_data_entry(instance_name, 'vlan_ids', self.get_instance_vlans(instance_name))
def build_inventory_network(self, container_name): def build_inventory_network(self, instance_name):
"""Add the network interfaces of the container to the inventory """Add the network interfaces of the instance to the inventory
Logic: Logic:
- if the container have no interface -> 'ansible_connection: local' - if the instance have no interface -> 'ansible_connection: local'
- get preferred_interface & prefered_container_network_family -> 'ansible_connection: ssh' & 'ansible_host: <IP>' - get preferred_interface & prefered_instance_network_family -> 'ansible_connection: ssh' & 'ansible_host: <IP>'
- first Interface from: network_interfaces prefered_container_network_family -> 'ansible_connection: ssh' & 'ansible_host: <IP>' - first Interface from: network_interfaces prefered_instance_network_family -> 'ansible_connection: ssh' & 'ansible_host: <IP>'
Args: Args:
str(container_name): name of container str(instance_name): name of instance
Kwargs: Kwargs:
None None
Raises: Raises:
@ -572,45 +600,45 @@ class InventoryModule(BaseInventoryPlugin):
Returns: Returns:
None""" None"""
def interface_selection(container_name): def interface_selection(instance_name):
"""Select container Interface for inventory """Select instance Interface for inventory
Logic: Logic:
- get preferred_interface & prefered_container_network_family -> str(IP) - get preferred_interface & prefered_instance_network_family -> str(IP)
- first Interface from: network_interfaces prefered_container_network_family -> str(IP) - first Interface from: network_interfaces prefered_instance_network_family -> str(IP)
Args: Args:
str(container_name): name of container str(instance_name): name of instance
Kwargs: Kwargs:
None None
Raises: Raises:
None None
Returns: Returns:
dict(interface_name: ip)""" dict(interface_name: ip)"""
prefered_interface = self._get_data_entry('inventory/{0}/preferred_interface'.format(container_name)) # name or None prefered_interface = self._get_data_entry('inventory/{0}/preferred_interface'.format(instance_name)) # name or None
prefered_container_network_family = self.prefered_container_network_family prefered_instance_network_family = self.prefered_instance_network_family
ip_address = '' ip_address = ''
if prefered_interface: if prefered_interface:
interface = self._get_data_entry('inventory/{0}/network_interfaces/{1}'.format(container_name, prefered_interface)) interface = self._get_data_entry('inventory/{0}/network_interfaces/{1}'.format(instance_name, prefered_interface))
for config in interface: for config in interface:
if config['family'] == prefered_container_network_family: if config['family'] == prefered_instance_network_family:
ip_address = config['address'] ip_address = config['address']
break break
else: else:
interface = self._get_data_entry('inventory/{0}/network_interfaces'.format(container_name)) interfaces = self._get_data_entry('inventory/{0}/network_interfaces'.format(instance_name))
for config in interface: for interface in interfaces.values():
if config['family'] == prefered_container_network_family: for config in interface:
ip_address = config['address'] if config['family'] == prefered_instance_network_family:
break ip_address = config['address']
break
return ip_address 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}/network_interfaces'.format(instance_name)): # instance have network interfaces
if self._get_data_entry('inventory/{0}/preferred_interface'.format(container_name)): # container have a preferred interface self.inventory.set_variable(instance_name, 'ansible_connection', 'ssh')
self.inventory.set_variable(container_name, 'ansible_connection', 'ssh') self.inventory.set_variable(instance_name, 'ansible_host', interface_selection(instance_name))
self.inventory.set_variable(container_name, 'ansible_host', interface_selection(container_name))
else: else:
self.inventory.set_variable(container_name, 'ansible_connection', 'local') self.inventory.set_variable(instance_name, 'ansible_connection', 'local')
def build_inventory_hosts(self): def build_inventory_hosts(self):
"""Build host-part dynamic inventory """Build host-part dynamic inventory
@ -626,29 +654,33 @@ class InventoryModule(BaseInventoryPlugin):
None None
Returns: Returns:
None""" None"""
for container_name in self.data['inventory']: for instance_name in self.data['inventory']:
# Only consider containers that match the "state" filter, if self.state is not None instance_state = str(self._get_data_entry('inventory/{0}/state'.format(instance_name)) or "STOPPED").lower()
# Only consider instances that match the "state" filter, if self.state is not None
if self.filter: if self.filter:
if self.filter.lower() != self._get_data_entry('inventory/{0}/state'.format(container_name)).lower(): if self.filter.lower() != instance_state:
continue continue
# add container # add instance
self.inventory.add_host(container_name) self.inventory.add_host(instance_name)
# add network informations # add network informations
self.build_inventory_network(container_name) self.build_inventory_network(instance_name)
# add os # add os
self.inventory.set_variable(container_name, 'ansible_lxd_os', self._get_data_entry('inventory/{0}/os'.format(container_name)).lower()) self.inventory.set_variable(instance_name, 'ansible_lxd_os', self._get_data_entry('inventory/{0}/os'.format(instance_name)).lower())
# add release # add release
self.inventory.set_variable(container_name, 'ansible_lxd_release', self._get_data_entry('inventory/{0}/release'.format(container_name)).lower()) self.inventory.set_variable(instance_name, 'ansible_lxd_release', self._get_data_entry('inventory/{0}/release'.format(instance_name)).lower())
# add profile # add profile
self.inventory.set_variable(container_name, 'ansible_lxd_profile', self._get_data_entry('inventory/{0}/profile'.format(container_name))) self.inventory.set_variable(instance_name, 'ansible_lxd_profile', self._get_data_entry('inventory/{0}/profile'.format(instance_name)))
# add state # add state
self.inventory.set_variable(container_name, 'ansible_lxd_state', self._get_data_entry('inventory/{0}/state'.format(container_name)).lower()) self.inventory.set_variable(instance_name, 'ansible_lxd_state', instance_state)
# add type
self.inventory.set_variable(instance_name, 'ansible_lxd_type', self._get_data_entry('inventory/{0}/type'.format(instance_name)))
# add location information # add location information
if self._get_data_entry('inventory/{0}/location'.format(container_name)) != "none": # wrong type by lxd 'none' != 'None' if self._get_data_entry('inventory/{0}/location'.format(instance_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))) self.inventory.set_variable(instance_name, 'ansible_lxd_location', self._get_data_entry('inventory/{0}/location'.format(instance_name)))
# add VLAN_ID information # add VLAN_ID information
if self._get_data_entry('inventory/{0}/vlan_ids'.format(container_name)): if self._get_data_entry('inventory/{0}/vlan_ids'.format(instance_name)):
self.inventory.set_variable(container_name, 'ansible_lxd_vlan_ids', self._get_data_entry('inventory/{0}/vlan_ids'.format(container_name))) self.inventory.set_variable(instance_name, 'ansible_lxd_vlan_ids', self._get_data_entry('inventory/{0}/vlan_ids'.format(instance_name)))
def build_inventory_groups_location(self, group_name): def build_inventory_groups_location(self, group_name):
"""create group by attribute: location """create group by attribute: location
@ -665,9 +697,9 @@ class InventoryModule(BaseInventoryPlugin):
if group_name not in self.inventory.groups: if group_name not in self.inventory.groups:
self.inventory.add_group(group_name) self.inventory.add_group(group_name)
for container_name in self.inventory.hosts: for instance_name in self.inventory.hosts:
if 'ansible_lxd_location' in self.inventory.get_host(container_name).get_vars(): if 'ansible_lxd_location' in self.inventory.get_host(instance_name).get_vars():
self.inventory.add_child(group_name, container_name) self.inventory.add_child(group_name, instance_name)
def build_inventory_groups_pattern(self, group_name): def build_inventory_groups_pattern(self, group_name):
"""create group by name pattern """create group by name pattern
@ -686,10 +718,10 @@ class InventoryModule(BaseInventoryPlugin):
regex_pattern = self.groupby[group_name].get('attribute') regex_pattern = self.groupby[group_name].get('attribute')
for container_name in self.inventory.hosts: for instance_name in self.inventory.hosts:
result = re.search(regex_pattern, container_name) result = re.search(regex_pattern, instance_name)
if result: if result:
self.inventory.add_child(group_name, container_name) self.inventory.add_child(group_name, instance_name)
def build_inventory_groups_network_range(self, group_name): def build_inventory_groups_network_range(self, group_name):
"""check if IP is in network-class """check if IP is in network-class
@ -712,14 +744,14 @@ class InventoryModule(BaseInventoryPlugin):
raise AnsibleParserError( raise AnsibleParserError(
'Error while parsing network range {0}: {1}'.format(self.groupby[group_name].get('attribute'), to_native(err))) 'Error while parsing network range {0}: {1}'.format(self.groupby[group_name].get('attribute'), to_native(err)))
for container_name in self.inventory.hosts: for instance_name in self.inventory.hosts:
if self.data['inventory'][container_name].get('network_interfaces') is not None: if self.data['inventory'][instance_name].get('network_interfaces') is not None:
for interface in self.data['inventory'][container_name].get('network_interfaces'): for interface in self.data['inventory'][instance_name].get('network_interfaces'):
for interface_family in self.data['inventory'][container_name].get('network_interfaces')[interface]: for interface_family in self.data['inventory'][instance_name].get('network_interfaces')[interface]:
try: try:
address = ipaddress.ip_address(to_text(interface_family['address'])) address = ipaddress.ip_address(to_text(interface_family['address']))
if address.version == network.version and address in network: if address.version == network.version and address in network:
self.inventory.add_child(group_name, container_name) self.inventory.add_child(group_name, instance_name)
except ValueError: except ValueError:
# Ignore invalid IP addresses returned by lxd # Ignore invalid IP addresses returned by lxd
pass pass
@ -730,7 +762,7 @@ class InventoryModule(BaseInventoryPlugin):
Args: Args:
str(group_name): Group name str(group_name): Group name
Kwargs: Kwargs:
Noneself.data['inventory'][container_name][interface] None
Raises: Raises:
None None
Returns: Returns:
@ -739,12 +771,12 @@ class InventoryModule(BaseInventoryPlugin):
if group_name not in self.inventory.groups: if group_name not in self.inventory.groups:
self.inventory.add_group(group_name) self.inventory.add_group(group_name)
gen_containers = [ gen_instances = [
container_name for container_name in self.inventory.hosts instance_name for instance_name in self.inventory.hosts
if 'ansible_lxd_os' in self.inventory.get_host(container_name).get_vars()] if 'ansible_lxd_os' in self.inventory.get_host(instance_name).get_vars()]
for container_name in gen_containers: for instance_name in gen_instances:
if self.groupby[group_name].get('attribute').lower() == self.inventory.get_host(container_name).get_vars().get('ansible_lxd_os'): if self.groupby[group_name].get('attribute').lower() == self.inventory.get_host(instance_name).get_vars().get('ansible_lxd_os'):
self.inventory.add_child(group_name, container_name) self.inventory.add_child(group_name, instance_name)
def build_inventory_groups_release(self, group_name): def build_inventory_groups_release(self, group_name):
"""create group by attribute: release """create group by attribute: release
@ -761,12 +793,12 @@ class InventoryModule(BaseInventoryPlugin):
if group_name not in self.inventory.groups: if group_name not in self.inventory.groups:
self.inventory.add_group(group_name) self.inventory.add_group(group_name)
gen_containers = [ gen_instances = [
container_name for container_name in self.inventory.hosts instance_name for instance_name in self.inventory.hosts
if 'ansible_lxd_release' in self.inventory.get_host(container_name).get_vars()] if 'ansible_lxd_release' in self.inventory.get_host(instance_name).get_vars()]
for container_name in gen_containers: for instance_name in gen_instances:
if self.groupby[group_name].get('attribute').lower() == self.inventory.get_host(container_name).get_vars().get('ansible_lxd_release'): if self.groupby[group_name].get('attribute').lower() == self.inventory.get_host(instance_name).get_vars().get('ansible_lxd_release'):
self.inventory.add_child(group_name, container_name) self.inventory.add_child(group_name, instance_name)
def build_inventory_groups_profile(self, group_name): def build_inventory_groups_profile(self, group_name):
"""create group by attribute: profile """create group by attribute: profile
@ -783,12 +815,12 @@ class InventoryModule(BaseInventoryPlugin):
if group_name not in self.inventory.groups: if group_name not in self.inventory.groups:
self.inventory.add_group(group_name) self.inventory.add_group(group_name)
gen_containers = [ gen_instances = [
container_name for container_name in self.inventory.hosts.keys() instance_name for instance_name in self.inventory.hosts.keys()
if 'ansible_lxd_profile' in self.inventory.get_host(container_name).get_vars().keys()] if 'ansible_lxd_profile' in self.inventory.get_host(instance_name).get_vars().keys()]
for container_name in gen_containers: for instance_name in gen_instances:
if self.groupby[group_name].get('attribute').lower() in self.inventory.get_host(container_name).get_vars().get('ansible_lxd_profile'): if self.groupby[group_name].get('attribute').lower() in self.inventory.get_host(instance_name).get_vars().get('ansible_lxd_profile'):
self.inventory.add_child(group_name, container_name) self.inventory.add_child(group_name, instance_name)
def build_inventory_groups_vlanid(self, group_name): def build_inventory_groups_vlanid(self, group_name):
"""create group by attribute: vlanid """create group by attribute: vlanid
@ -805,12 +837,34 @@ class InventoryModule(BaseInventoryPlugin):
if group_name not in self.inventory.groups: if group_name not in self.inventory.groups:
self.inventory.add_group(group_name) self.inventory.add_group(group_name)
gen_containers = [ gen_instances = [
container_name for container_name in self.inventory.hosts.keys() instance_name for instance_name in self.inventory.hosts.keys()
if 'ansible_lxd_vlan_ids' in self.inventory.get_host(container_name).get_vars().keys()] if 'ansible_lxd_vlan_ids' in self.inventory.get_host(instance_name).get_vars().keys()]
for container_name in gen_containers: for instance_name in gen_instances:
if self.groupby[group_name].get('attribute') in self.inventory.get_host(container_name).get_vars().get('ansible_lxd_vlan_ids').values(): if self.groupby[group_name].get('attribute') in self.inventory.get_host(instance_name).get_vars().get('ansible_lxd_vlan_ids').values():
self.inventory.add_child(group_name, container_name) self.inventory.add_child(group_name, instance_name)
def build_inventory_groups_type(self, group_name):
"""create group by attribute: type
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_instances = [
instance_name for instance_name in self.inventory.hosts
if 'ansible_lxd_type' in self.inventory.get_host(instance_name).get_vars()]
for instance_name in gen_instances:
if self.groupby[group_name].get('attribute').lower() == self.inventory.get_host(instance_name).get_vars().get('ansible_lxd_type'):
self.inventory.add_child(group_name, instance_name)
def build_inventory_groups(self): def build_inventory_groups(self):
"""Build group-part dynamic inventory """Build group-part dynamic inventory
@ -839,6 +893,7 @@ class InventoryModule(BaseInventoryPlugin):
* 'release' * 'release'
* 'profile' * 'profile'
* 'vlanid' * 'vlanid'
* 'type'
Args: Args:
str(group_name): Group name str(group_name): Group name
@ -864,6 +919,8 @@ class InventoryModule(BaseInventoryPlugin):
self.build_inventory_groups_profile(group_name) self.build_inventory_groups_profile(group_name)
elif self.groupby[group_name].get('type') == 'vlanid': elif self.groupby[group_name].get('type') == 'vlanid':
self.build_inventory_groups_vlanid(group_name) self.build_inventory_groups_vlanid(group_name)
elif self.groupby[group_name].get('type') == 'type':
self.build_inventory_groups_type(group_name)
else: else:
raise AnsibleParserError('Unknown group type: {0}'.format(to_native(group_name))) raise AnsibleParserError('Unknown group type: {0}'.format(to_native(group_name)))
@ -890,10 +947,30 @@ class InventoryModule(BaseInventoryPlugin):
self.build_inventory_hosts() self.build_inventory_hosts()
self.build_inventory_groups() self.build_inventory_groups()
def cleandata(self):
"""Clean the dynamic inventory
The first version of the inventory only supported container.
This will change in the future.
The following function cleans up the data and remove the all items with the wrong type.
Args:
None
Kwargs:
None
Raises:
None
Returns:
None"""
iter_keys = list(self.data['instances'].keys())
for instance_name in iter_keys:
if self._get_data_entry('instances/{0}/instances/metadata/type'.format(instance_name)) != self.type_filter:
del self.data['instances'][instance_name]
def _populate(self): def _populate(self):
"""Return the hosts and groups """Return the hosts and groups
Returns the processed container configurations from the lxd import Returns the processed instance configurations from the lxd import
Args: Args:
None None
@ -906,10 +983,16 @@ class InventoryModule(BaseInventoryPlugin):
if len(self.data) == 0: # If no data is injected by unittests open socket if len(self.data) == 0: # If no data is injected by unittests open socket
self.socket = self._connect_to_socket() self.socket = self._connect_to_socket()
self.get_container_data(self._get_containers()) self.get_instance_data(self._get_instances())
self.get_network_data(self._get_networks()) self.get_network_data(self._get_networks())
self.extract_information_from_container_configs() # The first version of the inventory only supported containers.
# This will change in the future.
# The following function cleans up the data.
if self.type_filter != 'both':
self.cleandata()
self.extract_information_from_instance_configs()
# self.display.vvv(self.save_json_data([os.path.abspath(__file__)])) # self.display.vvv(self.save_json_data([os.path.abspath(__file__)]))
@ -948,8 +1031,9 @@ class InventoryModule(BaseInventoryPlugin):
self.data = {} # store for inventory-data self.data = {} # store for inventory-data
self.groupby = self.get_option('groupby') self.groupby = self.get_option('groupby')
self.plugin = self.get_option('plugin') self.plugin = self.get_option('plugin')
self.prefered_container_network_family = self.get_option('prefered_container_network_family') self.prefered_instance_network_family = self.get_option('prefered_instance_network_family')
self.prefered_container_network_interface = self.get_option('prefered_container_network_interface') self.prefered_instance_network_interface = self.get_option('prefered_instance_network_interface')
self.type_filter = self.get_option('type_filter')
if self.get_option('state').lower() == 'none': # none in config is str() if self.get_option('state').lower() == 'none': # none in config is str()
self.filter = None self.filter = None
else: else:

View file

@ -1,7 +1,7 @@
{ {
"containers":{ "instances":{
"vlantest":{ "vlantest":{
"containers":{ "instances":{
"metadata":{ "metadata":{
"config":{ "config":{
"image.os":"ubuntu", "image.os":"ubuntu",

View file

@ -43,10 +43,11 @@ def inventory():
# Test Values # Test Values
inv.data = inv.load_json_data('tests/unit/plugins/inventory/fixtures/lxd_inventory.atd') # Load Test Data inv.data = inv.load_json_data('tests/unit/plugins/inventory/fixtures/lxd_inventory.atd') # Load Test Data
inv.groupby = GROUP_Config inv.groupby = GROUP_Config
inv.prefered_container_network_interface = 'eth' inv.prefered_instance_network_interface = 'eth'
inv.prefered_container_network_family = 'inet' inv.prefered_instance_network_family = 'inet'
inv.filter = 'running' inv.filter = 'running'
inv.dump_data = False inv.dump_data = False
inv.type_filter = 'both'
return inv return inv