summaryrefslogtreecommitdiffstats
path: root/ansible_collections/community/digitalocean/plugins/module_utils
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:04:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:04:41 +0000
commit975f66f2eebe9dadba04f275774d4ab83f74cf25 (patch)
tree89bd26a93aaae6a25749145b7e4bca4a1e75b2be /ansible_collections/community/digitalocean/plugins/module_utils
parentInitial commit. (diff)
downloadansible-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.py305
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],
+ )