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