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)
Пример #3
0
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)
Пример #4
0
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)
Пример #5
0
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)
Пример #6
0
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),
                )
Пример #8
0
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)
Пример #9
0
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)
Пример #11
0
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)
Пример #13
0
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)
Пример #14
0
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']))
Пример #18
0
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)
Пример #19
0
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),
            )