mirror of https://github.com/ansible/ansible.git
Add Digital Ocean Droplet API (#33984)
Create a new digital ocean droplet API to remove dopy dependency. Also enable all digital ocean v2 parameters for droplet creation.pull/51117/head
parent
6c96f29699
commit
bcd910a5e3
@ -0,0 +1,336 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright: Ansible Project
|
||||||
|
# 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
|
||||||
|
|
||||||
|
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||||
|
'status': ['preview'],
|
||||||
|
'supported_by': 'community'}
|
||||||
|
|
||||||
|
|
||||||
|
DOCUMENTATION = '''
|
||||||
|
---
|
||||||
|
module: digital_ocean_droplet
|
||||||
|
short_description: Create and delete a DigitalOcean droplet
|
||||||
|
description:
|
||||||
|
- Create and delete a droplet in DigitalOcean and optionally wait for it to be active.
|
||||||
|
version_added: "2.8"
|
||||||
|
author: "Gurchet Rai (@gurch101)"
|
||||||
|
options:
|
||||||
|
state:
|
||||||
|
description:
|
||||||
|
- Indicate desired state of the target.
|
||||||
|
default: present
|
||||||
|
choices: ['present', 'absent']
|
||||||
|
id:
|
||||||
|
description:
|
||||||
|
- Numeric, the droplet id you want to operate on.
|
||||||
|
aliases: ['droplet_id']
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- String, this is the name of the droplet - must be formatted by hostname rules.
|
||||||
|
unique_name:
|
||||||
|
description:
|
||||||
|
- require unique hostnames. By default, DigitalOcean allows multiple hosts with the same name. Setting this to "yes" allows only one host
|
||||||
|
per name. Useful for idempotence.
|
||||||
|
default: False
|
||||||
|
type: bool
|
||||||
|
size:
|
||||||
|
description:
|
||||||
|
- This is the slug of the size you would like the droplet created with.
|
||||||
|
aliases: ['size_id']
|
||||||
|
image:
|
||||||
|
description:
|
||||||
|
- This is the slug of the image you would like the droplet created with.
|
||||||
|
aliases: ['image_id']
|
||||||
|
region:
|
||||||
|
description:
|
||||||
|
- This is the slug of the region you would like your server to be created in.
|
||||||
|
aliases: ['region_id']
|
||||||
|
ssh_keys:
|
||||||
|
description:
|
||||||
|
- array of SSH key (numeric) ID that you would like to be added to the server.
|
||||||
|
required: False
|
||||||
|
private_networking:
|
||||||
|
description:
|
||||||
|
- add an additional, private network interface to droplet for inter-droplet communication.
|
||||||
|
default: False
|
||||||
|
type: bool
|
||||||
|
user_data:
|
||||||
|
description:
|
||||||
|
- opaque blob of data which is made available to the droplet
|
||||||
|
required: False
|
||||||
|
ipv6:
|
||||||
|
description:
|
||||||
|
- enable IPv6 for your droplet.
|
||||||
|
required: False
|
||||||
|
default: False
|
||||||
|
type: bool
|
||||||
|
wait:
|
||||||
|
description:
|
||||||
|
- Wait for the droplet to be active before returning. If wait is "no" an ip_address may not be returned.
|
||||||
|
required: False
|
||||||
|
default: True
|
||||||
|
type: bool
|
||||||
|
wait_timeout:
|
||||||
|
description:
|
||||||
|
- How long before wait gives up, in seconds, when creating a droplet.
|
||||||
|
default: 120
|
||||||
|
backups:
|
||||||
|
description:
|
||||||
|
- indicates whether automated backups should be enabled.
|
||||||
|
required: False
|
||||||
|
default: False
|
||||||
|
type: bool
|
||||||
|
monitoring:
|
||||||
|
description:
|
||||||
|
- indicates whether to install the DigitalOcean agent for monitoring.
|
||||||
|
required: False
|
||||||
|
default: False
|
||||||
|
type: bool
|
||||||
|
tags:
|
||||||
|
description:
|
||||||
|
- List, A list of tag names as strings to apply to the Droplet after it is created. Tag names can either be existing or new tags.
|
||||||
|
required: False
|
||||||
|
volumes:
|
||||||
|
description:
|
||||||
|
- List, A list including the unique string identifier for each Block Storage volume to be attached to the Droplet.
|
||||||
|
required: False
|
||||||
|
oauth_token:
|
||||||
|
description:
|
||||||
|
- DigitalOcean OAuth token. Can be specified in C(DO_API_KEY), C(DO_API_TOKEN), or C(DO_OAUTH_TOKEN) environment variables
|
||||||
|
aliases: ['API_TOKEN']
|
||||||
|
required: True
|
||||||
|
requirements:
|
||||||
|
- "python >= 2.6"
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
EXAMPLES = '''
|
||||||
|
- name: create a new droplet
|
||||||
|
digital_ocean_droplet:
|
||||||
|
state: present
|
||||||
|
name: mydroplet
|
||||||
|
oauth_token: XXX
|
||||||
|
size: 2gb
|
||||||
|
region: sfo1
|
||||||
|
image: ubuntu-16-04-x64
|
||||||
|
wait_timeout: 500
|
||||||
|
register: my_droplet
|
||||||
|
|
||||||
|
- debug:
|
||||||
|
msg: "ID is {{ my_droplet.droplet.id }}"
|
||||||
|
|
||||||
|
- name: ensure a droplet is present
|
||||||
|
digital_ocean_droplet:
|
||||||
|
state: present
|
||||||
|
id: 123
|
||||||
|
name: mydroplet
|
||||||
|
oauth_token: XXX
|
||||||
|
size: 2gb
|
||||||
|
region: sfo1
|
||||||
|
image: ubuntu-16-04-x64
|
||||||
|
wait_timeout: 500
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
RETURN = '''
|
||||||
|
# Digital Ocean API info https://developers.digitalocean.com/documentation/v2/#droplets
|
||||||
|
data:
|
||||||
|
description: a DigitalOcean Droplet
|
||||||
|
returned: changed
|
||||||
|
type: dict
|
||||||
|
sample: {
|
||||||
|
"ip_address": "104.248.118.172",
|
||||||
|
"ipv6_address": "2604:a880:400:d1::90a:6001",
|
||||||
|
"private_ipv4_address": "10.136.122.141",
|
||||||
|
"droplet": {
|
||||||
|
"id": 3164494,
|
||||||
|
"name": "example.com",
|
||||||
|
"memory": 512,
|
||||||
|
"vcpus": 1,
|
||||||
|
"disk": 20,
|
||||||
|
"locked": true,
|
||||||
|
"status": "new",
|
||||||
|
"kernel": {
|
||||||
|
"id": 2233,
|
||||||
|
"name": "Ubuntu 14.04 x64 vmlinuz-3.13.0-37-generic",
|
||||||
|
"version": "3.13.0-37-generic"
|
||||||
|
},
|
||||||
|
"created_at": "2014-11-14T16:36:31Z",
|
||||||
|
"features": ["virtio"],
|
||||||
|
"backup_ids": [],
|
||||||
|
"snapshot_ids": [],
|
||||||
|
"image": {},
|
||||||
|
"volume_ids": [],
|
||||||
|
"size": {},
|
||||||
|
"size_slug": "512mb",
|
||||||
|
"networks": {},
|
||||||
|
"region": {},
|
||||||
|
"tags": ["web"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
from ansible.module_utils.basic import AnsibleModule, env_fallback
|
||||||
|
from ansible.module_utils.digital_ocean import DigitalOceanHelper
|
||||||
|
|
||||||
|
|
||||||
|
class DODroplet(object):
|
||||||
|
def __init__(self, module):
|
||||||
|
self.rest = DigitalOceanHelper(module)
|
||||||
|
self.module = module
|
||||||
|
self.wait = self.module.params.pop('wait', True)
|
||||||
|
self.wait_timeout = self.module.params.pop('wait_timeout', 120)
|
||||||
|
self.unique_name = self.module.params.pop('unique_name', False)
|
||||||
|
# pop the oauth token so we don't include it in the POST data
|
||||||
|
self.module.params.pop('oauth_token')
|
||||||
|
|
||||||
|
def get_by_id(self, droplet_id):
|
||||||
|
if not droplet_id:
|
||||||
|
return None
|
||||||
|
response = self.rest.get('droplets/{0}'.format(droplet_id))
|
||||||
|
json_data = response.json
|
||||||
|
if response.status_code == 200:
|
||||||
|
return json_data
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_by_name(self, droplet_name):
|
||||||
|
if not droplet_name:
|
||||||
|
return None
|
||||||
|
page = 1
|
||||||
|
while page is not None:
|
||||||
|
response = self.rest.get('droplets?page={0}'.format(page))
|
||||||
|
json_data = response.json
|
||||||
|
if response.status_code == 200:
|
||||||
|
for droplet in json_data['droplets']:
|
||||||
|
if droplet['name'] == droplet_name:
|
||||||
|
return {'droplet': droplet}
|
||||||
|
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_addresses(self, data):
|
||||||
|
"""
|
||||||
|
Expose IP addresses as their own property allowing users extend to additional tasks
|
||||||
|
"""
|
||||||
|
_data = data
|
||||||
|
for k, v in data.items():
|
||||||
|
setattr(self, k, v)
|
||||||
|
networks = _data['droplet']['networks']
|
||||||
|
for network in networks.get('v4', []):
|
||||||
|
if network['type'] == 'public':
|
||||||
|
_data['ip_address'] = network['ip_address']
|
||||||
|
else:
|
||||||
|
_data['private_ipv4_address'] = network['ip_address']
|
||||||
|
for network in networks.get('v6', []):
|
||||||
|
if network['type'] == 'public':
|
||||||
|
_data['ipv6_address'] = network['ip_address']
|
||||||
|
else:
|
||||||
|
_data['private_ipv6_address'] = network['ip_address']
|
||||||
|
return _data
|
||||||
|
|
||||||
|
def get_droplet(self):
|
||||||
|
json_data = self.get_by_id(self.module.params['id'])
|
||||||
|
if not json_data and self.unique_name:
|
||||||
|
json_data = self.get_by_name(self.module.params['name'])
|
||||||
|
return json_data
|
||||||
|
|
||||||
|
def create(self):
|
||||||
|
json_data = self.get_droplet()
|
||||||
|
droplet_data = None
|
||||||
|
if json_data:
|
||||||
|
droplet_data = self.get_addresses(json_data)
|
||||||
|
self.module.exit_json(changed=False, data=droplet_data)
|
||||||
|
if self.module.check_mode:
|
||||||
|
self.module.exit_json(changed=True)
|
||||||
|
response = self.rest.post('droplets', data=self.module.params)
|
||||||
|
json_data = response.json
|
||||||
|
if response.status_code >= 400:
|
||||||
|
self.module.fail_json(changed=False, msg=json_data['message'])
|
||||||
|
if self.wait:
|
||||||
|
json_data = self.ensure_power_on(json_data['droplet']['id'])
|
||||||
|
droplet_data = self.get_addresses(json_data)
|
||||||
|
self.module.exit_json(changed=True, data=droplet_data)
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
|
json_data = self.get_droplet()
|
||||||
|
if json_data:
|
||||||
|
if self.module.check_mode:
|
||||||
|
self.module.exit_json(changed=True)
|
||||||
|
response = self.rest.delete('droplets/{0}'.format(json_data['droplet']['id']))
|
||||||
|
json_data = response.json
|
||||||
|
if response.status_code == 204:
|
||||||
|
self.module.exit_json(changed=True, msg='Droplet deleted')
|
||||||
|
self.module.fail_json(changed=False, msg='Failed to delete droplet')
|
||||||
|
else:
|
||||||
|
self.module.exit_json(changed=False, msg='Droplet not found')
|
||||||
|
|
||||||
|
def ensure_power_on(self, droplet_id):
|
||||||
|
end_time = time.time() + self.wait_timeout
|
||||||
|
while time.time() < end_time:
|
||||||
|
response = self.rest.get('droplets/{0}'.format(droplet_id))
|
||||||
|
json_data = response.json
|
||||||
|
if json_data['droplet']['status'] == 'active':
|
||||||
|
return json_data
|
||||||
|
time.sleep(min(2, end_time - time.time()))
|
||||||
|
self.module.fail_json(msg='Wait for droplet powering on timeout')
|
||||||
|
|
||||||
|
|
||||||
|
def core(module):
|
||||||
|
state = module.params.pop('state')
|
||||||
|
droplet = DODroplet(module)
|
||||||
|
if state == 'present':
|
||||||
|
droplet.create()
|
||||||
|
elif state == 'absent':
|
||||||
|
droplet.delete()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
module = AnsibleModule(
|
||||||
|
argument_spec=dict(
|
||||||
|
state=dict(choices=['present', 'absent'], default='present'),
|
||||||
|
oauth_token=dict(
|
||||||
|
aliases=['API_TOKEN'],
|
||||||
|
no_log=True,
|
||||||
|
fallback=(env_fallback, ['DO_API_TOKEN', 'DO_API_KEY', 'DO_OAUTH_TOKEN'])
|
||||||
|
),
|
||||||
|
name=dict(type='str'),
|
||||||
|
size=dict(aliases=['size_id']),
|
||||||
|
image=dict(aliases=['image_id']),
|
||||||
|
region=dict(aliases=['region_id']),
|
||||||
|
ssh_keys=dict(type='list'),
|
||||||
|
private_networking=dict(type='bool', default=False),
|
||||||
|
backups=dict(type='bool', default=False),
|
||||||
|
monitoring=dict(type='bool', default=False),
|
||||||
|
id=dict(aliases=['droplet_id'], type='int'),
|
||||||
|
user_data=dict(default=None),
|
||||||
|
ipv6=dict(type='bool', default=False),
|
||||||
|
volumes=dict(type='list'),
|
||||||
|
tags=dict(type='list'),
|
||||||
|
wait=dict(type='bool', default=True),
|
||||||
|
wait_timeout=dict(default=120, type='int'),
|
||||||
|
unique_name=dict(type='bool', default=False),
|
||||||
|
),
|
||||||
|
required_one_of=(
|
||||||
|
['id', 'name'],
|
||||||
|
),
|
||||||
|
required_if=([
|
||||||
|
('state', 'present', ['name', 'size', 'image', 'region']),
|
||||||
|
]),
|
||||||
|
supports_check_mode=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
core(module)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
Loading…
Reference in New Issue