def core(module): domain_name = module.params.get('domain_name', None) rest = DigitalOceanHelper(module) domain_results = [] if domain_name is not None: response = rest.get("domains/%s" % domain_name) status_code = response.status_code if status_code != 200: module.fail_json(msg="Failed to retrieve domain for DigitalOcean") resp_json = response.json domains = [resp_json['domain']] else: domains = rest.get_paginated_data(base_url="domains?", data_key_name='domains') for temp_domain in domains: temp_domain_dict = { "name": temp_domain['name'], "ttl": temp_domain['ttl'], "zone_file": temp_domain['zone_file'], "domain_records": list(), } base_url = "domains/%s/records?" % temp_domain['name'] temp_domain_dict["domain_records"] = rest.get_paginated_data(base_url=base_url, data_key_name='domain_records') domain_results.append(temp_domain_dict) module.exit_json(changed=False, data=domain_results)
def core(module): rest = DigitalOceanHelper(module) response = rest.get("account") if response.status_code != 200: module.fail_json(msg="Failed to fetch 'account' facts due to error : %s" % response.json['message']) module.exit_json(changed=False, data=response.json["account"])
def core(module): state = module.params['state'] name = module.params['name'] rest = DigitalOceanHelper(module) results = dict(changed=False) response = rest.get('certificates') status_code = response.status_code resp_json = response.json if status_code != 200: module.fail_json(msg="Failed to retrieve certificates for DigitalOcean") if state == 'present': for cert in resp_json['certificates']: if cert['name'] == name: module.fail_json(msg="Certificate name %s already exists" % name) # Certificate does not exist, let us create it cert_data = dict(name=name, private_key=module.params['private_key'], leaf_certificate=module.params['leaf_certificate']) if module.params['certificate_chain'] is not None: cert_data.update(certificate_chain=module.params['certificate_chain']) response = rest.post("certificates", data=cert_data) status_code = response.status_code if status_code == 500: module.fail_json(msg="Failed to upload certificates as the certificates are malformed.") resp_json = response.json if status_code == 201: results.update(changed=True, response=resp_json) elif status_code == 422: results.update(changed=False, response=resp_json) elif state == 'absent': cert_id_del = None for cert in resp_json['certificates']: if cert['name'] == name: cert_id_del = cert['id'] if cert_id_del is not None: url = "certificates/{0}".format(cert_id_del) response = rest.delete(url) if response.status_code == 204: results.update(changed=True) else: results.update(changed=False) else: module.fail_json(msg="Failed to find certificate %s" % name) module.exit_json(**results)
def core(module): rest = DigitalOceanHelper(module) response = rest.get("account/keys") status_code = response.status_code json = response.json if status_code == 200: module.exit_json(changed=False, ansible_facts=json) else: module.fail_json(msg='Error fetching facts [{0}: {1}]'.format( status_code, response.json['message']))
def core(module): certificate_id = module.params.get('certificate_id', None) rest = DigitalOceanHelper(module) base_url = 'certificates?' if certificate_id is not None: response = rest.get("%s/%s" % (base_url, certificate_id)) status_code = response.status_code if status_code != 200: module.fail_json(msg="Failed to retrieve certificates for DigitalOcean") resp_json = response.json certificate = resp_json['certificate'] else: certificate = rest.get_paginated_data(base_url=base_url, data_key_name='certificates') module.exit_json(changed=False, data=certificate)
def core(module): load_balancer_id = module.params.get('load_balancer_id', None) rest = DigitalOceanHelper(module) base_url = 'load_balancers?' if load_balancer_id is not None: response = rest.get("%s/%s" % (base_url, load_balancer_id)) status_code = response.status_code if status_code != 200: module.fail_json(msg="Failed to retrieve load balancers for DigitalOcean") resp_json = response.json load_balancer = resp_json['load_balancer'] else: load_balancer = rest.get_paginated_data(base_url=base_url, data_key_name='load_balancers') module.exit_json(changed=False, data=load_balancer)
class DOBlockStorage(object): def __init__(self, module): self.module = module self.rest = DigitalOceanHelper(module) def get_key_or_fail(self, k): v = self.module.params[k] if v is None: self.module.fail_json(msg='Unable to load %s' % k) return v def poll_action_for_complete_status(self, action_id): url = 'actions/{}'.format(action_id) end_time = time.time() + self.module.params['timeout'] while time.time() < end_time: time.sleep(2) response = self.rest.get(url) status = response.status_code json = response.json if status == 200: if json['action']['status'] == 'completed': return True elif json['action']['status'] == 'errored': raise DOBlockStorageException(json['message']) raise DOBlockStorageException('Unable to reach api.digitalocean.com') def get_attached_droplet_ID(self, volume_name, region): url = 'volumes?name={}®ion={}'.format(volume_name, region) response = self.rest.get(url) status = response.status_code json = response.json if status == 200: volumes = json['volumes'] if len(volumes) > 0: droplet_ids = volumes[0]['droplet_ids'] if len(droplet_ids) > 0: return droplet_ids[0] return None else: raise DOBlockStorageException(json['message']) def attach_detach_block_storage(self, method, volume_name, region, droplet_id): data = { 'type': method, 'volume_name': volume_name, 'region': region, 'droplet_id': droplet_id } response = self.rest.post('volumes/actions', data=data) status = response.status_code json = response.json if status == 202: return self.poll_action_for_complete_status(json['action']['id']) elif status == 200: return True elif status == 422: return False else: raise DOBlockStorageException(json['message']) def create_block_storage(self): block_size = self.get_key_or_fail('block_size') volume_name = self.get_key_or_fail('volume_name') region = self.get_key_or_fail('region') description = self.module.params['description'] data = { 'size_gigabytes': block_size, 'name': volume_name, 'description': description, 'region': region } response = self.rest.post("volumes", data=data) status = response.status_code json = response.json if status == 201: self.module.exit_json(changed=True, id=json['volume']['id']) elif status == 409 and json['id'] == 'already_exists': self.module.exit_json(changed=False) else: raise DOBlockStorageException(json['message']) def delete_block_storage(self): volume_name = self.get_key_or_fail('volume_name') region = self.get_key_or_fail('region') url = 'volumes?name={}®ion={}'.format(volume_name, region) attached_droplet_id = self.get_attached_droplet_ID(volume_name, region) if attached_droplet_id is not None: self.attach_detach_block_storage('detach', volume_name, region, attached_droplet_id) response = self.rest.delete(url) status = response.status_code json = response.json if status == 204: self.module.exit_json(changed=True) elif status == 404: self.module.exit_json(changed=False) else: raise DOBlockStorageException(json['message']) def attach_block_storage(self): volume_name = self.get_key_or_fail('volume_name') region = self.get_key_or_fail('region') droplet_id = self.get_key_or_fail('droplet_id') attached_droplet_id = self.get_attached_droplet_ID(volume_name, region) if attached_droplet_id is not None: if attached_droplet_id == droplet_id: self.module.exit_json(changed=False) else: self.attach_detach_block_storage('detach', volume_name, region, attached_droplet_id) changed_status = self.attach_detach_block_storage('attach', volume_name, region, droplet_id) self.module.exit_json(changed=changed_status) def detach_block_storage(self): volume_name = self.get_key_or_fail('volume_name') region = self.get_key_or_fail('region') droplet_id = self.get_key_or_fail('droplet_id') changed_status = self.attach_detach_block_storage('detach', volume_name, region, droplet_id) self.module.exit_json(changed=changed_status)
def core(module): state = module.params['state'] name = module.params['name'] resource_id = module.params['resource_id'] resource_type = module.params['resource_type'] rest = DigitalOceanHelper(module) # Check if api_token is valid or not response = rest.get('account') if response.status_code == 401: module.fail_json(msg='Failed to login using api_token, please verify ' 'validity of api_token') if state == 'present': response = rest.get('tags/{0}'.format(name)) status_code = response.status_code resp_json = response.json changed = False if status_code == 200 and resp_json['tag']['name'] == name: changed = False else: # Ensure Tag exists response = rest.post("tags", data={'name': name}) status_code = response.status_code resp_json = response.json if status_code == 201: changed = True elif status_code == 422: changed = False else: module.exit_json(changed=False, data=resp_json) if resource_id is None: # No resource defined, we're done. module.exit_json(changed=changed, data=resp_json) else: # Check if resource is already tagged or not found = False url = "{0}?tag_name={1}".format(resource_type, name) if resource_type == 'droplet': url = "droplets?tag_name={0}".format(name) response = rest.get(url) status_code = response.status_code resp_json = response.json if status_code == 200: for resource in resp_json['droplets']: if not found and resource['id'] == int(resource_id): found = True break if not found: # If resource is not tagged, tag a resource url = "tags/{0}/resources".format(name) payload = { 'resources': [{ 'resource_id': resource_id, 'resource_type': resource_type }] } response = rest.post(url, data=payload) if response.status_code == 204: module.exit_json(changed=True) else: module.fail_json( msg="error tagging resource '{0}': {1}".format( resource_id, response.json["message"])) else: # Already tagged resource module.exit_json(changed=False) else: # Unable to find resource specified by user module.fail_json(msg=resp_json['message']) elif state == 'absent': if resource_id: url = "tags/{0}/resources".format(name) payload = { 'resources': [{ 'resource_id': resource_id, 'resource_type': resource_type }] } response = rest.delete(url, data=payload) else: url = "tags/{0}".format(name) response = rest.delete(url) if response.status_code == 204: module.exit_json(changed=True) else: module.exit_json(changed=False, data=response.json)
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) request_params = dict(self.module.params) del request_params['id'] response = self.rest.post('droplets', data=request_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')
class DOBlockStorage(object): def __init__(self, module): self.module = module self.rest = DigitalOceanHelper(module) def get_key_or_fail(self, k): v = self.module.params[k] if v is None: self.module.fail_json(msg='Unable to load %s' % k) return v def poll_action_for_complete_status(self, action_id): def get_attached_droplet_ID(self, volume_name, region): url = 'volumes?name={}®ion={}'.format(volume_name, region) response = self.rest.get(url) status = response.status_code json = response.json if status == 200: volumes = json['volumes'] if len(volumes) > 0: droplet_ids = volumes[0]['droplet_ids'] if len(droplet_ids) > 0: return droplet_ids[0] return None else: raise DOBlockStorageException(json['message']) def attach_detach_block_storage(self, method, volume_name, region, droplet_id): data = { 'type': method, 'volume_name': volume_name, 'region': region, 'droplet_id': droplet_id } response = self.rest.post('volumes/actions', data=data) status = response.status_code json = response.json if status == 202: return self.rest.poll_action_for_status(json['action']['id'], status='completed') elif status == 200: return True elif status == 422: return False else: raise DOBlockStorageException(json['message']) def create_block_storage(self): block_size = self.get_key_or_fail('block_size') volume_name = self.get_key_or_fail('volume_name') region = self.get_key_or_fail('region') description = self.module.params['description'] data = { 'size_gigabytes': block_size, 'name': volume_name, 'description': description, 'region': region } response = self.rest.post("volumes", data=data) status = response.status_code json = response.json if status == 201: self.module.exit_json(changed=True, id=json['volume']['id']) elif status == 409 and json['id'] == 'already_exists': self.module.exit_json(changed=False) else: raise DOBlockStorageException(json['message']) def delete_block_storage(self): volume_name = self.get_key_or_fail('volume_name') region = self.get_key_or_fail('region') url = 'volumes?name={}®ion={}'.format(volume_name, region) attached_droplet_id = self.get_attached_droplet_ID(volume_name, region) if attached_droplet_id is not None: self.attach_detach_block_storage('detach', volume_name, region, attached_droplet_id) response = self.rest.delete(url) status = response.status_code json = response.json if status == 204: self.module.exit_json(changed=True) elif status == 404: self.module.exit_json(changed=False) else: raise DOBlockStorageException(json['message']) def attach_block_storage(self): volume_name = self.get_key_or_fail('volume_name') region = self.get_key_or_fail('region') droplet_id = self.get_key_or_fail('droplet_id') attached_droplet_id = self.get_attached_droplet_ID(volume_name, region) if attached_droplet_id is not None: if attached_droplet_id == droplet_id: self.module.exit_json(changed=False) else: self.attach_detach_block_storage('detach', volume_name, region, attached_droplet_id) changed_status = self.attach_detach_block_storage('attach', volume_name, region, droplet_id) self.module.exit_json(changed=changed_status) def detach_block_storage(self): volume_name = self.get_key_or_fail('volume_name') region = self.get_key_or_fail('region') droplet_id = self.get_key_or_fail('droplet_id') changed_status = self.attach_detach_block_storage('detach', volume_name, region, droplet_id) self.module.exit_json(changed=changed_status) def handle_request(module): block_storage = DOBlockStorage(module) command = module.params['command'] state = module.params['state'] if command == 'create': if state == 'present': block_storage.create_block_storage() elif state == 'absent': block_storage.delete_block_storage() elif command == 'attach': if state == 'present': block_storage.attach_block_storage() elif state == 'absent': block_storage.detach_block_storage() def main(): module = AnsibleModule( argument_spec=dict( state=dict(choices=['present', 'absent'], required=True), command=dict(choices=['create', 'attach'], required=True), api_token=dict(aliases=['API_TOKEN'], no_log=True), block_size=dict(type='int'), volume_name=dict(type='str', required=True), description=dict(type='str'), region=dict(type='str', required=True), droplet_id=dict(type='int'), timeout=dict(type='int', default=10), ), ) try: handle_request(module) except DOBlockStorageException as e: module.fail_json(msg=e.message, exception=traceback.format_exc()) except KeyError as e: module.fail_json(msg='Unable to load %s' % e.message, exception=traceback.format_exc()) if __name__ == '__main__': main()
class DOBlockStorage(object): def __init__(self, module): self.module = module self.rest = DigitalOceanHelper(module) def get_key_or_fail(self, k): v = self.module.params[k] if v is None: self.module.fail_json(msg='Unable to load %s' % k) return v def poll_action_for_complete_status(self, action_id): url = 'actions/{}'.format(action_id) end_time = time.time() + self.module.params['timeout'] while time.time() < end_time: time.sleep(2) response = self.rest.get(url) status = response.status_code json = response.json if status == 200: if json['action']['status'] == 'completed': return True elif json['action']['status'] == 'errored': raise DOBlockStorageException(json['message']) raise DOBlockStorageException('Unable to reach api.digitalocean.com') def get_attached_droplet_ID(self, volume_name, region): url = 'volumes?name={}®ion={}'.format(volume_name, region) response = self.rest.get(url) status = response.status_code json = response.json if status == 200: volumes = json['volumes'] if len(volumes) > 0: droplet_ids = volumes[0]['droplet_ids'] if len(droplet_ids) > 0: return droplet_ids[0] return None else: raise DOBlockStorageException(json['message']) def attach_detach_block_storage(self, method, volume_name, region, droplet_id): data = { 'type': method, 'volume_name': volume_name, 'region': region, 'droplet_id': droplet_id } response = self.rest.post('volumes/actions', data=data) status = response.status_code json = response.json if status == 202: return self.poll_action_for_complete_status(json['action']['id']) elif status == 200: return True elif status == 422: return False else: raise DOBlockStorageException(json['message']) def create_block_storage(self): volume_name = self.get_key_or_fail('volume_name') snapshot_id = self.module.params['snapshot_id'] if snapshot_id: self.module.params['block_size'] = None self.module.params['region'] = None block_size = None region = None else: block_size = self.get_key_or_fail('block_size') region = self.get_key_or_fail('region') description = self.module.params['description'] data = { 'size_gigabytes': block_size, 'name': volume_name, 'description': description, 'region': region, 'snapshot_id': snapshot_id, } response = self.rest.post("volumes", data=data) status = response.status_code json = response.json if status == 201: self.module.exit_json(changed=True, id=json['volume']['id']) elif status == 409 and json['id'] == 'conflict': self.module.exit_json(changed=False) else: raise DOBlockStorageException(json['message']) def delete_block_storage(self): volume_name = self.get_key_or_fail('volume_name') region = self.get_key_or_fail('region') url = 'volumes?name={}®ion={}'.format(volume_name, region) attached_droplet_id = self.get_attached_droplet_ID(volume_name, region) if attached_droplet_id is not None: self.attach_detach_block_storage('detach', volume_name, region, attached_droplet_id) response = self.rest.delete(url) status = response.status_code json = response.json if status == 204: self.module.exit_json(changed=True) elif status == 404: self.module.exit_json(changed=False) else: raise DOBlockStorageException(json['message']) def attach_block_storage(self): volume_name = self.get_key_or_fail('volume_name') region = self.get_key_or_fail('region') droplet_id = self.get_key_or_fail('droplet_id') attached_droplet_id = self.get_attached_droplet_ID(volume_name, region) if attached_droplet_id is not None: if attached_droplet_id == droplet_id: self.module.exit_json(changed=False) else: self.attach_detach_block_storage('detach', volume_name, region, attached_droplet_id) changed_status = self.attach_detach_block_storage('attach', volume_name, region, droplet_id) self.module.exit_json(changed=changed_status) def detach_block_storage(self): volume_name = self.get_key_or_fail('volume_name') region = self.get_key_or_fail('region') droplet_id = self.get_key_or_fail('droplet_id') changed_status = self.attach_detach_block_storage('detach', volume_name, region, droplet_id) self.module.exit_json(changed=changed_status)
def core(module): state = module.params['state'] name = module.params['name'] rest = DigitalOceanHelper(module) results = dict(changed=False) response = rest.get('certificates') status_code = response.status_code resp_json = response.json if status_code != 200: module.fail_json( msg="Failed to retrieve certificates for DigitalOcean") if state == 'present': for cert in resp_json['certificates']: if cert['name'] == name: module.fail_json(msg="Certificate name %s already exists" % name) # Certificate does not exists, let us create it cert_data = dict(name=name, private_key=module.params['private_key'], leaf_certificate=module.params['leaf_certificate']) if module.params['certificate_chain'] is not None: cert_data.update( certificate_chain=module.params['certificate_chain']) response = rest.post("certificates", data=cert_data) status_code = response.status_code if status_code == 500: module.fail_json( msg= "Failed to upload certificates as the certificates are malformed." ) resp_json = response.json if status_code == 201: results.update(changed=True, response=resp_json) elif status_code == 422: results.update(changed=False, response=resp_json) elif state == 'absent': cert_id_del = None for cert in resp_json['certificates']: if cert['name'] == name: cert_id_del = cert['id'] if cert_id_del is not None: url = "certificates/{0}".format(cert_id_del) response = rest.delete(url) if response.status_code == 204: results.update(changed=True) else: results.update(changed=False) else: module.fail_json(msg="Failed to find certificate %s" % name) module.exit_json(**results)
def core(module): state = module.params['state'] name = module.params['name'] resource_id = module.params['resource_id'] resource_type = module.params['resource_type'] rest = DigitalOceanHelper(module) # Check if api_token is valid or not response = rest.get('account') if response.status_code == 401: module.fail_json(msg='Failed to login using api_token, please verify ' 'validity of api_token') if state == 'present': response = rest.get('tags/{0}'.format(name)) status_code = response.status_code resp_json = response.json changed = False if status_code == 200 and resp_json['tag']['name'] == name: changed = False else: # Ensure Tag exists response = rest.post("tags", data={'name': name}) status_code = response.status_code resp_json = response.json if status_code == 201: changed = True elif status_code == 422: changed = False else: module.exit_json(changed=False, data=resp_json) if resource_id is None: # No resource defined, we're done. module.exit_json(changed=changed, data=resp_json) else: # Check if resource is already tagged or not found = False url = "{0}?tag_name={1}".format(resource_type, name) if resource_type == 'droplet': url = "droplets?tag_name={0}".format(name) response = rest.get(url) status_code = response.status_code resp_json = response.json if status_code == 200: for resource in resp_json['droplets']: if not found and resource['id'] == int(resource_id): found = True break if not found: # If resource is not tagged, tag a resource url = "tags/{0}/resources".format(name) payload = { 'resources': [{ 'resource_id': resource_id, 'resource_type': resource_type}]} response = rest.post(url, data=payload) if response.status_code == 204: module.exit_json(changed=True) else: module.fail_json(msg="error tagging resource '{0}': {1}".format(resource_id, response.json["message"])) else: # Already tagged resource module.exit_json(changed=False) else: # Unable to find resource specified by user module.fail_json(msg=resp_json['message']) elif state == 'absent': if resource_id: url = "tags/{0}/resources".format(name) payload = { 'resources': [{ 'resource_id': resource_id, 'resource_type': resource_type}]} response = rest.delete(url, data=payload) else: url = "tags/{0}".format(name) response = rest.delete(url) if response.status_code == 204: module.exit_json(changed=True) else: module.exit_json(changed=False, data=response.json)
class DOFloatingIP(object): def __init__(self, module): self.module = module self.rest = DigitalOceanHelper(module) def create(self): payload = {} if self.module.params['region'] is not None: payload["region"] = self.module.params['region'] if self.module.params['droplet_id'] is not None: payload["droplet_id"] = self.module.params['droplet_id'] response = self.rest.post("floating_ips", data=payload) status_code = response.status_code json_data = response.json if status_code == 202: self.module.exit_json(changed=True, data=json_data) else: self.module.fail_json( msg="Error creating floating ip [{0}: {1}]".format( status_code, json_data["message"]), region=self.module.params['region']) def delete(self): ip = self.module.params['ip'] # TODO: test what happens for unassigned IP # self.unassign() response = self.rest.delete("floating_ips/{0}".format(ip)) status_code = response.status_code json_data = response.json if status_code == 204: self.module.exit_json(changed=True) elif status_code == 404: self.module.exit_json(changed=False) else: self.module.exit_json(changed=False, data=json_data) def retrieve(self): ip = self.module.params['ip'] response = self.rest.get("floating_ips/{0}".format(ip)) status_code = response.status_code json_data = response.json if status_code == 200: return json_data['floating_ip'] else: self.module.fail_json( msg="Error retrieving floating ip [{0}: {1}]".format( status_code, json_data["message"])) def do_action(self, payload): ip = self.module.params['ip'] response = self.rest.post("floating_ips/{0}/actions".format(ip), data=payload) status_code = response.status_code json_data = response.json self.module.exit_json(msg='%s' % (status_code, )) if status_code == 201: self.rest.poll_action_for_status(json_data['action']['id'], status='completed') self.module.exit_json(changed=True, data=json_data) else: self.module.fail_json( msg="Error completing floating ip action [{0}: {1}]".format( payload['type'], json_data["message"])) def assign_to_droplet(self): details = self.retrieve() droplet = details['droplet'] if droplet is not None and str( droplet['id']) in [self.module.params['droplet_id']]: self.module.exit_json(changed=False) payload = { "type": "assign", "droplet_id": self.module.params['droplet_id'] } self.do_action(payload) def unassign(self): payload = {"type": "unassign"} self.do_action(payload)
class DODroplet(object): def __init__(self, module): self.ops = [] if module._verbosity >= 1: self.ops.append('{0} module start'.format(time.time())) self.module = module self._id = self.module.params['id'] self._name = self.module.params['name'] self._droplet = None # == _id if found by _id, _name otherwise. For diagnostic messages. self.rest = DigitalOceanHelper(module) # pop all the parameters which we never POST as data self.rebuild = self.module.params.pop('rebuild') self.wait = self.module.params.pop('wait') self.wait_timeout = self.module.params.pop('wait_timeout') self.wait_step = self.module.params.pop('wait_step') self.wait_build = self.module.params.pop('wait_build') self.module.params.pop('timeout') self.module.params.pop('validate_certs') self.module.params.pop('oauth_token') if self.module.params.pop('unique_name', None) is not None: self.module.warn( "Parameter `unique_name` is deprecated. Consider it always True." ) if self._id and self._name: self.module.warn( "Both id {0} and name {1} supplied. Your play may turn unexpectedly!" .format(self._id, self._name)) def get_by_id(self, droplet_id): if not droplet_id: return None response = self.rest.get('droplets/{0}'.format(droplet_id)) if response.status_code == 200: self._droplet = droplet_id if 'name' in response.json: self._name = response.json['name'] return response.json return None def find_by_name(self, droplet_name): if not droplet_name: return None if not self._droplet: self._droplet = droplet_name 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: self._id = droplet['id'] 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 expose_addresses(self, data): """ Expose IP addresses as their own property the same way M(digital_ocean) did. Append run log (if exists) """ networks = data['droplet']['networks'] for network in networks.get('v4', []): if network['type'] == 'public': data['ip_address'] = network['ip_address'] elif network['type'] == 'private': data['private_ipv4_address'] = network['ip_address'] for network in networks.get('v6', []): if network['type'] == 'public': data['ipv6_address'] = network['ip_address'] if self.ops: data['ops'] = self.ops return data def find_droplet(self): json_data = self.get_by_id(self._id) if not json_data: json_data = self.find_by_name(self._name) return json_data def _params_ok(self, name, image, region): """ When creating droplet we need at least name, image, and region. :return: True if all the parameters were supplied. """ return name and image and region @staticmethod def same_dc(j, dc): if (j and dc and 'droplet' in j and 'region' in j['droplet'] and 'slug' in j['droplet']['region']): return j['droplet']['region']['slug'] == dc return None @staticmethod def same_size(j, size): if (j and size and 'droplet' in j and 'size_slug' in j['droplet']): return j['droplet']['size_slug'] == size return None @staticmethod def same_image(j, image): if (j and image and 'droplet' in j and 'image' in j['droplet'] and 'id' in j['droplet']['image'] and 'slug' in j['droplet']['image']): return (j['droplet']['image']['slug'] == image or str(j['droplet']['image']['id']) == image) return None @staticmethod def same_tags(j, tags): pt = tags if tags else [] if (j and 'droplet' in j and 'tags' in j['droplet'] and len(j['droplet']['tags']) == len(pt)): return set(j['droplet']['tags']) - set(pt) == set([]) return None def get(self): """ Find the droplet (either by id or name), rebuild if requested so, build if not found. """ json_data = self.find_droplet() if json_data and self.module.params['region'] and not self.same_dc( json_data, self.module.params['region']): self.module.warn("Droplet found but 'region' differs!") if json_data and self.module.params['size'] and not self.same_size( json_data, self.module.params['size']): self.module.warn("Droplet found but size is not '{0}'.".format( self.module.params['size'])) if not self.rebuild and json_data and not self.same_image( json_data, self.module.params['image']): self.module.warn( "Droplet found but 'image' differs! To re-image the droplet add 'rebuild=yes'" ) if json_data and self.module.params['tags'] and not self.same_tags( json_data, self.module.params['tags']): self.module.warn("Droplet found but 'tags' differ!") if json_data and not self.rebuild: self.module.exit_json(changed=False, data=self.expose_addresses(json_data)) elif self.rebuild and not json_data: self.module.fail_json( changed=False, msg='droplet {0} not found. Rebuild failed.'.format( self._droplet)) elif self.rebuild and json_data: self._rebuild(json_data) else: self._build() def _build(self): _size = self.module.params['size'] _image = self.module.params['image'] _region = self.module.params['region'] # we are going to build a new droplet now. Final checks. if self._id: self.module.warn( "Trying to build {0}. Parameter id found, it makes no sense here!" .format(self._name)) if not _size: _default_size = 's-1vcpu-1gb' self.module.warn("Missing 'size' parameter. Using size={0}".format( _default_size)) _size = _default_size if not self._params_ok(self._name, _image, _region): self.module.fail_json( changed=False, msg= 'Droplet not created. Not enough parameters: name={0} region={1}' ' image={2} size={3}'.format(self._name, _region, _image, _size)) if self.module.check_mode: self.module.exit_json(changed=True) if self.ops: self.ops.append("{0} posting create droplet command.".format( time.time())) response = self.rest.post('droplets', data=self.module.params) if self.ops: if response.json and 'droplet' in response.json: dbg1 = response.json['droplet'] elif response.json: dbg1 = response.json else: dbg1 = "! no JSON !" self.ops.append("{0} create droplet response: {1}".format( time.time(), dbg1)) if response.status_code >= 400: self.module.fail_json(changed=False, msg=response.json['message']) jr = self.ready(response.json['droplet']['id'], action="create", build=True) if self.wait \ else response.json self.module.exit_json(changed=True, data=self.expose_addresses(jr)) def _rebuild(self, json_data): if self._droplet is self._id and self._name: self.module.warn( "Trying to rebuild droplet id={0}. Parameter name is found too," " it makes no sense here!".format(self._id)) if self.module.check_mode: self.module.exit_json(changed=True) droplet_id = json_data['droplet']['id'] curr_image = json_data['droplet']['image']['id'] do_image = self.module.params['image'] if 'image' in self.module.params and self.module.params['image'] \ else curr_image cmd_data = {'type': "rebuild", 'image': do_image} response = self.rest.post('droplets/{0}/actions'.format(droplet_id), data=cmd_data) if response.status_code >= 400: self.module.fail_json(changed=False, msg=response.json['message']) self.module.exit_json( # wait for rebuild to finish, enrich received JSON, then return it. changed=True, data=self.expose_addresses( self.ready(droplet_id, action="rebuild", build=True))) @staticmethod def ids(name, networks): ip_list = [name] for k, v in networks.items(): for net in v: if "ip_address" in net: ip_list.append(net["ip_address"]) return ip_list def delete(self): json_data = self.find_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'])) if response.status_code == 204: host_ids = self.ids(self._name, json_data['droplet']['networks']) self.module.exit_json(changed=True, msg="Droplet '{0}' deleted.".format( self._droplet), host_ids=host_ids) self.module.fail_json(changed=False, msg='Failed to delete droplet {0}'.format( self._droplet)) else: self.module.exit_json(changed=False, msg='Droplet {0} not found'.format( self._droplet)) def ready(self, droplet_id, action, build=False): if build and self.wait_build > self.wait_timeout: timeout = self.wait_build else: timeout = self.wait_timeout end_time = time.time( ) + timeout + 0.2 # compensate for time.sleep() inaccuracy if build: time.sleep(self.wait_build) locked = True while time.time() < end_time: response = self.rest.get('droplets/{0}'.format(droplet_id)) if self.ops: rj = response.json if 'droplet' in rj and 'locked' in rj['droplet']: if 'v4' in rj['droplet']['networks'] and len( rj['droplet']['networks']['v4']): net = rj['droplet']['networks']['v4'][0] else: net = rj['droplet']['networks'] lck = rj['droplet']['locked'] sta = rj['droplet']['status'] self.ops.append("{0} status {1} locked {2} net {3}".format( time.time(), sta, lck, net)) locked = response.json['droplet']['locked'] has_net = len(response.json['droplet']['networks']['v4']) if not locked and has_net: return response.json time.sleep(min(self.wait_step, end_time - time.time())) if action == 'create' and not locked: # but not has_net self.module.warn( 'Droplet created but no IPv4 net found in {0} seconds.'.format( timeout)) return response.json self.module.fail_json( msg='Droplet {0} action "{1}" not finished in {2} seconds.'.format( self._droplet, action, timeout))