diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:04:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:04:41 +0000 |
commit | 975f66f2eebe9dadba04f275774d4ab83f74cf25 (patch) | |
tree | 89bd26a93aaae6a25749145b7e4bca4a1e75b2be /ansible_collections/vultr/cloud/plugins/inventory | |
parent | Initial commit. (diff) | |
download | ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.tar.xz ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.zip |
Adding upstream version 7.7.0+dfsg.upstream/7.7.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/vultr/cloud/plugins/inventory')
-rw-r--r-- | ansible_collections/vultr/cloud/plugins/inventory/vultr.py | 348 |
1 files changed, 348 insertions, 0 deletions
diff --git a/ansible_collections/vultr/cloud/plugins/inventory/vultr.py b/ansible_collections/vultr/cloud/plugins/inventory/vultr.py new file mode 100644 index 000000000..c08446134 --- /dev/null +++ b/ansible_collections/vultr/cloud/plugins/inventory/vultr.py @@ -0,0 +1,348 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (c) jasites <jsites@vultr.com> +# Copyright: Contributors to the Ansible project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +# flake8: noqa: E402 + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + + +DOCUMENTATION = """ +--- +name: vultr +short_description: Retrieves list of instances via Vultr v2 API +description: + - Vultr inventory plugin. + - Retrieves list of instances via Vultr v2 API. + - Configuration of this plugin is done with files ending with '(vultr|vultr_hosts|vultr_instances).(yaml|yml)' +version_added: '1.4.0' +author: + - jasites (@jasites) +extends_documentation_fragment: + - constructed + - inventory_cache +options: + api_endpoint: + description: + - URL to API endpint (without trailing slash). + - Fallback environment variable C(VULTR_API_ENDPOINT). + type: str + env: + - name: VULTR_API_ENDPOINT + default: https://api.vultr.com/v2 + api_key: + description: + - API key of the Vultr API. + - Fallback environment variable C(VULTR_API_KEY). + type: str + env: + - name: VULTR_API_KEY + required: true + api_results_per_page: + description: + - When receiving large numbers of instances, specify how many instances should be returned per call to API. + - This does not determine how many results are returned; all instances are returned according to other filters. + - Vultr API maximum is 500. + - Fallback environment variable C(VULTR_API_RESULTS_PER_PAGE). + type: int + env: + - name: VULTR_API_RESULTS_PER_PAGE + default: 100 + api_timeout: + description: + - HTTP timeout to Vultr API. + - Fallback environment variable C(VULTR_API_TIMEOUT). + type: int + env: + - name: VULTR_API_TIMEOUT + default: 60 + attributes: + description: + - Instance attributes to add as host variables to each host added to inventory. + - See U(https://www.vultr.com/api/#operation/list-instances) for valid values. + type: list + elements: str + default: + - id + - region + - label + - plan + - hostname + - main_ip + - v6_main_ip + - tags + filters: + description: + - Filter hosts with Jinja2 templates. + - If not provided, all hosts are added to inventory. + type: list + elements: str + default: [] + instance_type: + description: + - Type of instance. + type: str + default: cloud + choices: + - cloud + - bare_metal + version_added: '1.8.0' + plugin: + description: + - Name of Vultr inventory plugin. + - This should always be C(vultr.cloud.vultr). + type: str + choices: ['vultr.cloud.vultr'] + required: true + variable_prefix: + description: + - Prefix of generated variables (e.g. C(id) becomes C(vultr_id)). + type: str + default: 'vultr_' + validate_certs: + description: + - Validate SSL certs of the Vultr API. + type: bool + default: true +notes: + - Also see the API documentation on U(https://www.vultr.com/api/). +""" + +EXAMPLES = """ +--- +# File endings vultr{,-{hosts,instances}}.y{,a}ml +# All configuration done via environment variables: +plugin: vultr.cloud.vultr + +# Grouping and filtering configuration in inventory file +plugin: vultr.cloud.vultr +api_key: '{{ lookup("pipe"), "./get_vultr_api_key.sh" }}' +keyed_groups: + - key: vultr_tags | lower + prefix: '' + separator: '' +filters: + - '"vpc" in vultr_tags' + - 'vultr_plan == "vc2-2c-4gb"' + +# Unless you can connect to your servers via it's vultr label, +# we suggest setting ansible_host with compose: +plugin: vultr.cloud.vultr +compose: + ansible_host: vultr_main_ip + +# Respectively for IPv6: +plugin: vultr.cloud.vultr +compose: + ansible_host: vultr_v6_main_ip + +# Prioritize IPv6 over IPv4 if available. +plugin: vultr.cloud.vultr +compose: + ansible_host: vultr_v6_main_ip or vultr_main_ip + +# Querying the bare metal instances +plugin: vultr.cloud.vultr +instance_type: bare_metal +""" + +RETURN = r""" # """ + +import json + +from ansible.errors import AnsibleError, AnsibleParserError +from ansible.module_utils._text import to_native +from ansible.module_utils.six.moves.urllib.error import HTTPError, URLError +from ansible.module_utils.urls import Request +from ansible.plugins.inventory import BaseInventoryPlugin, Cacheable, Constructable + +from ..module_utils.vultr_v2 import VULTR_USER_AGENT + + +class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable): + + NAME = "vultr.cloud.vultr" + + RESOURCES_PER_TYPE = { + "cloud": { + "resource": "instances", + "response": "instances", + }, + "bare_metal": { + "resource": "bare-metals", + "response": "bare_metals", + }, + } + + def _get_instances(self): + instances = [] + api_key = self.get_option("api_key") + if self.templar.is_template(api_key): + api_key = self.templar.template(api_key) + + headers = { + "Content-Type": "application/json", + "User-Agent": VULTR_USER_AGENT, + "Authorization": "Bearer {0}".format(api_key), + } + + self.req = Request( + headers=headers, + timeout=int(self.get_option("api_timeout")), # type: ignore + validate_certs=self.get_option("validate_certs"), # type: ignore + ) + + instance_type_config = self.get_option("instance_type") or "cloud" + self.display.vvv("Type is: {0}".format(instance_type_config)) + + instance_type = self.RESOURCES_PER_TYPE[instance_type_config] + + api_endpoint = "{0}/{1}?per_page={2}".format( + self.get_option("api_endpoint"), + instance_type["resource"], # type: ignore + self.get_option("api_results_per_page"), + ) + + cursor = "" + req_url = api_endpoint + try: + while True: + self.display.vvv("Querying API: {0}".format(req_url)) + + page = json.load(self.req.get(req_url)) + instances.extend(page[instance_type["response"]]) # type: ignore + cursor = page["meta"]["links"]["next"] + + if cursor == "": + return instances + + req_url = "{0}&cursor={1}".format(api_endpoint, cursor) + + except (KeyError, ValueError): + raise AnsibleParserError("Unable to parse JSON response.") + except (URLError, HTTPError) as err: + raise AnsibleParserError(err) + + def _populate(self, instances): + attributes = self.get_option("attributes") + host_filters = self.get_option("filters") + strict = self.get_option("strict") + variable_prefix = self.get_option("variable_prefix") + + for instance in instances: + instance_label = instance.get("label") + + if not instance_label: + self.display.warning(msg="instance ID {0} has no label, skipping.".format(instance.get("id"))) + continue + + host_variables = {} + for k, v in instance.items(): + if k in attributes: + host_variables["{0}{1}".format(variable_prefix, k)] = v + + if not self._passes_filters( + host_filters, + host_variables, + instance_label, + strict, # type: ignore + ): + self.display.vvv("Host {0} excluded by filters".format(instance_label)) + continue + + self.inventory.add_host(instance_label) # type: ignore + + for var_name, var_val in host_variables.items(): + self.inventory.set_variable(instance_label, var_name, var_val) # type: ignore + + self._set_composite_vars( + self.get_option("compose"), + self.inventory.get_host(instance_label).get_vars(), # type: ignore + instance_label, + strict, # type: ignore + ) + + self._add_host_to_composed_groups( + self.get_option("groups"), + dict(), + instance_label, + strict, # type: ignore + ) + + self._add_host_to_keyed_groups( + self.get_option("keyed_groups"), + dict(), + instance_label, + strict, # type: ignore + ) + + def _passes_filters(self, filters, variables, host, strict=False): + if filters and isinstance(filters, list): + for template in filters: + try: + if not self._compose(template, variables): + return False + except Exception as e: + if strict: + raise AnsibleError( + "Could not evaluate host filter {0} for {1}: {2}".format( + template, + host, + to_native(e), + ), + ) + return False + return True + + def verify_file(self, path): + valid = False + if super(InventoryModule, self).verify_file(path): + if path.endswith( + ( + "vultr.yaml", + "vultr.yml", + "vultr_hosts.yaml", + "vultr_hosts.yml", + "vultr_instances.yaml", + "vultr_instances.yml", + ) + ): + valid = True + else: + self.display.vvv( + "Skipping due to inventory configuration file name mismatch. " + "Valid filename endings: " + "vultr.yaml, vultr.yml, vultr_hosts.yaml, vultr_hosts.yml, " + "vultr_instances.yaml, vultr_instances.yml" + ) + return valid + + def parse(self, inventory, loader, path, cache=True): + super(InventoryModule, self).parse(inventory, loader, path) + + self._read_config_data(path) + + cache_key = self.get_cache_key(path) + use_cache = self.get_option("cache") and cache + update_cache = self.get_option("cache") and not cache + + instances = None + if use_cache: + try: + instances = self._cache[cache_key] + except KeyError: + update_cache = True + + if instances is None: + instances = self._get_instances() + + if update_cache: + self._cache[cache_key] = instances + + self._populate(instances) |