1# Copyright (c) 2017 Ansible Project 2# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 3 4from __future__ import (absolute_import, division, print_function) 5__metaclass__ = type 6 7DOCUMENTATION = ''' 8 name: generator 9 plugin_type: inventory 10 version_added: "2.6" 11 short_description: Uses Jinja2 to construct hosts and groups from patterns 12 description: 13 - Uses a YAML configuration file with a valid YAML or C(.config) extension to define var expressions and group conditionals 14 - Create a template pattern that describes each host, and then use independent configuration layers 15 - Every element of every layer is combined to create a host for every layer combination 16 - Parent groups can be defined with reference to hosts and other groups using the same template variables 17 options: 18 plugin: 19 description: token that ensures this is a source file for the 'generator' plugin. 20 required: True 21 choices: ['generator'] 22 hosts: 23 description: 24 - The C(name) key is a template used to generate 25 hostnames based on the C(layers) option. Each variable in the name is expanded to create a 26 cartesian product of all possible layer combinations. 27 - The C(parents) are a list of parent groups that the host belongs to. Each C(parent) item 28 contains a C(name) key, again expanded from the template, and an optional C(parents) key 29 that lists its parents. 30 - Parents can also contain C(vars), which is a dictionary of vars that 31 is then always set for that variable. This can provide easy access to the group name. E.g 32 set an C(application) variable that is set to the value of the C(application) layer name. 33 layers: 34 description: 35 - A dictionary of layers, with the key being the layer name, used as a variable name in the C(host) 36 C(name) and C(parents) keys. Each layer value is a list of possible values for that layer. 37''' 38 39EXAMPLES = ''' 40 # inventory.config file in YAML format 41 # remember to enable this inventory plugin in the ansible.cfg before using 42 # View the output using `ansible-inventory -i inventory.config --list` 43 plugin: generator 44 hosts: 45 name: "{{ operation }}_{{ application }}_{{ environment }}_runner" 46 parents: 47 - name: "{{ operation }}_{{ application }}_{{ environment }}" 48 parents: 49 - name: "{{ operation }}_{{ application }}" 50 parents: 51 - name: "{{ operation }}" 52 - name: "{{ application }}" 53 - name: "{{ application }}_{{ environment }}" 54 parents: 55 - name: "{{ application }}" 56 vars: 57 application: "{{ application }}" 58 - name: "{{ environment }}" 59 vars: 60 environment: "{{ environment }}" 61 - name: runner 62 layers: 63 operation: 64 - build 65 - launch 66 environment: 67 - dev 68 - test 69 - prod 70 application: 71 - web 72 - api 73''' 74 75import os 76 77from itertools import product 78 79from ansible import constants as C 80from ansible.errors import AnsibleParserError 81from ansible.plugins.inventory import BaseInventoryPlugin 82 83 84class InventoryModule(BaseInventoryPlugin): 85 """ constructs groups and vars using Jinja2 template expressions """ 86 87 NAME = 'generator' 88 89 def __init__(self): 90 91 super(InventoryModule, self).__init__() 92 93 def verify_file(self, path): 94 95 valid = False 96 if super(InventoryModule, self).verify_file(path): 97 file_name, ext = os.path.splitext(path) 98 99 if not ext or ext in ['.config'] + C.YAML_FILENAME_EXTENSIONS: 100 valid = True 101 102 return valid 103 104 def template(self, pattern, variables): 105 self.templar.available_variables = variables 106 return self.templar.do_template(pattern) 107 108 def add_parents(self, inventory, child, parents, template_vars): 109 for parent in parents: 110 try: 111 groupname = self.template(parent['name'], template_vars) 112 except (AttributeError, ValueError): 113 raise AnsibleParserError("Element %s has a parent with no name element" % child['name']) 114 if groupname not in inventory.groups: 115 inventory.add_group(groupname) 116 group = inventory.groups[groupname] 117 for (k, v) in parent.get('vars', {}).items(): 118 group.set_variable(k, self.template(v, template_vars)) 119 inventory.add_child(groupname, child) 120 self.add_parents(inventory, groupname, parent.get('parents', []), template_vars) 121 122 def parse(self, inventory, loader, path, cache=False): 123 ''' parses the inventory file ''' 124 125 super(InventoryModule, self).parse(inventory, loader, path, cache=cache) 126 127 config = self._read_config_data(path) 128 129 template_inputs = product(*config['layers'].values()) 130 for item in template_inputs: 131 template_vars = dict() 132 for i, key in enumerate(config['layers'].keys()): 133 template_vars[key] = item[i] 134 host = self.template(config['hosts']['name'], template_vars) 135 inventory.add_host(host) 136 self.add_parents(inventory, host, config['hosts'].get('parents', []), template_vars) 137