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 | |
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')
32 files changed, 5939 insertions, 0 deletions
diff --git a/ansible_collections/vultr/cloud/plugins/doc_fragments/__init__.py b/ansible_collections/vultr/cloud/plugins/doc_fragments/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/vultr/cloud/plugins/doc_fragments/__init__.py diff --git a/ansible_collections/vultr/cloud/plugins/doc_fragments/vultr_v2.py b/ansible_collections/vultr/cloud/plugins/doc_fragments/vultr_v2.py new file mode 100644 index 000000000..692103e8c --- /dev/null +++ b/ansible_collections/vultr/cloud/plugins/doc_fragments/vultr_v2.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2021 René Moser <mail@renemoser.net> +# 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 + + +class ModuleDocFragment(object): + + DOCUMENTATION = """ +options: + api_key: + description: + - API key of the Vultr API. + - Fallback environment variable C(VULTR_API_KEY). + type: str + required: true + api_timeout: + description: + - HTTP timeout to Vultr API. + - Fallback environment variable C(VULTR_API_TIMEOUT). + type: int + default: 180 + api_retries: + description: + - Amount of retries in case of the Vultr API retuns an HTTP 503 code. + - Fallback environment variable C(VULTR_API_RETRIES). + type: int + default: 5 + api_retry_max_delay: + description: + - Retry backoff delay in seconds is exponential up to this max. value, in seconds. + - Fallback environment variable C(VULTR_API_RETRY_MAX_DELAY). + type: int + default: 12 + api_endpoint: + description: + - URL to API endpint (without trailing slash). + - Fallback environment variable C(VULTR_API_ENDPOINT). + type: str + default: https://api.vultr.com/v2 + 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/). +""" 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) diff --git a/ansible_collections/vultr/cloud/plugins/module_utils/vultr_v2.py b/ansible_collections/vultr/cloud/plugins/module_utils/vultr_v2.py new file mode 100644 index 000000000..fb49d6180 --- /dev/null +++ b/ansible_collections/vultr/cloud/plugins/module_utils/vultr_v2.py @@ -0,0 +1,368 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, René Moser <mail@renemoser.net> +# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import random +import time + +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import env_fallback +from ansible.module_utils.six.moves.urllib.parse import quote +from ansible.module_utils.urls import fetch_url + +VULTR_USER_AGENT = "Ansible Vultr v2" + + +def vultr_argument_spec(): + return dict( + api_endpoint=dict( + type="str", + fallback=(env_fallback, ["VULTR_API_ENDPOINT"]), + default="https://api.vultr.com/v2", + ), + api_key=dict( + type="str", + fallback=(env_fallback, ["VULTR_API_KEY"]), + no_log=True, + required=True, + ), + api_timeout=dict( + type="int", + fallback=(env_fallback, ["VULTR_API_TIMEOUT"]), + default=180, + ), + api_retries=dict( + type="int", + fallback=(env_fallback, ["VULTR_API_RETRIES"]), + default=5, + ), + api_retry_max_delay=dict( + type="int", + fallback=(env_fallback, ["VULTR_API_RETRY_MAX_DELAY"]), + default=12, + ), + validate_certs=dict( + type="bool", + default=True, + ), + ) + + +def backoff(retry, retry_max_delay=12): + randomness = random.randint(0, 1000) / 1000.0 + delay = 2**retry + randomness + if delay > retry_max_delay: + delay = retry_max_delay + randomness + time.sleep(delay) + + +class AnsibleVultr: + def __init__( + self, + module, + namespace, + resource_path, + ressource_result_key_singular, + ressource_result_key_plural=None, + resource_key_name=None, + resource_key_id="id", + resource_get_details=False, + resource_create_param_keys=None, + resource_update_param_keys=None, + resource_update_method="PATCH", + ): + + self.module = module + self.namespace = namespace + + # The API resource path e.g ssh_key + self.ressource_result_key_singular = ressource_result_key_singular + + # The API result data key e.g ssh_keys + self.ressource_result_key_plural = ressource_result_key_plural or "%ss" % ressource_result_key_singular + + # The API resource path e.g /ssh-keys + self.resource_path = resource_path + + # The name key of the resource, usually 'name' + self.resource_key_name = resource_key_name + + # The name key of the resource, usually 'id' + self.resource_key_id = resource_key_id + + # Some resources need an additional GET request to get all attributes + self.resource_get_details = resource_get_details + + # List of params used to create the resource + self.resource_create_param_keys = resource_create_param_keys or [] + + # List of params used to update the resource + self.resource_update_param_keys = resource_update_param_keys or [] + + # Some resources have PUT, many have PATCH + self.resource_update_method = resource_update_method + + self.result = { + "changed": False, + namespace: dict(), + "diff": dict(before=dict(), after=dict()), + "vultr_api": { + "api_timeout": module.params["api_timeout"], + "api_retries": module.params["api_retries"], + "api_retry_max_delay": module.params["api_retry_max_delay"], + "api_endpoint": module.params["api_endpoint"], + }, + } + + self.headers = { + "Authorization": "Bearer %s" % self.module.params["api_key"], + "User-Agent": VULTR_USER_AGENT, + "Accept": "application/json", + } + + # Hook custom configurations + self.configure() + + def configure(self): + pass + + def transform_resource(self, resource): + """ + Transforms (optional) the resource dict queried from the API + """ + return resource + + def api_query(self, path, method="GET", data=None, query_params=None): + if query_params: + query = "?" + for k, v in query_params.items(): + query += "&%s=%s" % (to_text(k), quote(to_text(v))) + path += query + + if data: + data = self.module.jsonify(data) + + retry_max_delay = self.module.params["api_retry_max_delay"] + + info = dict() + resp_body = None + for retry in range(0, self.module.params["api_retries"]): + resp, info = fetch_url( + self.module, + self.module.params["api_endpoint"] + path, + method=method, + data=data, + headers=self.headers, + timeout=self.module.params["api_timeout"], + ) + + resp_body = resp.read() if resp is not None else "" + + # Check for: + # 429 Too Many Requests + # 500 Internal Server Error + if info["status"] not in (429, 500): + break + + # Vultr has a rate limiting requests per second, try to be polite + # Use exponential backoff plus a little bit of randomness + backoff(retry=retry, retry_max_delay=retry_max_delay) + + # Success with content + if info["status"] in (200, 201, 202): + return self.module.from_json(to_text(resp_body, errors="surrogate_or_strict")) + + # Success without content + if info["status"] in (404, 204): + return dict() + + self.module.fail_json( + msg='Failure while calling the Vultr API v2 with %s for "%s".' % (method, path), + fetch_url_info=info, + ) + + def query_filter_list_by_name( + self, + path, + key_name, + result_key, + param_key=None, + key_id=None, + query_params=None, + get_details=False, + fail_not_found=False, + skip_transform=True, + ): + param_value = self.module.params.get(param_key or key_name) + + found = dict() + for resource in self.query_list(path=path, result_key=result_key, query_params=query_params): + if resource.get(key_name) == param_value: + if found: + self.module.fail_json(msg="More than one record with name=%s found. " "Use multiple=true if module supports it." % param_value) + found = resource + if found: + if get_details: + return self.query_by_id(resource_id=found[key_id], skip_transform=skip_transform) + else: + if skip_transform: + return found + else: + return self.transform_resource(found) + + elif fail_not_found: + self.module.fail_json(msg="No Resource %s with %s found: %s" % (path, key_name, param_value)) + + return dict() + + def query_filter_list(self): + # Returns a single dict representing the resource queryied by name + return self.query_filter_list_by_name( + key_name=self.resource_key_name, + key_id=self.resource_key_id, + get_details=self.resource_get_details, + path=self.resource_path, + result_key=self.ressource_result_key_plural, + skip_transform=False, + ) + + def query_by_id(self, resource_id=None, path=None, result_key=None, skip_transform=True): + # Defaults + path = path or self.resource_path + result_key = result_key or self.ressource_result_key_singular + + resource = self.api_query(path="%s%s" % (path, "/" + resource_id if resource_id else resource_id)) + if resource: + if skip_transform: + return resource[result_key] + else: + return self.transform_resource(resource[result_key]) + + return dict() + + def query(self): + # Returns a single dict representing the resource + return self.query_filter_list() + + def query_list(self, path=None, result_key=None, query_params=None): + # Defaults + path = path or self.resource_path + result_key = result_key or self.ressource_result_key_plural + + resources = self.api_query(path=path, query_params=query_params) + return resources[result_key] if resources else [] + + def wait_for_state(self, resource, key, states, cmp="="): + for retry in range(0, 60): + resource = self.query_by_id(resource_id=resource[self.resource_key_id], skip_transform=False) + if cmp == "=": + if key not in resource or resource[key] in states or not resource[key]: + break + else: + if key not in resource or resource[key] not in states or not resource[key]: + break + backoff(retry=retry) + else: + if cmp == "=": + self.module.fail_json(msg="Wait for %s to become %s timed out" % (key, states)) + else: + self.module.fail_json(msg="Wait for %s to not be in %s timed out" % (key, states)) + + return resource + + def create_or_update(self): + resource = self.query() + if not resource: + resource = self.create() + else: + resource = self.update(resource) + return resource + + def present(self): + self.get_result(self.create_or_update()) + + def create(self): + data = dict() + for param in self.resource_create_param_keys: + data[param] = self.module.params.get(param) + + self.result["changed"] = True + resource = dict() + + self.result["diff"]["before"] = dict() + self.result["diff"]["after"] = data + + if not self.module.check_mode: + resource = self.api_query( + path=self.resource_path, + method="POST", + data=data, + ) + return resource.get(self.ressource_result_key_singular) if resource else dict() + + def is_diff(self, param, resource): + value = self.module.params.get(param) + if value is None: + return False + + if param not in resource: + self.module.fail_json(msg="Can not diff, key %s not found in resource" % param) + + if isinstance(value, list): + for v in value: + if v not in resource[param]: + return True + elif resource[param] != value: + return True + + return False + + def update(self, resource): + data = dict() + + for param in self.resource_update_param_keys: + if self.is_diff(param, resource): + self.result["changed"] = True + data[param] = self.module.params.get(param) + + if self.result["changed"]: + self.result["diff"]["before"] = dict(**resource) + self.result["diff"]["after"] = dict(**resource) + self.result["diff"]["after"].update(data) + + if not self.module.check_mode: + self.api_query( + path="%s/%s" % (self.resource_path, resource[self.resource_key_id]), + method=self.resource_update_method, + data=data, + ) + resource = self.query_by_id(resource_id=resource[self.resource_key_id]) + return resource + + def absent(self, resource=None): + if resource is None: + resource = self.query() + + if resource: + self.result["changed"] = True + + self.result["diff"]["before"] = dict(**resource) + self.result["diff"]["after"] = dict() + + if not self.module.check_mode: + self.api_query( + path="%s/%s" % (self.resource_path, resource[self.resource_key_id]), + method="DELETE", + ) + self.get_result(resource) + + def transform_result(self, resource): + return resource + + def get_result(self, resource): + self.result[self.namespace] = self.transform_result(resource) + self.module.exit_json(**self.result) diff --git a/ansible_collections/vultr/cloud/plugins/modules/__init__.py b/ansible_collections/vultr/cloud/plugins/modules/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/vultr/cloud/plugins/modules/__init__.py diff --git a/ansible_collections/vultr/cloud/plugins/modules/account_info.py b/ansible_collections/vultr/cloud/plugins/modules/account_info.py new file mode 100644 index 000000000..ecf55e2b8 --- /dev/null +++ b/ansible_collections/vultr/cloud/plugins/modules/account_info.py @@ -0,0 +1,117 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2021, René Moser <mail@renemoser.net> +# 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 = """ +--- +module: account_info +short_description: Get information about the Vultr account +description: + - Get infos about account balance, charges and payments. +version_added: "1.0.0" +author: "René Moser (@resmo)" +extends_documentation_fragment: + - vultr.cloud.vultr_v2 +""" + +EXAMPLES = """ +- name: Get Vultr account infos + vultr.cloud.account_info: + register: result + +- name: Print the infos + ansible.builtin.debug: + var: result.vultr_account_info +""" + +RETURN = """ +--- +vultr_api: + description: Response from Vultr API with a few additions/modification. + returned: success + type: dict + contains: + api_account: + description: Account used in the ini file to select the key. + returned: success + type: str + sample: default + api_timeout: + description: Timeout used for the API requests. + returned: success + type: int + sample: 60 + api_retries: + description: Amount of max retries for the API requests. + returned: success + type: int + sample: 5 + api_retry_max_delay: + description: Exponential backoff delay in seconds between retries up to this max delay value. + returned: success + type: int + sample: 12 + api_endpoint: + description: Endpoint used for the API requests. + returned: success + type: str + sample: "https://api.vultr.com/v2" +vultr_account_info: + description: Response from Vultr API. + returned: success + type: dict + contains: + balance: + description: Your account balance. + returned: success + type: float + sample: -214.69 + pending_charges: + description: Charges pending. + returned: success + type: float + sample: 57.03 + last_payment_date: + description: Date of the last payment. + returned: success + type: str + sample: "2021-11-07T05:57:59-05:00" + last_payment_amount: + description: The amount of the last payment transaction. + returned: success + type: float + sample: -250.0 +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils.vultr_v2 import AnsibleVultr, vultr_argument_spec + + +def main(): + argument_spec = vultr_argument_spec() + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + vultr = AnsibleVultr( + module=module, + namespace="vultr_account_info", + resource_path="/account", + ressource_result_key_singular="account", + ) + + vultr.get_result(vultr.query_by_id(resource_id="")) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/vultr/cloud/plugins/modules/block_storage.py b/ansible_collections/vultr/cloud/plugins/modules/block_storage.py new file mode 100644 index 000000000..59b8cc068 --- /dev/null +++ b/ansible_collections/vultr/cloud/plugins/modules/block_storage.py @@ -0,0 +1,271 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2022, René Moser <mail@renemoser.net> +# 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 = """ +--- +module: block_storage +short_description: Manages block storage volumes on Vultr +description: + - Manage block storage volumes. +version_added: "1.0.0" +author: + - "René Moser (@resmo)" + - "Yanis Guenane (@Spredzy)" +options: + label: + description: + - Name of the block storage volume. + required: true + aliases: [ name ] + type: str + size_gb: + description: + - Size of the block storage volume in GB. + - Required if I(state) is present. + - If it is larger than the volume's current size, the volume will be resized. + aliases: [ size ] + type: int + block_type: + description: + - The type of block storage volume that will be created. + default: high_perf + choices: [ high_perf, storage_opt ] + type: str + version_added: "1.2.0" + region: + description: + - Region the block storage volume is deployed into. + - Required if I(state) is present. + type: str + state: + description: + - State of the block storage volume. + default: present + choices: [ present, absent] + type: str + attached_to_instance: + description: + - The ID of the server instance the volume is attached to. + type: str + live: + description: + - Whether the volume should be attached/detached without restarting the instance. + type: bool + default: false +extends_documentation_fragment: + - vultr.cloud.vultr_v2 +""" + +EXAMPLES = """ +--- +- name: Ensure a block storage volume is present + vultr.cloud.block_storage: + name: myvolume + size_gb: 10 + block_type: storage_opt + region: ams + +- name: Ensure a block storage volume is absent + vultr.cloud.block_storage: + name: myvolume + state: absent + +- name: Ensure a block storage volume exists and is attached a server instance + vultr.cloud.block_storage: + name: myvolume + attached_to_instance: cb676a46-66fd-4dfb-b839-443f2e6c0b60 + size_gb: 50 + block_type: high_perf + +- name: Ensure a block storage volume exists but is not attached to any server instance + vultr.cloud.block_storage: + name: myvolume + attached_to_instance: "" + size_gb: 50 + block_type: high_perf +""" + +RETURN = """ +--- +vultr_api: + description: Response from Vultr API with a few additions/modification. + returned: success + type: dict + contains: + api_account: + description: Account used in the ini file to select the key. + returned: success + type: str + sample: default + api_timeout: + description: Timeout used for the API requests. + returned: success + type: int + sample: 60 + api_retries: + description: Amount of max retries for the API requests. + returned: success + type: int + sample: 5 + api_retry_max_delay: + description: Exponential backoff delay in seconds between retries up to this max delay value. + returned: success + type: int + sample: 12 + api_endpoint: + description: Endpoint used for the API requests. + returned: success + type: str + sample: "https://api.vultr.com/v2" +vultr_block_storage: + description: Response from Vultr API. + returned: success + type: dict + contains: + attached_to_instance: + description: The ID of the server instance the volume is attached to. + returned: success + type: str + sample: cb676a46-66fd-4dfb-b839-443f2e6c0b60 + cost: + description: Cost per month for the volume. + returned: success + type: float + sample: 1.00 + date_created: + description: Date when the volume was created. + returned: success + type: str + sample: "2020-10-10T01:56:20+00:00" + id: + description: ID of the block storage volume. + returned: success + type: str + sample: cb676a46-66fd-4dfb-b839-443f2e6c0b60 + label: + description: Label of the volume. + returned: success + type: str + sample: my volume + region: + description: Region the volume was deployed into. + returned: success + type: str + sample: ews + size_gb: + description: Information about the volume size in GB. + returned: success + type: int + sample: 50 + block_type: + description: HDD or NVMe (storage_opt or high_perf) + returned: success + type: str + sample: high_perf + status: + description: Status about the deployment of the volume. + returned: success + type: str + sample: active + mount_id: + description: Mount ID of the volume. + returned: success + type: str + sample: ewr-2f5d7a314fe44f +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils.vultr_v2 import AnsibleVultr, vultr_argument_spec + + +class AnsibleVultrBlockStorage(AnsibleVultr): + def update(self, resource): + current_size = resource["size_gb"] + desired_size = self.module.params["size_gb"] + if desired_size < current_size: + self.module.params["size_gb"] = current_size + self.module.warn("Shrinking is not supported: current size %s, desired size %s" % (current_size, desired_size)) + return super(AnsibleVultrBlockStorage, self).update(resource=resource) + + def present(self): + resource = self.create_or_update() or dict() + + instance_to_attach = self.module.params.get("attached_to_instance") + if instance_to_attach is None: + # exit and show result if no attach/detach needed. + self.get_result(resource) + + instance_attached = resource.get("attached_to_instance", "") + if instance_attached != instance_to_attach: + self.result["changed"] = True + + mode = "detach" if not instance_to_attach else "attach" + self.result["diff"]["after"].update({"attached_to_instance": instance_to_attach}) + + data = { + "instance_id": instance_to_attach if instance_to_attach else None, + "live": self.module.params.get("live"), + } + + if not self.module.check_mode: + self.api_query( + path="%s/%s/%s" % (self.resource_path, resource[self.resource_key_id], mode), + method="POST", + data=data, + ) + resource = self.query_by_id(resource_id=resource[self.resource_key_id]) + + self.get_result(resource) + + +def main(): + argument_spec = vultr_argument_spec() + argument_spec.update( + dict( + label=dict(type="str", required=True, aliases=["name"]), + size_gb=dict(type="int", aliases=["size"]), + block_type=dict(type="str", choices=["high_perf", "storage_opt"], default="high_perf"), + region=dict(type="str"), + state=dict(type="str", choices=["present", "absent"], default="present"), + attached_to_instance=dict(type="str"), + live=dict(type="bool", default=False), + ) # type: ignore + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "present", ["size_gb", "region"]], + ], + ) + + vultr = AnsibleVultrBlockStorage( + module=module, + namespace="vultr_block_storage", + resource_path="/blocks", + ressource_result_key_singular="block", + resource_create_param_keys=["label", "size_gb", "region", "block_type"], + resource_update_param_keys=["size_gb"], + resource_key_name="label", + # Query details information about block type + resource_get_details=True, + ) + + if module.params.get("state") == "absent": # type: ignore + vultr.absent() + else: + vultr.present() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/vultr/cloud/plugins/modules/block_storage_info.py b/ansible_collections/vultr/cloud/plugins/modules/block_storage_info.py new file mode 100644 index 000000000..a52591d94 --- /dev/null +++ b/ansible_collections/vultr/cloud/plugins/modules/block_storage_info.py @@ -0,0 +1,139 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2022, René Moser <mail@renemoser.net> +# 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 = """ +--- +module: block_storage_info +short_description: Get information about the Vultr block storage +version_added: "1.0.0" +description: + - Get infos about block storages available. +author: + - "René Moser (@resmo)" + - "Yanis Guenane (@Spredzy)" +extends_documentation_fragment: + - vultr.cloud.vultr_v2 +""" + +EXAMPLES = """ +- name: Get Vultr block_storage infos + vultr.cloud.block_storage_info: + register: result + +- name: Print the infos + ansible.builtin.debug: + var: result.vultr_block_storage_info +""" + +RETURN = """ +--- +vultr_api: + description: Response from Vultr API with a few additions/modification. + returned: success + type: dict + contains: + api_timeout: + description: Timeout used for the API requests. + returned: success + type: int + sample: 60 + api_retries: + description: Amount of max retries for the API requests. + returned: success + type: int + sample: 5 + api_retry_max_delay: + description: Exponential backoff delay in seconds between retries up to this max delay value. + returned: success + type: int + sample: 12 + api_endpoint: + description: Endpoint used for the API requests. + returned: success + type: str + sample: "https://api.vultr.com/v2" +vultr_block_storage_info: + description: Response from Vultr API as list. + returned: success + type: list + contains: + attached_to_instance: + description: The ID of the server instance the volume is attached to. + returned: success + type: str + sample: cb676a46-66fd-4dfb-b839-443f2e6c0b60 + cost: + description: Cost per month for the volume. + returned: success + type: float + sample: 1.00 + date_created: + description: Date when the volume was created. + returned: success + type: str + sample: "2020-10-10T01:56:20+00:00" + id: + description: ID of the block storage volume. + returned: success + type: str + sample: cb676a46-66fd-4dfb-b839-443f2e6c0b60 + label: + description: Label of the volume. + returned: success + type: str + sample: my volume + region: + description: Region the volume was deployed into. + returned: success + type: str + sample: ews + size_gb: + description: Information about the volume size in GB. + returned: success + type: int + sample: 50 + status: + description: Status about the deployment of the volume. + returned: success + type: str + sample: active + mount_id: + description: Mount ID of the volume. + returned: success + type: str + sample: ewr-2f5d7a314fe44f +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils.vultr_v2 import AnsibleVultr, vultr_argument_spec + + +def main(): + argument_spec = vultr_argument_spec() + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + vultr = AnsibleVultr( + module=module, + namespace="vultr_block_storage_info", + resource_path="/blocks", + ressource_result_key_singular="block", + ) + + vultr.get_result(vultr.query_list()) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/vultr/cloud/plugins/modules/dns_domain.py b/ansible_collections/vultr/cloud/plugins/modules/dns_domain.py new file mode 100644 index 000000000..3f0132b0c --- /dev/null +++ b/ansible_collections/vultr/cloud/plugins/modules/dns_domain.py @@ -0,0 +1,155 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2021, René Moser <mail@renemoser.net> +# 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 = """ +--- +module: dns_domain +short_description: Manages DNS domains on Vultr +description: + - Create and remove DNS domains. +version_added: "1.0.0" +author: "René Moser (@resmo)" +options: + domain: + description: + - The domain name. + required: true + aliases: [ name ] + type: str + ip: + description: + - The default server IP. + - Use M(vultr.cloud.dns_record) to change it once the domain is created. + - Required if C(state=present). + type: str + aliases: [ server_ip ] + dns_sec: + description: + - Ensure DNSSEC is enabled or disabled. + type: str + choices: [ enabled, disabled ] + default: disabled + state: + description: + - State of the DNS domain. + default: present + choices: [ present, absent ] + type: str +extends_documentation_fragment: + - vultr.cloud.vultr_v2 +""" + +EXAMPLES = """ +- name: Ensure a domain exists with DNSSEC + vultr.cloud.dns_domain: + name: example.com + dns_sec: enabled + server_ip: 10.10.10.10 + +- name: Ensure a domain is absent + vultr.cloud.dns_domain: + name: example.com + state: absent +""" + +RETURN = """ +--- +vultr_api: + description: Response from Vultr API with a few additions/modification. + returned: success + type: dict + contains: + api_timeout: + description: Timeout used for the API requests. + returned: success + type: int + sample: 60 + api_retries: + description: Amount of max retries for the API requests. + returned: success + type: int + sample: 5 + api_retry_max_delay: + description: Exponential backoff delay in seconds between retries up to this max delay value. + returned: success + type: int + sample: 12 + api_endpoint: + description: Endpoint used for the API requests. + returned: success + type: str + sample: "https://api.vultr.com/v2" +vultr_dns_domain: + description: Response from Vultr API. + returned: success + type: dict + contains: + name: + description: Name of the DNS Domain. + returned: success + type: str + sample: example.com + dns_sec: + description: Whether DNSSEC is enabled or disabled. + returned: success + type: str + sample: disabled + date_created: + description: Date the DNS domain was created. + returned: success + type: str + sample: "2020-10-10T01:56:20+00:00" +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils.vultr_v2 import AnsibleVultr, vultr_argument_spec + + +def main(): + argument_spec = vultr_argument_spec() + argument_spec.update( + dict( + domain=dict(type="str", required=True, aliases=["name"]), + ip=dict(type="str", aliases=["server_ip"]), + dns_sec=dict(type="str", choices=["enabled", "disabled"], default="disabled"), + state=dict(type="str", choices=["present", "absent"], default="present"), + ) # type: ignore + ) + + module = AnsibleModule( + argument_spec=argument_spec, + required_if=[ + ("state", "present", ["ip"]), + ], + supports_check_mode=True, + ) + + vultr = AnsibleVultr( + module=module, + namespace="vultr_dns_domain", + resource_path="/domains", + ressource_result_key_singular="domain", + resource_create_param_keys=["domain", "dns_sec", "ip"], + resource_update_param_keys=["domain", "dns_sec"], + resource_key_name="domain", + resource_key_id="domain", + resource_update_method="PUT", + ) + + if module.params.get("state") == "absent": # type: ignore + vultr.absent() + else: + vultr.present() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/vultr/cloud/plugins/modules/dns_domain_info.py b/ansible_collections/vultr/cloud/plugins/modules/dns_domain_info.py new file mode 100644 index 000000000..aa198b176 --- /dev/null +++ b/ansible_collections/vultr/cloud/plugins/modules/dns_domain_info.py @@ -0,0 +1,109 @@ +#!/usr/bin/python +# +# Copyright (c) 2018, Yanis Guenane <yanis+ansible@guenane.org> +# Copyright (c) 2021, René Moser <mail@renemoser.net> +# 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 = """ +--- +module: dns_domain_info +short_description: Gather information about the Vultr DNS domains +description: + - Gather information about DNS domains available. +version_added: "1.0.0" +author: + - "Yanis Guenane (@Spredzy)" + - "René Moser (@resmo)" +extends_documentation_fragment: + - vultr.cloud.vultr_v2 +""" + +EXAMPLES = """ +- name: Gather Vultr DNS domains information + vultr.cloud.dns_domain_info: + register: result + +- name: Print the gathered information + ansible.builtin.debug: + var: result.vultr_dns_domain_info +""" + +RETURN = """ +--- +vultr_api: + description: Response from Vultr API with a few additions/modification. + returned: success + type: dict + contains: + api_timeout: + description: Timeout used for the API requests. + returned: success + type: int + sample: 60 + api_retries: + description: Amount of max retries for the API requests. + returned: success + type: int + sample: 5 + api_retry_max_delay: + description: Exponential backoff delay in seconds between retries up to this max delay value. + returned: success + type: int + sample: 12 + api_endpoint: + description: Endpoint used for the API requests. + returned: success + type: str + sample: "https://api.vultr.com/v2" +vultr_dns_domain_info: + description: Response from Vultr API as list. + returned: success + type: list + contains: + domain: + description: Name of the DNS Domain. + returned: success + type: str + sample: example.com + dns_sec: + description: Whether DNSSEC is enabled or disabled. + returned: success + type: str + sample: disabled + date_created: + description: Date the DNS domain was created. + returned: success + type: str + sample: "2020-10-10T01:56:20+00:00" +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils.vultr_v2 import AnsibleVultr, vultr_argument_spec + + +def main(): + argument_spec = vultr_argument_spec() + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + vultr = AnsibleVultr( + module=module, + namespace="vultr_dns_domain_info", + resource_path="/domains", + ressource_result_key_singular="domain", + ) + + vultr.get_result(vultr.query_list()) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/vultr/cloud/plugins/modules/dns_record.py b/ansible_collections/vultr/cloud/plugins/modules/dns_record.py new file mode 100644 index 000000000..2e4352e93 --- /dev/null +++ b/ansible_collections/vultr/cloud/plugins/modules/dns_record.py @@ -0,0 +1,267 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2021, René Moser <mail@renemoser.net> +# 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 = """ +--- +module: dns_record +short_description: Manages DNS records on Vultr +description: + - Create, update and remove DNS records. +version_added: "1.0.0" +author: "René Moser (@resmo)" +options: + name: + description: + - The record name. + type: str + default: "" + domain: + description: + - The domain the record is related to. + type: str + required: true + type: + description: + - Type of the record. + default: A + choices: + - A + - AAAA + - CNAME + - NS + - MX + - SRV + - TXT + - CAA + - SSHFP + aliases: [ record_type ] + type: str + data: + description: + - Data of the record. + - Required if C(state=present). + type: str + ttl: + description: + - TTL of the record. + default: 300 + type: int + priority: + description: + - Priority of the record. + type: int + multiple: + description: + - Whether to use more than one record with similar I(name) including no name and I(type). + - Only allowed for a few record types, e.g. C(type=A), C(type=NS) or C(type=MX). + - I(data) will not be updated, instead it is used as a key to find existing records. + default: false + type: bool + state: + description: + - State of the DNS record. + default: present + choices: [ present, absent ] + type: str +extends_documentation_fragment: + - vultr.cloud.vultr_v2 +""" + + +EXAMPLES = """ +- name: Ensure an A record exists + vultr.cloud.dns_record: + name: www + domain: example.com + data: 10.10.10.10 + ttl: 3600 + +- name: Ensure a second A record exists for round robin LB + vultr.cloud.dns_record: + name: www + domain: example.com + data: 10.10.10.11 + ttl: 60 + multiple: true + +- name: Ensure a CNAME record exists + vultr.cloud.dns_record: + name: web + type: CNAME + domain: example.com + data: www.example.com + +- name: Ensure MX record exists + vultr.cloud.dns_record: + type: MX + domain: example.com + data: "{{ item.data }}" + priority: "{{ item.priority }}" + multiple: true + with_items: + - { data: mx1.example.com, priority: 10 } + - { data: mx2.example.com, priority: 10 } + - { data: mx3.example.com, priority: 20 } + +- name: Ensure a record is absent + vultr.cloud.dns_record: + name: www + domain: example.com + state: absent + +- name: Ensure one MX record is absent if multiple exists + vultr.cloud.dns_record: + record_type: MX + domain: example.com + data: mx1.example.com + multiple: true + state: absent +""" + +RETURN = """ +--- +vultr_api: + description: Response from Vultr API with a few additions/modification. + returned: success + type: dict + contains: + api_timeout: + description: Timeout used for the API requests. + returned: success + type: int + sample: 60 + api_retries: + description: Amount of max retries for the API requests. + returned: success + type: int + sample: 5 + api_retry_max_delay: + description: Exponential backoff delay in seconds between retries up to this max delay value. + returned: success + type: int + sample: 12 + api_endpoint: + description: Endpoint used for the API requests. + returned: success + type: str + sample: "https://api.vultr.com/v2" +dns_record: + description: Response from Vultr API. + returned: success + type: dict + contains: + id: + description: The ID of the DNS record. + returned: success + type: str + sample: cb676a46-66fd-4dfb-b839-443f2e6c0b60 + name: + description: The name of the DNS record. + returned: success + type: str + sample: web + type: + description: The name of the DNS record. + returned: success + type: str + sample: A + data: + description: Data of the DNS record. + returned: success + type: str + sample: 10.10.10.10 + priority: + description: Priority of the DNS record. + returned: success + type: int + sample: 10 + ttl: + description: Time to live of the DNS record. + returned: success + type: int + sample: 300 +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils.vultr_v2 import AnsibleVultr, vultr_argument_spec + +RECORD_TYPES = ["A", "AAAA", "CNAME", "MX", "TXT", "NS", "SRV", "CAA", "SSHFP"] + + +class AnsibleVultrDnsRecord(AnsibleVultr): + def query(self): + multiple = self.module.params.get("multiple") + name = self.module.params.get("name") + data = self.module.params.get("data") + record_type = self.module.params.get("type") + + result = dict() + for resource in self.query_list(): + if resource.get("type") != record_type: + continue + + if resource.get("name") == name: + if not multiple: + if result: + self.module.fail_json( + msg="More than one record with record_type=%s and name=%s params. " + "Use multiple=true for more than one record." % (record_type, name) + ) + else: + result = resource + elif resource.get("data") == data: + return resource + return result + + +def main(): + argument_spec = vultr_argument_spec() + argument_spec.update( + dict( + domain=dict(type="str", required=True), + name=dict(type="str", default=""), + state=dict(type="str", choices=["present", "absent"], default="present"), + ttl=dict(type="int", default=300), + type=dict(type="str", choices=RECORD_TYPES, default="A", aliases=["record_type"]), + multiple=dict(type="bool", default=False), + priority=dict(type="int"), + data=dict(type="str"), + ) # type: ignore + ) + + module = AnsibleModule( + argument_spec=argument_spec, + required_if=[ + ("state", "present", ["data"]), + ("multiple", True, ["data"]), + ], + supports_check_mode=True, + ) + + vultr = AnsibleVultrDnsRecord( + module=module, + namespace="vultr_dns_record", + resource_path="/domains/%s/records" % module.params.get("domain"), # type: ignore + ressource_result_key_singular="record", + resource_create_param_keys=["name", "ttl", "data", "priority", "type"], + resource_update_param_keys=["name", "ttl", "data", "priority"], + resource_key_name="name", + ) # type: ignore + + if module.params.get("state") == "absent": # type: ignore + vultr.absent() + else: + vultr.present() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/vultr/cloud/plugins/modules/firewall_group.py b/ansible_collections/vultr/cloud/plugins/modules/firewall_group.py new file mode 100644 index 000000000..962936a78 --- /dev/null +++ b/ansible_collections/vultr/cloud/plugins/modules/firewall_group.py @@ -0,0 +1,138 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2021, René Moser <mail@renemoser.net> +# 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 = """ +--- +module: firewall_group +short_description: Manages firewall groups on Vultr +description: + - Create and remove firewall groups. +version_added: "1.0.0" +author: "René Moser (@resmo)" +options: + description: + description: + - Description of the firewall group. + required: true + aliases: [ name ] + type: str + state: + description: + - State of the firewall group. + default: present + choices: [ present, absent ] + type: str +extends_documentation_fragment: + - vultr.cloud.vultr_v2 +""" + +EXAMPLES = """ +- name: ensure a firewall group is present + vultr.cloud.firewall_group: + description: my http firewall. + +- name: ensure a firewall group is absent + vultr.cloud.firewall_group: + description: my http firewall. + state: absent +""" + +RETURN = """ +--- +vultr_api: + description: Response from Vultr API with a few additions/modification. + returned: success + type: dict + contains: + api_timeout: + description: Timeout used for the API requests. + returned: success + type: int + sample: 60 + api_retries: + description: Amount of max retries for the API requests. + returned: success + type: int + sample: 5 + api_retry_max_delay: + description: Exponential backoff delay in seconds between retries up to this max delay value. + returned: success + type: int + sample: 12 + api_endpoint: + description: Endpoint used for the API requests. + returned: success + type: str + sample: "https://api.vultr.com/v2" +vultr_firewall_group: + description: Response from Vultr API. + returned: success + type: dict + contains: + id: + description: ID of the firewall group. + returned: success + type: str + sample: cb676a46-66fd-4dfb-b839-443f2e6c0b60 + description: + description: Description (name) of the firewall group + returned: success + type: str + sample: my firewall group + date_created: + description: Date the firewall group was created. + returned: success + type: str + sample: "2020-10-10T01:56:20+00:00" + date_modified: + description: Date the firewall group was modified. + returned: success + type: str + sample: "2020-10-10T01:56:20+00:00" +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils.vultr_v2 import AnsibleVultr, vultr_argument_spec + + +def main(): + argument_spec = vultr_argument_spec() + argument_spec.update( + dict( + description=dict(type="str", required=True, aliases=["name"]), + state=dict(type="str", choices=["present", "absent"], default="present"), + ) # type: ignore + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + vultr = AnsibleVultr( + module=module, + namespace="vultr_firewall_group", + resource_path="/firewalls", + ressource_result_key_singular="firewall_group", + resource_create_param_keys=["description"], + resource_update_param_keys=["description"], + resource_key_name="description", + ) + + if module.params.get("state") == "absent": # type: ignore + vultr.absent() + else: + vultr.present() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/vultr/cloud/plugins/modules/firewall_group_info.py b/ansible_collections/vultr/cloud/plugins/modules/firewall_group_info.py new file mode 100644 index 000000000..866ac4818 --- /dev/null +++ b/ansible_collections/vultr/cloud/plugins/modules/firewall_group_info.py @@ -0,0 +1,114 @@ +#!/usr/bin/python +# +# Copyright (c) 2018, Yanis Guenane <yanis+ansible@guenane.org> +# Copyright (c) 2021, René Moser <mail@renemoser.net> +# 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 = """ +--- +module: firewall_group_info +short_description: Gather information about the Vultr firewall groups +description: + - Gather information about firewall groups available. +version_added: "1.0.0" +author: + - "Yanis Guenane (@Spredzy)" + - "René Moser (@resmo)" +extends_documentation_fragment: + - vultr.cloud.vultr_v2 +""" + +EXAMPLES = """ +- name: Gather Vultr firewall groups information + vultr.cloud.firewall_group_info: + register: result + +- name: Print the gathered information + ansible.builtin.debug: + var: result.vultr_firewall_group_info +""" + +RETURN = """ +--- +vultr_api: + description: Response from Vultr API with a few additions/modification. + returned: success + type: dict + contains: + api_timeout: + description: Timeout used for the API requests. + returned: success + type: int + sample: 60 + api_retries: + description: Amount of max retries for the API requests. + returned: success + type: int + sample: 5 + api_retry_max_delay: + description: Exponential backoff delay in seconds between retries up to this max delay value. + returned: success + type: int + sample: 12 + api_endpoint: + description: Endpoint used for the API requests. + returned: success + type: str + sample: "https://api.vultr.com/v2" +vultr_firewall_group_info: + description: Response from Vultr API as list. + returned: success + type: list + contains: + id: + description: ID of the firewall group. + returned: success + type: str + sample: cb676a46-66fd-4dfb-b839-443f2e6c0b60 + description: + description: Name of the firewall group. + returned: success + type: str + sample: my firewall group + date_created: + description: Date the firewall group was created. + returned: success + type: str + sample: "2020-10-10T01:56:20+00:00" + date_modified: + description: Date the firewall group was modified. + returned: success + type: str + sample: "2020-10-10T01:56:20+00:00" +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils.vultr_v2 import AnsibleVultr, vultr_argument_spec + + +def main(): + argument_spec = vultr_argument_spec() + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + vultr = AnsibleVultr( + module=module, + namespace="vultr_firewall_group_info", + resource_path="/firewalls", + ressource_result_key_singular="firewall_group", + ) + + vultr.get_result(vultr.query_list()) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/vultr/cloud/plugins/modules/firewall_rule.py b/ansible_collections/vultr/cloud/plugins/modules/firewall_rule.py new file mode 100644 index 000000000..474372d08 --- /dev/null +++ b/ansible_collections/vultr/cloud/plugins/modules/firewall_rule.py @@ -0,0 +1,297 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2022, René Moser <mail@renemoser.net> +# 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 = """ +--- +module: firewall_rule +short_description: Manages firewall rules on Vultr +description: + - Create and remove firewall rules. +version_added: "1.0.0" +author: "René Moser (@resmo)" +options: + group: + description: + - Name of the firewall group. + required: true + type: str + ip_type: + description: + - IP address version + choices: [ v4, v6 ] + type: str + default: v4 + protocol: + description: + - Protocol of the firewall rule. + choices: [ icmp, tcp, udp, gre, esp, ah ] + type: str + default: tcp + subnet: + description: + - The network or IP, e.g. 192.0.2.123 or 0.0.0.0. + - Mutally exclusive with I(source). + type: str + subnet_size: + description: + - The number of bits for the netmask in CIDR notation, e.g. C(32). + type: int + port: + description: + - Single port or port range, e.g. C(80) or C(8000:8080). + - Required if I(protocol) is tcp or udp and I(state=present). + aliases: [ port_range ] + type: str + source: + description: + - Possible values are C(cloudflare) or a loadbalancer label. + - Mutally exclusive with I(subnet). + type: str + notes: + description: + - Notes of the firewall rule. + type: str + state: + description: + - State of the firewall rule. + default: present + choices: [ present, absent ] + type: str +extends_documentation_fragment: + - vultr.cloud.vultr_v2 +""" + +EXAMPLES = """ +- name: Ensure a firewall rule is present + vultr.cloud.firewall_rule: + group: web + port: 80 + protocol: tcp + ip_type: v4 + subnet: "0.0.0.0" + subnet_size: 0 + notes: "open HTTP to the world" + +- name: Ensure a firewall rule with port range is present + vultr.cloud.firewall_rule: + group: apps + port: "8000:8999" + protocol: tcp + ip_type: v4 + subnet: "10.10.10.0" + subnet_size: 24 + +- name: Ensure a firewall rule is absent + vultr.cloud.firewall_rule: + group: apps + port: "443" + protocol: tcp + ip_type: v6 + subnet: "::" + subnet_size: 0 + state: absent +""" + +RETURN = """ +--- +vultr_api: + description: Response from Vultr API with a few additions/modification. + returned: success + type: dict + contains: + api_timeout: + description: Timeout used for the API requests. + returned: success + type: int + sample: 60 + api_retries: + description: Amount of max retries for the API requests. + returned: success + type: int + sample: 5 + api_retry_max_delay: + description: Exponential backoff delay in seconds between retries up to this max delay value. + returned: success + type: int + sample: 12 + api_endpoint: + description: Endpoint used for the API requests. + returned: success + type: str + sample: "https://api.vultr.com/v2" +vultr_firewall_rule: + description: Response from Vultr API. + returned: success + type: dict + contains: + id: + description: ID of the firewall rule. + returned: success + type: int + sample: 1 + action: + description: Action of the firewall rule. + returned: success + type: str + sample: accept + protocol: + description: Protocol of the firewall rule. + returned: success + type: str + sample: tcp + port: + description: Port or port range of the firewall rule. + returned: success + type: str + sample: "80" + source: + description: Source string of the firewall rule. + returned: success + type: str + sample: cloudflare + notes: + description: Supplied description of the firewall rule. + returned: success + type: str + sample: my rule + subnet: + description: Subnet of the firewall rule. + returned: success + type: str + sample: 0.0.0.0 + subnet_size: + description: Size of the subnet of the firewall rule. + returned: success + type: int + sample: 0 + ip_type: + description: IP type of the firewall rule. + returned: success + type: str + sample: v4 +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils.vultr_v2 import AnsibleVultr, vultr_argument_spec + + +class AnsibleVultrFirewallRule(AnsibleVultr): + def get_firewall_group(self): + return self.query_filter_list_by_name( + key_name="description", + param_key="group", + path="/firewalls", + result_key="firewall_groups", + fail_not_found=True, + ) + + def get_load_balancer(self): + return self.query_filter_list_by_name( + key_name="label", + param_key="source", + path="/load-balancers", + result_key="load_balancers", + fail_not_found=True, + ) + + def configure(self): + # Set firewall group id to resource path, ensures firewall group exists + self.resource_path = self.resource_path % self.get_firewall_group()["id"] + + # Set loadbalancer ID for source + source = self.module.params.get("source") + if source is not None and source != "cloudflare": + self.module.params["source"] = self.get_load_balancer()["id"] + + def query(self): + result = dict() + for resource in self.query_list(): + for key in ( + "ip_type", + "protocol", + "port", + "source", + "subnet", + "subnet_size", + ): + param = self.module.params.get(key) + + if param is None: + continue + + if resource.get(key) != param: + break + else: + result = resource + + if result: + break + + return result + + def update(self, resource): + return resource + + +def main(): + argument_spec = vultr_argument_spec() + argument_spec.update( + dict( + notes=dict(type="str"), + group=dict(type="str", required=True), + port=dict(type="str", aliases=["port_range"]), + subnet=dict(type="str"), + subnet_size=dict(type="int"), + source=dict(type="str"), + protocol=dict( + type="str", + choices=["icmp", "tcp", "udp", "gre", "esp", "ah"], + default="tcp", + ), + ip_type=dict(type="str", choices=["v4", "v6"], default="v4"), + state=dict(type="str", choices=["present", "absent"], default="present"), + ) # type: ignore + ) + + module = AnsibleModule( + argument_spec=argument_spec, + required_one_of=(("source", "subnet"),), + mutually_exclusive=(("source", "subnet"),), + required_together=(("subnet", "subnet_size"),), + supports_check_mode=True, + ) + + vultr = AnsibleVultrFirewallRule( + module=module, + namespace="vultr_firewall_rule", + resource_path="/firewalls/%s/rules", + ressource_result_key_singular="firewall_rule", + resource_key_name="##unused##", + resource_create_param_keys=[ + "notes", + "port", + "subnet", + "subnet_size", + "source", + "protocol", + "ip_type", + ], + ) + + if module.params.get("state") == "absent": # type: ignore + vultr.absent() + else: + vultr.present() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/vultr/cloud/plugins/modules/firewall_rule_info.py b/ansible_collections/vultr/cloud/plugins/modules/firewall_rule_info.py new file mode 100644 index 000000000..6d51d6f9c --- /dev/null +++ b/ansible_collections/vultr/cloud/plugins/modules/firewall_rule_info.py @@ -0,0 +1,162 @@ +#!/usr/bin/python +# +# Copyright (c) 2022, René Moser <mail@renemoser.net> +# 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 = """ +--- +module: firewall_rule_info +short_description: Gather information about the Vultr firewall rules +description: + - Gather information about firewall rules available. +version_added: "1.0.0" +author: "René Moser (@resmo)" +options: + group: + description: + - Name of the firewall group. + required: true + type: str +extends_documentation_fragment: + - vultr.cloud.vultr_v2 +""" + +EXAMPLES = """ +- name: Gather Vultr firewall rule information + vultr.cloud.firewall_rule_info: + group: my group + register: result + +- name: Print the gathered information + ansible.builtin.debug: + var: result.vultr_firewall_rule_info +""" + +RETURN = """ +--- +vultr_api: + description: Response from Vultr API with a few additions/modification. + returned: success + type: dict + contains: + api_timeout: + description: Timeout used for the API requests. + returned: success + type: int + sample: 60 + api_retries: + description: Amount of max retries for the API requests. + returned: success + type: int + sample: 5 + api_retry_max_delay: + description: Exponential backoff delay in seconds between retries up to this max delay value. + returned: success + type: int + sample: 12 + api_endpoint: + description: Endpoint used for the API requests. + returned: success + type: str + sample: "https://api.vultr.com/v2" +vultr_firewall_rule_info: + description: Response from Vultr API as list. + returned: success + type: list + contains: + id: + description: ID of the firewall rule. + returned: success + type: int + sample: 1 + action: + description: Action of the firewall rule. + returned: success + type: str + sample: accept + protocol: + description: Protocol of the firewall rule. + returned: success + type: str + sample: tcp + port: + description: Port or port range of the firewall rule. + returned: success + type: str + sample: "80" + source: + description: Source string of the firewall rule. + returned: success + type: str + sample: cloudflare + notes: + description: Supplied description of the firewall rule. + returned: success + type: str + sample: my rule + subnet: + description: Subnet of the firewall rule. + returned: success + type: str + sample: 0.0.0.0 + subnet_size: + description: Size of the subnet of the firewall rule. + returned: success + type: int + sample: 0 + ip_type: + description: IP type of the firewall rule. + returned: success + type: str + sample: v4 +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils.vultr_v2 import AnsibleVultr, vultr_argument_spec + + +class AnsibleVultrFirewallRuleInfo(AnsibleVultr): + def get_firewall_group(self): + return self.query_filter_list_by_name( + key_name="description", + param_key="group", + path="/firewalls", + result_key="firewall_groups", + fail_not_found=True, + ) + + def configure(self): + # Set firewall group id to resource path, ensures firewall group exists + self.resource_path = self.resource_path % self.get_firewall_group()["id"] + + +def main(): + argument_spec = vultr_argument_spec() + argument_spec.update( + dict( + group=dict(type="str", required=True), + ) # type: ignore + ) + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + vultr = AnsibleVultrFirewallRuleInfo( + module=module, + namespace="vultr_firewall_rule_info", + resource_path="/firewalls/%s/rules", + ressource_result_key_singular="firewall_rule", + ) + + vultr.get_result(vultr.query_list()) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/vultr/cloud/plugins/modules/instance.py b/ansible_collections/vultr/cloud/plugins/modules/instance.py new file mode 100644 index 000000000..73099164d --- /dev/null +++ b/ansible_collections/vultr/cloud/plugins/modules/instance.py @@ -0,0 +1,739 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2022, René Moser <mail@renemoser.net> +# 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 = """ +--- +module: instance +short_description: Manages server instances on Vultr. +description: + - Manage server instances on Vultr. +version_added: "1.1.0" +author: + - "René Moser (@resmo)" +options: + label: + description: + - Name of the instance. + required: true + aliases: [ name ] + type: str + hostname: + description: + - The hostname to assign to this instance. + type: str + os: + description: + - The operating system name. + - Mutually exclusive with I(image) and I(app). + type: str + app: + description: + - The app deploy name of Vultr OneClick apps. + - Mutually exclusive with I(image) and I(os). + type: str + image: + description: + - The image deploy name of Vultr Marketplace apps. + - Mutually exclusive with I(os) and I(app). + type: str + firewall_group: + description: + - The firewall group description to assign this instance to. + type: str + plan: + description: + - The plan name to use for the instance. + - Required if the instance does not yet exist. + type: str + activation_email: + description: + - Whether to send an activation email when the instance is ready or not. + - Only considered on creation. + type: bool + default: false + backups: + description: + - Whether to enable automatic backups or not. + type: bool + ddos_protection: + description: + - Whether to enable ddos_protection or not. + type: bool + enable_ipv6: + description: + - Whether to enable IPv6 or not. + type: bool + tags: + description: + - Tags for the instance. + type: list + elements: str + user_data: + description: + - User data to be passed to the instance. + type: str + startup_script: + description: + - Name or ID of the startup script to execute on boot. + - Only considered while creating the instance. + type: str + ssh_keys: + description: + - List of SSH key names passed to the instance on creation. + type: list + elements: str + snapshot: + description: + - Description or ID of the snapshot. + - Only considered while creating the instance. + type: str + version_added: "1.7.0" + reserved_ipv4: + description: + - IP address of the floating IP to use as the main IP of this instance. + - Only considered on creation. + type: str + region: + description: + - Region the instance is deployed into. + type: str + required: true + vpcs: + description: + - A list of VPCs identified by their description to be assigned to the instance. + type: list + elements: str + version_added: "1.5.0" + state: + description: + - State of the instance. + - The state I(reinstalled) was added in version 1.8.0. + default: present + choices: [ present, absent, started, stopped, restarted, reinstalled ] + type: str +extends_documentation_fragment: + - vultr.cloud.vultr_v2 +""" + +EXAMPLES = """ +--- +- name: Create an instance using OS + vultr.cloud.instance: + label: my web server + hostname: my-hostname + user_data: | + #cloud-config + packages: + - nginx + firewall_group: my firewall group + plan: vc2-1c-2gb + ddos_protection: true + backups: true + enable_ipv6: true + ssh_keys: + - my ssh key + vpcs: + - my vpc description + tags: + - web + - project-genesis + region: ams + os: Debian 11 x64 (bullseye) + +- name: Deploy an instance of a marketplace app + vultr.cloud.instance: + label: git-server + hostname: git + firewall_group: my firewall group + plan: vc2-1c-2gb + ddos_protection: true + backups: true + enable_ipv6: true + region: ams + image: Gitea on Ubuntu 20.04 + +- name: Stop an existing instance + vultr.cloud.instance: + label: my web server + region: ams + state: stopped + +- name: Start an existing instance + vultr.cloud.instance: + label: my web server + region: ams + state: started + +- name: Reinstall an instance + vultr.cloud.instance: + label: my web server + region: ams + state: reinstalled + +- name: Delete an instance + vultr.cloud.instance: + label: my web server + region: ams + state: absent +""" + +RETURN = """ +--- +vultr_api: + description: Response from Vultr API with a few additions/modification. + returned: success + type: dict + contains: + api_timeout: + description: Timeout used for the API requests. + returned: success + type: int + sample: 60 + api_retries: + description: Amount of max retries for the API requests. + returned: success + type: int + sample: 5 + api_retry_max_delay: + description: Exponential backoff delay in seconds between retries up to this max delay value. + returned: success + type: int + sample: 12 + api_endpoint: + description: Endpoint used for the API requests. + returned: success + type: str + sample: "https://api.vultr.com/v2" +vultr_instance: + description: Response from Vultr API. + returned: success + type: dict + contains: + id: + description: ID of the instance. + returned: success + type: str + sample: cb676a46-66fd-4dfb-b839-443f2e6c0b60 + v6_main_ip: + description: IPv6 of the instance. + returned: success + type: str + sample: "" + v6_network: + description: IPv6 network of the instance. + returned: success + type: str + sample: "" + v6_network_size: + description: IPv6 network size of the instance. + returned: success + type: int + sample: 0 + main_ip: + description: IPv4 of the instance. + returned: success + type: str + sample: 95.179.189.95 + netmask_v4: + description: Netmask IPv4 of the instance. + returned: success + type: str + sample: 255.255.254.0 + hostname: + description: Hostname of the instance. + returned: success + type: str + sample: vultr.guest + internal_ip: + description: Internal IP of the instance. + returned: success + type: str + sample: "" + gateway_v4: + description: Gateway IPv4. + returned: success + type: str + sample: 95.179.188.1 + kvm: + description: KVM of the instance. + returned: success + type: str + sample: "https://my.vultr.com/subs/vps/novnc/api.php?data=..." + disk: + description: Disk size of the instance. + returned: success + type: int + sample: 25 + allowed_bandwidth: + description: Allowed bandwidth of the instance. + returned: success + type: int + sample: 1000 + vcpu_count: + description: vCPUs of the instance. + returned: success + type: int + sample: 1 + firewall_group_id: + description: Firewall group ID of the instance. + returned: success + type: str + sample: "" + plan: + description: Plan of the instance. + returned: success + type: str + sample: vc2-1c-1gb + image_id: + description: Image ID of the instance. + returned: success + type: str + sample: "" + os_id: + description: OS ID of the instance. + returned: success + type: int + sample: 186 + app_id: + description: App ID of the instance. + returned: success + type: int + sample: 37 + date_created: + description: Date when the instance was created. + returned: success + type: str + sample: "2020-10-10T01:56:20+00:00" + label: + description: Label of the instance. + returned: success + type: str + sample: my instance + region: + description: Region the instance was deployed into. + returned: success + type: str + sample: ews + status: + description: Status about the deployment of the instance. + returned: success + type: str + sample: active + server_status: + description: Server status of the instance. + returned: success + type: str + sample: installingbooting + power_status: + description: Power status of the instance. + returned: success + type: str + sample: running + ram: + description: RAM in MB of the instance. + returned: success + type: int + sample: 1024 + os: + description: OS of the instance. + returned: success + type: str + sample: Application + tags: + description: Tags of the instance. + returned: success + type: list + sample: [ my-tag ] + features: + description: Features of the instance. + returned: success + type: list + sample: [ ddos_protection, ipv6, auto_backups ] + user_data: + description: Base64 encoded user data (cloud init) of the instance. + returned: success + type: str + sample: I2Nsb3VkLWNvbmZpZwpwYWNrYWdlczoKICAtIGh0b3AK + backups: + description: Whether backups are enabled or disabled. + returned: success + type: str + sample: enabled + version_added: "1.3.0" + ddos_protection: + description: Whether DDOS protections is enabled or not. + returned: success + type: bool + sample: true + version_added: "1.3.0" + enable_ipv6: + description: Whether IPv6 is enabled or not. + returned: success + type: bool + sample: true + version_added: "1.3.0" + vpcs: + description: List of VPCs attached. + returned: success + type: list + version_added: "1.5.0" + contains: + id: + description: ID of the VPC. + returned: success + type: str + sample: 5536d2a4-66fd-4dfb-b839-7672fd5bc116 + description: + description: Description of the VPC. + returned: success + type: str + sample: my vpc + ip_address: + description: IP assigned from the VPC. + returned: success + type: str + sample: "192.168.23.3" + mac_address: + description: MAC address of the network interface. + returned: success + type: str + sample: "5a:01:04:3d:5e:72" +""" + +import base64 + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils.vultr_v2 import AnsibleVultr, vultr_argument_spec + + +class AnsibleVultrInstance(AnsibleVultr): + def get_ssh_key_ids(self): + ssh_key_names = list(self.module.params["ssh_keys"]) + ssh_keys = self.query_list(path="/ssh-keys", result_key="ssh_keys") + + ssh_key_ids = list() + for ssh_key in ssh_keys: + if ssh_key["name"] in ssh_key_names: + ssh_key_ids.append(ssh_key["id"]) + ssh_key_names.remove(ssh_key["name"]) + + if ssh_key_names: + self.module.fail_json(msg="SSH key names not found: %s" % ", ".join(ssh_key_names)) + + return ssh_key_ids + + def get_vpc_ids(self): + vpc_names = list(self.module.params["vpcs"]) + vpcs = self.query_list(path="/vpcs", result_key="vpcs") + + vpc_ids = list() + for vpc in vpcs: + if vpc["description"] in vpc_names: + vpc_ids.append(vpc["id"]) + vpc_names.remove(vpc["description"]) + + if vpc_names: + self.module.fail_json(msg="VPCs not found: %s" % ", ".join(vpc_names)) + + return vpc_ids + + def get_instance_vpcs(self, resource): + path = "/instances/%s/vpcs" % resource["id"] + vpcs = self.query_list(path=path, result_key="vpcs") + + # Workaround to get the description field into the list + result = list() + for vpc in vpcs: + vpc_detail = self.query_by_id(resource_id=vpc["id"], path="/vpcs", result_key="vpc") + vpc["description"] = vpc_detail["description"] + result.append(vpc) + return result + + def get_firewall_group(self): + return self.query_filter_list_by_name( + key_name="description", + param_key="firewall_group", + path="/firewalls", + result_key="firewall_groups", + fail_not_found=True, + ) + + def get_snapshot(self): + return self.query_filter_list_by_name( + key_name="description", + param_key="snapshot", + path="/snapshots", + result_key="snapshots", + fail_not_found=True, + ) + + def get_startup_script(self): + return self.query_filter_list_by_name( + key_name="name", + param_key="startup_script", + path="/startup-scripts", + result_key="startup_scripts", + fail_not_found=True, + ) + + def get_os(self): + return self.query_filter_list_by_name( + key_name="name", + param_key="os", + path="/os", + result_key="os", + fail_not_found=True, + ) + + def get_app(self): + return self.query_filter_list_by_name( + key_name="deploy_name", + param_key="app", + path="/applications", + result_key="applications", + fail_not_found=True, + query_params={"type": "one-click"}, + ) + + def get_image(self): + return self.query_filter_list_by_name( + key_name="deploy_name", + param_key="image", + path="/applications", + result_key="applications", + fail_not_found=True, + query_params={"type": "marketplace"}, + ) + + def get_user_data(self, resource): + res = self.api_query( + path="%s/%s/%s" % (self.resource_path, resource[self.resource_key_id], "user-data"), + ) + if res: + return str(res.get("user_data", dict()).get("data")) + return "" + + def transform_resource(self, resource): + if not resource: + return resource + + features = resource.get("features", list()) + resource["backups"] = "enabled" if "auto_backups" in features else "disabled" + resource["enable_ipv6"] = "ipv6" in features + resource["ddos_protection"] = "ddos_protection" in features + resource["vpcs"] = self.get_instance_vpcs(resource=resource) + + return resource + + def get_detach_vpcs_ids(self, resource): + detach_vpc_ids = [] + for vpc in resource.get("vpcs", list()): + if vpc["id"] not in list(self.module.params["attach_vpc"]): + detach_vpc_ids.append(vpc["id"]) + return detach_vpc_ids + + def configure(self): + if self.module.params["state"] != "absent": + if self.module.params["startup_script"] is not None: + self.module.params["script_id"] = self.get_startup_script()["id"] + + if self.module.params["snapshot"] is not None: + self.module.params["snapshot_id"] = self.get_snapshot()["id"] + + if self.module.params["firewall_group"] is not None: + self.module.params["firewall_group_id"] = self.get_firewall_group()["id"] + + if self.module.params["os"] is not None: + self.module.params["os_id"] = self.get_os()["id"] + + if self.module.params["app"] is not None: + self.module.params["app_id"] = self.get_app()["id"] + + if self.module.params["image"] is not None: + self.module.params["image_id"] = self.get_image()["image_id"] + + if self.module.params["user_data"] is not None: + self.module.params["user_data"] = base64.b64encode(self.module.params["user_data"].encode()) + + if self.module.params["ssh_keys"] is not None: + # sshkey_id ist a list of ids + self.module.params["sshkey_id"] = self.get_ssh_key_ids() + + if self.module.params["backups"] is not None: + self.module.params["backups"] = "enabled" if self.module.params["backups"] else "disabled" + + if self.module.params["vpcs"] is not None: + # attach_vpc is a list of ids used while creating + self.module.params["attach_vpc"] = self.get_vpc_ids() + + def handle_power_status(self, resource, state, action, power_status, force=False, wait_for_state=True): + if state == self.module.params["state"] and (resource["power_status"] != power_status or force): + self.result["changed"] = True + if not self.module.check_mode: + resource = self.wait_for_state(resource=resource, key="server_status", states=["none", "locked"], cmp="!=") + self.api_query( + path="%s/%s/%s" % (self.resource_path, resource[self.resource_key_id], action), + method="POST", + ) + if wait_for_state: + resource = self.wait_for_state(resource=resource, key="power_status", states=[power_status]) + return resource + + def create(self): + param_keys = ("os", "image", "app", "snapshot") + if not any(self.module.params.get(x) is not None for x in param_keys): + self.module.fail_json(msg="missing required arguements, one of the following required: %s" % ", ".join(param_keys)) + return super(AnsibleVultrInstance, self).create() + + def update(self, resource): + user_data = self.get_user_data(resource=resource) + resource["user_data"] = user_data.encode() + + if self.module.params["vpcs"] is not None: + resource["attach_vpc"] = list() + for vpc in list(resource["vpcs"]): + resource["attach_vpc"].append(vpc["id"]) + + # detach_vpc is a list of ids to be detached + resource["detach_vpc"] = list() + self.module.params["detach_vpc"] = self.get_detach_vpcs_ids(resource=resource) + + return super(AnsibleVultrInstance, self).update(resource=resource) + + def create_or_update(self): + resource = super(AnsibleVultrInstance, self).create_or_update() + if resource: + resource = self.wait_for_state(resource=resource, key="status", states=["active"]) + resource = self.wait_for_state(resource=resource, key="server_status", states=["none", "locked"], cmp="!=") + + # Hanlde power status + resource = self.handle_power_status(resource=resource, state="stopped", action="halt", power_status="stopped") + resource = self.handle_power_status(resource=resource, state="started", action="start", power_status="running") + resource = self.handle_power_status(resource=resource, state="restarted", action="reboot", power_status="running", force=True) + resource = self.handle_power_status( + resource=resource, + state="reinstalled", + action="reinstall", + power_status="running", + force=True, + wait_for_state=False, + ) + + return resource + + def transform_result(self, resource): + if resource: + resource["user_data"] = self.get_user_data(resource=resource) + return resource + + def absent(self): + resource = self.query() + if resource and not self.module.check_mode: + resource = self.wait_for_state(resource=resource, key="server_status", states=["none", "locked"], cmp="!=") + + return super(AnsibleVultrInstance, self).absent(resource=resource) + + +def main(): + argument_spec = vultr_argument_spec() + argument_spec.update( + dict( + label=dict(type="str", required=True, aliases=["name"]), + hostname=dict(type="str"), + app=dict(type="str"), + image=dict(type="str"), + snapshot=dict(type="str"), + os=dict(type="str"), + plan=dict(type="str"), + activation_email=dict(type="bool", default=False), + ddos_protection=dict(type="bool"), + backups=dict(type="bool"), + enable_ipv6=dict(type="bool"), + tags=dict(type="list", elements="str"), + vpcs=dict(type="list", elements="str"), + reserved_ipv4=dict(type="str"), + firewall_group=dict(type="str"), + startup_script=dict(type="str"), + user_data=dict(type="str"), + ssh_keys=dict(type="list", elements="str", no_log=False), + region=dict(type="str", required=True), + state=dict( + choices=[ + "present", + "absent", + "started", + "stopped", + "restarted", + "reinstalled", + ], + default="present", + ), + ) # type: ignore + ) + + module = AnsibleModule( + argument_spec=argument_spec, + required_if=(("state", "present", ("plan",)),), + mutually_exclusive=(("os", "app", "image", "snapshot"),), + supports_check_mode=True, + ) + + vultr = AnsibleVultrInstance( + module=module, + namespace="vultr_instance", + resource_path="/instances", + ressource_result_key_singular="instance", + resource_create_param_keys=[ + "label", + "hostname", + "plan", + "app_id", + "os_id", + "iso_id", + "image_id", + "snapshot_id", + "script_id", + "region", + "enable_ipv6", + "reserved_ipv4", + "firewall_group_id", + "user_data", + "tags", + "activation_email", + "ddos_protection", + "sshkey_id", + "backups", + "attach_vpc", + ], + resource_update_param_keys=[ + "plan", + "tags", + "firewall_group_id", + "enable_ipv6", + "ddos_protection", + "backups", + "user_data", + "attach_vpc", + "detach_vpc", + ], + resource_key_name="label", + ) + + state = module.params.get("state") # type: ignore + if state == "absent": + vultr.absent() + else: + vultr.present() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/vultr/cloud/plugins/modules/instance_info.py b/ansible_collections/vultr/cloud/plugins/modules/instance_info.py new file mode 100644 index 000000000..2a5c311bb --- /dev/null +++ b/ansible_collections/vultr/cloud/plugins/modules/instance_info.py @@ -0,0 +1,271 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2022, René Moser <mail@renemoser.net> +# 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 = """ +--- +module: instance_info +short_description: Get information about the Vultr instances +description: + - Get infos about available instances. +version_added: "1.5.0" +author: + - "René Moser (@resmo)" +options: + label: + description: + - Name of the instance. + aliases: [ name ] + type: str + region: + description: + - Filter instances by region. + type: str +extends_documentation_fragment: + - vultr.cloud.vultr_v2 +""" + +EXAMPLES = """ +- name: Get Vultr instance infos of region ams + vultr.cloud.instance_info: + region: ams + +- name: Get Vultr instance infos of a single host + vultr.cloud.instance_info: + label: myhost + +- name: Get all Vultr instance infos + vultr.cloud.instance_info: + register: results + +- name: Print the gathered infos + ansible.builtin.debug: + var: results.vultr_instance_info +""" + +RETURN = """ +--- +vultr_api: + description: Response from Vultr API with a few additions/modification. + returned: success + type: dict + contains: + api_timeout: + description: Timeout used for the API requests. + returned: success + type: int + sample: 60 + api_retries: + description: Amount of max retries for the API requests. + returned: success + type: int + sample: 5 + api_retry_max_delay: + description: Exponential backoff delay in seconds between retries up to this max delay value. + returned: success + type: int + sample: 12 + api_endpoint: + description: Endpoint used for the API requests. + returned: success + type: str + sample: "https://api.vultr.com/v2" +vultr_instance_info: + description: Response from Vultr API as list. + returned: available + type: list + contains: + id: + description: ID of the instance. + returned: success + type: str + sample: cb676a46-66fd-4dfb-b839-443f2e6c0b60 + v6_main_ip: + description: IPv6 of the instance. + returned: success + type: str + sample: "" + v6_network: + description: IPv6 network of the instance. + returned: success + type: str + sample: "" + v6_network_size: + description: IPv6 network size of the instance. + returned: success + type: int + sample: 0 + main_ip: + description: IPv4 of the instance. + returned: success + type: str + sample: 95.179.189.95 + netmask_v4: + description: Netmask IPv4 of the instance. + returned: success + type: str + sample: 255.255.254.0 + hostname: + description: Hostname of the instance. + returned: success + type: str + sample: vultr.guest + internal_ip: + description: Internal IP of the instance. + returned: success + type: str + sample: "" + gateway_v4: + description: Gateway IPv4. + returned: success + type: str + sample: 95.179.188.1 + kvm: + description: KVM of the instance. + returned: success + type: str + sample: "https://my.vultr.com/subs/vps/novnc/api.php?data=..." + disk: + description: Disk size of the instance. + returned: success + type: int + sample: 25 + allowed_bandwidth: + description: Allowed bandwidth of the instance. + returned: success + type: int + sample: 1000 + vcpu_count: + description: vCPUs of the instance. + returned: success + type: int + sample: 1 + firewall_group_id: + description: Firewall group ID of the instance. + returned: success + type: str + sample: "" + plan: + description: Plan of the instance. + returned: success + type: str + sample: vc2-1c-1gb + image_id: + description: Image ID of the instance. + returned: success + type: str + sample: "" + os_id: + description: OS ID of the instance. + returned: success + type: int + sample: 186 + app_id: + description: App ID of the instance. + returned: success + type: int + sample: 37 + date_created: + description: Date when the instance was created. + returned: success + type: str + sample: "2020-10-10T01:56:20+00:00" + label: + description: Label of the instance. + returned: success + type: str + sample: my instance + region: + description: Region the instance was deployed into. + returned: success + type: str + sample: ews + status: + description: Status about the deployment of the instance. + returned: success + type: str + sample: active + server_status: + description: Server status of the instance. + returned: success + type: str + sample: installingbooting + power_status: + description: Power status of the instance. + returned: success + type: str + sample: running + ram: + description: RAM in MB of the instance. + returned: success + type: int + sample: 1024 + os: + description: OS of the instance. + returned: success + type: str + sample: Application + tags: + description: Tags of the instance. + returned: success + type: list + sample: [ my-tag ] + features: + description: Features of the instance. + returned: success + type: list + sample: [ ddos_protection, ipv6, auto_backups ] + user_data: + description: Base64 encoded user data (cloud init) of the instance. + returned: success + type: str + sample: I2Nsb3VkLWNvbmZpZwpwYWNrYWdlczoKICAtIGh0b3AK + +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils.vultr_v2 import AnsibleVultr, vultr_argument_spec + + +def main(): + argument_spec = vultr_argument_spec() + argument_spec.update( + dict( + region=dict(type="str", aliases=["name"]), + label=dict(type="str"), + ) # type: ignore + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + vultr = AnsibleVultr( + module=module, + namespace="vultr_instance_info", + resource_path="/instances", + ressource_result_key_singular="instance", + ressource_result_key_plural="instances", + ) + + query_params = dict() + if module.params["region"] is not None: # type: ignore + query_params.update({"region": module.params["region"]}) # type: ignore + + if module.params["label"] is not None: # type: ignore + query_params.update({"label": module.params["label"]}) # type: ignore + + vultr.get_result(vultr.query_list(query_params=query_params)) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/vultr/cloud/plugins/modules/os_info.py b/ansible_collections/vultr/cloud/plugins/modules/os_info.py new file mode 100644 index 000000000..8edb7a7f1 --- /dev/null +++ b/ansible_collections/vultr/cloud/plugins/modules/os_info.py @@ -0,0 +1,115 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2018, Yanis Guenane <yanis+ansible@guenane.org> +# Copyright (c) 2021, René Moser <mail@renemoser.net> +# 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 = """ +--- +module: os_info +short_description: Get information about the Vultr operation systems +description: + - Get infos about operating systems available to boot servers. +version_added: "1.0.0" +author: + - "Yanis Guenane (@Spredzy)" + - "René Moser (@resmo)" +extends_documentation_fragment: + - vultr.cloud.vultr_v2 +""" + +EXAMPLES = """ +- name: Get Vultr OSes infos + vultr.cloud.os_info: + register: results + +- name: Print the gathered infos + ansible.builtin.debug: + var: results.vultr_os_info +""" + +RETURN = """ +--- +vultr_api: + description: Response from Vultr API with a few additions/modification. + returned: success + type: dict + contains: + api_timeout: + description: Timeout used for the API requests. + returned: success + type: int + sample: 60 + api_retries: + description: Amount of max retries for the API requests. + returned: success + type: int + sample: 5 + api_retry_max_delay: + description: Exponential backoff delay in seconds between retries up to this max delay value. + returned: success + type: int + sample: 12 + api_endpoint: + description: Endpoint used for the API requests. + returned: success + type: str + sample: "https://api.vultr.com/v2" +vultr_os_info: + description: Response from Vultr API as list. + returned: available + type: list + contains: + arch: + description: OS Architecture. + returned: success + type: str + sample: x64 + family: + description: OS family. + returned: success + type: str + sample: openbsd + name: + description: OS name. + returned: success + type: str + sample: OpenBSD 6 x64 + windows: + description: OS is a MS Windows. + returned: success + type: bool +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils.vultr_v2 import AnsibleVultr, vultr_argument_spec + + +def main(): + argument_spec = vultr_argument_spec() + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + vultr = AnsibleVultr( + module=module, + namespace="vultr_os_info", + resource_path="/os", + ressource_result_key_singular="os", + ressource_result_key_plural="os", + ) + + vultr.get_result(vultr.query_list()) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/vultr/cloud/plugins/modules/plan_info.py b/ansible_collections/vultr/cloud/plugins/modules/plan_info.py new file mode 100644 index 000000000..639a10731 --- /dev/null +++ b/ansible_collections/vultr/cloud/plugins/modules/plan_info.py @@ -0,0 +1,140 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2018, Yanis Guenane <yanis+ansible@guenane.org> +# Copyright (c) 2021, René Moser <mail@renemoser.net> +# 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 = """ +--- +module: plan_info +short_description: Gather information about the Vultr plans +description: + - Gather information about plans available to boot servers. +version_added: "1.0.0" +author: + - "Yanis Guenane (@Spredzy)" + - "René Moser (@resmo)" +extends_documentation_fragment: + - vultr.cloud.vultr_v2 +""" + +EXAMPLES = """ +- name: Gather Vultr plans information + vultr.cloud.plan_info: + register: result + +- name: Print the gathered information + ansible.builtin.debug: + var: result.vultr_plan_info +""" + +RETURN = """ +--- +vultr_api: + description: Response from Vultr API with a few additions/modification. + returned: success + type: dict + contains: + api_timeout: + description: Timeout used for the API requests. + returned: success + type: int + sample: 60 + api_retries: + description: Amount of max retries for the API requests. + returned: success + type: int + sample: 5 + api_retry_max_delay: + description: Exponential backoff delay in seconds between retries up to this max delay value. + returned: success + type: int + sample: 12 + api_endpoint: + description: Endpoint used for the API requests. + returned: success + type: str + sample: "https://api.vultr.com/v2" +vultr_plan_info: + description: Response from Vultr API as list. + returned: success + type: list + contains: + id: + description: ID of the plan. + returned: success + type: str + sample: vhf-8c-32gb + vcpu_count: + description: Amount of CPUs. + returned: success + type: int + sample: 8 + ram: + description: Amount of RAM in MB. + returned: success + type: int + sample: 32768 + disk: + description: Disk size in GB. + returned: success + type: int + sample: 512 + disk_count: + description: Amount of disks. + returned: success + type: int + sample: 1 + bandwidth: + description: Bandwidth in MB. + returned: success + type: int + sample: 6144 + monthly_cost: + description: Monthly cost in $. + returned: success + type: int + sample: 192 + type: + description: Type of plan. + returned: success + type: str + sample: vhf + locations: + description: List of locations the plan is available in. + returned: success + type: list + sample: ["ewr"] +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils.vultr_v2 import AnsibleVultr, vultr_argument_spec + + +def main(): + argument_spec = vultr_argument_spec() + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + vultr = AnsibleVultr( + module=module, + namespace="vultr_plan_info", + resource_path="/plans", + ressource_result_key_singular="plan", + ) + + vultr.get_result(vultr.query_list()) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/vultr/cloud/plugins/modules/plan_metal_info.py b/ansible_collections/vultr/cloud/plugins/modules/plan_metal_info.py new file mode 100644 index 000000000..c7ff478bf --- /dev/null +++ b/ansible_collections/vultr/cloud/plugins/modules/plan_metal_info.py @@ -0,0 +1,153 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2019, Nate River <vitikc@gmail.com> +# Copyright (c) 2020, Simon Baerlocher <s.baerlocher@sbaerlocher.ch> +# Copyright (c) 2021, René Moser <mail@renemoser.net> +# 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 = """ +--- +module: plan_metal_info +short_description: Gather information about the Vultr bare metal plans +description: + - Gather information about plans available to boot servers. +version_added: "1.0.0" +author: + - "Nate River (@vitikc)" + - "Simon Baerlocher (@sbaerlocher)" + - "René Moser (@resmo)" +extends_documentation_fragment: + - vultr.cloud.vultr_v2 +""" + +EXAMPLES = """ +- name: Gather Vultr bare metal plans information + vultr.cloud.plan_metal_info: + register: result + +- name: Print the gathered information + ansible.builtin.debug: + var: result.vultr_plan_metal_info +""" + +RETURN = """ +--- +vultr_api: + description: Response from Vultr API with a few additions/modification. + returned: success + type: dict + contains: + api_timeout: + description: Timeout used for the API requests. + returned: success + type: int + sample: 60 + api_retries: + description: Amount of max retries for the API requests. + returned: success + type: int + sample: 5 + api_retry_max_delay: + description: Exponential backoff delay in seconds between retries up to this max delay value. + returned: success + type: int + sample: 12 + api_endpoint: + description: Endpoint used for the API requests. + returned: success + type: str + sample: "https://api.vultr.com/v2" +vultr_plan_info: + description: Response from Vultr API as list. + returned: success + type: list + contains: + id: + description: ID of the plan. + returned: success + type: str + sample: vbm-4c-32gb + cpu_count: + description: Amount of CPUs. + returned: success + type: int + sample: 4 + cpu_threads: + description: Amount of CPU threads. + returned: success + type: int + sample: 8 + cpu_model: + description: CPU model. + returned: success + type: str + sample: E3-1270v6 + ram: + description: Amount of RAM in MB. + returned: success + type: int + sample: 32768 + disk: + description: Disk size in GB. + returned: success + type: int + sample: 240 + disk_count: + description: Amount of disks. + returned: success + type: int + sample: 2 + bandwidth: + description: Bandwidth in MB. + returned: success + type: int + sample: 5120 + monthly_cost: + description: Monthly cost in $. + returned: success + type: int + sample: 300 + type: + description: Type of plan. + returned: success + type: str + sample: SSD + locations: + description: List of locations the plan is available in. + returned: success + type: list + sample: ["ewr"] +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils.vultr_v2 import AnsibleVultr, vultr_argument_spec + + +def main(): + argument_spec = vultr_argument_spec() + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + vultr = AnsibleVultr( + module=module, + namespace="vultr_plan_metal_info", + resource_path="/plans-metal", + ressource_result_key_singular="plan_metal", + ressource_result_key_plural="plans_metal", + ) + + vultr.get_result(vultr.query_list()) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/vultr/cloud/plugins/modules/region_info.py b/ansible_collections/vultr/cloud/plugins/modules/region_info.py new file mode 100644 index 000000000..1e222b9f9 --- /dev/null +++ b/ansible_collections/vultr/cloud/plugins/modules/region_info.py @@ -0,0 +1,106 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2018, Yanis Guenane <yanis+ansible@guenane.org> +# Copyright (c) 2021, René Moser <mail@renemoser.net> +# 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 = """ +--- +module: region_info +short_description: Gather information about the Vultr regions +description: + - Gather information about regions available to boot servers. +version_added: "1.0.0" +author: + - "Yanis Guenane (@Spredzy)" + - "René Moser (@resmo)" +extends_documentation_fragment: + - vultr.cloud.vultr_v2 +""" + +EXAMPLES = """ +- name: Gather Vultr regions information + vultr.cloud.region_info: + register: result + +- name: Print the gathered information + ansible.builtin.debug: + var: result.vultr_region_info +""" + +RETURN = """ +--- +vultr_api: + description: Response from Vultr API with a few additions/modification. + returned: success + type: dict + contains: + api_timeout: + description: Timeout used for the API requests. + returned: success + type: int + sample: 60 + api_retries: + description: Amount of max retries for the API requests. + returned: success + type: int + sample: 5 + api_retry_max_delay: + description: Exponential backoff delay in seconds between retries up to this max delay value. + returned: success + type: int + sample: 12 + api_endpoint: + description: Endpoint used for the API requests. + returned: success + type: str + sample: "https://api.vultr.com/v2" +vultr_region_info: + description: Response from Vultr API. + returned: success + type: list + sample: [ + { + "block_storage": false, + "continent": "Europe", + "country": "GB", + "ddos_protection": true, + "id": 8, + "name": "London", + "regioncode": "LHR", + "state": "" + } + ] +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils.vultr_v2 import AnsibleVultr, vultr_argument_spec + + +def main(): + argument_spec = vultr_argument_spec() + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + vultr = AnsibleVultr( + module=module, + namespace="vultr_region_info", + resource_path="/regions", + ressource_result_key_singular="region", + ) + + vultr.get_result(vultr.query_list()) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/vultr/cloud/plugins/modules/reserved_ip.py b/ansible_collections/vultr/cloud/plugins/modules/reserved_ip.py new file mode 100644 index 000000000..9bc2e254b --- /dev/null +++ b/ansible_collections/vultr/cloud/plugins/modules/reserved_ip.py @@ -0,0 +1,290 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2021, René Moser <mail@renemoser.net> +# 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 = """ +--- +module: reserved_ip +short_description: Manages reserved IPs on Vultr +description: + - Create, attach, detach and remove reserved IPs. +version_added: "1.0.0" +author: + - "René Moser (@resmo)" +options: + label: + description: + - Label of the reserved IP. + required: true + aliases: [ name ] + type: str + instance_name: + description: + - Name of the Instance the reserved IP should be attached to. + - Mutually exclusive with I(instance_id). + type: str + instance_id: + description: + - ID of the Instance the reserved IP should be attached to. + - Mutually exclusive with I(instance_name). + type: str + region: + description: + - Region of the reserved IP will be related to. + type: str + required: true + ip_type: + description: + - Type of the IP. + type: str + choices: [ v4, v6 ] + required: true + state: + description: + - State of the reserved IP. + default: present + choices: [ present, absent ] + type: str +extends_documentation_fragment: + - vultr.cloud.vultr_v2 +""" + +EXAMPLES = """ +- name: Ensure a reserved IP present and attached to an instance + vultr.cloud.reserved_ip: + label: my attached IP + region: ewr + ip_type: v4 + instance_name: web-01 + +- name: Ensure a reserved IP is detached + vultr.cloud.reserved_ip: + label: my reserved IP + region: ewr + ip_type: v4 + instance_id: "" + +- name: Ensure a reserved IP is absent + vultr.cloud.reserved_ip: + label: my attached IP + region: ewr + ip_type: v4 + state: absent +""" + +RETURN = """ +--- +vultr_api: + description: Response from Vultr API with a few additions/modification. + returned: success + type: dict + contains: + api_timeout: + description: Timeout used for the API requests. + returned: success + type: int + sample: 60 + api_retries: + description: Amount of max retries for the API requests. + returned: success + type: int + sample: 5 + api_retry_max_delay: + description: Exponential backoff delay in seconds between retries up to this max delay value. + returned: success + type: int + sample: 12 + api_endpoint: + description: Endpoint used for the API requests. + returned: success + type: str + sample: "https://api.vultr.com/v2" +vultr_reserved_ip: + description: Response from Vultr API. + returned: success + type: dict + contains: + id: + description: ID of the reserved IP. + returned: success + type: str + sample: cb676a46-66fd-4dfb-b839-443f2e6c0b60 + label: + description: Name of the reserved IP. + returned: success + type: str + sample: example.com + region: + description: Region of the reserved IP is related to. + returned: success + type: str + sample: ewr + ip_type: + description: Type of the reserved IP. + returned: success + type: str + sample: v4 + subnet: + description: Subnet of the reserved IP. + returned: success + type: str + sample: v4 + subnet_size: + description: Size of the subnet of the reserved IP. + returned: success + type: int + sample: 32 + instance_id: + description: ID of the Instance the reserved IP is attached to. + returned: success + type: str + sample: cb676a46-66fd-4dfb-b839-443f2e6c0b +""" + +import urllib + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils.vultr_v2 import AnsibleVultr, vultr_argument_spec + + +class AnsibleVultrReservedIp(AnsibleVultr): + def configure(self): + self.instance_id = self.get_instance_id() + + def get_instance_id(self): + instance_id = self.module.params["instance_id"] + if instance_id is not None: + return instance_id + + instance_name = self.module.params["instance_name"] + if instance_name is not None: + + # Empty string ID means detach instance + if len(instance_name) == 0: + return "" + + # URL encode label + try: + label = urllib.quote(instance_name) # type: ignore + except AttributeError: + label = urllib.parse.quote(instance_name) # type: ignore + + # Filter instances by label + resources = self.api_query(path="/instances?label=%s" % label) or dict() + if not resources or not resources["instances"]: + self.module.fail_json(msg="No instance with name found: %s" % instance_name) + + if len(resources["instances"]) > 1: + self.module.fail_json(msg="More then one instance with name found: %s" % instance_name) + + return resources["instances"][0]["id"] + + def query_list(self, path=None, result_key=None): + resources = self.api_query(path=self.resource_path) or dict() + + resources_filtered = list() + for resource in resources[self.ressource_result_key_plural]: + # Skip IP with different type + if resource["ip_type"] != self.module.params["ip_type"]: + continue + # Skip IP in different region + if resource["region"] != self.module.params["region"]: + continue + resources_filtered.append(resource) + + return resources_filtered + + def create(self): + resource = super().create() or dict() + if resource and self.instance_id: + if not self.module.check_mode: + # Attach instance + self.api_query( + path="%s/%s/%s" + % ( + self.resource_path, + resource[self.resource_key_id], + "attach", + ), + method="POST", + data=dict(instance_id=self.instance_id), + ) + # Refresh + resource = self.query_by_id(resource_id=resource[self.resource_key_id]) + return resource + + def update(self, resource): + if self.instance_id is None: + return resource + + # Detach instance + elif resource["instance_id"] and not self.instance_id: + self.result["changed"] = True + if not self.module.check_mode: + self.api_query( + path="%s/%s/%s" % (self.resource_path, resource[self.resource_key_id], "detach"), + method="POST", + data=dict(instance_id=self.instance_id), + ) + # Refresh + resource = self.query_by_id(resource_id=resource[self.resource_key_id]) + + # Attach instance or change attached instance + elif self.instance_id and resource["instance_id"] != self.instance_id: + self.result["changed"] = True + if not self.module.check_mode: + self.api_query( + path="%s/%s/%s" % (self.resource_path, resource[self.resource_key_id], "attach"), + method="POST", + data=dict(instance_id=self.instance_id), + ) + # Refresh + resource = self.query_by_id(resource_id=resource[self.resource_key_id]) + + return resource + + +def main(): + argument_spec = vultr_argument_spec() + argument_spec.update( + dict( + label=dict(type="str", required=True, aliases=["name"]), + instance_id=dict(type="str"), + instance_name=dict(type="str"), + ip_type=dict(type="str", required=True, choices=["v4", "v6"]), + region=dict(type="str", required=True), + state=dict(type="str", choices=["present", "absent"], default="present"), + ) # type: ignore + ) + + module = AnsibleModule( + argument_spec=argument_spec, + mutually_exclusive=(["instance_id", "instance_name"],), + supports_check_mode=True, + ) + + vultr = AnsibleVultrReservedIp( + module=module, + namespace="vultr_reserved_ip", + resource_path="/reserved-ips", + ressource_result_key_singular="reserved_ip", + resource_create_param_keys=["region", "ip_type", "label"], + resource_key_name="label", + ) + + if module.params.get("state") == "absent": # type: ignore + vultr.absent() + else: + vultr.present() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/vultr/cloud/plugins/modules/snapshot.py b/ansible_collections/vultr/cloud/plugins/modules/snapshot.py new file mode 100644 index 000000000..9dba03c74 --- /dev/null +++ b/ansible_collections/vultr/cloud/plugins/modules/snapshot.py @@ -0,0 +1,218 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2023, René Moser <mail@renemoser.net> +# 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 = """ +--- +module: snapshot +short_description: Manages snapshots on Vultr +description: + - Create and remove snapshots. +version_added: "1.7.0" +author: "René Moser (@resmo)" +options: + description: + description: + - Description of the snapshot. + required: true + aliases: [ name ] + type: str + instance: + description: + - The description or ID of the instance from which to take the snapshot. + - Mutually exclusive with I(url). + - I(instance) or I(url) is required if I(state=present). + type: str + url: + description: + - The URL of the snapshot image (RAW) to be uploaded. + - Mutually exclusive with I(instance). + - I(instance) or I(url) is required if I(state=present). + type: str + state: + description: + - State of the snapshot. + default: present + choices: [ present, absent ] + type: str +extends_documentation_fragment: + - vultr.cloud.vultr_v2 +""" + +EXAMPLES = """ +- name: Ensure a snapshot is present + vultr.cloud.snapshot: + description: my snapshot of my instance + instance: my instance + +- name: Ensure a snapshot is present + vultr.cloud.snapshot: + description: debian 11 generic + url: https://cloud.debian.org/images/cloud/bullseye/latest/debian-11-generic-amd64.raw + +- name: Ensure a snapshot is absent + vultr.cloud.snapshot: + description: my snapshot of my instance + state: absent +""" + +RETURN = """ +--- +vultr_api: + description: Response from Vultr API with a few additions/modification. + returned: success + type: dict + contains: + api_timeout: + description: Timeout used for the API requests. + returned: success + type: int + sample: 60 + api_retries: + description: Amount of max retries for the API requests. + returned: success + type: int + sample: 5 + api_retry_max_delay: + description: Exponential backoff delay in seconds between retries up to this max delay value. + returned: success + type: int + sample: 12 + api_endpoint: + description: Endpoint used for the API requests. + returned: success + type: str + sample: "https://api.vultr.com/v2" +vultr_snapshot: + description: Response from Vultr API. + returned: success + type: dict + contains: + id: + description: ID of the snapshot. + returned: success + type: str + sample: cb676a46-66fd-4dfb-b839-443f2e6c0b60 + description: + description: Description of the snapshot. + returned: success + type: str + sample: my vpc + date_created: + description: Date the snapshot was created. + returned: success + type: str + sample: "2020-10-10T01:56:20+00:00" + size: + description: Size of the snapshot. + returned: success + type: int + sample: 42949672960 + compressed_size: + description: Compressed size of the snapshot. + returned: success + type: int + sample: 949678560 + status: + description: Status of the snapshot. + returned: success + type: str + sample: complete + os_id: + description: ID of the OS. + returned: success + type: int + sample: 215 + app_id: + description: ID of the app. + returned: success + type: int + sample: 0 +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils.vultr_v2 import AnsibleVultr, vultr_argument_spec + + +class AnsibleVultrSnapshot(AnsibleVultr): + def get_instance(self): + return self.query_filter_list_by_name( + key_name="label", + param_key="instance", + path="/instances", + result_key="instances", + fail_not_found=True, + ) + + def create(self): + param_keys = ("url", "instance") + if not any(self.module.params.get(x) is not None for x in param_keys): + self.module.fail_json(msg="missing required arguements, one of the following required: %s" % ", ".join(param_keys)) + + if self.module.params.get("url") is not None: + self.resource_create_param_keys.append("url") + # Upload by URL has a different endpoint + self.resource_path = self.resource_path + "/create-from-url" + else: + self.module.params["instance_id"] = self.get_instance()["id"] + self.resource_create_param_keys.append("instance_id") + + resource = super(AnsibleVultrSnapshot, self).create() + + # Reset endpoint + self.resource_path = "/snapshots" + + if resource: + resource = self.wait_for_state(resource=resource, key="status", states=["complete"]) + + return resource + + +def main(): + argument_spec = vultr_argument_spec() + argument_spec.update( + dict( + description=dict(type="str", required=True, aliases=["name"]), + instance=dict(type="str"), + url=dict(type="str"), + state=dict(type="str", choices=["present", "absent"], default="present"), + ) # type: ignore + ) + + module = AnsibleModule( + argument_spec=argument_spec, + mutually_exclusive=(("instance", "url"),), + supports_check_mode=True, + ) + + vultr = AnsibleVultrSnapshot( + module=module, + namespace="vultr_snapshot", + resource_path="/snapshots", + ressource_result_key_singular="snapshot", + resource_create_param_keys=[ + "description", + ], + resource_update_param_keys=[ + "description", + ], + resource_key_name="description", + resource_update_method="PUT", + ) + + if module.params.get("state") == "absent": # type: ignore + vultr.absent() + else: + vultr.present() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/vultr/cloud/plugins/modules/snapshot_info.py b/ansible_collections/vultr/cloud/plugins/modules/snapshot_info.py new file mode 100644 index 000000000..38fa4b85e --- /dev/null +++ b/ansible_collections/vultr/cloud/plugins/modules/snapshot_info.py @@ -0,0 +1,132 @@ +#!/usr/bin/python +# +# Copyright (c) 2023, René Moser <mail@renemoser.net> +# 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 = """ +--- +module: snapshot_info +short_description: Gather information about the Vultr snapshots +description: + - Gather information about snapshots available. +version_added: "1.7.0" +author: + - "René Moser (@resmo)" +extends_documentation_fragment: + - vultr.cloud.vultr_v2 +""" + +EXAMPLES = """ +- name: Gather Vultr snapshots information + vultr.cloud.snapshot_info: + register: result + +- name: Print the gathered information + ansible.builtin.debug: + var: result.vultr_snapshot_info +""" + +RETURN = """ +--- +vultr_api: + description: Response from Vultr API with a few additions/modification. + returned: success + type: dict + contains: + api_timeout: + description: Timeout used for the API requests. + returned: success + type: int + sample: 60 + api_retries: + description: Amount of max retries for the API requests. + returned: success + type: int + sample: 5 + api_retry_max_delay: + description: Exponential backoff delay in seconds between retries up to this max delay value. + returned: success + type: int + sample: 12 + api_endpoint: + description: Endpoint used for the API requests. + returned: success + type: str + sample: "https://api.vultr.com/v2" +vultr_snapshot_info: + description: Response from Vultr API as list. + returned: success + type: list + contains: + id: + description: ID of the snapshot. + returned: success + type: str + sample: cb676a46-66fd-4dfb-b839-443f2e6c0b60 + description: + description: Description of the snapshot. + returned: success + type: str + sample: my vpc + date_created: + description: Date the snapshot was created. + returned: success + type: str + sample: "2020-10-10T01:56:20+00:00" + size: + description: Size of the snapshot. + returned: success + type: int + sample: 42949672960 + compressed_size: + description: Compressed size of the snapshot. + returned: success + type: int + sample: 949678560 + status: + description: Status of the snapshot. + returned: success + type: str + sample: complete + os_id: + description: ID of the OS. + returned: success + type: int + sample: 215 + app_id: + description: ID of the app. + returned: success + type: int + sample: 0 +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils.vultr_v2 import AnsibleVultr, vultr_argument_spec + + +def main(): + argument_spec = vultr_argument_spec() + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + vultr = AnsibleVultr( + module=module, + namespace="vultr_snapshot_info", + resource_path="/snapshots", + ressource_result_key_singular="snapshot", + ) + + vultr.get_result(vultr.query_list()) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/vultr/cloud/plugins/modules/ssh_key.py b/ansible_collections/vultr/cloud/plugins/modules/ssh_key.py new file mode 100644 index 000000000..2c85eebff --- /dev/null +++ b/ansible_collections/vultr/cloud/plugins/modules/ssh_key.py @@ -0,0 +1,148 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2021, René Moser <mail@renemoser.net> +# 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 = """ +--- +module: ssh_key +short_description: Manages ssh keys on Vultr. +description: + - Create, update and remove ssh keys. +version_added: "1.0.0" +author: "René Moser (@resmo)" +options: + name: + description: + - Name of the ssh key. + required: true + type: str + ssh_key: + description: + - SSH public key. + - Required if C(state=present). + type: str + state: + description: + - State of the ssh key. + default: present + choices: [ present, absent ] + type: str +extends_documentation_fragment: + - vultr.cloud.vultr_v2 + +""" + +EXAMPLES = """ +- name: ensure an SSH key is present + vultr.cloud.ssh_key: + name: my ssh key + ssh_key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}" + +- name: ensure an SSH key is absent + vultr.cloud.ssh_key: + name: my ssh key + state: absent +""" + +RETURN = """ +--- +vultr_api: + description: Response from Vultr API with a few additions/modification. + returned: success + type: dict + contains: + api_timeout: + description: Timeout used for the API requests. + returned: success + type: int + sample: 60 + api_retries: + description: Amount of max retries for the API requests. + returned: success + type: int + sample: 5 + api_retry_max_delay: + description: Exponential backoff delay in seconds between retries up to this max delay value. + returned: success + type: int + sample: 12 + api_endpoint: + description: Endpoint used for the API requests. + returned: success + type: str + sample: "https://api.vultr.com/v2" +vultr_ssh_key: + description: Response from Vultr API. + returned: success + type: dict + contains: + id: + description: ID of the ssh key. + returned: success + type: str + sample: cb676a46-66fd-4dfb-b839-443f2e6c0b60 + name: + description: Name of the ssh key. + returned: success + type: str + sample: my ssh key + date_created: + description: Date the ssh key was created. + returned: success + type: str + sample: "2020-10-10T01:56:20+00:00" + ssh_key: + description: SSH public key. + returned: success + type: str + sample: ssh-rsa AA... someother@example.com +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils.vultr_v2 import AnsibleVultr, vultr_argument_spec + + +def main(): + argument_spec = vultr_argument_spec() + argument_spec.update( + dict( + name=dict(type="str", required=True), + ssh_key=dict(type="str", no_log=False), + state=dict(type="str", choices=["present", "absent"], default="present"), + ) # type: ignore + ) + + module = AnsibleModule( + argument_spec=argument_spec, + required_if=[ + ("state", "present", ["ssh_key"]), + ], + supports_check_mode=True, + ) + + vultr = AnsibleVultr( + module=module, + namespace="vultr_ssh_key", + resource_path="/ssh-keys", + ressource_result_key_singular="ssh_key", + resource_create_param_keys=["name", "ssh_key"], + resource_update_param_keys=["name", "ssh_key"], + resource_key_name="name", + ) + + if module.params.get("state") == "absent": # type: ignore + vultr.absent() + else: + vultr.present() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/vultr/cloud/plugins/modules/ssh_key_info.py b/ansible_collections/vultr/cloud/plugins/modules/ssh_key_info.py new file mode 100644 index 000000000..24441885b --- /dev/null +++ b/ansible_collections/vultr/cloud/plugins/modules/ssh_key_info.py @@ -0,0 +1,117 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2018, Yanis Guenane <yanis+ansible@guenane.org> +# Copyright (c) 2021, René Moser <mail@renemoser.net> + +# 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 = """ +--- +module: ssh_key_info +short_description: Get information about the Vultr SSH keys +description: + - Get infos about SSH keys available. +version_added: "1.0.0" +author: + - "Yanis Guenane (@Spredzy)" + - "René Moser (@resmo)" +extends_documentation_fragment: + - vultr.cloud.vultr_v2 + +""" + +EXAMPLES = """ +- name: Get Vultr SSH keys infos + vultr.cloud.ssh_key_info: + register: result + +- name: Print the infos + ansible.builtin.debug: + var: result.vultr_ssh_key_info +""" + +RETURN = """ +--- +vultr_api: + description: Response from Vultr API with a few additions/modification. + returned: success + type: dict + contains: + api_timeout: + description: Timeout used for the API requests. + returned: success + type: int + sample: 60 + api_retries: + description: Amount of max retries for the API requests. + returned: success + type: int + sample: 5 + api_retry_max_delay: + description: Exponential backoff delay in seconds between retries up to this max delay value. + returned: success + type: int + sample: 12 + api_endpoint: + description: Endpoint used for the API requests. + returned: success + type: str + sample: "https://api.vultr.com/v2" +vultr_ssh_key_info: + description: Response from Vultr API as list. + returned: success + type: list + contains: + id: + description: ID of the ssh key. + returned: success + type: str + sample: 7d726ffe-9be2-4f88-8cda-fa7eba1da2b5 + name: + description: Name of the ssh key. + returned: success + type: str + sample: my ssh key + date_created: + description: Date the ssh key was created. + returned: success + type: str + sample: "2021-11-07T05:57:59-05:00" + ssh_key: + description: SSH public key. + returned: success + type: str + sample: "ssh-rsa AA... someother@example.com" +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils.vultr_v2 import AnsibleVultr, vultr_argument_spec + + +def main(): + argument_spec = vultr_argument_spec() + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + vultr = AnsibleVultr( + module=module, + namespace="vultr_ssh_key_info", + resource_path="/ssh-keys", + ressource_result_key_singular="ssh_key", + ) + + vultr.get_result(vultr.query_list()) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/vultr/cloud/plugins/modules/startup_script.py b/ansible_collections/vultr/cloud/plugins/modules/startup_script.py new file mode 100644 index 000000000..4211b1aba --- /dev/null +++ b/ansible_collections/vultr/cloud/plugins/modules/startup_script.py @@ -0,0 +1,196 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2021, René Moser <mail@renemoser.net> +# 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 = """ +--- +module: startup_script +short_description: Manages startup scripts on Vultr +description: + - Create, update and remove startup scripts. +version_added: "1.0.0" +author: "René Moser (@resmo)" +options: + name: + description: + - The script name. + required: true + type: str + type: + description: + - The script type, can not be changed once created. + default: boot + choices: [ boot, pxe ] + aliases: [ script_type ] + type: str + script: + description: + - The script source code. + - Required if I(state=present). + type: str + state: + description: + - State of the script. + default: present + choices: [ present, absent ] + type: str +extends_documentation_fragment: + - vultr.cloud.vultr_v2 +""" + +EXAMPLES = """ +- name: ensure a pxe script exists, source from a file + vultr.cloud.startup_script: + name: my_web_script + script_type: pxe + script: "{{ lookup('file', 'path/to/script') }}" + +- name: ensure a boot script exists + vultr.cloud.startup_script: + name: vultr_startup_script + script: "#!/bin/bash\necho Hello World > /root/hello" + +- name: ensure a script is absent + vultr.cloud.startup_script: + name: my_web_script + state: absent +""" + +RETURN = """ +--- +vultr_api: + description: Response from Vultr API with a few additions/modification. + returned: success + type: dict + contains: + api_timeout: + description: Timeout used for the API requests. + returned: success + type: int + sample: 60 + api_retries: + description: Amount of max retries for the API requests. + returned: success + type: int + sample: 5 + api_retry_max_delay: + description: Exponential backoff delay in seconds between retries up to this max delay value. + returned: success + type: int + sample: 12 + api_endpoint: + description: Endpoint used for the API requests. + returned: success + type: str + sample: "https://api.vultr.com/v2" +vultr_startup_script: + description: Response from Vultr API. + returned: success + type: dict + contains: + id: + description: ID of the startup script. + returned: success + type: str + sample: 7d726ffe-9be2-4f88-8cda-fa7eba1da2b5 + name: + description: Name of the startup script. + returned: success + type: str + sample: my startup script + script: + description: The source code of the startup script. + returned: success + type: str + sample: "#!/bin/bash\necho Hello World > /root/hello" + type: + description: The type of the startup script. + returned: success + type: str + sample: pxe + date_created: + description: Date the startup script was created. + returned: success + type: str + sample: "2020-10-10T01:56:20+00:00" + date_modified: + description: Date the startup script was modified. + returned: success + type: str + sample: "2020-10-10T01:56:20+00:00" +""" + +import base64 + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils.vultr_v2 import AnsibleVultr, vultr_argument_spec + + +class AnsibleVultrStartupScript(AnsibleVultr): + def configure(self): + if self.module.params["script"]: + self.module.params["script"] = base64.b64encode(self.module.params["script"].encode()) + + def update(self, resource): + resource["script"] = resource["script"].encode() + return super(AnsibleVultrStartupScript, self).update(resource=resource) + + def transform_result(self, resource): + if resource: + resource["script"] = base64.b64decode(resource["script"]).decode() + return resource + + +def main(): + argument_spec = vultr_argument_spec() + argument_spec.update( + dict( + name=dict(type="str", required=True), + script=dict( + type="str", + ), + type=dict( + type="str", + default="boot", + choices=["boot", "pxe"], + aliases=["script_type"], + ), + state=dict(type="str", choices=["present", "absent"], default="present"), + ) # type: ignore + ) + + module = AnsibleModule( + argument_spec=argument_spec, + required_if=[ + ("state", "present", ["script"]), + ], + supports_check_mode=True, + ) + + vultr = AnsibleVultrStartupScript( + module=module, + namespace="vultr_startup_script", + resource_path="/startup-scripts", + ressource_result_key_singular="startup_script", + resource_get_details=True, + resource_create_param_keys=["name", "type", "script"], + resource_update_param_keys=["name", "script"], + resource_key_name="name", + ) + + if module.params.get("state") == "absent": # type: ignore + vultr.absent() + else: + vultr.present() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/vultr/cloud/plugins/modules/startup_script_info.py b/ansible_collections/vultr/cloud/plugins/modules/startup_script_info.py new file mode 100644 index 000000000..dd91166aa --- /dev/null +++ b/ansible_collections/vultr/cloud/plugins/modules/startup_script_info.py @@ -0,0 +1,120 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2018, Yanis Guenane <yanis+ansible@guenane.org> +# Copyright (c) 2021, René Moser <mail@renemoser.net> +# 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 = """ +--- +module: startup_script_info +short_description: Gather information about the Vultr startup scripts +description: + - Gather information about startup scripts available. +version_added: "1.0.0" +author: + - "Yanis Guenane (@Spredzy)" + - "René Moser (@resmo)" +extends_documentation_fragment: + - vultr.cloud.vultr_v2 +""" + +EXAMPLES = """ +- name: Gather Vultr startup scripts information + vultr.cloud.startup_script_info: + register: result + +- name: Print the gathered information + ansible.builtin.debug: + var: result.vultr_startup_script_info +""" + +RETURN = """ +--- +vultr_api: + description: Response from Vultr API with a few additions/modification. + returned: success + type: dict + contains: + api_timeout: + description: Timeout used for the API requests. + returned: success + type: int + sample: 60 + api_retries: + description: Amount of max retries for the API requests. + returned: success + type: int + sample: 5 + api_retry_max_delay: + description: Exponential backoff delay in seconds between retries up to this max delay value. + returned: success + type: int + sample: 12 + api_endpoint: + description: Endpoint used for the API requests. + returned: success + type: str + sample: "https://api.vultr.com/v2" +vultr_startup_script_info: + description: Response from Vultr API. + returned: success + type: list + contains: + id: + description: ID of the startup script. + returned: success + type: str + sample: 56e5b8b5-120c-40b1-a087-3abc9cd8df57 + name: + description: Name of the startup script. + returned: success + type: str + sample: my startup script + type: + description: The type of the startup script. + returned: success + type: str + sample: pxe + date_created: + description: Date the startup script was created. + returned: success + type: str + sample: "2020-10-10T01:56:20+00:00" + date_modified: + description: Date the startup script was modified. + returned: success + type: str + sample: "2020-10-10T01:56:20+00:00" +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils.vultr_v2 import AnsibleVultr, vultr_argument_spec + + +def main(): + argument_spec = vultr_argument_spec() + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + vultr = AnsibleVultr( + module=module, + namespace="vultr_startup_script_info", + resource_path="/startup-scripts", + ressource_result_key_singular="startup_script", + ) + + vultr.get_result(vultr.query_list()) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/vultr/cloud/plugins/modules/user.py b/ansible_collections/vultr/cloud/plugins/modules/user.py new file mode 100644 index 000000000..dd5124345 --- /dev/null +++ b/ansible_collections/vultr/cloud/plugins/modules/user.py @@ -0,0 +1,229 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2021, René Moser <mail@renemoser.net> +# 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 = """ +--- +module: user +short_description: Manages users on Vultr +description: + - Create, update and remove users. +version_added: "1.0.0" +author: "René Moser (@resmo)" +options: + name: + description: + - Name of the user + required: true + type: str + email: + description: + - Email of the user. + - Required if C(state=present). + type: str + password: + description: + - Password of the user. + - Only considered while creating a user or when C(force=true). + type: str + force: + description: + - Password will only be changed with enforcement. + default: false + type: bool + api_enabled: + description: + - Whether the API is enabled or not. + default: true + type: bool + acls: + description: + - List of ACLs this users should have. + - Required if C(state=present). + - One or more of the choices list, some depend on each other. + choices: + - manage_users + - subscriptions_view + - subscriptions + - provisioning + - billing + - support + - abuse + - dns + - upgrade + - objstore + - loadbalancer + aliases: [ acl ] + type: list + elements: str + state: + description: + - State of the user. + default: present + choices: [ present, absent ] + type: str +extends_documentation_fragment: + - vultr.cloud.vultr_v2 +""" + +EXAMPLES = """ +- name: Ensure a user exists + vultr.cloud.user: + name: john + email: john.doe@example.com + password: s3cr3t + acls: + - manage_users + - subscriptions + +- name: Remove a user + vultr.cloud.user: + name: john + state: absent +""" + +RETURN = """ +--- +vultr_api: + description: Response from Vultr API with a few additions/modification. + returned: success + type: dict + contains: + api_timeout: + description: Timeout used for the API requests. + returned: success + type: int + sample: 60 + api_retries: + description: Amount of max retries for the API requests. + returned: success + type: int + sample: 5 + api_retry_max_delay: + description: Exponential backoff delay in seconds between retries up to this max delay value. + returned: success + type: int + sample: 12 + api_endpoint: + description: Endpoint used for the API requests. + returned: success + type: str + sample: "https://api.vultr.com/v2" +vultr_user: + description: Response from Vultr API. + returned: success + type: dict + contains: + id: + description: ID of the user. + returned: success + type: str + sample: 7d726ffe-9be2-4f88-8cda-fa7eba1da2b5 + api_key: + description: API key of the user. + returned: only after resource was created + type: str + sample: 567E6K567E6K567E6K567E6K567E6K + name: + description: Name of the user. + returned: success + type: str + sample: john + email: + description: Email of the user. + returned: success + type: str + sample: "john@example.com" + api_enabled: + description: Whether the API is enabled or not. + returned: success + type: bool + sample: true + acls: + description: List of ACLs of the user. + returned: success + type: list + sample: [manage_users, support, upgrade] +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils.vultr_v2 import AnsibleVultr, vultr_argument_spec + +ACLS = [ + "manage_users", + "subscriptions_view", + "subscriptions", + "provisioning", + "billing", + "support", + "abuse", + "dns", + "upgrade", + "objstore", + "loadbalancer", +] + + +class AnsibleVultrUser(AnsibleVultr): + def create(self): + # Password is required in create mode. + self.module.fail_on_missing_params(required_params=["password"]) + return super(AnsibleVultrUser, self).create() + + def update(self, resource): + # Password is never returned and we can not compare. + # That is why we update it only if forced + force = self.module.params.get("force") + if force: + self.resource_update_param_keys.append("password") + return super(AnsibleVultrUser, self).update(resource=resource) + + +def main(): + argument_spec = vultr_argument_spec() + argument_spec.update( + dict( + name=dict(type="str", required=True), + email=dict(type="str"), + password=dict(type="str", no_log=True), + force=dict(type="bool", default=False), + api_enabled=dict(type="bool", default=True), + acls=dict(type="list", elements="str", choices=ACLS, aliases=["acl"]), + state=dict(type="str", choices=["present", "absent"], default="present"), + ) # type: ignore + ) + + module = AnsibleModule( + argument_spec=argument_spec, + required_if=[ + ("state", "present", ["email", "acls"]), + ], + supports_check_mode=True, + ) + + vultr = AnsibleVultrUser( + module=module, + namespace="vultr_user", + resource_path="/users", + ressource_result_key_singular="user", + resource_create_param_keys=["name", "email", "password", "api_enabled", "acls"], + resource_update_param_keys=["name", "email", "api_enabled", "acls"], + resource_key_name="name", + ) + + if module.params.get("state") == "absent": # type: ignore + vultr.absent() + else: + vultr.present() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/vultr/cloud/plugins/modules/user_info.py b/ansible_collections/vultr/cloud/plugins/modules/user_info.py new file mode 100644 index 000000000..5d0a61efe --- /dev/null +++ b/ansible_collections/vultr/cloud/plugins/modules/user_info.py @@ -0,0 +1,125 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2018, Yanis Guenane <yanis+ansible@guenane.org> +# Copyright (c) 2021, René Moser <mail@renemoser.net> +# 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 = """ +--- +module: user_info +short_description: Get information about the Vultr users +version_added: "1.0.0" +description: + - Get infos about users available. +author: + - "Yanis Guenane (@Spredzy)" + - "René Moser (@resmo)" +extends_documentation_fragment: + - vultr.cloud.vultr_v2 +""" + +EXAMPLES = """ +- name: Get Vultr user infos + vultr.cloud.user_info: + register: result + +- name: Print the infos + ansible.builtin.debug: + var: result.vultr_user_info +""" + +RETURN = """ +--- +vultr_api: + description: Response from Vultr API with a few additions/modification. + returned: success + type: dict + contains: + api_timeout: + description: Timeout used for the API requests. + returned: success + type: int + sample: 60 + api_retries: + description: Amount of max retries for the API requests. + returned: success + type: int + sample: 5 + api_retry_max_delay: + description: Exponential backoff delay in seconds between retries up to this max delay value. + returned: success + type: int + sample: 12 + api_endpoint: + description: Endpoint used for the API requests. + returned: success + type: str + sample: "https://api.vultr.com/v2" +vultr_user_info: + description: Response from Vultr API as list. + returned: available + type: list + contains: + id: + description: ID of the user. + returned: success + type: str + sample: 7d726ffe-9be2-4f88-8cda-fa7eba1da2b5 + api_key: + description: API key of the user. + returned: only after resource was created + type: str + sample: 567E6K567E6K567E6K567E6K567E6K + name: + description: Name of the user. + returned: success + type: str + sample: john + email: + description: Email of the user. + returned: success + type: str + sample: "john@example.com" + api_enabled: + description: Whether the API is enabled or not. + returned: success + type: bool + sample: true + acls: + description: List of ACLs of the user. + returned: success + type: list + sample: [ manage_users, support, upgrade ] +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils.vultr_v2 import AnsibleVultr, vultr_argument_spec + + +def main(): + argument_spec = vultr_argument_spec() + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + vultr = AnsibleVultr( + module=module, + namespace="vultr_user_info", + resource_path="/users", + ressource_result_key_singular="user", + ) + + vultr.get_result(vultr.query_list()) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/vultr/cloud/plugins/modules/vpc.py b/ansible_collections/vultr/cloud/plugins/modules/vpc.py new file mode 100644 index 000000000..83a183c8d --- /dev/null +++ b/ansible_collections/vultr/cloud/plugins/modules/vpc.py @@ -0,0 +1,182 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2022, René Moser <mail@renemoser.net> +# 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 = """ +--- +module: vpc +short_description: Manages VPCs on Vultr +description: + - Create and remove VPCs. +version_added: "1.0.0" +author: "René Moser (@resmo)" +options: + description: + description: + - Description of the VPC. + required: true + aliases: [ name ] + type: str + v4_subnet: + description: + - IPv4 subnet of the VPC. + - Required if I(state=present). + type: str + v4_subnet_mask: + description: + - IPv4 subnet mask of the VPC. + - Required if I(state=present). + type: int + region: + description: + - Region the VPC will be related to. + - Required if I(state=present). + type: str + state: + description: + - State of the VPC. + default: present + choices: [ present, absent ] + type: str +extends_documentation_fragment: + - vultr.cloud.vultr_v2 +""" + +EXAMPLES = """ +- name: Ensure a VPC is present + vultr.cloud.vpc: + description: my VPC. + subnet: 10.99.1.0 + subnet_mask: 24 + region: ewr + +- name: Ensure a VPC is absent + vultr.cloud.vpc: + description: my VPC. + state: absent +""" + +RETURN = """ +--- +vultr_api: + description: Response from Vultr API with a few additions/modification. + returned: success + type: dict + contains: + api_timeout: + description: Timeout used for the API requests. + returned: success + type: int + sample: 60 + api_retries: + description: Amount of max retries for the API requests. + returned: success + type: int + sample: 5 + api_retry_max_delay: + description: Exponential backoff delay in seconds between retries up to this max delay value. + returned: success + type: int + sample: 12 + api_endpoint: + description: Endpoint used for the API requests. + returned: success + type: str + sample: "https://api.vultr.com/v2" +vultr_vpc: + description: Response from Vultr API. + returned: success + type: dict + contains: + id: + description: ID of the VPC. + returned: success + type: str + sample: cb676a46-66fd-4dfb-b839-443f2e6c0b60 + description: + description: Description of the VPC. + returned: success + type: str + sample: my vpc + v4_subnet: + description: Subnet of the VPC. + returned: success + type: str + sample: 10.99.1.0 + v4_subnet_maks: + description: Subnet mask of the VPC. + returned: success + type: str + sample: 10.99.1.0 + date_created: + description: Date the VPC was created. + returned: success + type: str + sample: "2020-10-10T01:56:20+00:00" + date_modified: + description: Date the VPC was modified. + returned: success + type: str + sample: "2020-10-10T01:56:20+00:00" +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils.vultr_v2 import AnsibleVultr, vultr_argument_spec + + +def main(): + argument_spec = vultr_argument_spec() + argument_spec.update( + dict( + description=dict(type="str", required=True, aliases=["name"]), + v4_subnet=dict(type="str"), + v4_subnet_mask=dict(type="int"), + region=dict(type="str"), + state=dict(type="str", choices=["present", "absent"], default="present"), + ) # type: ignore + ) + + module = AnsibleModule( + argument_spec=argument_spec, + required_if=( + ( + "state", + "present", + ("v4_subnet", "v4_subnet_mask", "region"), + ), + ), + supports_check_mode=True, + ) + + vultr = AnsibleVultr( + module=module, + namespace="vultr_vpc", + resource_path="/vpcs", + ressource_result_key_singular="vpc", + resource_create_param_keys=[ + "description", + "v4_subnet", + "v4_subnet_mask", + "region", + ], + resource_update_param_keys=["description"], + resource_key_name="description", + resource_update_method="PUT", + ) + + if module.params.get("state") == "absent": # type: ignore + vultr.absent() + else: + vultr.present() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/vultr/cloud/plugins/modules/vpc_info.py b/ansible_collections/vultr/cloud/plugins/modules/vpc_info.py new file mode 100644 index 000000000..5dc67a529 --- /dev/null +++ b/ansible_collections/vultr/cloud/plugins/modules/vpc_info.py @@ -0,0 +1,122 @@ +#!/usr/bin/python +# +# Copyright (c) 2022, René Moser <mail@renemoser.net> +# 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 = """ +--- +module: vpc_info +short_description: Gather information about the Vultr VPCs +description: + - Gather information about VPCs available. +version_added: "1.0.0" +author: + - "René Moser (@resmo)" +extends_documentation_fragment: + - vultr.cloud.vultr_v2 +""" + +EXAMPLES = """ +- name: Gather Vultr VPCs information + vultr.cloud.vpc_info: + register: result + +- name: Print the gathered information + ansible.builtin.debug: + var: result.vultr_vpc_info +""" + +RETURN = """ +--- +vultr_api: + description: Response from Vultr API with a few additions/modification. + returned: success + type: dict + contains: + api_timeout: + description: Timeout used for the API requests. + returned: success + type: int + sample: 60 + api_retries: + description: Amount of max retries for the API requests. + returned: success + type: int + sample: 5 + api_retry_max_delay: + description: Exponential backoff delay in seconds between retries up to this max delay value. + returned: success + type: int + sample: 12 + api_endpoint: + description: Endpoint used for the API requests. + returned: success + type: str + sample: "https://api.vultr.com/v2" +vultr_vpc_info: + description: Response from Vultr API as list. + returned: success + type: list + contains: + id: + description: ID of the VPC. + returned: success + type: str + sample: "cb676a46-66fd-4dfb-b839-443f2e6c0b60" + description: + description: Description of the VPC. + returned: success + type: str + sample: myvpc + date_created: + description: Date when the VPC was created. + returned: success + type: str + sample: "2020-10-10T01:56:20+00:00" + region: + description: Region the VPC was deployed into. + returned: success + type: str + sample: ewr + v4_subnet: + description: IPv4 Network address + returned: success + type: str + sample: 192.168.42.0 + v4_subnet_mask: + description: Ipv4 Network mask + returned: success + type: int + sample: 24 +""" + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils.vultr_v2 import AnsibleVultr, vultr_argument_spec + + +def main(): + argument_spec = vultr_argument_spec() + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + vultr = AnsibleVultr( + module=module, + namespace="vultr_vpc_info", + resource_path="/vpcs", + ressource_result_key_singular="vpc", + ) + + vultr.get_result(vultr.query_list()) + + +if __name__ == "__main__": + main() |