Files
offline_kubespray/collection/community/digitalocean/plugins/inventory/digitalocean.py
ByeonJungHun 360c6eef4a offline 작업
2024-02-19 16:02:29 +09:00

272 lines
8.7 KiB
Python

# -*- coding: utf-8 -*-
# Copyright: (c), Ansible Project
# 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
__metaclass__ = type
DOCUMENTATION = r"""
name: digitalocean
author:
- Janos Gerzson (@grzs)
- Tadej Borovšak (@tadeboro)
- Max Truxa (@maxtruxa)
short_description: DigitalOcean Inventory Plugin
version_added: "1.1.0"
description:
- DigitalOcean (DO) inventory plugin.
- Acquires droplet list from DO API.
- Uses configuration file that ends with '(do_hosts|digitalocean|digital_ocean).(yaml|yml)'.
extends_documentation_fragment:
- community.digitalocean.digital_ocean.documentation
- constructed
- inventory_cache
options:
plugin:
description:
- The name of the DigitalOcean Inventory Plugin,
this should always be C(community.digitalocean.digitalocean).
required: true
choices: ['community.digitalocean.digitalocean']
api_token:
description:
- DigitalOcean OAuth token.
- Template expressions can be used in this field.
required: true
type: str
aliases: [ oauth_token ]
env:
- name: DO_API_TOKEN
attributes:
description: >-
Droplet attributes to add as host vars to each inventory host.
Check out the DO API docs for full list of attributes at
U(https://docs.digitalocean.com/reference/api/api-reference/#operation/list_all_droplets).
type: list
elements: str
default:
- id
- name
- networks
- region
- size_slug
var_prefix:
description:
- Prefix of generated varible names (e.g. C(tags) -> C(do_tags))
type: str
default: 'do_'
pagination:
description:
- Maximum droplet objects per response page.
- If the number of droplets related to the account exceeds this value,
the query will be broken to multiple requests (pages).
- DigitalOcean currently allows a maximum of 200.
type: int
default: 200
filters:
description:
- Filter hosts with Jinja templates.
- If no filters are specified, all hosts are added to the inventory.
type: list
elements: str
default: []
version_added: '1.5.0'
"""
EXAMPLES = r"""
# Using keyed groups and compose for hostvars
plugin: community.digitalocean.digitalocean
api_token: '{{ lookup("pipe", "./get-do-token.sh" }}'
attributes:
- id
- name
- memory
- vcpus
- disk
- size
- image
- networks
- volume_ids
- tags
- region
keyed_groups:
- key: do_region.slug
prefix: 'region'
separator: '_'
- key: do_tags | lower
prefix: ''
separator: ''
compose:
ansible_host: do_networks.v4 | selectattr('type','eq','public')
| map(attribute='ip_address') | first
class: do_size.description | lower
distro: do_image.distribution | lower
filters:
- '"kubernetes" in do_tags'
- 'do_region.slug == "fra1"'
"""
import re
import json
from ansible.errors import AnsibleError, AnsibleParserError
from ansible.inventory.group import to_safe_group_name
from ansible.module_utils._text import to_native
from ansible.module_utils.urls import Request
from ansible.module_utils.six.moves.urllib.error import URLError, HTTPError
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable
class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
NAME = "community.digitalocean.digitalocean"
# Constructable methods use the following function to construct group names. By
# default, characters that are not valid in python variables, are always replaced by
# underscores. We are overriding this with a function that respects the
# TRANSFORM_INVALID_GROUP_CHARS configuration option and allows users to control the
# behavior.
_sanitize_group_name = staticmethod(to_safe_group_name)
def verify_file(self, path):
valid = False
if super(InventoryModule, self).verify_file(path):
if path.endswith(
(
"do_hosts.yaml",
"do_hosts.yml",
"digitalocean.yaml",
"digitalocean.yml",
"digital_ocean.yaml",
"digital_ocean.yml",
)
):
valid = True
else:
self.display.vvv(
"Skipping due to inventory source file name mismatch. "
"The file name has to end with one of the following: "
"do_hosts.yaml, do_hosts.yml "
"digitalocean.yaml, digitalocean.yml, "
"digital_ocean.yaml, digital_ocean.yml."
)
return valid
def _template_option(self, option):
value = self.get_option(option)
self.templar.available_variables = {}
return self.templar.template(value)
def _get_payload(self):
# request parameters
api_token = self._template_option("api_token")
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer {0}".format(api_token),
}
# build url
pagination = self.get_option("pagination")
url = "https://api.digitalocean.com/v2/droplets?per_page=" + str(pagination)
# send request(s)
self.req = Request(headers=headers, timeout=self.get_option("timeout"))
payload = []
try:
while url:
self.display.vvv("Sending request to {0}".format(url))
response = json.load(self.req.get(url))
payload.extend(response["droplets"])
url = response.get("links", {}).get("pages", {}).get("next")
except ValueError:
raise AnsibleParserError("something went wrong with JSON loading")
except (URLError, HTTPError) as error:
raise AnsibleParserError(error)
return payload
def _populate(self, records):
attributes = self.get_option("attributes")
var_prefix = self.get_option("var_prefix")
strict = self.get_option("strict")
host_filters = self.get_option("filters")
for record in records:
host_name = record.get("name")
if not host_name:
continue
host_vars = {}
for k, v in record.items():
if k in attributes:
host_vars[var_prefix + k] = v
if not self._passes_filters(host_filters, host_vars, host_name, strict):
self.display.vvv("Host {0} did not pass all filters".format(host_name))
continue
# add host to inventory
self.inventory.add_host(host_name)
# set variables for host
for k, v in host_vars.items():
self.inventory.set_variable(host_name, k, v)
self._set_composite_vars(
self.get_option("compose"),
self.inventory.get_host(host_name).get_vars(),
host_name,
strict,
)
# set composed and keyed groups
self._add_host_to_composed_groups(
self.get_option("groups"), dict(), host_name, strict
)
self._add_host_to_keyed_groups(
self.get_option("keyed_groups"), dict(), host_name, strict
)
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 host {1}: {2}".format(
template, host, to_native(e)
)
)
# Better be safe and not include any hosts by accident.
return False
return True
def parse(self, inventory, loader, path, cache=True):
super(InventoryModule, self).parse(inventory, loader, path)
self._read_config_data(path)
# cache settings
cache_key = self.get_cache_key(path)
use_cache = self.get_option("cache") and cache
update_cache = self.get_option("cache") and not cache
records = None
if use_cache:
try:
records = self._cache[cache_key]
except KeyError:
update_cache = True
if records is None:
records = self._get_payload()
if update_cache:
self._cache[cache_key] = records
self._populate(records)