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:
Brian Coca 2021-02-12 11:14:50 -05:00 committed by GitHub
parent 30a4ef4414
commit ea2f37d253
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 81 additions and 11 deletions

View file

@ -0,0 +1,2 @@
minor_changes:
- The constructed inventory plugin has new option to force using vars plugins on previouslly processed inventory sources.

View file

@ -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):

View file

@ -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):

View file

@ -381,11 +381,12 @@ 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):
variables = combine_vars(variables, self.inventory.get_host(host).get_vars()) if fetch_hostvars:
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:
conditional = "{%% if %s %%} True {%% else %%} False {%% endif %%}" % groups[group_name] conditional = "{%% if %s %%} True {%% else %%} False {%% endif %%}" % groups[group_name]
@ -403,13 +404,14 @@ 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):
variables = combine_vars(variables, self.inventory.get_host(host).get_vars()) if fetch_hostvars:
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)
except Exception as e: except Exception as e:

View file

@ -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)

View file

@ -0,0 +1 @@
iamdefined: group4testing

View file

@ -0,0 +1 @@
hola: lola

View file

@ -0,0 +1,5 @@
all:
children:
stuff:
hosts:
testing:

View file

@ -0,0 +1,7 @@
plugin: constructed
use_vars_plugins: true
keyed_groups:
- key: iamdefined
prefix: c
- key: hola
prefix: c

View file

@ -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