Repository URL to install this package:
|
Version:
6.0.0 ▾
|
# 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],
)