Exemple #1
0
class DOProject(object):
    def __init__(self, module):
        self.rest = DigitalOceanHelper(module)
        self.module = module
        # pop the oauth token so we don't include it in the POST data
        self.module.params.pop("oauth_token")
        self.id = None
        self.name = None
        self.purpose = None
        self.description = None
        self.environment = None
        self.is_default = None

    def get_by_id(self, project_id):
        if not project_id:
            return None
        response = self.rest.get("projects/{0}".format(project_id))
        json_data = response.json
        if response.status_code == 200:
            project = json_data.get("project", None)
            if project is not None:
                self.id = project.get("id", None)
                self.name = project.get("name", None)
                self.purpose = project.get("purpose", None)
                self.description = project.get("description", None)
                self.environment = project.get("environment", None)
                self.is_default = project.get("is_default", None)
            return json_data
        return None

    def get_by_name(self, project_name):
        if not project_name:
            return None
        page = 1
        while page is not None:
            response = self.rest.get("projects?page={0}".format(page))
            json_data = response.json
            if response.status_code == 200:
                for project in json_data["projects"]:
                    if project.get("name", None) == project_name:
                        self.id = project.get("id", None)
                        self.name = project.get("name", None)
                        self.description = project.get("description", None)
                        self.purpose = project.get("purpose", None)
                        self.environment = project.get("environment", None)
                        self.is_default = project.get("is_default", None)
                        return {"project": project}
                if (
                    "links" in json_data
                    and "pages" in json_data["links"]
                    and "next" in json_data["links"]["pages"]
                ):
                    page += 1
                else:
                    page = None
        return None

    def get_project(self):
        json_data = self.get_by_id(self.module.params["id"])
        if not json_data:
            json_data = self.get_by_name(self.module.params["name"])
        return json_data

    def create(self, state):
        json_data = self.get_project()
        request_params = dict(self.module.params)

        if json_data is not None:
            changed = False
            valid_purpose = [
                "Just trying out DigitalOcean",
                "Class project/Educational Purposes",
                "Website or blog",
                "Web Application",
                "Service or API",
                "Mobile Application",
                "Machine Learning/AI/Data Processing",
                "IoT",
                "Operational/Developer tooling",
            ]
            for key in request_params.keys():
                if (
                    key == "purpose"
                    and request_params[key] is not None
                    and request_params[key] not in valid_purpose
                ):
                    param = "Other: " + request_params[key]
                else:
                    param = request_params[key]

                if json_data["project"][key] != param and param is not None:
                    changed = True

            if changed:
                response = self.rest.put(
                    "projects/{0}".format(json_data["project"]["id"]),
                    data=request_params,
                )
                if response.status_code != 200:
                    self.module.fail_json(changed=False, msg="Unable to update project")
                self.module.exit_json(changed=True, data=response.json)
            else:
                self.module.exit_json(changed=False, data=json_data)
        else:
            response = self.rest.post("projects", data=request_params)

            if response.status_code != 201:
                self.module.fail_json(changed=False, msg="Unable to create project")
            self.module.exit_json(changed=True, data=response.json)

    def delete(self):
        json_data = self.get_project()
        if json_data:
            if self.module.check_mode:
                self.module.exit_json(changed=True)
            response = self.rest.delete(
                "projects/{0}".format(json_data["project"]["id"])
            )
            json_data = response.json
            if response.status_code == 204:
                self.module.exit_json(changed=True, msg="Project deleted")
            self.module.fail_json(changed=False, msg="Failed to delete project")
        else:
            self.module.exit_json(changed=False, msg="Project not found")
class 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']))
Exemple #3
0
class DOLoadBalancer(object):
    def __init__(self, module):
        self.rest = DigitalOceanHelper(module)
        self.module = module
        self.id = None
        self.name = self.module.params.get("name")
        self.region = self.module.params.get("region")
        self.updates = []
        # Pop these values so we don't include them in the POST data
        self.module.params.pop("oauth_token")
        self.wait = self.module.params.pop("wait", True)
        self.wait_timeout = self.module.params.pop("wait_timeout", 600)

    def get_by_id(self):
        """Fetch an existing DigitalOcean Load Balancer (by id)
        API reference: https://docs.digitalocean.com/reference/api/api-reference/#operation/get_load_balancer
        """
        response = self.rest.get("load_balancers/{0}".format(self.id))
        json_data = response.json
        if response.status_code == 200:
            # Found one with the given id:
            lb = json_data.get("load_balancer", None)
            if lb is not None:
                self.lb = lb
                return lb
            else:
                self.module.fail_json(
                    msg="Unexpected error; please file a bug: get_by_id")
        return None

    def get_by_name(self):
        """Fetch all existing DigitalOcean Load Balancers
        API reference: https://docs.digitalocean.com/reference/api/api-reference/#operation/list_all_load_balancers
        """
        page = 1
        while page is not None:
            response = self.rest.get("load_balancers?page={0}".format(page))
            json_data = response.json
            if json_data is None:
                self.module.fail_json(
                    msg=
                    "Empty response from the DigitalOcean API; please try again or open a bug if it never succeeds."
                )
            if response.status_code == 200:
                lbs = json_data.get("load_balancers", [])
                for lb in lbs:
                    # Found one with the same name:
                    name = lb.get("name", None)
                    if name == self.name:
                        # Make sure the region is the same!
                        region = lb.get("region", None)
                        if region is not None:
                            region_slug = region.get("slug", None)
                            if region_slug is not None:
                                if region_slug == self.region:
                                    self.lb = lb
                                    return lb
                                else:
                                    self.module.fail_json(
                                        msg=
                                        "Cannot change load balancer region -- delete and re-create"
                                    )
                            else:
                                self.module.fail_json(
                                    msg=
                                    "Unexpected error; please file a bug: get_by_name"
                                )
                        else:
                            self.module.fail_json(
                                msg=
                                "Unexpected error; please file a bug: get_by_name"
                            )
                if ("links" in json_data and "pages" in json_data["links"]
                        and "next" in json_data["links"]["pages"]):
                    page += 1
                else:
                    page = None
            else:
                self.module.fail_json(
                    msg="Unexpected error; please file a bug: get_by_name")
        return None

    def ensure_active(self):
        """Wait for the existing Load Balancer to be active"""
        end_time = time.monotonic() + self.wait_timeout
        while time.monotonic() < end_time:
            if self.get_by_id():
                status = self.lb.get("status", None)
                if status is not None:
                    if status == "active":
                        return True
                else:
                    self.module.fail_json(
                        msg="Unexpected error; please file a bug: ensure_active"
                    )
            else:
                self.module.fail_json(
                    msg="Load Balancer {0} in {1} not found".format(
                        self.id, self.region))
            time.sleep(min(10, end_time - time.monotonic()))
        self.module.fail_json(
            msg="Timed out waiting for Load Balancer {0} in {1} to be active".
            format(self.id, self.region))

    def is_same(self, found_lb):
        """Checks if exising Load Balancer is the same as requested"""

        check_attributes = [
            "droplet_ids",
            "size",
            "algorithm",
            "forwarding_rules",
            "health_check",
            "sticky_sessions",
            "redirect_http_to_https",
            "enable_proxy_protocol",
            "enable_backend_keepalive",
        ]

        for attribute in check_attributes:
            if self.module.params.get(attribute, None) != found_lb.get(
                    attribute, None):
                # raise Exception(str(self.module.params.get(attribute, None)), str(found_lb.get(attribute, None)))
                self.updates.append(attribute)

        # Check if the VPC needs changing.
        vpc_uuid = self.lb.get("vpc_uuid", None)
        if vpc_uuid is not None:
            if vpc_uuid != found_lb.get("vpc_uuid", None):
                self.updates.append("vpc_uuid")

        if len(self.updates):
            return False
        else:
            return True

    def update(self):
        """Updates a DigitalOcean Load Balancer
        API reference: https://docs.digitalocean.com/reference/api/api-reference/#operation/update_load_balancer
        """
        request_params = dict(self.module.params)
        self.id = self.lb.get("id", None)
        self.name = self.lb.get("name", None)
        if self.id is not None and self.name is not None:
            response = self.rest.put("load_balancers/{0}".format(self.id),
                                     data=request_params)
            json_data = response.json
            if response.status_code == 200:
                self.module.exit_json(
                    changed=True,
                    msg="Load Balancer {0} ({1}) in {2} updated: {3}".format(
                        self.name, self.id, self.region,
                        ", ".join(self.updates)),
                )
            else:
                self.module.fail_json(
                    changed=False,
                    msg="Error updating Load Balancer {0} ({1}) in {2}: {3}".
                    format(self.name, self.id, self.region,
                           json_data["message"]),
                )
        else:
            self.module.fail_json(
                msg="Unexpected error; please file a bug: update")

    def create(self):
        """Creates a DigitalOcean Load Balancer
        API reference: https://docs.digitalocean.com/reference/api/api-reference/#operation/create_load_balancer
        """

        # Check if it exists already (the API docs aren't up-to-date right now,
        # "name" is required and must be unique across the account.
        found_lb = self.get_by_name()
        if found_lb is not None:
            # Do we need to update it?
            if not self.is_same(found_lb):
                self.update()
            else:
                self.module.exit_json(
                    changed=False,
                    msg=
                    "Load Balancer {0} already exists in {1} (and needs no changes)"
                    .format(self.name, self.region),
                )

        # Create it.
        request_params = dict(self.module.params)
        response = self.rest.post("load_balancers", data=request_params)
        json_data = response.json
        if response.status_code != 202:
            self.module.fail_json(
                msg="Failed creating Load Balancer {0} in {1}: {2}".format(
                    self.name, self.region, json_data["message"]))

        # Store it.
        lb = json_data.get("load_balancer", None)
        if lb is None:
            self.module.fail_json(
                msg="Unexpected error; please file a bug: create empty lb")

        self.id = lb.get("id", None)
        if self.id is None:
            self.module.fail_json(
                msg="Unexpected error; please file a bug: create missing id")

        if self.wait:
            self.ensure_active()

        self.module.exit_json(changed=True, data=json_data)

    def delete(self):
        """Deletes a DigitalOcean Load Balancer
        API reference: https://docs.digitalocean.com/reference/api/api-reference/#operation/delete_load_balancer
        """

        lb = self.get_by_name()
        if lb is not None:
            id = lb.get("id", None)
            name = lb.get("name", None)
            if id is None or name is None:
                self.module.fail_json(
                    msg="Unexpected error; please file a bug: delete")
            else:
                lb_region = lb.get("region", None)
                region = lb_region.get("slug", None)
                if region is None:
                    self.module.fail_json(
                        msg="Unexpected error; please file a bug: delete")
                response = self.rest.delete("load_balancers/{0}".format(id))
                json_data = response.json
                if response.status_code == 204:
                    # Response body should be empty
                    self.module.exit_json(
                        changed=True,
                        msg="Load Balancer {0} ({1}) in {2} deleted".format(
                            name, id, region),
                    )
                else:
                    message = json_data.get(
                        "message",
                        "Empty failure message from the DigitalOcean API!")
                    self.module.fail_json(
                        changed=False,
                        msg=
                        "Failed to delete Load Balancer {0} ({1}) in {2}: {3}".
                        format(name, id, region, message),
                    )
        else:
            self.module.fail_json(
                changed=False,
                msg="Load Balancer {0} not found in {1}".format(
                    self.name, self.region),
            )
Exemple #4
0
class DOVPC(object):
    def __init__(self, module):
        self.rest = DigitalOceanHelper(module)
        self.module = module
        # pop the oauth token so we don't include it in the POST data
        self.module.params.pop("oauth_token")
        self.name = module.params.get("name", None)
        self.description = module.params.get("description", None)
        self.default = module.params.get("default", False)
        self.region = module.params.get("region", None)
        self.ip_range = module.params.get("ip_range", None)
        self.vpc_id = module.params.get("vpc_id", None)

    def get_by_name(self):
        page = 1
        while page is not None:
            response = self.rest.get("vpcs?page={0}".format(page))
            json_data = response.json
            if response.status_code == 200:
                for vpc in json_data["vpcs"]:
                    if vpc.get("name", None) == self.name:
                        return vpc
                if ("links" in json_data and "pages" in json_data["links"]
                        and "next" in json_data["links"]["pages"]):
                    page += 1
                else:
                    page = None
        return None

    def create(self):
        if self.module.check_mode:
            return self.module.exit_json(changed=True)

        vpc = self.get_by_name()
        if vpc is not None:  # update
            vpc_id = vpc.get("id", None)
            if vpc_id is not None:
                data = {
                    "name": self.name,
                }
                if self.description is not None:
                    data["description"] = self.description
                if self.default is not False:
                    data["default"] = True
                response = self.rest.put("vpcs/{0}".format(vpc_id), data=data)
                json = response.json
                if response.status_code != 200:
                    self.module.fail_json(
                        msg="Failed to update VPC {0}: {1}".format(
                            self.name, json["message"]))
                else:
                    self.module.exit_json(changed=False, data=json)
            else:
                self.module.fail_json(
                    changed=False, msg="Unexpected error, please file a bug")

        else:  # create
            data = {
                "name": self.name,
                "region": self.region,
            }
            if self.description is not None:
                data["description"] = self.description
            if self.ip_range is not None:
                data["ip_range"] = self.ip_range

            response = self.rest.post("vpcs", data=data)
            status = response.status_code
            json = response.json
            if status == 201:
                self.module.exit_json(changed=True, data=json["vpc"])
            else:
                self.module.fail_json(
                    changed=False,
                    msg="Failed to create VPC: {0}".format(json["message"]),
                )

    def delete(self):
        if self.module.check_mode:
            return self.module.exit_json(changed=True)

        vpc = self.get_by_name()
        if vpc is None:
            self.module.fail_json(
                msg="Unable to find VPC {0}".format(self.name))
        else:
            vpc_id = vpc.get("id", None)
            if vpc_id is not None:
                response = self.rest.delete("vpcs/{0}".format(str(vpc_id)))
                status = response.status_code
                json = response.json
                if status == 204:
                    self.module.exit_json(
                        changed=True,
                        msg="Deleted VPC {0} ({1})".format(self.name, vpc_id),
                    )
                else:
                    json = response.json
                    self.module.fail_json(
                        changed=False,
                        msg="Failed to delete VPC {0} ({1}): {2}".format(
                            self.name, vpc_id, json["message"]),
                    )
Exemple #5
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)
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"]),
            )