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/community/digitalocean/plugins/module_utils | |
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/community/digitalocean/plugins/module_utils')
-rw-r--r-- | ansible_collections/community/digitalocean/plugins/module_utils/digital_ocean.py | 305 |
1 files changed, 305 insertions, 0 deletions
diff --git a/ansible_collections/community/digitalocean/plugins/module_utils/digital_ocean.py b/ansible_collections/community/digitalocean/plugins/module_utils/digital_ocean.py new file mode 100644 index 000000000..44ca3ccd1 --- /dev/null +++ b/ansible_collections/community/digitalocean/plugins/module_utils/digital_ocean.py @@ -0,0 +1,305 @@ +# This code is part of Ansible, but is an independent component. +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by Ansible +# still belong to the author of the module, and may assign their own license +# to the complete work. +# +# Copyright (c), Ansible Project 2017 +# 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 json +import os +from ansible.module_utils.urls import fetch_url +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import env_fallback + + +class Response(object): + def __init__(self, resp, info): + self.body = None + if resp: + self.body = resp.read() + self.info = info + + @property + def json(self): + if not self.body: + if "body" in self.info: + return json.loads(to_text(self.info["body"])) + return None + try: + return json.loads(to_text(self.body)) + except ValueError: + return None + + @property + def status_code(self): + return self.info["status"] + + +class DigitalOceanHelper: + baseurl = "https://api.digitalocean.com/v2" + + def __init__(self, module): + self.module = module + self.baseurl = module.params.get("baseurl", DigitalOceanHelper.baseurl) + self.timeout = module.params.get("timeout", 30) + self.oauth_token = module.params.get("oauth_token") + self.headers = { + "Authorization": "Bearer {0}".format(self.oauth_token), + "Content-type": "application/json", + } + + # Check if api_token is valid or not + response = self.get("account") + if response.status_code == 401: + self.module.fail_json( + msg="Failed to login using API token, please verify validity of API token." + ) + + def _url_builder(self, path): + if path[0] == "/": + path = path[1:] + return "%s/%s" % (self.baseurl, path) + + def send(self, method, path, data=None): + url = self._url_builder(path) + data = self.module.jsonify(data) + + if method == "DELETE": + if data == "null": + data = None + + resp, info = fetch_url( + self.module, + url, + data=data, + headers=self.headers, + method=method, + timeout=self.timeout, + ) + + return Response(resp, info) + + def get(self, path, data=None): + return self.send("GET", path, data) + + def put(self, path, data=None): + return self.send("PUT", path, data) + + def post(self, path, data=None): + return self.send("POST", path, data) + + def delete(self, path, data=None): + return self.send("DELETE", path, data) + + @staticmethod + def digital_ocean_argument_spec(): + return dict( + baseurl=dict( + type="str", required=False, default="https://api.digitalocean.com/v2" + ), + validate_certs=dict(type="bool", required=False, default=True), + oauth_token=dict( + no_log=True, + # Support environment variable for DigitalOcean OAuth Token + fallback=( + env_fallback, + ["DO_API_TOKEN", "DO_API_KEY", "DO_OAUTH_TOKEN", "OAUTH_TOKEN"], + ), + required=False, + aliases=["api_token"], + ), + timeout=dict(type="int", default=30), + ) + + def get_paginated_data( + self, + base_url=None, + data_key_name=None, + data_per_page=40, + expected_status_code=200, + ): + """ + Function to get all paginated data from given URL + Args: + base_url: Base URL to get data from + data_key_name: Name of data key value + data_per_page: Number results per page (Default: 40) + expected_status_code: Expected returned code from DigitalOcean (Default: 200) + Returns: List of data + + """ + page = 1 + has_next = True + ret_data = [] + status_code = None + response = None + while has_next or status_code != expected_status_code: + required_url = "{0}page={1}&per_page={2}".format( + base_url, page, data_per_page + ) + response = self.get(required_url) + status_code = response.status_code + # stop if any error during pagination + if status_code != expected_status_code: + break + page += 1 + ret_data.extend(response.json[data_key_name]) + try: + has_next = ( + "pages" in response.json["links"] + and "next" in response.json["links"]["pages"] + ) + except KeyError: + # There's a bug in the API docs: GET v2/cdn/endpoints doesn't return a "links" key + has_next = False + + if status_code != expected_status_code: + msg = "Failed to fetch %s from %s" % (data_key_name, base_url) + if response: + msg += " due to error : %s" % response.json["message"] + self.module.fail_json(msg=msg) + + return ret_data + + +class DigitalOceanProjects: + def __init__(self, module, rest): + self.module = module + self.rest = rest + self.get_all_projects() + + def get_all_projects(self): + """Fetches all projects.""" + self.projects = self.rest.get_paginated_data( + base_url="projects?", data_key_name="projects" + ) + + def get_default(self): + """Fetches the default project. + + Returns: + error_message -- project fetch error message (or "" if no error) + project -- project dictionary representation (or {} if error) + """ + project = [ + project for project in self.projects if project.get("is_default", False) + ] + if len(project) == 0: + return "Unexpected error; no default project found", {} + if len(project) > 1: + return "Unexpected error; more than one default project", {} + return "", project[0] + + def get_by_id(self, id): + """Fetches the project with the given id. + + Returns: + error_message -- project fetch error message (or "" if no error) + project -- project dictionary representation (or {} if error) + """ + project = [project for project in self.projects if project.get("id") == id] + if len(project) == 0: + return "No project with id {0} found".format(id), {} + elif len(project) > 1: + return "Unexpected error; more than one project with the same id", {} + return "", project[0] + + def get_by_name(self, name): + """Fetches the project with the given name. + + Returns: + error_message -- project fetch error message (or "" if no error) + project -- project dictionary representation (or {} if error) + """ + project = [project for project in self.projects if project.get("name") == name] + if len(project) == 0: + return "No project with name {0} found".format(name), {} + elif len(project) > 1: + return "Unexpected error; more than one project with the same name", {} + return "", project[0] + + def assign_to_project(self, project_name, urn): + """Assign resource (urn) to project (name). + + Keyword arguments: + project_name -- project name to associate the resource with + urn -- resource URN (has the form do:resource_type:resource_id) + + Returns: + assign_status -- ok, not_found, assigned, already_assigned, service_down + error_message -- assignment error message (empty on success) + resources -- resources assigned (or {} if error) + + Notes: + For URN examples, see https://docs.digitalocean.com/reference/api/api-reference/#tag/Project-Resources + + Projects resources are identified by uniform resource names or URNs. + A valid URN has the following format: do:resource_type:resource_id. + + The following resource types are supported: + Resource Type | Example URN + Database | do:dbaas:83c7a55f-0d84-4760-9245-aba076ec2fb2 + Domain | do:domain:example.com + Droplet | do:droplet:4126873 + Floating IP | do:floatingip:192.168.99.100 + Load Balancer | do:loadbalancer:39052d89-8dd4-4d49-8d5a-3c3b6b365b5b + Space | do:space:my-website-assets + Volume | do:volume:6fc4c277-ea5c-448a-93cd-dd496cfef71f + """ + error_message, project = self.get_by_name(project_name) + if not project: + return "", error_message, {} + + project_id = project.get("id", None) + if not project_id: + return ( + "", + "Unexpected error; cannot find project id for {0}".format(project_name), + {}, + ) + + data = {"resources": [urn]} + response = self.rest.post( + "projects/{0}/resources".format(project_id), data=data + ) + status_code = response.status_code + json = response.json + if status_code != 200: + message = json.get("message", "No error message returned") + return ( + "", + "Unable to assign resource {0} to project {1} [HTTP {2}: {3}]".format( + urn, project_name, status_code, message + ), + {}, + ) + + resources = json.get("resources", []) + if len(resources) == 0: + return ( + "", + "Unexpected error; no resources returned (but assignment was successful)", + {}, + ) + if len(resources) > 1: + return ( + "", + "Unexpected error; more than one resource returned (but assignment was successful)", + {}, + ) + + status = resources[0].get( + "status", + "Unexpected error; no status returned (but assignment was successful)", + ) + return ( + status, + "Assigned {0} to project {1}".format(urn, project_name), + resources[0], + ) |