From a897193bcee28e3698362e8b47cebc53f585dd61 Mon Sep 17 00:00:00 2001 From: Brian Coca Date: Mon, 21 Aug 2017 16:06:15 -0400 Subject: 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 ++++- lib/ansible/plugins/inventory/constructed.py | 116 +++++++++++++++++++++ .../plugins/inventory/constructed_groups.py | 111 -------------------- lib/ansible/plugins/inventory/virtualbox.py | 12 ++- 6 files changed, 151 insertions(+), 117 deletions(-) create mode 100644 lib/ansible/plugins/inventory/constructed.py delete mode 100644 lib/ansible/plugins/inventory/constructed_groups.py mode change 100755 => 100644 lib/ansible/plugins/inventory/virtualbox.py diff --git a/CHANGELOG.md b/CHANGELOG.md index f084bf2fc4..27f1d31bd5 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 d6892b2bac..7abae073f2 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 ddad67861f..658d38168f 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.py b/lib/ansible/plugins/inventory/constructed.py new file mode 100644 index 0000000000..52a6769577 --- /dev/null +++ b/lib/ansible/plugins/inventory/constructed.py @@ -0,0 +1,116 @@ +# Copyright 2017 RedHat, inc +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +############################################# +''' +DOCUMENTATION: + name: constructed + plugin_type: inventory + version_added: "2.4" + short_description: Uses Jinja2 to construct vars and groups based on existing inventory. + description: + - 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: comstructed + compose: + var_sum: var1 + var2 + groups: + # simple name matching + webservers: inventory_hostname.startswith('web') + + # using ec2 'tags' (assumes aws inventory) + development: "'devel' in (ec2_tags|list)" + + # using other host properties populated in inventory + private_only: not (public_dns_name is defined or ip_address is defined) + + # complex group membership + multi_group: (group_names|intersection(['alpha', 'beta', 'omega']))|length >= 2 +''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os + +from ansible.errors import AnsibleParserError +from ansible.plugins.inventory import BaseInventoryPlugin +from ansible.module_utils._text import to_native +from ansible.utils.vars import combine_vars + + +class InventoryModule(BaseInventoryPlugin): + """ constructs groups and vars using Jinaj2 template expressions """ + + NAME = 'constructed' + + def __init__(self): + + super(InventoryModule, self).__init__() + + def verify_file(self, path): + + valid = False + if super(InventoryModule, self).verify_file(path): + file_name, ext = os.path.splitext(path) + + if not ext or ext == '.config': + valid = True + + return valid + + def parse(self, inventory, loader, path, cache=False): + ''' parses the inventory file ''' + + super(InventoryModule, self).parse(inventory, loader, path, cache=True) + + try: + data = self.loader.load_from_file(path) + except Exception as e: + raise AnsibleParserError("Unable to parse %s: %s" % (to_native(path), to_native(e))) + + if not data or data.get('plugin') != self.NAME: + raise AnsibleParserError("%s is empty or not a constructed groups config file" % (to_native(path))) + + try: + # Go over hosts (less var copies) + for host in inventory.hosts: + + # get available variables to templar + hostvars = inventory.hosts[host].get_vars() + if host in inventory.cache: # adds facts if cache is active + hostvars = combine_vars(hostvars, inventory.cache[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/constructed_groups.py b/lib/ansible/plugins/inventory/constructed_groups.py deleted file mode 100644 index 302b289bdb..0000000000 --- a/lib/ansible/plugins/inventory/constructed_groups.py +++ /dev/null @@ -1,111 +0,0 @@ -# Copyright 2017 RedHat, inc -# -# This file is part of Ansible -# -# Ansible is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Ansible is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Ansible. If not, see . -############################################# -''' -DOCUMENTATION: - name: constructed_groups - plugin_type: inventory - version_added: "2.4" - short_description: Uses Jinja2 expressions to construct groups. - 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). - - Failed expressions will be ignored (assumes vars were missing). -EXAMPLES: | # inventory.config file in YAML format - plugin: constructed_groups - groups: - # simple name matching - webservers: inventory_hostname.startswith('web') - - # using ec2 'tags' (assumes aws inventory) - development: "'devel' in (ec2_tags|list)" - - # using other host properties populated in inventory - private_only: not (public_dns_name is defined or ip_address is defined) - - # complex group membership - multi_group: (group_names|intersection(['alpha', 'beta', 'omega']))|length >= 2 -''' - -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -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 """ - - NAME = 'constructed_groups' - - def __init__(self): - - super(InventoryModule, self).__init__() - - def verify_file(self, path): - - valid = False - if super(InventoryModule, self).verify_file(path): - file_name, ext = os.path.splitext(path) - - if not ext or ext == '.config': - valid = True - - return valid - - def parse(self, inventory, loader, path, cache=False): - ''' parses the inventory file ''' - - super(InventoryModule, self).parse(inventory, loader, path, cache=True) - - try: - data = self.loader.load_from_file(path) - except Exception as e: - raise AnsibleParserError("Unable to parse %s: %s" % (to_native(path), to_native(e))) - - if not data or data.get('plugin') != self.NAME: - 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: - - # get available variables to templar - 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) - 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 0dab7c4253..98013e7d22 --- 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 = '' -- cgit v1.2.1