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 plugin: generator 42 strict: False 43 hosts: 44 name: "{{ operation }}-{{ application }}-{{ environment }}-runner" 45 parents: 46 - name: "{{ operation }}-{{ application }}-{{ environment }}" 47 parents: 48 - name: "{{ operation }}-{{ application }}" 49 parents: 50 - name: "{{ operation }}" 51 - name: "{{ application }}" 52 - name: "{{ application }}-{{ environment }}" 53 parents: 54 - name: "{{ application }}" 55 vars: 56 application: "{{ application }}" 57 - name: "{{ environment }}" 58 vars: 59 environment: "{{ environment }}" 60 - name: runner 61 layers: 62 operation: 63 - build 64 - launch 65 environment: 66 - dev 67 - test 68 - prod 69 application: 70 - web 71 - api 72''' 73 74import os 75 76from itertools import product 77 78from ansible import constants as C 79from ansible.errors import AnsibleParserError 80from ansible.plugins.inventory import BaseInventoryPlugin 81 82 83class InventoryModule(BaseInventoryPlugin): 84 """ constructs groups and vars using Jinja2 template expressions """ 85 86 NAME = 'generator' 87 88 def __init__(self): 89 90 super(InventoryModule, self).__init__() 91 92 def verify_file(self, path): 93 94 valid = False 95 if super(InventoryModule, self).verify_file(path): 96 file_name, ext = os.path.splitext(path) 97 98 if not ext or ext in ['.config'] + C.YAML_FILENAME_EXTENSIONS: 99 valid = True 100 101 return valid 102 103 def template(self, pattern, variables): 104 self.templar.available_variables = variables 105 return self.templar.do_template(pattern) 106 107 def add_parents(self, inventory, child, parents, template_vars): 108 for parent in parents: 109 try: 110 groupname = self.template(parent['name'], template_vars) 111 except (AttributeError, ValueError): 112 raise AnsibleParserError("Element %s has a parent with no name element" % child['name']) 113 if groupname not in inventory.groups: 114 inventory.add_group(groupname) 115 group = inventory.groups[groupname] 116 for (k, v) in parent.get('vars', {}).items(): 117 group.set_variable(k, self.template(v, template_vars)) 118 inventory.add_child(groupname, child) 119 self.add_parents(inventory, groupname, parent.get('parents', []), template_vars) 120 121 def parse(self, inventory, loader, path, cache=False): 122 ''' parses the inventory file ''' 123 124 super(InventoryModule, self).parse(inventory, loader, path, cache=cache) 125 126 config = self._read_config_data(path) 127 128 template_inputs = product(*config['layers'].values()) 129 for item in template_inputs: 130 template_vars = dict() 131 for i, key in enumerate(config['layers'].keys()): 132 template_vars[key] = item[i] 133 host = self.template(config['hosts']['name'], template_vars) 134 inventory.add_host(host) 135 self.add_parents(inventory, host, config['hosts'].get('parents', []), template_vars) 136