class DOMonitoringAlertsInfo(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") def get_alerts(self): alerts = self.rest.get_paginated_data(base_url="monitoring/alerts?", data_key_name="policies") self.module.exit_json( changed=False, data=alerts, ) def get_alert(self, uuid): alerts = self.rest.get_paginated_data(base_url="monitoring/alerts?", data_key_name="policies") for alert in alerts: alert_uuid = alert.get("uuid", None) if alert_uuid is not None: if alert_uuid == uuid: self.module.exit_json( changed=False, data=alert, ) else: self.module.fail_json( changed=False, msg="Unexpected error; please file a bug: get_alert") self.module.exit_json( changed=False, data=[], )
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["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 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 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 core(module): rest = DigitalOceanHelper(module) base_url = 'regions?' regions = rest.get_paginated_data(base_url=base_url, data_key_name='regions') module.exit_json(changed=False, data=regions)
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): region_name = module.params.get("region_name", None) rest = DigitalOceanHelper(module) base_url = "volumes?" if region_name is not None: base_url += "region=%s&" % region_name volumes = rest.get_paginated_data(base_url=base_url, data_key_name="volumes") module.exit_json(changed=False, data=volumes)
def core(module): image_type = module.params["image_type"] rest = DigitalOceanHelper(module) base_url = "images?" if image_type == "distribution": base_url += "type=distribution&" elif image_type == "application": base_url += "type=application&" elif image_type == "private": base_url += "private=true&" images = rest.get_paginated_data(base_url=base_url, data_key_name="images") module.exit_json(changed=False, data=images)
def core(module): image_type = module.params['image_type'] rest = DigitalOceanHelper(module) base_url = 'images?' if image_type == 'distribution': base_url += "type=distribution&" elif image_type == 'application': base_url += "type=application&" elif image_type == 'private': base_url += "private=true&" images = rest.get_paginated_data(base_url=base_url, data_key_name='images') module.exit_json(changed=False, data=images)
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)
class DOMonitoringAlerts(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") def get_alerts(self): alerts = self.rest.get_paginated_data(base_url="monitoring/alerts?", data_key_name="policies") return alerts def get_alert(self): alerts = self.rest.get_paginated_data(base_url="monitoring/alerts?", data_key_name="policies") for alert in alerts: for alert_key in alert_keys: if alert.get(alert_key, None) != self.module.params.get( alert_key, None): break # This key doesn't match, try the next alert. else: return alert # Didn't hit break, this alert matches. return None def create(self): # Check for an existing (same) one. alert = self.get_alert() if alert is not None: self.module.exit_json( changed=False, data=alert, ) # Check mode if self.module.check_mode: self.module.exit_json(changed=True) # Create it. request_params = dict(self.module.params) response = self.rest.post("monitoring/alerts", data=request_params) if response.status_code == 200: alert = self.get_alert() if alert is not None: self.module.exit_json( changed=True, data=alert, ) else: self.module.fail_json( changed=False, msg="Unexpected error; please file a bug: create") else: self.module.fail_json( msg="Create Monitoring Alert '{0}' failed [HTTP {1}: {2}]". format( self.module.params.get("description"), response.status_code, response.json.get("message", None), )) def delete(self): uuid = self.module.params.get("uuid", None) if uuid is not None: # Check mode if self.module.check_mode: self.module.exit_json(changed=True) # Delete it response = self.rest.delete("monitoring/alerts/{0}".format(uuid)) if response.status_code == 204: self.module.exit_json( changed=True, msg="Deleted Monitoring Alert {0}".format(uuid), ) else: self.module.fail_json( msg="Delete Monitoring Alert {0} failed [HTTP {1}: {2}]". format( uuid, response.status_code, response.json.get("message", None), )) else: self.module.fail_json( changed=False, msg="Unexpected error; please file a bug: delete")
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 status_code_success = 200 if status_code != status_code_success: error = response.json info = response.info if error: error.update({"status_code": status_code}) error.update({"status_code_success": status_code_success}) self.module.fail_json(msg=error) elif info: info.update({"status_code_success": status_code_success}) self.module.fail_json(msg=info) else: msg_error = "Failed to retrieve firewalls from DigitalOcean" self.module.fail_json( msg=msg_error + " (url=" + self.rest.baseurl + "/" + self.baseurl + ", status=" + str(status_code or "") + " - expected:" + str(status_code_success) + ")") 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") or [] droplet_ids = obj.get("droplet_ids") or [] droplet_ids = [str(droplet_id) for droplet_id in droplet_ids] load_balancer_uids = obj.get("load_balancer_uids") or [] load_balancer_uids = [str(uid) for uid in load_balancer_uids] tags = obj.get("tags") or [] 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") or [] droplet_ids = [str(droplet_id) for droplet_id in droplet_ids] tags = obj.get("tags") or [] 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({ "context": "error when trying to " + ("create" if (id is None) else "update") + " firewalls" }) 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 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 DOCDNEndpoint(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.token = self.module.params.pop("oauth_token") def get_cdn_endpoints(self): cdns = self.rest.get_paginated_data( base_url="cdn/endpoints?", data_key_name="endpoints" ) return cdns def get_cdn_endpoint(self): cdns = self.rest.get_paginated_data( base_url="cdn/endpoints?", data_key_name="endpoints" ) found = None for cdn in cdns: if cdn.get("origin") == self.module.params.get("origin"): found = cdn for key in ["ttl", "certificate_id"]: if self.module.params.get(key) != cdn.get(key): return found, True return found, False def create(self): cdn, needs_update = self.get_cdn_endpoint() if cdn is not None: if not needs_update: # Have it already self.module.exit_json(changed=False, msg=cdn) if needs_update: # Check mode if self.module.check_mode: self.module.exit_json(changed=True) # Update it request_params = dict(self.module.params) endpoint = "cdn/endpoints" response = self.rest.put( "{0}/{1}".format(endpoint, cdn.get("id")), data=request_params ) status_code = response.status_code json_data = response.json # The API docs are wrong (they say 202 but return 200) if status_code != 200: self.module.fail_json( changed=False, msg="Failed to put {0} information due to error [HTTP {1}: {2}]".format( endpoint, status_code, json_data.get("message", "(empty error message)"), ), ) self.module.exit_json(changed=True, data=json_data) else: # Check mode if self.module.check_mode: self.module.exit_json(changed=True) # Create it request_params = dict(self.module.params) endpoint = "cdn/endpoints" response = self.rest.post(endpoint, data=request_params) status_code = response.status_code json_data = response.json if status_code != 201: self.module.fail_json( changed=False, msg="Failed to post {0} information due to error [HTTP {1}: {2}]".format( endpoint, status_code, json_data.get("message", "(empty error message)"), ), ) self.module.exit_json(changed=True, data=json_data) def delete(self): cdn, needs_update = self.get_cdn_endpoint() if cdn is not None: # Check mode if self.module.check_mode: self.module.exit_json(changed=True) # Delete it endpoint = "cdn/endpoints/{0}".format(cdn.get("id")) response = self.rest.delete(endpoint) status_code = response.status_code json_data = response.json if status_code != 204: self.module.fail_json( changed=False, msg="Failed to delete {0} information due to error [HTTP {1}: {2}]".format( endpoint, status_code, json_data.get("message", "(empty error message)"), ), ) self.module.exit_json( changed=True, msg="Deleted CDN Endpoint {0} ({1})".format( cdn.get("origin"), cdn.get("id") ), ) else: self.module.exit_json(changed=False)
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), )