class DOVPCInfo(object): def __init__(self, module): self.rest = DigitalOceanHelper(module) self.module = module # pop the oauth token so we don't include it in the POST data self.module.params.pop("oauth_token") self.name = self.module.params.pop("name", "") self.members = self.module.params.pop("members", False) def get_by_name(self): page = 1 while page is not None: response = self.rest.get("vpcs?page={0}".format(page)) json_data = response.json if response.status_code == 200: for vpc in json_data["vpcs"]: if vpc.get("name", None) == self.name: return vpc 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(self): if self.module.check_mode: return self.module.exit_json(changed=False) if not self.members: base_url = "vpcs?" vpcs = self.rest.get_paginated_data(base_url=base_url, data_key_name="vpcs") self.module.exit_json(changed=False, data=vpcs) else: vpc = self.get_by_name() if vpc is not None: vpc_id = vpc.get("id", None) if vpc_id is not None: response = self.rest.get("vpcs/{0}/members".format(vpc_id)) json = response.json if response.status_code != 200: self.module.fail_json( msg="Failed to find VPC named {0}: {1}".format( self.name, json["message"])) else: self.module.exit_json(changed=False, data=json) else: self.module.fail_json( changed=False, msg="Unexpected error, please file a bug") else: self.module.fail_json( changed=False, msg="Could not find a VPC named {0}".format(self.name), )
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): snapshot_type = module.params["snapshot_type"] rest = DigitalOceanHelper(module) base_url = "snapshots" snapshot = [] if snapshot_type == "by_id": base_url += "/{0}".format(module.params.get("snapshot_id")) response = rest.get(base_url) status_code = response.status_code if status_code != 200: module.fail_json( msg="Failed to fetch snapshot information due to error : %s" % response.json["message"] ) snapshot.extend(response.json["snapshots"]) else: if snapshot_type == "droplet": base_url += "?resource_type=droplet&" elif snapshot_type == "volume": base_url += "?resource_type=volume&" else: base_url += "?" snapshot = rest.get_paginated_data(base_url=base_url, data_key_name="snapshots") module.exit_json(changed=False, data=snapshot)
def core(module): snapshot_type = module.params['snapshot_type'] rest = DigitalOceanHelper(module) base_url = 'snapshots?' snapshot = [] if snapshot_type == 'by_id': base_url += "/{0}".format(module.params.get('snapshot_id')) response = rest.get(base_url) status_code = response.status_code if status_code != 200: module.fail_json( msg="Failed to fetch snapshot information due to error : %s" % response.json['message']) snapshot.extend(response.json["snapshot"]) else: if snapshot_type == 'droplet': base_url += "resource_type=droplet&" elif snapshot_type == 'volume': base_url += "resource_type=volume&" snapshot = rest.get_paginated_data(base_url=base_url, data_key_name='snapshots') module.exit_json(changed=False, data=snapshot)
def run(module): rest = DigitalOceanHelper(module) if module.params["id"]: response = rest.get("projects/{0}".format(module.params["id"])) if response.status_code != 200: module.fail_json( msg="Failed to fetch 'projects' information due to error: %s" % response.json["message"]) else: response = rest.get_paginated_data(base_url="projects?", data_key_name="projects") if module.params["id"]: data = [response.json["project"]] elif module.params["name"]: data = [d for d in response if d["name"] == module.params["name"]] if not data: module.fail_json( msg= "Failed to fetch 'projects' information due to error: Unable to find project with name %s" % module.params["name"]) else: data = response module.exit_json(changed=False, data=data)
def run(module): rest = DigitalOceanHelper(module) response = rest.get("customers/my/balance") if response.status_code != 200: module.fail_json(msg="Failed to fetch 'customers/my/balance' information due to error : %s" % response.json['message']) module.exit_json(changed=False, data=response.json)
def core(module): rest = DigitalOceanHelper(module) response = rest.get('sizes') if response.status_code != 200: module.fail_json(msg="Failed to fetch 'sizes' information due to error : %s" % response.json['message']) module.exit_json(changed=False, data=response.json['sizes'])
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): 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, data=json['ssh_keys']) else: module.fail_json( msg='Error fetching SSH Key information [{0}: {1}]'.format( status_code, response.json['message']))
def run(module): rest = DigitalOceanHelper(module) endpoint = "cdn/endpoints" response = rest.get(endpoint) json_data = response.json status_code = response.status_code if status_code != 200: module.fail_json( changed=False, msg="Failed to get {0} information due to error [HTTP {1}: {2}]". format(endpoint, status_code, json_data.get("message", "(empty error message)")), ) module.exit_json(changed=False, data=json_data)
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): tag_name = module.params.get('tag_name', None) rest = DigitalOceanHelper(module) base_url = 'tags' if tag_name is not None: response = rest.get("%s/%s" % (base_url, tag_name)) status_code = response.status_code if status_code != 200: module.fail_json(msg="Failed to retrieve tags for DigitalOcean") resp_json = response.json tag = resp_json['tag'] else: tag = rest.get_paginated_data(base_url=base_url + '?', data_key_name='tags') module.exit_json(changed=False, data=tag)
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") load_balancer = [response.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)
def core(module): firewall_name = module.params.get('name', None) rest = DigitalOceanHelper(module) base_url = 'firewalls?' response = rest.get("%s" % base_url) status_code = response.status_code if status_code != 200: module.fail_json(msg="Failed to retrieve firewalls from Digital Ocean") firewalls = rest.get_paginated_data(base_url=base_url, data_key_name='firewalls') if firewall_name is not None: rule = {} for firewall in firewalls: if firewall['name'] == firewall_name: rule.update(firewall) module.exit_json(changed=False, data=rule) else: module.exit_json(changed=False, data=firewalls)
def core(module): rest = DigitalOceanHelper(module) page = 1 has_next = True floating_ips = [] status_code = None while has_next or status_code != 200: response = rest.get("floating_ips?page={0}&per_page=20".format(page)) status_code = response.status_code # stop if any error during pagination if status_code != 200: break page += 1 floating_ips.extend(response.json["floating_ips"]) has_next = ("pages" in response.json["links"] and "next" in response.json["links"]["pages"]) if status_code == 200: module.exit_json(changed=False, floating_ips=floating_ips) else: module.fail_json(msg="Error fetching information [{0}: {1}]".format( status_code, response.json["message"]))
class DODroplet(object): failure_message = { "empty_response": "Empty response from the DigitalOcean API; please try again or open a bug if it never " "succeeds.", "resizing_off": "Droplet must be off prior to resizing: " "https://docs.digitalocean.com/reference/api/api-reference/#operation/post_droplet_action", "unexpected": "Unexpected error [{0}]; please file a bug: " "https://github.com/ansible-collections/community.digitalocean/issues", "support_action": "Error status on Droplet action [{0}], please try again or contact DigitalOcean support: " "https://docs.digitalocean.com/support/", "failed_to": "Failed to {0} {1} [HTTP {2}: {3}]", } 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") self.id = None self.name = None self.size = None self.status = None if self.module.params.get("project"): # only load for non-default project assignments self.projects = DigitalOceanProjects(module, self.rest) self.firewalls = self.get_firewalls() self.sleep_interval = self.module.params.pop("sleep_interval", 10) if self.wait: if self.sleep_interval > self.wait_timeout: self.module.fail_json( msg="Sleep interval {0} should be less than {1}".format( self.sleep_interval, self.wait_timeout)) if self.sleep_interval <= 0: self.module.fail_json( msg="Sleep interval {0} should be greater than zero". format(self.sleep_interval)) def get_firewalls(self): response = self.rest.get("firewalls") status_code = response.status_code json_data = response.json if status_code != 200: self.module.fail_json(msg="Failed to get firewalls", data=json_data) return self.rest.get_paginated_data(base_url="firewalls?", data_key_name="firewalls") def get_firewall_by_name(self): rule = {} item = 0 for firewall in self.firewalls: for firewall_name in self.module.params["firewall"]: if firewall_name in firewall["name"]: rule[item] = {} rule[item].update(firewall) item += 1 if len(rule) > 0: return rule return None def add_droplet_to_firewalls(self): changed = False rule = self.get_firewall_by_name() if rule is None: err = "Failed to find firewalls: {0}".format( self.module.params["firewall"]) return err json_data = self.get_droplet() if json_data is not None: request_params = {} droplet = json_data.get("droplet", None) droplet_id = droplet.get("id", None) request_params["droplet_ids"] = [droplet_id] for firewall in rule: if droplet_id not in rule[firewall]["droplet_ids"]: response = self.rest.post( "firewalls/{0}/droplets".format(rule[firewall]["id"]), data=request_params, ) json_data = response.json status_code = response.status_code if status_code != 204: err = "Failed to add droplet {0} to firewall {1}".format( droplet_id, rule[firewall]["id"]) return err, changed changed = True return None, changed def remove_droplet_from_firewalls(self): changed = False json_data = self.get_droplet() if json_data is not None: request_params = {} droplet = json_data.get("droplet", None) droplet_id = droplet.get("id", None) request_params["droplet_ids"] = [droplet_id] for firewall in self.firewalls: if (firewall["name"] not in self.module.params["firewall"] and droplet_id in firewall["droplet_ids"]): response = self.rest.delete( "firewalls/{0}/droplets".format(firewall["id"]), data=request_params, ) json_data = response.json status_code = response.status_code if status_code != 204: err = "Failed to remove droplet {0} from firewall {1}".format( droplet_id, firewall["id"]) return err, changed changed = True return None, changed def get_by_id(self, droplet_id): if not droplet_id: return None response = self.rest.get("droplets/{0}".format(droplet_id)) status_code = response.status_code json_data = response.json if json_data is None: self.module.fail_json( changed=False, msg=DODroplet.failure_message["empty_response"], ) else: if status_code == 200: droplet = json_data.get("droplet", None) if droplet is not None: self.id = droplet.get("id", None) self.name = droplet.get("name", None) self.size = droplet.get("size_slug", None) self.status = droplet.get("status", None) 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 status_code = response.status_code if json_data is None: self.module.fail_json( changed=False, msg=DODroplet.failure_message["empty_response"], ) else: if status_code == 200: droplets = json_data.get("droplets", []) for droplet in droplets: if droplet.get("name", None) == droplet_name: self.id = droplet.get("id", None) self.name = droplet.get("name", None) self.size = droplet.get("size_slug", None) self.status = droplet.get("status", None) 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 resize_droplet(self, state, droplet_id): if self.status != "off": self.module.fail_json( changed=False, msg=DODroplet.failure_message["resizing_off"], ) self.wait_action( droplet_id, { "type": "resize", "disk": self.module.params["resize_disk"], "size": self.module.params["size"], }, ) if state == "active": self.ensure_power_on(droplet_id) # Get updated Droplet data json_data = self.get_droplet() droplet = json_data.get("droplet", None) if droplet is None: self.module.fail_json( changed=False, msg=DODroplet.failure_message["unexpected"].format( "no Droplet"), ) self.module.exit_json( changed=True, msg="Resized Droplet {0} ({1}) from {2} to {3}".format( self.name, self.id, self.size, self.module.params["size"]), data={"droplet": droplet}, ) def wait_status(self, droplet_id, desired_statuses): # Make sure Droplet is active first end_time = time.monotonic() + self.wait_timeout while time.monotonic() < end_time: response = self.rest.get("droplets/{0}".format(droplet_id)) json_data = response.json status_code = response.status_code message = json_data.get("message", "no error message") droplet = json_data.get("droplet", None) droplet_status = droplet.get("status", None) if droplet else None if droplet is None or droplet_status is None: self.module.fail_json( changed=False, msg=DODroplet.failure_message["unexpected"].format( "no Droplet or status"), ) if status_code >= 400: self.module.fail_json( changed=False, msg=DODroplet.failure_message["failed_to"].format( "get", "Droplet", status_code, message), ) if droplet_status in desired_statuses: return time.sleep(self.sleep_interval) self.module.fail_json(msg="Wait for Droplet [{0}] status timeout". format(",".join(desired_statuses))) def wait_check_action(self, droplet_id, action_id): end_time = time.monotonic() + self.wait_timeout while time.monotonic() < end_time: response = self.rest.get("droplets/{0}/actions/{1}".format( droplet_id, action_id)) json_data = response.json status_code = response.status_code message = json_data.get("message", "no error message") action = json_data.get("action", None) action_id = action.get("id", None) action_status = action.get("status", None) if action is None or action_id is None or action_status is None: self.module.fail_json( changed=False, msg=DODroplet.failure_message["unexpected"].format( "no action, ID, or status"), ) if status_code >= 400: self.module.fail_json( changed=False, msg=DODroplet.failure_message["failed_to"].format( "get", "action", status_code, message), ) if action_status == "errored": self.module.fail_json( changed=True, msg=DODroplet.failure_message["support_action"].format( action_id), ) if action_status == "completed": return time.sleep(self.sleep_interval) self.module.fail_json(msg="Wait for Droplet action timeout") def wait_action(self, droplet_id, desired_action_data): action_type = desired_action_data.get("type", "undefined") response = self.rest.post("droplets/{0}/actions".format(droplet_id), data=desired_action_data) json_data = response.json status_code = response.status_code message = json_data.get("message", "no error message") action = json_data.get("action", None) action_id = action.get("id", None) action_status = action.get("status", None) if action is None or action_id is None or action_status is None: self.module.fail_json( changed=False, msg=DODroplet.failure_message["unexpected"].format( "no action, ID, or status"), ) if status_code >= 400: self.module.fail_json( changed=False, msg=DODroplet.failure_message["failed_to"].format( "post", "action", status_code, message), ) # Keep checking till it is done or times out self.wait_check_action(droplet_id, action_id) def ensure_power_on(self, droplet_id): # Make sure Droplet is active or off first self.wait_status(droplet_id, ["active", "off"]) # Trigger power-on self.wait_action(droplet_id, {"type": "power_on"}) def ensure_power_off(self, droplet_id): # Make sure Droplet is active first self.wait_status(droplet_id, ["active"]) # Trigger power-off self.wait_action(droplet_id, {"type": "power_off"}) def create(self, state): json_data = self.get_droplet() # We have the Droplet if json_data is not None: droplet = json_data.get("droplet", None) droplet_id = droplet.get("id", None) droplet_size = droplet.get("size_slug", None) if droplet_id is None or droplet_size is None: self.module.fail_json( changed=False, msg=DODroplet.failure_message["unexpected"].format( "no Droplet ID or size"), ) # Add droplet to a firewall if specified if self.module.params["firewall"] is not None: firewall_changed = False if len(self.module.params["firewall"]) > 0: firewall_add, add_changed = self.add_droplet_to_firewalls() if firewall_add is not None: self.module.fail_json( changed=False, msg=firewall_add, data={ "droplet": droplet, "firewall": firewall_add }, ) firewall_changed = firewall_changed or add_changed firewall_remove, remove_changed = self.remove_droplet_from_firewalls( ) if firewall_remove is not None: self.module.fail_json( changed=False, msg=firewall_remove, data={ "droplet": droplet, "firewall": firewall_remove }, ) firewall_changed = firewall_changed or remove_changed self.module.exit_json( changed=firewall_changed, data={"droplet": droplet}, ) # Check mode if self.module.check_mode: self.module.exit_json(changed=False) # Ensure Droplet size if droplet_size != self.module.params.get("size", None): self.resize_droplet(state, droplet_id) # Ensure Droplet power state droplet_data = self.get_addresses(json_data) droplet_id = droplet.get("id", None) droplet_status = droplet.get("status", None) if droplet_id is not None and droplet_status is not None: if state == "active" and droplet_status != "active": self.ensure_power_on(droplet_id) # Get updated Droplet data (fallback to current data) json_data = self.get_droplet() droplet = json_data.get("droplet", droplet) self.module.exit_json(changed=True, data={"droplet": droplet}) elif state == "inactive" and droplet_status != "off": self.ensure_power_off(droplet_id) # Get updated Droplet data (fallback to current data) json_data = self.get_droplet() droplet = json_data.get("droplet", droplet) self.module.exit_json(changed=True, data={"droplet": droplet}) else: self.module.exit_json(changed=False, data={"droplet": droplet}) # We don't have the Droplet, create it # Check mode 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 status_code = response.status_code message = json_data.get("message", "no error message") droplet = json_data.get("droplet", None) # Ensure that the Droplet is created if status_code != 202: self.module.fail_json( changed=False, msg=DODroplet.failure_message["failed_to"].format( "create", "Droplet", status_code, message), ) droplet_id = droplet.get("id", None) if droplet is None or droplet_id is None: self.module.fail_json( changed=False, msg=DODroplet.failure_message["unexpected"].format( "no Droplet or ID"), ) if status_code >= 400: self.module.fail_json( changed=False, msg=DODroplet.failure_message["failed_to"].format( "create", "Droplet", status_code, message), ) if self.wait: if state == "present" or state == "active": self.ensure_power_on(droplet_id) if state == "inactive": self.ensure_power_off(droplet_id) else: if state == "inactive": self.ensure_power_off(droplet_id) # Get updated Droplet data (fallback to current data) if self.wait: json_data = self.get_by_id(droplet_id) if json_data: droplet = json_data.get("droplet", droplet) project_name = self.module.params.get("project") if project_name: # empty string is the default project, skip project assignment urn = "do:droplet:{0}".format(droplet_id) assign_status, error_message, resources = self.projects.assign_to_project( project_name, urn) self.module.exit_json( changed=True, data={"droplet": droplet}, msg=error_message, assign_status=assign_status, resources=resources, ) # Add droplet to firewall if specified if self.module.params["firewall"] is not None: # raise Exception(self.module.params["firewall"]) firewall_add = self.add_droplet_to_firewalls() if firewall_add is not None: self.module.fail_json( changed=False, msg=firewall_add, data={ "droplet": droplet, "firewall": firewall_add }, ) firewall_remove = self.remove_droplet_from_firewalls() if firewall_remove is not None: self.module.fail_json( changed=False, msg=firewall_remove, data={ "droplet": droplet, "firewall": firewall_remove }, ) self.module.exit_json(changed=True, data={"droplet": droplet}) self.module.exit_json(changed=True, data={"droplet": droplet}) def delete(self): # to delete a droplet we need to know the droplet id or unique name, ie # name is not None and unique_name is True, but as "id or name" is # enforced elsewhere, we only need to enforce "id or unique_name" here if not self.module.params["id"] and not self.unique_name: self.module.fail_json( changed=False, msg="id must be set or unique_name must be true for deletes", ) json_data = self.get_droplet() if json_data is None: self.module.exit_json(changed=False, msg="Droplet not found") # Check mode if self.module.check_mode: self.module.exit_json(changed=True) # Delete it droplet = json_data.get("droplet", None) droplet_id = droplet.get("id", None) droplet_name = droplet.get("name", None) if droplet is None or droplet_id is None: self.module.fail_json( changed=False, msg=DODroplet.failure_message["unexpected"].format( "no Droplet, name, or ID"), ) response = self.rest.delete("droplets/{0}".format(droplet_id)) json_data = response.json status_code = response.status_code if status_code == 204: self.module.exit_json( changed=True, msg="Droplet {0} ({1}) deleted".format(droplet_name, droplet_id), ) else: self.module.fail_json( changed=False, msg="Failed to delete Droplet {0} ({1})".format( droplet_name, droplet_id), )
class DOFirewall(object): def __init__(self, module): self.rest = DigitalOceanHelper(module) self.module = module self.name = self.module.params.get('name') self.baseurl = 'firewalls' self.firewalls = self.get_firewalls() def get_firewalls(self): base_url = self.baseurl + "?" response = self.rest.get("%s" % base_url) status_code = response.status_code if status_code != 200: self.module.fail_json( msg="Failed to retrieve firewalls from DigitalOcean") return self.rest.get_paginated_data(base_url=base_url, data_key_name='firewalls') def get_firewall_by_name(self): rule = {} for firewall in self.firewalls: if firewall['name'] == self.name: rule.update(firewall) return rule return None def ordered(self, obj): if isinstance(obj, dict): return sorted((k, self.ordered(v)) for k, v in obj.items()) if isinstance(obj, list): return sorted(self.ordered(x) for x in obj) else: return obj def fill_protocol_defaults(self, obj): if obj.get('protocol') is None: obj['protocol'] = 'tcp' return obj def fill_source_and_destination_defaults_inner(self, obj): addresses = obj.get('addresses') if addresses is None: addresses = [] droplet_ids = obj.get('droplet_ids') if droplet_ids is None: droplet_ids = [] load_balancer_uids = obj.get('load_balancer_uids') if load_balancer_uids is None: load_balancer_uids = [] tags = obj.get('tags') if tags is None: tags = [] data = { "addresses": addresses, "droplet_ids": droplet_ids, "load_balancer_uids": load_balancer_uids, "tags": tags } return data def fill_sources_and_destinations_defaults(self, obj, prop): value = obj.get(prop) if value is None: value = {} else: value = self.fill_source_and_destination_defaults_inner(value) obj[prop] = value return obj def fill_data_defaults(self, obj): inbound_rules = obj.get('inbound_rules') if inbound_rules is None: inbound_rules = [] else: inbound_rules = [ self.fill_protocol_defaults(x) for x in inbound_rules ] inbound_rules = [ self.fill_sources_and_destinations_defaults(x, 'sources') for x in inbound_rules ] outbound_rules = obj.get('outbound_rules') if outbound_rules is None: outbound_rules = [] else: outbound_rules = [ self.fill_protocol_defaults(x) for x in outbound_rules ] outbound_rules = [ self.fill_sources_and_destinations_defaults(x, 'destinations') for x in outbound_rules ] droplet_ids = obj.get('droplet_ids') if droplet_ids is None: droplet_ids = [] tags = obj.get('tags') if tags is None: tags = [] data = { "name": obj.get('name'), "inbound_rules": inbound_rules, "outbound_rules": outbound_rules, "droplet_ids": droplet_ids, "tags": tags } return data def data_to_compare(self, obj): return self.ordered(self.fill_data_defaults(obj)) def update(self, obj, id): if id is None: status_code_success = 202 resp = self.rest.post(path=self.baseurl, data=obj) else: status_code_success = 200 resp = self.rest.put(path=self.baseurl + '/' + id, data=obj) status_code = resp.status_code if status_code != status_code_success: error = resp.json error.update({'status_code': status_code}) error.update({'status_code_success': status_code_success}) self.module.fail_json(msg=error) self.module.exit_json(changed=True, data=resp.json['firewall']) def create(self): rule = self.get_firewall_by_name() data = { "name": self.module.params.get('name'), "inbound_rules": self.module.params.get('inbound_rules'), "outbound_rules": self.module.params.get('outbound_rules'), "droplet_ids": self.module.params.get('droplet_ids'), "tags": self.module.params.get('tags') } if rule is None: self.update(data, None) else: rule_data = { "name": rule.get('name'), "inbound_rules": rule.get('inbound_rules'), "outbound_rules": rule.get('outbound_rules'), "droplet_ids": rule.get('droplet_ids'), "tags": rule.get('tags') } user_data = { "name": data.get('name'), "inbound_rules": data.get('inbound_rules'), "outbound_rules": data.get('outbound_rules'), "droplet_ids": data.get('droplet_ids'), "tags": data.get('tags') } if self.data_to_compare(user_data) == self.data_to_compare( rule_data): self.module.exit_json(changed=False, data=rule) else: self.update(data, rule.get('id')) def destroy(self): rule = self.get_firewall_by_name() if rule is None: self.module.exit_json(changed=False, data="Firewall does not exist") else: endpoint = self.baseurl + '/' + rule['id'] resp = self.rest.delete(path=endpoint) status_code = resp.status_code if status_code != 204: self.module.fail_json(msg="Failed to delete firewall") self.module.exit_json( changed=True, data="Deleted firewall rule: {0} - {1}".format( rule['name'], rule['id']))
class DOKubernetesInfo(object): def __init__(self, module): self.rest = DigitalOceanHelper(module) self.module = module # Pop these values so we don't include them in the POST data self.module.params.pop("oauth_token") self.return_kubeconfig = self.module.params.pop("return_kubeconfig") self.cluster_id = None def get_by_id(self): """Returns an existing DigitalOcean Kubernetes cluster matching on id""" response = self.rest.get("kubernetes/clusters/{0}".format( self.cluster_id)) json_data = response.json if response.status_code == 200: return json_data return None def get_all_clusters(self): """Returns all DigitalOcean Kubernetes clusters""" response = self.rest.get("kubernetes/clusters") json_data = response.json if response.status_code == 200: return json_data return None def get_by_name(self, cluster_name): """Returns an existing DigitalOcean Kubernetes cluster matching on name""" if not cluster_name: return None clusters = self.get_all_clusters() for cluster in clusters["kubernetes_clusters"]: if cluster["name"] == cluster_name: return cluster return None def get_kubernetes_kubeconfig(self): """Returns the kubeconfig for an existing DigitalOcean Kubernetes cluster""" response = self.rest.get("kubernetes/clusters/{0}/kubeconfig".format( self.cluster_id)) if response.status_code == 200: return response.body else: self.module.fail_json(msg="Failed to retrieve kubeconfig") def get_kubernetes(self): """Returns an existing DigitalOcean Kubernetes cluster by name""" json_data = self.get_by_name(self.module.params["name"]) if json_data: self.cluster_id = json_data["id"] return json_data else: return None def get(self): """Fetches an existing DigitalOcean Kubernetes cluster API reference: https://docs.digitalocean.com/reference/api/api-reference/#operation/list_all_kubernetes_clusters """ json_data = self.get_kubernetes() if json_data: if self.return_kubeconfig: json_data["kubeconfig"] = self.get_kubernetes_kubeconfig() self.module.exit_json(changed=False, data=json_data) self.module.fail_json(changed=False, msg="Kubernetes cluster not found")
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) response = rest.get("tags/{0}".format(name)) if state == "present": 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 response.status_code == 200: 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)
class DOLoadBalancer(object): def __init__(self, module): self.rest = DigitalOceanHelper(module) self.module = module self.id = None self.name = self.module.params.get("name") self.region = self.module.params.get("region") self.updates = [] # Pop these values so we don't include them in the POST data self.module.params.pop("oauth_token") self.wait = self.module.params.pop("wait", True) self.wait_timeout = self.module.params.pop("wait_timeout", 600) def get_by_id(self): """Fetch an existing DigitalOcean Load Balancer (by id) API reference: https://docs.digitalocean.com/reference/api/api-reference/#operation/get_load_balancer """ response = self.rest.get("load_balancers/{0}".format(self.id)) json_data = response.json if response.status_code == 200: # Found one with the given id: lb = json_data.get("load_balancer", None) if lb is not None: self.lb = lb return lb else: self.module.fail_json( msg="Unexpected error; please file a bug: get_by_id") return None def get_by_name(self): """Fetch all existing DigitalOcean Load Balancers API reference: https://docs.digitalocean.com/reference/api/api-reference/#operation/list_all_load_balancers """ page = 1 while page is not None: response = self.rest.get("load_balancers?page={0}".format(page)) json_data = response.json if json_data is None: self.module.fail_json( msg= "Empty response from the DigitalOcean API; please try again or open a bug if it never succeeds." ) if response.status_code == 200: lbs = json_data.get("load_balancers", []) for lb in lbs: # Found one with the same name: name = lb.get("name", None) if name == self.name: # Make sure the region is the same! region = lb.get("region", None) if region is not None: region_slug = region.get("slug", None) if region_slug is not None: if region_slug == self.region: self.lb = lb return lb else: self.module.fail_json( msg= "Cannot change load balancer region -- delete and re-create" ) else: self.module.fail_json( msg= "Unexpected error; please file a bug: get_by_name" ) else: self.module.fail_json( msg= "Unexpected error; please file a bug: get_by_name" ) if ("links" in json_data and "pages" in json_data["links"] and "next" in json_data["links"]["pages"]): page += 1 else: page = None else: self.module.fail_json( msg="Unexpected error; please file a bug: get_by_name") return None def ensure_active(self): """Wait for the existing Load Balancer to be active""" end_time = time.monotonic() + self.wait_timeout while time.monotonic() < end_time: if self.get_by_id(): status = self.lb.get("status", None) if status is not None: if status == "active": return True else: self.module.fail_json( msg="Unexpected error; please file a bug: ensure_active" ) else: self.module.fail_json( msg="Load Balancer {0} in {1} not found".format( self.id, self.region)) time.sleep(min(10, end_time - time.monotonic())) self.module.fail_json( msg="Timed out waiting for Load Balancer {0} in {1} to be active". format(self.id, self.region)) def is_same(self, found_lb): """Checks if exising Load Balancer is the same as requested""" check_attributes = [ "droplet_ids", "size", "algorithm", "forwarding_rules", "health_check", "sticky_sessions", "redirect_http_to_https", "enable_proxy_protocol", "enable_backend_keepalive", ] for attribute in check_attributes: if self.module.params.get(attribute, None) != found_lb.get( attribute, None): # raise Exception(str(self.module.params.get(attribute, None)), str(found_lb.get(attribute, None))) self.updates.append(attribute) # Check if the VPC needs changing. vpc_uuid = self.lb.get("vpc_uuid", None) if vpc_uuid is not None: if vpc_uuid != found_lb.get("vpc_uuid", None): self.updates.append("vpc_uuid") if len(self.updates): return False else: return True def update(self): """Updates a DigitalOcean Load Balancer API reference: https://docs.digitalocean.com/reference/api/api-reference/#operation/update_load_balancer """ request_params = dict(self.module.params) self.id = self.lb.get("id", None) self.name = self.lb.get("name", None) if self.id is not None and self.name is not None: response = self.rest.put("load_balancers/{0}".format(self.id), data=request_params) json_data = response.json if response.status_code == 200: self.module.exit_json( changed=True, msg="Load Balancer {0} ({1}) in {2} updated: {3}".format( self.name, self.id, self.region, ", ".join(self.updates)), ) else: self.module.fail_json( changed=False, msg="Error updating Load Balancer {0} ({1}) in {2}: {3}". format(self.name, self.id, self.region, json_data["message"]), ) else: self.module.fail_json( msg="Unexpected error; please file a bug: update") def create(self): """Creates a DigitalOcean Load Balancer API reference: https://docs.digitalocean.com/reference/api/api-reference/#operation/create_load_balancer """ # Check if it exists already (the API docs aren't up-to-date right now, # "name" is required and must be unique across the account. found_lb = self.get_by_name() if found_lb is not None: # Do we need to update it? if not self.is_same(found_lb): self.update() else: self.module.exit_json( changed=False, msg= "Load Balancer {0} already exists in {1} (and needs no changes)" .format(self.name, self.region), ) # Create it. request_params = dict(self.module.params) response = self.rest.post("load_balancers", data=request_params) json_data = response.json if response.status_code != 202: self.module.fail_json( msg="Failed creating Load Balancer {0} in {1}: {2}".format( self.name, self.region, json_data["message"])) # Store it. lb = json_data.get("load_balancer", None) if lb is None: self.module.fail_json( msg="Unexpected error; please file a bug: create empty lb") self.id = lb.get("id", None) if self.id is None: self.module.fail_json( msg="Unexpected error; please file a bug: create missing id") if self.wait: self.ensure_active() self.module.exit_json(changed=True, data=json_data) def delete(self): """Deletes a DigitalOcean Load Balancer API reference: https://docs.digitalocean.com/reference/api/api-reference/#operation/delete_load_balancer """ lb = self.get_by_name() if lb is not None: id = lb.get("id", None) name = lb.get("name", None) if id is None or name is None: self.module.fail_json( msg="Unexpected error; please file a bug: delete") else: lb_region = lb.get("region", None) region = lb_region.get("slug", None) if region is None: self.module.fail_json( msg="Unexpected error; please file a bug: delete") response = self.rest.delete("load_balancers/{0}".format(id)) json_data = response.json if response.status_code == 204: # Response body should be empty self.module.exit_json( changed=True, msg="Load Balancer {0} ({1}) in {2} deleted".format( name, id, region), ) else: message = json_data.get( "message", "Empty failure message from the DigitalOcean API!") self.module.fail_json( changed=False, msg= "Failed to delete Load Balancer {0} ({1}) in {2}: {3}". format(name, id, region, message), ) else: self.module.fail_json( changed=False, msg="Load Balancer {0} not found in {1}".format( self.name, self.region), )
class DOVPC(object): def __init__(self, module): self.rest = DigitalOceanHelper(module) self.module = module # pop the oauth token so we don't include it in the POST data self.module.params.pop("oauth_token") self.name = module.params.get("name", None) self.description = module.params.get("description", None) self.default = module.params.get("default", False) self.region = module.params.get("region", None) self.ip_range = module.params.get("ip_range", None) self.vpc_id = module.params.get("vpc_id", None) def get_by_name(self): page = 1 while page is not None: response = self.rest.get("vpcs?page={0}".format(page)) json_data = response.json if response.status_code == 200: for vpc in json_data["vpcs"]: if vpc.get("name", None) == self.name: return vpc 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 create(self): if self.module.check_mode: return self.module.exit_json(changed=True) vpc = self.get_by_name() if vpc is not None: # update vpc_id = vpc.get("id", None) if vpc_id is not None: data = { "name": self.name, } if self.description is not None: data["description"] = self.description if self.default is not False: data["default"] = True response = self.rest.put("vpcs/{0}".format(vpc_id), data=data) json = response.json if response.status_code != 200: self.module.fail_json( msg="Failed to update VPC {0}: {1}".format( self.name, json["message"])) else: self.module.exit_json(changed=False, data=json) else: self.module.fail_json( changed=False, msg="Unexpected error, please file a bug") else: # create data = { "name": self.name, "region": self.region, } if self.description is not None: data["description"] = self.description if self.ip_range is not None: data["ip_range"] = self.ip_range response = self.rest.post("vpcs", data=data) status = response.status_code json = response.json if status == 201: self.module.exit_json(changed=True, data=json["vpc"]) else: self.module.fail_json( changed=False, msg="Failed to create VPC: {0}".format(json["message"]), ) def delete(self): if self.module.check_mode: return self.module.exit_json(changed=True) vpc = self.get_by_name() if vpc is None: self.module.fail_json( msg="Unable to find VPC {0}".format(self.name)) else: vpc_id = vpc.get("id", None) if vpc_id is not None: response = self.rest.delete("vpcs/{0}".format(str(vpc_id))) status = response.status_code json = response.json if status == 204: self.module.exit_json( changed=True, msg="Deleted VPC {0} ({1})".format(self.name, vpc_id), ) else: json = response.json self.module.fail_json( changed=False, msg="Failed to delete VPC {0} ({1}): {2}".format( self.name, vpc_id, json["message"]), )
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/{0}'.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_block_storage_by_name(self, volume_name, region): url = 'volumes?name={0}®ion={1}'.format(volume_name, region) resp = self.rest.get(url) if resp.status_code != 200: raise DOBlockStorageException(resp.json['message']) volumes = resp.json['volumes'] if not volumes: return None return volumes[0] def get_attached_droplet_ID(self, volume_name, region): volume = self.get_block_storage_by_name(volume_name, region) if not volume or not volume['droplet_ids']: return None return volume['droplet_ids'][0] 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 resize_block_storage(self, volume_name, region, desired_size): if not desired_size: return False volume = self.get_block_storage_by_name(volume_name, region) if volume['size_gigabytes'] == desired_size: return False data = { 'type': 'resize', 'size_gigabytes': desired_size, } resp = self.rest.post( 'volumes/{0}/actions'.format(volume['id']), data=data, ) if resp.status_code == 202: return self.poll_action_for_complete_status( resp.json['action']['id']) else: # we'd get status 422 if desired_size <= current volume size raise DOBlockStorageException(resp.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': # The volume exists already, but it might not have the desired size resized = self.resize_block_storage(volume_name, region, block_size) self.module.exit_json(changed=resized) 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={0}®ion={1}'.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)
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') self.id = None self.name = None self.size = None self.status = None 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: droplet = json_data.get('droplet', None) if droplet is not None: self.id = droplet.get('id', None) self.name = droplet.get('name', None) self.size = droplet.get('size_slug', None) self.status = droplet.get('status', None) 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.get('name', None) == droplet_name: self.id = droplet.get('id', None) self.name = droplet.get('name', None) self.size = droplet.get('size_slug', None) self.status = droplet.get('status', None) 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 resize_droplet(self): """API reference: https://developers.digitalocean.com/documentation/v2/#resize-a-droplet (Must be powered off)""" if self.status == 'off': response = self.rest.post('droplets/{0}/actions'.format(self.id), data={ 'type': 'resize', 'disk': self.module.params['resize_disk'], 'size': self.module.params['size'] }) json_data = response.json if response.status_code == 201: self.module.exit_json( changed=True, msg='Resized Droplet {0} ({1}) from {2} to {3}'.format( self.name, self.id, self.size, self.module.params['size'])) else: self.module.fail_json( msg="Resizing Droplet {0} ({1}) failed [HTTP {2}: {3}]". format(self.name, self.id, response.status_code, response.json.get('message', None))) else: self.module.fail_json( msg= 'Droplet must be off prior to resizing (https://developers.digitalocean.com/documentation/v2/#resize-a-droplet)' ) def create(self, state): json_data = self.get_droplet() droplet_data = None if json_data: droplet = json_data.get('droplet', None) if droplet is not None: droplet_size = droplet.get('size_slug', None) if droplet_size is not None: if droplet_size != self.module.params['size']: self.resize_droplet() droplet_data = self.get_addresses(json_data) # If state is active or inactive, ensure requested and desired power states match droplet = json_data.get('droplet', None) if droplet is not None: droplet_id = droplet.get('id', None) droplet_status = droplet.get('status', None) if droplet_id is not None and droplet_status is not None: if state == 'active' and droplet_status != 'active': power_on_json_data = self.ensure_power_on(droplet_id) self.module.exit_json( changed=True, data=self.get_addresses(power_on_json_data)) elif state == 'inactive' and droplet_status != 'off': power_off_json_data = self.ensure_power_off(droplet_id) self.module.exit_json( changed=True, data=self.get_addresses(power_off_json_data)) else: 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): response = self.rest.post('droplets/{0}/actions'.format(droplet_id), data={'type': 'power_on'}) 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 ensure_power_off(self, droplet_id): response = self.rest.post('droplets/{0}/actions'.format(droplet_id), data={'type': 'power_off'}) 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'] == 'off': return json_data time.sleep(min(2, end_time - time.time())) self.module.fail_json(msg='Wait for droplet powering off timeout')
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") self.id = None self.name = None self.size = None self.status = None 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 json_data is None: self.module.fail_json( changed=False, msg= "Empty response from the DigitalOcean API; please try again or open a bug if it never succeeds.", ) else: if response.status_code == 200: droplet = json_data.get("droplet", None) if droplet is not None: self.id = droplet.get("id", None) self.name = droplet.get("name", None) self.size = droplet.get("size_slug", None) self.status = droplet.get("status", None) 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 json_data is None: self.module.fail_json( changed=False, msg= "Empty response from the DigitalOcean API; please try again or open a bug if it never succeeds.", ) else: if response.status_code == 200: droplets = json_data.get("droplets", []) for droplet in droplets: if droplet.get("name", None) == droplet_name: self.id = droplet.get("id", None) self.name = droplet.get("name", None) self.size = droplet.get("size_slug", None) self.status = droplet.get("status", None) 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 resize_droplet(self, state): """API reference: https://developers.digitalocean.com/documentation/v2/#resize-a-droplet (Must be powered off)""" if self.status == "off": response = self.rest.post( "droplets/{0}/actions".format(self.id), data={ "type": "resize", "disk": self.module.params["resize_disk"], "size": self.module.params["size"], }, ) json_data = response.json if json_data is None: self.module.fail_json( changed=False, msg= "Empty response from the DigitalOcean API; please try again or open a bug if it never succeeds.", ) else: if response.status_code == 201: if state == "active": self.ensure_power_on(self.id) self.module.exit_json( changed=True, msg="Resized Droplet {0} ({1}) from {2} to {3}".format( self.name, self.id, self.size, self.module.params["size"]), ) else: self.module.fail_json( msg="Resizing Droplet {0} ({1}) failed [HTTP {2}: {3}]" .format( self.name, self.id, response.status_code, response.json.get("message", None), )) else: self.module.fail_json( msg= "Droplet must be off prior to resizing (https://developers.digitalocean.com/documentation/v2/#resize-a-droplet)" ) def create(self, state): json_data = self.get_droplet() droplet_data = None if json_data is not None: droplet = json_data.get("droplet", None) if droplet is not None: droplet_size = droplet.get("size_slug", None) if droplet_size is not None: if droplet_size != self.module.params["size"]: self.resize_droplet(state) droplet_data = self.get_addresses(json_data) # If state is active or inactive, ensure requested and desired power states match droplet = json_data.get("droplet", None) if droplet is not None: droplet_id = droplet.get("id", None) droplet_status = droplet.get("status", None) if droplet_id is not None and droplet_status is not None: if state == "active" and droplet_status != "active": power_on_json_data = self.ensure_power_on(droplet_id) self.module.exit_json( changed=True, data=self.get_addresses(power_on_json_data)) elif state == "inactive" and droplet_status != "off": power_off_json_data = self.ensure_power_off(droplet_id) self.module.exit_json( changed=True, data=self.get_addresses(power_off_json_data)) else: 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 json_data is None: self.module.fail_json( changed=False, msg= "Empty response from the DigitalOcean API; please try again or open a bug if it never succeeds.", ) else: if response.status_code >= 400: message = json_data.get( "message", "Empty failure message from the DigitalOcean API!") self.module.fail_json(changed=False, msg=message) droplet_data = json_data.get("droplet", None) if droplet_data is not None: droplet_id = droplet_data.get("id", None) if droplet_id is not None: if self.wait: if state == "present" or state == "active": json_data = self.ensure_power_on(droplet_id) if state == "inactive": json_data = self.ensure_power_off(droplet_id) droplet_data = self.get_addresses(json_data) else: if state == "inactive": response = self.rest.post( "droplets/{0}/actions".format(droplet_id), data={"type": "power_off"}, ) else: self.module.fail_json( changed=False, msg="Unexpected error, please file a bug") else: self.module.fail_json( changed=False, msg="Unexpected error, please file a bug") 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): # Make sure Droplet is active first end_time = time.monotonic() + self.wait_timeout while time.monotonic() < end_time: response = self.rest.get("droplets/{0}".format(droplet_id)) json_data = response.json if json_data is not None: if response.status_code >= 400: message = json_data.get( "message", "Empty failure message from the DigitalOcean API!") self.module.fail_json(changed=False, msg=message) else: self.module.fail_json( changed=False, msg= "Empty response from the DigitalOcean API; please try again or open a bug if it never succeeds.", ) droplet = json_data.get("droplet", None) if droplet is None: self.module.fail_json( changed=False, msg="Unexpected error, please file a bug (no droplet)", ) droplet_status = droplet.get("status", None) if droplet_status is None: self.module.fail_json( changed=False, msg="Unexpected error, please file a bug (no status)") if droplet_status == "active": break time.sleep(min(10, end_time - time.monotonic())) # Trigger power-on response = self.rest.post("droplets/{0}/actions".format(droplet_id), data={"type": "power_on"}) json_data = response.json if json_data is not None: if response.status_code >= 400: message = json_data.get( "message", "Empty failure message from the DigitalOcean API!") self.module.fail_json(changed=False, msg=message) else: self.module.fail_json( changed=False, msg= "Empty response from the DigitalOcean API; please try again or open a bug if it never succeeds.", ) # Save the power-on action action = json_data.get("action", None) action_id = action.get("id", None) if action is None or action_id is None: self.module.fail_json( changed=False, msg= "Unexpected error, please file a bug (no power-on action or id)", ) # Keep checking till it is done or times out end_time = time.monotonic() + self.wait_timeout while time.monotonic() < end_time: response = self.rest.get("droplets/{0}/actions/{1}".format( droplet_id, action_id)) json_data = response.json if json_data is not None: if response.status_code >= 400: message = json_data.get( "message", "Empty failure message from the DigitalOcean API!") self.module.fail_json(changed=False, msg=message) else: self.module.fail_json( changed=False, msg= "Empty response from the DigitalOcean API; please try again or open a bug if it never succeeds.", ) action = json_data.get("action", None) action_status = action.get("status", None) if action is None or action_status is None: self.module.fail_json( changed=False, msg= "Unexpected error, please file a bug (no action or status)", ) if action_status == "errored": self.module.fail_json( changed=False, msg= "Error status on droplet power on action, please try again or contact DigitalOcean support", ) if action_status == "completed": response = self.rest.get("droplets/{0}".format(droplet_id)) json_data = response.json if json_data is not None: if response.status_code >= 400: message = json_data.get( "message", "Empty failure message from the DigitalOcean API!", ) self.module.fail_json(changed=False, msg=message) else: self.module.fail_json( changed=False, msg= "Empty response from the DigitalOcean API; please try again or open a bug if it never succeeds.", ) return json_data time.sleep(min(10, end_time - time.monotonic())) self.module.fail_json(msg="Wait for droplet powering on timeout") def ensure_power_off(self, droplet_id): # Make sure Droplet is active first end_time = time.monotonic() + self.wait_timeout while time.monotonic() < end_time: response = self.rest.get("droplets/{0}".format(droplet_id)) json_data = response.json if json_data is not None: if response.status_code >= 400: message = json_data.get( "message", "Empty failure message from the DigitalOcean API!") self.module.fail_json(changed=False, msg=message) else: self.module.fail_json( changed=False, msg= "Empty response from the DigitalOcean API; please try again or open a bug if it never succeeds.", ) droplet = json_data.get("droplet", None) if droplet is None: self.module.fail_json( changed=False, msg="Unexpected error, please file a bug (no droplet)", ) droplet_status = droplet.get("status", None) if droplet_status is None: self.module.fail_json( changed=False, msg="Unexpected error, please file a bug (no status)") if droplet_status == "active": break time.sleep(min(10, end_time - time.monotonic())) # Trigger power-off response = self.rest.post("droplets/{0}/actions".format(droplet_id), data={"type": "power_off"}) json_data = response.json if json_data is not None: if response.status_code >= 400: message = json_data.get( "message", "Empty failure message from the DigitalOcean API!") self.module.fail_json(changed=False, msg=message) else: self.module.fail_json( changed=False, msg= "Empty response from the DigitalOcean API; please try again or open a bug if it never succeeds.", ) # Save the power-off action action = json_data.get("action", None) action_id = action.get("id", None) if action is None or action_id is None: self.module.fail_json( changed=False, msg= "Unexpected error, please file a bug (no power-off action or id)", ) # Keep checking till it is done or times out end_time = time.monotonic() + self.wait_timeout while time.monotonic() < end_time: response = self.rest.get("droplets/{0}/actions/{1}".format( droplet_id, action_id)) json_data = response.json if json_data is not None: if response.status_code >= 400: message = json_data.get( "message", "Empty failure message from the DigitalOcean API!") self.module.fail_json(changed=False, msg=message) else: self.module.fail_json( changed=False, msg= "Empty response from the DigitalOcean API; please try again or open a bug if it never succeeds.", ) action = json_data.get("action", None) action_status = action.get("status", None) if action is None or action_status is None: self.module.fail_json( changed=False, msg= "Unexpected error, please file a bug (no action or status)", ) if action_status == "errored": self.module.fail_json( changed=False, msg= "Error status on droplet power off action, please try again or contact DigitalOcean support", ) if action_status == "completed": response = self.rest.get("droplets/{0}".format(droplet_id)) json_data = response.json if response.status_code >= 400: self.module.fail_json(changed=False, msg=json_data["message"]) return json_data time.sleep(min(10, end_time - time.monotonic())) self.module.fail_json(msg="Wait for droplet powering off timeout")
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 DODatabaseInfo(object): def __init__(self, module): self.module = module self.rest = DigitalOceanHelper(module) # 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 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) 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: for database in json_data["databases"]: if database.get("name", None) == database_name: self.id = database.get("id", None) self.name = database.get("name", 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 get_databases(self): all_databases = [] 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 not None and isinstance(databases, list): all_databases.append(databases) if ("links" in json_data and "pages" in json_data["links"] and "next" in json_data["links"]["pages"]): page += 1 else: page = None return {"databases": all_databases}
class DOKubernetes(object): def __init__(self, module): self.rest = DigitalOceanHelper(module) self.module = module # Pop these values so we don't include them in the POST data self.return_kubeconfig = self.module.params.pop( "return_kubeconfig", False) self.wait = self.module.params.pop("wait", True) self.wait_timeout = self.module.params.pop("wait_timeout", 600) self.module.params.pop("oauth_token") self.cluster_id = None def get_by_id(self): """Returns an existing DigitalOcean Kubernetes cluster matching on id""" response = self.rest.get("kubernetes/clusters/{0}".format( self.cluster_id)) json_data = response.json if response.status_code == 200: return json_data return None def get_all_clusters(self): """Returns all DigitalOcean Kubernetes clusters""" response = self.rest.get("kubernetes/clusters") json_data = response.json if response.status_code == 200: return json_data return None def get_by_name(self, cluster_name): """Returns an existing DigitalOcean Kubernetes cluster matching on name""" if not cluster_name: return None clusters = self.get_all_clusters() for cluster in clusters["kubernetes_clusters"]: if cluster["name"] == cluster_name: return cluster return None def get_kubernetes_kubeconfig(self): """Returns the kubeconfig for an existing DigitalOcean Kubernetes cluster""" response = self.rest.get("kubernetes/clusters/{0}/kubeconfig".format( self.cluster_id)) text_data = response.text return text_data def get_kubernetes(self): """Returns an existing DigitalOcean Kubernetes cluster by name""" json_data = self.get_by_name(self.module.params["name"]) if json_data: self.cluster_id = json_data["id"] return json_data else: return None def get_kubernetes_options(self): """Fetches DigitalOcean Kubernetes options: regions, sizes, versions. API reference: https://developers.digitalocean.com/documentation/v2/#list-available-regions--node-sizes--and-versions-of-kubernetes """ response = self.rest.get("kubernetes/options") json_data = response.json if response.status_code == 200: return json_data return None def ensure_running(self): """Waits for the newly created DigitalOcean Kubernetes cluster to be running""" end_time = time.time() + self.wait_timeout while time.time() < end_time: cluster = self.get_by_id() if cluster["kubernetes_cluster"]["status"]["state"] == "running": return cluster time.sleep(min(2, end_time - time.time())) self.module.fail_json(msg="Wait for Kubernetes cluster to be running") def create(self): """Creates a DigitalOcean Kubernetes cluster API reference: https://developers.digitalocean.com/documentation/v2/#create-a-new-kubernetes-cluster """ # Get valid Kubernetes options (regions, sizes, versions) kubernetes_options = self.get_kubernetes_options()["options"] # Validate region valid_regions = [str(x["slug"]) for x in kubernetes_options["regions"]] if self.module.params.get("region") not in valid_regions: self.module.fail_json( msg="Invalid region {0} (valid regions are {1})".format( self.module.params.get("region"), ", ".join( valid_regions))) # Validate version valid_versions = [ str(x["slug"]) for x in kubernetes_options["versions"] ] valid_versions.append("latest") if self.module.params.get("version") not in valid_versions: self.module.fail_json( msg="Invalid version {0} (valid versions are {1})".format( self.module.params.get("version"), ", ".join( valid_versions))) # Validate size valid_sizes = [str(x["slug"]) for x in kubernetes_options["sizes"]] for node_pool in self.module.params.get("node_pools"): if node_pool["size"] not in valid_sizes: self.module.fail_json( msg="Invalid size {0} (valid sizes are {1})".format( node_pool["size"], ", ".join(valid_sizes))) # Create the Kubernetes cluster json_data = self.get_kubernetes() if json_data: # Add the kubeconfig to the return if self.return_kubeconfig: json_data["kubeconfig"] = self.get_kubernetes_kubeconfig() self.module.exit_json(changed=False, data=json_data) if self.module.check_mode: self.module.exit_json(changed=True) request_params = dict(self.module.params) response = self.rest.post("kubernetes/clusters", data=request_params) json_data = response.json if response.status_code >= 400: self.module.fail_json(changed=False, msg=json_data) # Set the cluster_id self.cluster_id = json_data["kubernetes_cluster"]["id"] if self.wait: json_data = self.ensure_running() # Add the kubeconfig to the return if self.return_kubeconfig: json_data["kubeconfig"] = self.get_kubernetes_kubeconfig() self.module.exit_json(changed=True, data=json_data) def delete(self): """Deletes a DigitalOcean Kubernetes cluster API reference: https://developers.digitalocean.com/documentation/v2/#delete-a-kubernetes-cluster """ json_data = self.get_kubernetes() if json_data: if self.module.check_mode: self.module.exit_json(changed=True) response = self.rest.delete("kubernetes/clusters/{0}".format( json_data["id"])) json_data = response.json if response.status_code == 204: self.module.exit_json(changed=True, msg="Kubernetes cluster deleted") self.module.fail_json(changed=False, msg="Failed to delete Kubernetes cluster") else: self.module.exit_json(changed=False, msg="Kubernetes cluster not found")
class DOProject(object): def __init__(self, module): self.rest = DigitalOceanHelper(module) self.module = module # 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.purpose = None self.description = None self.environment = None self.is_default = None def get_by_id(self, project_id): if not project_id: return None response = self.rest.get("projects/{0}".format(project_id)) json_data = response.json if response.status_code == 200: project = json_data.get("project", None) if project is not None: self.id = project.get("id", None) self.name = project.get("name", None) self.purpose = project.get("purpose", None) self.description = project.get("description", None) self.environment = project.get("environment", None) self.is_default = project.get("is_default", None) return json_data return None def get_by_name(self, project_name): if not project_name: return None page = 1 while page is not None: response = self.rest.get("projects?page={0}".format(page)) json_data = response.json if response.status_code == 200: for project in json_data["projects"]: if project.get("name", None) == project_name: self.id = project.get("id", None) self.name = project.get("name", None) self.description = project.get("description", None) self.purpose = project.get("purpose", None) self.environment = project.get("environment", None) self.is_default = project.get("is_default", None) return {"project": project} 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_project(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 create(self, state): json_data = self.get_project() request_params = dict(self.module.params) if json_data is not None: changed = False valid_purpose = [ "Just trying out DigitalOcean", "Class project/Educational Purposes", "Website or blog", "Web Application", "Service or API", "Mobile Application", "Machine Learning/AI/Data Processing", "IoT", "Operational/Developer tooling", ] for key in request_params.keys(): if ( key == "purpose" and request_params[key] is not None and request_params[key] not in valid_purpose ): param = "Other: " + request_params[key] else: param = request_params[key] if json_data["project"][key] != param and param is not None: changed = True if changed: response = self.rest.put( "projects/{0}".format(json_data["project"]["id"]), data=request_params, ) if response.status_code != 200: self.module.fail_json(changed=False, msg="Unable to update project") self.module.exit_json(changed=True, data=response.json) else: self.module.exit_json(changed=False, data=json_data) else: response = self.rest.post("projects", data=request_params) if response.status_code != 201: self.module.fail_json(changed=False, msg="Unable to create project") self.module.exit_json(changed=True, data=response.json) def delete(self): json_data = self.get_project() if json_data: if self.module.check_mode: self.module.exit_json(changed=True) response = self.rest.delete( "projects/{0}".format(json_data["project"]["id"]) ) json_data = response.json if response.status_code == 204: self.module.exit_json(changed=True, msg="Project deleted") self.module.fail_json(changed=False, msg="Failed to delete project") else: self.module.exit_json(changed=False, msg="Project not found")
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/{0}".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_block_storage_by_name(self, volume_name, region): url = "volumes?name={0}®ion={1}".format(volume_name, region) resp = self.rest.get(url) if resp.status_code != 200: raise DOBlockStorageException(resp.json["message"]) volumes = resp.json["volumes"] if not volumes: return None return volumes[0] def get_attached_droplet_ID(self, volume_name, region): volume = self.get_block_storage_by_name(volume_name, region) if not volume or not volume["droplet_ids"]: return None return volume["droplet_ids"][0] 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 == 404 and method == "detach": return False # Already detached elif status == 422: return False else: raise DOBlockStorageException(json["message"]) def resize_block_storage(self, volume_name, region, desired_size): if not desired_size: return False volume = self.get_block_storage_by_name(volume_name, region) if volume["size_gigabytes"] == desired_size: return False data = { "type": "resize", "size_gigabytes": desired_size, } resp = self.rest.post( "volumes/{0}/actions".format(volume["id"]), data=data, ) if resp.status_code == 202: return self.poll_action_for_complete_status( resp.json["action"]["id"]) else: # we'd get status 422 if desired_size <= current volume size raise DOBlockStorageException(resp.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": # The volume exists already, but it might not have the desired size resized = self.resize_block_storage(volume_name, region, block_size) self.module.exit_json(changed=resized) 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={0}®ion={1}".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)