From a897193bcee28e3698362e8b47cebc53f585dd61 Mon Sep 17 00:00:00 2001 From: Brian Coca Date: Mon, 21 Aug 2017 16:06:15 -0400 Subject: [PATCH] Moar constructive (#28254) * made composite vars and groups generic now you can do both in every plugin that chooses to suport it renamed constructed_groups as it now also constructs vars ... to constructed moved most of constructed_groups logic into base class to easily share * documented inventory_hostname * typo fix --- CHANGELOG.md | 2 +- examples/ansible.cfg | 2 +- lib/ansible/plugins/inventory/__init__.py | 25 +++++++++- .../{constructed_groups.py => constructed.py} | 47 ++++++++++--------- lib/ansible/plugins/inventory/virtualbox.py | 12 +++-- 5 files changed, 61 insertions(+), 27 deletions(-) rename lib/ansible/plugins/inventory/{constructed_groups.py => constructed.py} (73%) mode change 100755 => 100644 lib/ansible/plugins/inventory/virtualbox.py diff --git a/CHANGELOG.md b/CHANGELOG.md index f084bf2fc4c..27f1d31bd50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -134,7 +134,7 @@ Ansible Changes By Release #### New Inventory Plugins: - advanced_host_list -- constructed_groups +- constructed - host_list - ini - script diff --git a/examples/ansible.cfg b/examples/ansible.cfg index d6892b2bac0..7abae073f20 100644 --- a/examples/ansible.cfg +++ b/examples/ansible.cfg @@ -73,7 +73,7 @@ #callback_whitelist = timer, mail # enable inventory plugins, default: 'host_list', 'script', 'yaml', 'ini' -#inventory_enabled = host_list, aws, openstack, docker +#inventory_enabled = host_list, virtualbox, yaml, constructed # Determine whether includes in tasks and handlers are "static" by # default. As of 2.0, includes are dynamic by default. Setting these diff --git a/lib/ansible/plugins/inventory/__init__.py b/lib/ansible/plugins/inventory/__init__.py index ddad67861f4..658d38168f3 100644 --- a/lib/ansible/plugins/inventory/__init__.py +++ b/lib/ansible/plugins/inventory/__init__.py @@ -50,6 +50,7 @@ class BaseInventoryPlugin(object): self.loader = loader self.inventory = inventory + self.templar = Templar(loader=loader) def verify_file(self, path): ''' Verify if file is usable by this plugin, base does minimal accessability check ''' @@ -81,9 +82,31 @@ class BaseInventoryPlugin(object): def _compose(self, template, variables): ''' helper method for pluigns to compose variables for Ansible based on jinja2 expression and inventory vars''' - t = Templar(loader=self.loader, variables=variables) + t = self.templar + t.set_available_variables(variables) return t.do_template('%s%s%s' % (t.environment.variable_start_string, template, t.environment.variable_end_string), disable_lookups=True) + def _set_composite_vars(self, compose, variables, host): + ''' loops over compose entries to create vars for hosts ''' + if compose and isinstance(compose, dict): + for varname in compose: + composite = self._compose(compose[varname], variables) + self.inventory.set_variable(host, varname, composite) + + def _add_host_to_composed_groups(self, groups, variables, host): + ''' helper to create complex groups for plugins based on jinaj2 conditionals, hosts that meet the conditional are added to group''' + # process each 'group entry' + if groups and isinstance(groups, dict): + self.templar.set_available_variables(variables) + for group_name in groups: + conditional = "{%% if %s %%} True {%% else %%} False {%% endif %%}" % groups[group_name] + result = self.templar.template(conditional) + if result and bool(result): + # ensure group exists + self.inventory.add_group(group_name) + # add host to group + self.inventory.add_child(group_name, host) + class BaseFileInventoryPlugin(BaseInventoryPlugin): """ Parses a File based Inventory Source""" diff --git a/lib/ansible/plugins/inventory/constructed_groups.py b/lib/ansible/plugins/inventory/constructed.py similarity index 73% rename from lib/ansible/plugins/inventory/constructed_groups.py rename to lib/ansible/plugins/inventory/constructed.py index 302b289bdb2..52a67695773 100644 --- a/lib/ansible/plugins/inventory/constructed_groups.py +++ b/lib/ansible/plugins/inventory/constructed.py @@ -17,16 +17,28 @@ ############################################# ''' DOCUMENTATION: - name: constructed_groups + name: constructed plugin_type: inventory version_added: "2.4" - short_description: Uses Jinja2 expressions to construct groups. + short_description: Uses Jinja2 to construct vars and groups based on existing inventory. description: - - Uses a YAML configuration file to identify group and the Jinja2 expressions that qualify a host for membership. - - Only variables already in inventory are available for expressions (no facts). + - Uses a YAML configuration file to define var expresisions and group conditionals + - The Jinja2 conditionals that qualify a host for membership. + - The JInja2 exprpessions are calculated and assigned to the variables + - Only variables already available from previous inventories can be used for templating. - Failed expressions will be ignored (assumes vars were missing). + compose: + description: create vars from jinja2 expressions + type: dictionary + default: {} + groups: + description: add hosts to group based on Jinja2 conditionals + type: dictionary + default: {} EXAMPLES: | # inventory.config file in YAML format - plugin: constructed_groups + plugin: comstructed + compose: + var_sum: var1 + var2 groups: # simple name matching webservers: inventory_hostname.startswith('web') @@ -48,15 +60,14 @@ import os from ansible.errors import AnsibleParserError from ansible.plugins.inventory import BaseInventoryPlugin -from ansible.template import Templar from ansible.module_utils._text import to_native from ansible.utils.vars import combine_vars class InventoryModule(BaseInventoryPlugin): - """ constructs groups using Jinaj2 template expressions """ + """ constructs groups and vars using Jinaj2 template expressions """ - NAME = 'constructed_groups' + NAME = 'constructed' def __init__(self): @@ -87,8 +98,6 @@ class InventoryModule(BaseInventoryPlugin): raise AnsibleParserError("%s is empty or not a constructed groups config file" % (to_native(path))) try: - templar = Templar(loader=loader) - # Go over hosts (less var copies) for host in inventory.hosts: @@ -96,16 +105,12 @@ class InventoryModule(BaseInventoryPlugin): hostvars = inventory.hosts[host].get_vars() if host in inventory.cache: # adds facts if cache is active hostvars = combine_vars(hostvars, inventory.cache[host]) - templar.set_available_variables(hostvars) - - # process each 'group entry' - for group_name in data.get('groups', {}): - conditional = u"{%% if %s %%} True {%% else %%} False {%% endif %%}" % data['groups'][group_name] - result = templar.template(conditional) - if result and bool(result): - # ensure group exists - inventory.add_group(group_name) - # add host to group - inventory.add_child(group_name, host) + + # create composite vars + self._set_composite_vars(data.get('compose'), hostvars, host) + + # constructed groups based on conditionals + self._add_host_to_composed_groups(data.get('groups'), hostvars, host) + except Exception as e: raise AnsibleParserError("failed to parse %s: %s " % (to_native(path), to_native(e))) diff --git a/lib/ansible/plugins/inventory/virtualbox.py b/lib/ansible/plugins/inventory/virtualbox.py old mode 100755 new mode 100644 index 0dab7c4253c..98013e7d225 --- a/lib/ansible/plugins/inventory/virtualbox.py +++ b/lib/ansible/plugins/inventory/virtualbox.py @@ -24,6 +24,7 @@ DOCUMENTATION: description: - Get inventory hosts from the local virtualbox installation. - Uses a .vbox.yaml (or .vbox.yml) YAML configuration file. + - The inventory_hostname is always the 'Name' of the virtualbox instance. options: running_only: description: toggles showing all vms vs only those currently running @@ -42,6 +43,10 @@ DOCUMENTATION: description: create vars from jinja2 expressions, these are created AFTER the query block type: dictionary default: {} + groups: + description: add hosts to group based on Jinja2 conditionals, these also run after query block + type: dictionary + default: {} EXAMPLES: # file must be named vbox.yaml or vbox.yml simple_config_file: @@ -94,14 +99,15 @@ class InventoryModule(BaseInventoryPlugin): hostvars[host][varname] = self._query_vbox_data(host, data['query'][varname]) # create composite vars - if data.get('compose') and isinstance(data['compose'], dict): - for varname in data['compose']: - hostvars[host][varname] = self._compose(data['compose'][varname], hostvars[host]) + self._set_composite_vars(data.get('compose'), hostvars, host) # actually update inventory for key in hostvars[host]: self.inventory.set_variable(host, key, hostvars[host][key]) + # constructed groups based on conditionals + self._add_host_to_composed_groups(data.get('groups'), hostvars, host) + def _populate_from_source(self, source_data, config_data): hostvars = {} prevkey = pref_k = ''