allow constructed to use vars plugin (#73418)
Allow constructed to optionally use vars plugin data * mostly for those looking to leverage group_vars/ and host_vars/ * limited to already processed sources
This commit is contained in:
parent
30a4ef4414
commit
ea2f37d253
10 changed files with 81 additions and 11 deletions
2
changelogs/fragments/constructed_vars_plugins.yml
Normal file
2
changelogs/fragments/constructed_vars_plugins.yml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
minor_changes:
|
||||||
|
- The constructed inventory plugin has new option to force using vars plugins on previouslly processed inventory sources.
|
|
@ -51,6 +51,7 @@ class InventoryData(object):
|
||||||
self.localhost = None
|
self.localhost = None
|
||||||
|
|
||||||
self.current_source = None
|
self.current_source = None
|
||||||
|
self.processed_sources = []
|
||||||
|
|
||||||
# Always create the 'all' and 'ungrouped' groups,
|
# Always create the 'all' and 'ungrouped' groups,
|
||||||
for group in ('all', 'ungrouped'):
|
for group in ('all', 'ungrouped'):
|
||||||
|
@ -64,6 +65,7 @@ class InventoryData(object):
|
||||||
'hosts': self.hosts,
|
'hosts': self.hosts,
|
||||||
'local': self.localhost,
|
'local': self.localhost,
|
||||||
'source': self.current_source,
|
'source': self.current_source,
|
||||||
|
'processed_sources': self.processed_sources
|
||||||
}
|
}
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
@ -73,6 +75,7 @@ class InventoryData(object):
|
||||||
self.groups = data.get('groups')
|
self.groups = data.get('groups')
|
||||||
self.localhost = data.get('local')
|
self.localhost = data.get('local')
|
||||||
self.current_source = data.get('source')
|
self.current_source = data.get('source')
|
||||||
|
self.processed_sources = data.get('processed_sources')
|
||||||
|
|
||||||
def _create_implicit_localhost(self, pattern):
|
def _create_implicit_localhost(self, pattern):
|
||||||
|
|
||||||
|
|
|
@ -307,7 +307,9 @@ class InventoryManager(object):
|
||||||
else:
|
else:
|
||||||
display.vvv("%s declined parsing %s as it did not pass its verify_file() method" % (plugin_name, source))
|
display.vvv("%s declined parsing %s as it did not pass its verify_file() method" % (plugin_name, source))
|
||||||
|
|
||||||
if not parsed:
|
if parsed:
|
||||||
|
self._inventory.processed_sources.append(self._inventory.current_source)
|
||||||
|
else:
|
||||||
# only warn/error if NOT using the default or using it and the file is present
|
# only warn/error if NOT using the default or using it and the file is present
|
||||||
# TODO: handle 'non file' inventorya and detect vs hardcode default
|
# TODO: handle 'non file' inventorya and detect vs hardcode default
|
||||||
if source != '/etc/ansible/hosts' or os.path.exists(source):
|
if source != '/etc/ansible/hosts' or os.path.exists(source):
|
||||||
|
|
|
@ -381,10 +381,11 @@ class Constructable(object):
|
||||||
continue
|
continue
|
||||||
self.inventory.set_variable(host, varname, composite)
|
self.inventory.set_variable(host, varname, composite)
|
||||||
|
|
||||||
def _add_host_to_composed_groups(self, groups, variables, host, strict=False):
|
def _add_host_to_composed_groups(self, groups, variables, host, strict=False, fetch_hostvars=True):
|
||||||
''' helper to create complex groups for plugins based on jinja2 conditionals, hosts that meet the conditional are added to group'''
|
''' helper to create complex groups for plugins based on jinja2 conditionals, hosts that meet the conditional are added to group'''
|
||||||
# process each 'group entry'
|
# process each 'group entry'
|
||||||
if groups and isinstance(groups, dict):
|
if groups and isinstance(groups, dict):
|
||||||
|
if fetch_hostvars:
|
||||||
variables = combine_vars(variables, self.inventory.get_host(host).get_vars())
|
variables = combine_vars(variables, self.inventory.get_host(host).get_vars())
|
||||||
self.templar.available_variables = variables
|
self.templar.available_variables = variables
|
||||||
for group_name in groups:
|
for group_name in groups:
|
||||||
|
@ -403,12 +404,13 @@ class Constructable(object):
|
||||||
# add host to group
|
# add host to group
|
||||||
self.inventory.add_child(group_name, host)
|
self.inventory.add_child(group_name, host)
|
||||||
|
|
||||||
def _add_host_to_keyed_groups(self, keys, variables, host, strict=False):
|
def _add_host_to_keyed_groups(self, keys, variables, host, strict=False, fetch_hostvars=True):
|
||||||
''' helper to create groups for plugins based on variable values and add the corresponding hosts to it'''
|
''' helper to create groups for plugins based on variable values and add the corresponding hosts to it'''
|
||||||
if keys and isinstance(keys, list):
|
if keys and isinstance(keys, list):
|
||||||
for keyed in keys:
|
for keyed in keys:
|
||||||
if keyed and isinstance(keyed, dict):
|
if keyed and isinstance(keyed, dict):
|
||||||
|
|
||||||
|
if fetch_hostvars:
|
||||||
variables = combine_vars(variables, self.inventory.get_host(host).get_vars())
|
variables = combine_vars(variables, self.inventory.get_host(host).get_vars())
|
||||||
try:
|
try:
|
||||||
key = self._compose(keyed.get('key'), variables)
|
key = self._compose(keyed.get('key'), variables)
|
||||||
|
|
|
@ -19,6 +19,16 @@ DOCUMENTATION = '''
|
||||||
description: token that ensures this is a source file for the 'constructed' plugin.
|
description: token that ensures this is a source file for the 'constructed' plugin.
|
||||||
required: True
|
required: True
|
||||||
choices: ['constructed']
|
choices: ['constructed']
|
||||||
|
use_vars_plugins:
|
||||||
|
description:
|
||||||
|
- Normally, for performance reasons, vars plugins get executed after the inventory sources complete the base inventory,
|
||||||
|
this option allows for getting vars related to hosts/groups from those plugins.
|
||||||
|
- The host_group_vars (enabled by default) 'vars plugin' is the one responsible for reading host_vars/ and group_vars/ directories.
|
||||||
|
- This will not execute plugins that are not supposed to execute at the 'inventory' stage, see vars plugins docs for details.
|
||||||
|
required: false
|
||||||
|
default: false
|
||||||
|
type: boolean
|
||||||
|
version_added: '2.11'
|
||||||
extends_documentation_fragment:
|
extends_documentation_fragment:
|
||||||
- constructed
|
- constructed
|
||||||
'''
|
'''
|
||||||
|
@ -70,12 +80,13 @@ EXAMPLES = r'''
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from ansible import constants as C
|
from ansible import constants as C
|
||||||
from ansible.errors import AnsibleParserError
|
from ansible.errors import AnsibleParserError, AnsibleOptionsError
|
||||||
from ansible.inventory.helpers import get_group_vars
|
from ansible.inventory.helpers import get_group_vars
|
||||||
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable
|
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable
|
||||||
from ansible.module_utils._text import to_native
|
from ansible.module_utils._text import to_native
|
||||||
from ansible.utils.vars import combine_vars
|
from ansible.utils.vars import combine_vars
|
||||||
from ansible.vars.fact_cache import FactCache
|
from ansible.vars.fact_cache import FactCache
|
||||||
|
from ansible.vars.plugins import get_vars_from_inventory_sources
|
||||||
|
|
||||||
|
|
||||||
class InventoryModule(BaseInventoryPlugin, Constructable):
|
class InventoryModule(BaseInventoryPlugin, Constructable):
|
||||||
|
@ -100,6 +111,28 @@ class InventoryModule(BaseInventoryPlugin, Constructable):
|
||||||
|
|
||||||
return valid
|
return valid
|
||||||
|
|
||||||
|
def get_all_host_vars(self, host, loader, sources):
|
||||||
|
''' requires host object '''
|
||||||
|
return combine_vars(self.host_groupvars(host, loader, sources), self.host_vars(host, loader, sources))
|
||||||
|
|
||||||
|
def host_groupvars(self, host, loader, sources):
|
||||||
|
''' requires host object '''
|
||||||
|
gvars = get_group_vars(host.get_groups())
|
||||||
|
|
||||||
|
if self.get_option('use_vars_plugins'):
|
||||||
|
gvars = combine_vars(gvars, get_vars_from_inventory_sources(loader, sources, host.get_groups(), 'all'))
|
||||||
|
|
||||||
|
return gvars
|
||||||
|
|
||||||
|
def host_vars(self, host, loader, sources):
|
||||||
|
''' requires host object '''
|
||||||
|
hvars = host.get_vars()
|
||||||
|
|
||||||
|
if self.get_option('use_vars_plugins'):
|
||||||
|
hvars = combine_vars(hvars, get_vars_from_inventory_sources(loader, sources, [host], 'all'))
|
||||||
|
|
||||||
|
return hvars
|
||||||
|
|
||||||
def parse(self, inventory, loader, path, cache=False):
|
def parse(self, inventory, loader, path, cache=False):
|
||||||
''' parses the inventory file '''
|
''' parses the inventory file '''
|
||||||
|
|
||||||
|
@ -107,6 +140,13 @@ class InventoryModule(BaseInventoryPlugin, Constructable):
|
||||||
|
|
||||||
self._read_config_data(path)
|
self._read_config_data(path)
|
||||||
|
|
||||||
|
sources = []
|
||||||
|
try:
|
||||||
|
sources = inventory.processed_sources
|
||||||
|
except AttributeError:
|
||||||
|
if self.get_option('use_vars_plugins'):
|
||||||
|
raise AnsibleOptionsError("The option use_vars_plugins requires ansible >= 2.11.")
|
||||||
|
|
||||||
strict = self.get_option('strict')
|
strict = self.get_option('strict')
|
||||||
fact_cache = FactCache()
|
fact_cache = FactCache()
|
||||||
try:
|
try:
|
||||||
|
@ -114,7 +154,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable):
|
||||||
for host in inventory.hosts:
|
for host in inventory.hosts:
|
||||||
|
|
||||||
# get available variables to templar
|
# get available variables to templar
|
||||||
hostvars = combine_vars(get_group_vars(inventory.hosts[host].get_groups()), inventory.hosts[host].get_vars())
|
hostvars = self.get_all_host_vars(inventory.hosts[host], loader, sources)
|
||||||
if host in fact_cache: # adds facts if cache is active
|
if host in fact_cache: # adds facts if cache is active
|
||||||
hostvars = combine_vars(hostvars, fact_cache[host])
|
hostvars = combine_vars(hostvars, fact_cache[host])
|
||||||
|
|
||||||
|
@ -122,15 +162,15 @@ class InventoryModule(BaseInventoryPlugin, Constructable):
|
||||||
self._set_composite_vars(self.get_option('compose'), hostvars, host, strict=strict)
|
self._set_composite_vars(self.get_option('compose'), hostvars, host, strict=strict)
|
||||||
|
|
||||||
# refetch host vars in case new ones have been created above
|
# refetch host vars in case new ones have been created above
|
||||||
hostvars = combine_vars(get_group_vars(inventory.hosts[host].get_groups()), inventory.hosts[host].get_vars())
|
hostvars = self.get_all_host_vars(inventory.hosts[host], loader, sources)
|
||||||
if host in self._cache: # adds facts if cache is active
|
if host in self._cache: # adds facts if cache is active
|
||||||
hostvars = combine_vars(hostvars, self._cache[host])
|
hostvars = combine_vars(hostvars, self._cache[host])
|
||||||
|
|
||||||
# constructed groups based on conditionals
|
# constructed groups based on conditionals
|
||||||
self._add_host_to_composed_groups(self.get_option('groups'), hostvars, host, strict=strict)
|
self._add_host_to_composed_groups(self.get_option('groups'), hostvars, host, strict=strict, fetch_hostvars=False)
|
||||||
|
|
||||||
# constructed groups based variable values
|
# constructed groups based variable values
|
||||||
self._add_host_to_keyed_groups(self.get_option('keyed_groups'), hostvars, host, strict=strict)
|
self._add_host_to_keyed_groups(self.get_option('keyed_groups'), hostvars, host, strict=strict, fetch_hostvars=False)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise AnsibleParserError("failed to parse %s: %s " % (to_native(path), to_native(e)))
|
raise AnsibleParserError("failed to parse %s: %s " % (to_native(path), to_native(e)), orig_exc=e)
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
iamdefined: group4testing
|
|
@ -0,0 +1 @@
|
||||||
|
hola: lola
|
|
@ -0,0 +1,5 @@
|
||||||
|
all:
|
||||||
|
children:
|
||||||
|
stuff:
|
||||||
|
hosts:
|
||||||
|
testing:
|
|
@ -0,0 +1,7 @@
|
||||||
|
plugin: constructed
|
||||||
|
use_vars_plugins: true
|
||||||
|
keyed_groups:
|
||||||
|
- key: iamdefined
|
||||||
|
prefix: c
|
||||||
|
- key: hola
|
||||||
|
prefix: c
|
|
@ -23,3 +23,10 @@ grep '@key0separatorvalue0' out.txt
|
||||||
grep '@prefix_hostvalue1' out.txt
|
grep '@prefix_hostvalue1' out.txt
|
||||||
grep '@prefix_item0' out.txt
|
grep '@prefix_item0' out.txt
|
||||||
grep '@prefix_key0_value0' out.txt
|
grep '@prefix_key0_value0' out.txt
|
||||||
|
|
||||||
|
|
||||||
|
# test using use_vars_plugins
|
||||||
|
ansible-inventory -i invs/1/one.yml -i invs/2/constructed.yml --graph | tee out.txt
|
||||||
|
|
||||||
|
grep '@c_lola' out.txt
|
||||||
|
grep '@c_group4testing' out.txt
|
||||||
|
|
Loading…
Reference in a new issue