diff --git a/lib/ansible/plugins/inventory/vultr.py b/lib/ansible/plugins/inventory/vultr.py index 2961ba95a2d..1c9c6018823 100644 --- a/lib/ansible/plugins/inventory/vultr.py +++ b/lib/ansible/plugins/inventory/vultr.py @@ -1,4 +1,5 @@ -# (c) 2018, Yanis Guenane +# Copyright (c) 2018, Yanis Guenane +# Copyright (c) 2019, René Moser # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import (absolute_import, division, print_function) @@ -9,27 +10,37 @@ DOCUMENTATION = r''' name: vultr plugin_type: inventory author: - - "Yanis Guenane (@Spredzy)" + - Yanis Guenane (@Spredzy) + - René Moser (@resmo) short_description: Vultr inventory source + version_added: "2.7" + extends_documentation_fragment: + - constructed description: - Get inventory hosts from Vultr public cloud. - - Uses C(api_config), C(~/.vultr.ini), C(./vultr.ini) or VULTR_API_CONFIG path to config file. + - Uses an YAML configuration file ending with either I(vultr.yml) or I(vultr.yaml) to set parameter values (also see examples). + - Uses I(api_config), I(~/.vultr.ini), I(./vultr.ini) or C(VULTR_API_CONFIG) pointing to a Vultr credentials INI file + (see U(https://docs.ansible.com/ansible/latest/scenario_guides/guide_vultr.html)). options: plugin: - description: token that ensures this is a source file for the 'vultr' plugin. + description: Token that ensures this is a source file for the 'vultr' plugin. + type: string required: True - choices: ['vultr'] + choices: [ vultr ] api_account: description: Specify the account to be used. + type: string default: default api_config: description: Path to the vultr configuration file. If not specified will be taken from regular Vultr configuration. + type: path env: - - name: VULTR_API_CONFIG + - name: VULTR_API_CONFIG api_key: description: Vultr API key. If not specified will be taken from regular Vultr configuration. + type: string env: - - name: VULTR_API_KEY + - name: VULTR_API_KEY hostname: description: Field to match the hostname. Note v4_main_ip corresponds to the main_ip field returned from the API and name to label. type: string @@ -38,23 +49,46 @@ DOCUMENTATION = r''' - v4_main_ip - v6_main_ip - name + filter_by_tag: + description: Only return servers filtered by this tag + type: string + version_added: "2.8" + strict: + version_added: "2.8" + compose: + version_added: "2.8" + groups: + version_added: "2.8" + keyed_groups: + version_added: "2.8" ''' EXAMPLES = r''' -# vultr_inventory.yml file in YAML format -# Example command line: ansible-inventory --list -i vultr_inventory.yml +# inventory_vultr.yml file in YAML format +# Example command line: ansible-inventory --list -i inventory_vultr.yml +# Group by a region as lower case and with prefix e.g. "vultr_region_amsterdam" and by OS without prefix e.g. "CentOS_7_x64" plugin: vultr +keyed_groups: + - prefix: vultr_region + key: region | lower + - separator: "" + key: os + +# Pass a tag filter to the API +plugin: vultr +filter_by_tag: Cache ''' import json from ansible.errors import AnsibleError -from ansible.plugins.inventory import BaseInventoryPlugin +from ansible.plugins.inventory import BaseInventoryPlugin, Constructable from ansible.module_utils.six.moves import configparser from ansible.module_utils.urls import open_url from ansible.module_utils._text import to_native from ansible.module_utils.vultr import Vultr, VULTR_API_ENDPOINT, VULTR_USER_AGENT +from ansible.module_utils.six.moves.urllib.parse import quote SCHEMA = { @@ -104,8 +138,10 @@ def _load_conf(path, account): return Vultr.read_ini_config(account) -def _retrieve_servers(api_key): +def _retrieve_servers(api_key, tag_filter=None): api_url = '%s/v1/server/list' % VULTR_API_ENDPOINT + if tag_filter is not None: + api_url = api_url + '?tag=%s' % quote(tag_filter) try: response = open_url( @@ -121,9 +157,17 @@ def _retrieve_servers(api_key): raise AnsibleError("Error while fetching %s: %s" % (api_url, to_native(e))) -class InventoryModule(BaseInventoryPlugin): +class InventoryModule(BaseInventoryPlugin, Constructable): + NAME = 'vultr' + def verify_file(self, path): + valid = False + if super(InventoryModule, self).verify_file(path): + if path.endswith(('vultr.yaml', 'vultr.yml')): + valid = True + return valid + def parse(self, inventory, loader, path, cache=True): super(InventoryModule, self).parse(inventory, loader, path) self._read_config_data(path=path) @@ -135,14 +179,32 @@ class InventoryModule(BaseInventoryPlugin): raise AnsibleError('Could not find an API key. Check inventory file and Vultr configuration files.') hostname_preference = self.get_option('hostname') - for server in _retrieve_servers(api_key): + + # Add a top group 'vultr' + self.inventory.add_group(group='vultr') + + # Filter by tag is supported by the api with a query + filter_by_tag = self.get_option('filter_by_tag') + for server in _retrieve_servers(api_key, filter_by_tag): + server = Vultr.normalize_result(server, SCHEMA) - for group in ['region', 'os']: - self.inventory.add_group(group=server[group]) - self.inventory.add_host(group=server[group], host=server['name']) + + self.inventory.add_host(host=server['name'], group='vultr') for attribute, value in server.items(): self.inventory.set_variable(server['name'], attribute, value) if hostname_preference != 'name': self.inventory.set_variable(server['name'], 'ansible_host', server[hostname_preference]) + + # Use constructed if applicable + strict = self.get_option('strict') + + # Composed variables + self._set_composite_vars(self.get_option('compose'), server, server['name'], strict=strict) + + # Complex groups based on jinja2 conditionals, hosts that meet the conditional are added to group + self._add_host_to_composed_groups(self.get_option('groups'), server, server['name'], strict=strict) + + # Create groups based on variable values and add the corresponding hosts to it + self._add_host_to_keyed_groups(self.get_option('keyed_groups'), server, server['name'], strict=strict)