Repository URL to install this package:
|
Version:
6.0.0 ▾
|
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright: Ansible Project
# Copyright: (c) 2021, Mark Mercado <mmercado@digitalocean.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r"""
---
module: digital_ocean_database
short_description: Create and delete a DigitalOcean database
description:
- Create and delete a database in DigitalOcean and optionally wait for it to be online.
- DigitalOcean's managed database service simplifies the creation and management of highly available database clusters.
- Currently, it offers support for PostgreSQL, Redis, MySQL, and MongoDB.
version_added: 1.3.0
author: "Mark Mercado (@mamercad)"
options:
state:
description:
- Indicates the desired state of the target.
default: present
choices: ['present', 'absent']
type: str
id:
description:
- A unique ID that can be used to identify and reference a database cluster.
type: int
aliases: ['database_id']
name:
description:
- A unique, human-readable name for the database cluster.
type: str
required: true
engine:
description:
- A slug representing the database engine used for the cluster.
- The possible values are C(pg) for PostgreSQL, C(mysql) for MySQL, C(redis) for Redis, and C(mongodb) for MongoDB.
type: str
required: true
choices: ['pg', 'mysql', 'redis', 'mongodb']
version:
description:
- A string representing the version of the database engine in use for the cluster.
- For C(pg), versions are 10, 11 and 12.
- For C(mysql), version is 8.
- For C(redis), version is 5.
- For C(mongodb), version is 4.
type: str
size:
description:
- The slug identifier representing the size of the nodes in the database cluster.
- See U(https://docs.digitalocean.com/reference/api/api-reference/#operation/create_database_cluster) for supported sizes.
type: str
required: true
aliases: ['size_id']
region:
description:
- The slug identifier for the region where the database cluster is located.
type: str
required: true
aliases: ['region_id']
num_nodes:
description:
- The number of nodes in the database cluster.
- Valid choices are 1, 2 or 3.
type: int
default: 1
choices: [1, 2, 3]
tags:
description:
- An array of tags that have been applied to the database cluster.
type: list
elements: str
private_network_uuid:
description:
- A string specifying the UUID of the VPC to which the database cluster is assigned.
type: str
wait:
description:
- Wait for the database to be online before returning.
required: False
default: True
type: bool
wait_timeout:
description:
- How long before wait gives up, in seconds, when creating a database.
default: 600
type: int
project_name:
aliases: ["project"]
description:
- Project to assign the resource to (project name, not UUID).
- Defaults to the default project of the account (empty string).
- Currently only supported when creating databases.
type: str
required: false
default: ""
extends_documentation_fragment:
- community.digitalocean.digital_ocean.documentation
"""
EXAMPLES = r"""
- name: Create a Redis database
community.digitalocean.digital_ocean_database:
oauth_token: "{{ lookup('ansible.builtin.env', 'DO_API_KEY') }}"
state: present
name: testdatabase1
engine: redis
size: db-s-1vcpu-1gb
region: nyc1
num_nodes: 1
register: my_database
- name: Create a Redis database (and assign to Project "test")
community.digitalocean.digital_ocean_database:
oauth_token: "{{ lookup('ansible.builtin.env', 'DO_API_KEY') }}"
state: present
name: testdatabase1
engine: redis
size: db-s-1vcpu-1gb
region: nyc1
num_nodes: 1
project_name: test
register: my_database
"""
RETURN = r"""
data:
description: A DigitalOcean database
returned: success
type: dict
sample:
database:
connection:
database: ""
host: testdatabase1-do-user-3097135-0.b.db.ondigitalocean.com
password: REDACTED
port: 25061
protocol: rediss
ssl: true
uri: rediss://default:REDACTED@testdatabase1-do-user-3097135-0.b.db.ondigitalocean.com:25061
user: default
created_at: "2021-04-21T15:41:14Z"
db_names: null
engine: redis
id: 37de10e4-808b-4f4b-b25f-7b5b3fd194ac
maintenance_window:
day: monday
hour: 11:33:47
pending: false
name: testdatabase1
num_nodes: 1
private_connection:
database: ""
host: private-testdatabase1-do-user-3097135-0.b.db.ondigitalocean.com
password: REDIS
port: 25061
protocol: rediss
ssl: true
uri: rediss://default:REDACTED@private-testdatabase1-do-user-3097135-0.b.db.ondigitalocean.com:25061
user: default
private_network_uuid: 0db3519b-9efc-414a-8868-8f2e6934688c,
region: nyc1
size: db-s-1vcpu-1gb
status: online
tags: null
users: null
version: 6
msg:
description: Informational or error message encountered during execution
returned: changed
type: str
sample: No project named test2 found
assign_status:
description: Assignment status (ok, not_found, assigned, already_assigned, service_down)
returned: changed
type: str
sample: assigned
resources:
description: Resource assignment involved in project assignment
returned: changed
type: dict
sample:
assigned_at: '2021-10-25T17:39:38Z'
links:
self: https://api.digitalocean.com/v2/databases/126355fa-b147-40a6-850a-c44f5d2ad418
status: assigned
urn: do:dbaas:126355fa-b147-40a6-850a-c44f5d2ad418
"""
import json
import time
from ansible.module_utils.basic import AnsibleModule, env_fallback
from ansible_collections.community.digitalocean.plugins.module_utils.digital_ocean import (
DigitalOceanHelper,
DigitalOceanProjects,
)
class DODatabase(object):
def __init__(self, module):
self.module = module
self.rest = DigitalOceanHelper(module)
if self.module.params.get("project"):
# only load for non-default project assignments
self.projects = DigitalOceanProjects(module, self.rest)
# pop wait and wait_timeout so we don't include it in the POST data
self.wait = self.module.params.pop("wait", True)
self.wait_timeout = self.module.params.pop("wait_timeout", 600)
# pop the oauth token so we don't include it in the POST data
self.module.params.pop("oauth_token")
self.id = None
self.name = None
self.engine = None
self.version = None
self.num_nodes = None
self.region = None
self.status = None
self.size = None
def get_by_id(self, database_id):
if database_id is None:
return None
response = self.rest.get("databases/{0}".format(database_id))
json_data = response.json
if response.status_code == 200:
database = json_data.get("database", None)
if database is not None:
self.id = database.get("id", None)
self.name = database.get("name", None)
self.engine = database.get("engine", None)
self.version = database.get("version", None)
self.num_nodes = database.get("num_nodes", None)
self.region = database.get("region", None)
self.status = database.get("status", None)
self.size = database.get("size", None)
return json_data
return None
def get_by_name(self, database_name):
if database_name is None:
return None
page = 1
while page is not None:
response = self.rest.get("databases?page={0}".format(page))
json_data = response.json
if response.status_code == 200:
databases = json_data.get("databases", None)
if databases is None or not isinstance(databases, list):
return None
for database in databases:
if database.get("name", None) == database_name:
self.id = database.get("id", None)
self.name = database.get("name", None)
self.engine = database.get("engine", None)
self.version = database.get("version", None)
self.status = database.get("status", None)
self.num_nodes = database.get("num_nodes", None)
self.region = database.get("region", None)
self.size = database.get("size", None)
return {"database": database}
if (
"links" in json_data
and "pages" in json_data["links"]
and "next" in json_data["links"]["pages"]
):
page += 1
else:
page = None
return None
def get_database(self):
json_data = self.get_by_id(self.module.params["id"])
if not json_data:
json_data = self.get_by_name(self.module.params["name"])
return json_data
def ensure_online(self, database_id):
end_time = time.monotonic() + self.wait_timeout
while time.monotonic() < end_time:
response = self.rest.get("databases/{0}".format(database_id))
json_data = response.json
database = json_data.get("database", None)
if database is not None:
status = database.get("status", None)
if status is not None:
if status == "online":
return json_data
time.sleep(10)
self.module.fail_json(msg="Waiting for database online timeout")
def create(self):
json_data = self.get_database()
if json_data is not None:
database = json_data.get("database", None)
if database is not None:
self.module.exit_json(changed=False, data=json_data)
else:
self.module.fail_json(
changed=False, msg="Unexpected error, please file a bug"
)
if self.module.check_mode:
self.module.exit_json(changed=True)
request_params = dict(self.module.params)
del request_params["id"]
response = self.rest.post("databases", data=request_params)
json_data = response.json
if response.status_code >= 400:
self.module.fail_json(changed=False, msg=json_data["message"])
database = json_data.get("database", None)
if database is None:
self.module.fail_json(
changed=False,
msg="Unexpected error; please file a bug https://github.com/ansible-collections/community.digitalocean/issues",
)
database_id = database.get("id", None)
if database_id is None:
self.module.fail_json(
changed=False,
msg="Unexpected error; please file a bug https://github.com/ansible-collections/community.digitalocean/issues",
)
if self.wait:
json_data = self.ensure_online(database_id)
project_name = self.module.params.get("project")
if project_name: # empty string is the default project, skip project assignment
urn = "do:dbaas:{0}".format(database_id)
assign_status, error_message, resources = self.projects.assign_to_project(
project_name, urn
)
self.module.exit_json(
changed=True,
data=json_data,
msg=error_message,
assign_status=assign_status,
resources=resources,
)
else:
self.module.exit_json(changed=True, data=json_data)
def delete(self):
json_data = self.get_database()
if json_data is not None:
if self.module.check_mode:
self.module.exit_json(changed=True)
database = json_data.get("database", None)
database_id = database.get("id", None)
database_name = database.get("name", None)
database_region = database.get("region", None)
if database_id is not None:
response = self.rest.delete("databases/{0}".format(database_id))
json_data = response.json
if response.status_code == 204:
self.module.exit_json(
changed=True,
msg="Deleted database {0} ({1}) in region {2}".format(
database_name, database_id, database_region
),
)
self.module.fail_json(
changed=False,
msg="Failed to delete database {0} ({1}) in region {2}: {3}".format(
database_name,
database_id,
database_region,
json_data["message"],
),
)
else:
self.module.fail_json(
changed=False, msg="Unexpected error, please file a bug"
)
else:
self.module.exit_json(
changed=False,
msg="Database {0} in region {1} not found".format(
self.module.params["name"], self.module.params["region"]
),
)
def run(module):
state = module.params.pop("state")
database = DODatabase(module)
if state == "present":
database.create()
elif state == "absent":
database.delete()
def main():
argument_spec = DigitalOceanHelper.digital_ocean_argument_spec()
argument_spec.update(
state=dict(choices=["present", "absent"], default="present"),
id=dict(type="int", aliases=["database_id"]),
name=dict(type="str", required=True),
engine=dict(choices=["pg", "mysql", "redis", "mongodb"], required=True),
version=dict(type="str"),
size=dict(type="str", aliases=["size_id"], required=True),
region=dict(type="str", aliases=["region_id"], required=True),
num_nodes=dict(type="int", choices=[1, 2, 3], default=1),
tags=dict(type="list", elements="str"),
private_network_uuid=dict(type="str"),
wait=dict(type="bool", default=True),
wait_timeout=dict(default=600, type="int"),
project_name=dict(type="str", aliases=["project"], required=False, default=""),
)
module = AnsibleModule(
argument_spec=argument_spec,
required_one_of=(["id", "name"],),
required_if=(
[
("state", "present", ["name", "size", "engine", "region"]),
("state", "absent", ["name", "size", "engine", "region"]),
]
),
supports_check_mode=True,
)
run(module)
if __name__ == "__main__":
main()