| # Licensed to the Apache Software Foundation (ASF) under one or more |
| # contributor license agreements. See the NOTICE file distributed with |
| # this work for additional information regarding copyright ownership. |
| # The ASF licenses this file to You under the Apache License, Version 2.0 |
| # (the "License"); you may not use this file except in compliance with |
| # the License.You may obtain a copy of the License at |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| __all__ = ["HostVirtualDNSDriver"] |
| |
| try: |
| import simplejson as json |
| except Exception: |
| import json |
| |
| from libcloud.dns.base import Zone, Record, DNSDriver |
| from libcloud.dns.types import Provider, RecordType, ZoneDoesNotExistError, RecordDoesNotExistError |
| from libcloud.utils.py3 import httplib |
| from libcloud.utils.misc import get_new_obj, merge_valid_keys |
| from libcloud.common.hostvirtual import HostVirtualResponse, HostVirtualConnection |
| from libcloud.compute.drivers.hostvirtual import API_ROOT |
| |
| VALID_RECORD_EXTRA_PARAMS = ["prio", "ttl"] |
| |
| |
| class HostVirtualDNSResponse(HostVirtualResponse): |
| def parse_error(self): |
| context = self.connection.context |
| status = int(self.status) |
| |
| if status == httplib.NOT_FOUND: |
| if context["resource"] == "zone": |
| raise ZoneDoesNotExistError( |
| value=self.parse_body()["error"]["message"], |
| driver=self, |
| zone_id=context["id"], |
| ) |
| elif context["resource"] == "record": |
| raise RecordDoesNotExistError( |
| value=self.parse_body()["error"]["message"], |
| driver=self, |
| record_id=context["id"], |
| ) |
| |
| super().parse_error() |
| return self.body |
| |
| |
| class HostVirtualDNSConnection(HostVirtualConnection): |
| responseCls = HostVirtualDNSResponse |
| |
| |
| class HostVirtualDNSDriver(DNSDriver): |
| type = Provider.HOSTVIRTUAL |
| name = "Host Virtual DNS" |
| website = "https://www.hostvirtual.com/" |
| connectionCls = HostVirtualDNSConnection |
| |
| RECORD_TYPE_MAP = { |
| RecordType.A: "A", |
| RecordType.AAAA: "AAAA", |
| RecordType.CNAME: "CNAME", |
| RecordType.MX: "MX", |
| RecordType.NS: "SPF", |
| RecordType.SRV: "SRV", |
| RecordType.TXT: "TXT", |
| } |
| |
| def __init__(self, key, secure=True, host=None, port=None): |
| super().__init__(key=key, secure=secure, host=host, port=port) |
| |
| def _to_zones(self, items): |
| zones = [] |
| for item in items: |
| zones.append(self._to_zone(item)) |
| return zones |
| |
| def _to_zone(self, item): |
| extra = {} |
| if "records" in item: |
| extra["records"] = item["records"] |
| if item["type"] == "NATIVE": |
| item["type"] = "master" |
| zone = Zone( |
| id=item["id"], |
| domain=item["name"], |
| type=item["type"], |
| ttl=item["ttl"], |
| driver=self, |
| extra=extra, |
| ) |
| return zone |
| |
| def _to_records(self, items, zone=None): |
| records = [] |
| |
| for item in items: |
| records.append(self._to_record(item=item, zone=zone)) |
| return records |
| |
| def _to_record(self, item, zone=None): |
| extra = {"ttl": item["ttl"]} |
| type = self._string_to_record_type(item["type"]) |
| name = item["name"][: -len(zone.domain) - 1] |
| record = Record( |
| id=item["id"], |
| name=name, |
| type=type, |
| data=item["content"], |
| zone=zone, |
| driver=self, |
| ttl=item["ttl"], |
| extra=extra, |
| ) |
| return record |
| |
| def list_zones(self): |
| result = self.connection.request(API_ROOT + "/dns/zones/").object |
| zones = self._to_zones(result) |
| return zones |
| |
| def list_records(self, zone): |
| params = {"id": zone.id} |
| self.connection.set_context({"resource": "zone", "id": zone.id}) |
| try: |
| result = self.connection.request(API_ROOT + "/dns/records/", params=params).object |
| except ZoneDoesNotExistError as e: |
| if "Not Found: No Records Found" in e.value: |
| return [] |
| raise e |
| records = self._to_records(items=result, zone=zone) |
| return records |
| |
| def get_zone(self, zone_id): |
| params = {"id": zone_id} |
| self.connection.set_context({"resource": "zone", "id": zone_id}) |
| result = self.connection.request(API_ROOT + "/dns/zone/", params=params).object |
| if "id" not in result: |
| raise ZoneDoesNotExistError(value="", driver=self, zone_id=zone_id) |
| zone = self._to_zone(result) |
| return zone |
| |
| def get_record(self, zone_id, record_id): |
| zone = self.get_zone(zone_id=zone_id) |
| params = {"id": record_id} |
| self.connection.set_context({"resource": "record", "id": record_id}) |
| result = self.connection.request(API_ROOT + "/dns/record/", params=params).object |
| if "id" not in result: |
| raise RecordDoesNotExistError(value="", driver=self, record_id=record_id) |
| record = self._to_record(item=result, zone=zone) |
| return record |
| |
| def delete_zone(self, zone): |
| params = {"id": zone.id} |
| self.connection.set_context({"resource": "zone", "id": zone.id}) |
| result = self.connection.request( |
| API_ROOT + "/dns/zone/", params=params, method="DELETE" |
| ).object |
| return bool(result) |
| |
| def delete_record(self, record): |
| params = {"id": record.id} |
| self.connection.set_context({"resource": "record", "id": record.id}) |
| result = self.connection.request( |
| API_ROOT + "/dns/record/", params=params, method="DELETE" |
| ).object |
| |
| return bool(result) |
| |
| def create_zone(self, domain, type="NATIVE", ttl=None, extra=None): |
| if type == "master": |
| type = "NATIVE" |
| elif type == "slave": |
| type = "SLAVE" |
| params = {"name": domain, "type": type, "ttl": ttl} |
| result = self.connection.request( |
| API_ROOT + "/dns/zone/", data=json.dumps(params), method="POST" |
| ).object |
| extra = {"soa": result["soa"], "ns": result["ns"]} |
| zone = Zone(id=result["id"], domain=domain, type=type, ttl=ttl, extra=extra, driver=self) |
| return zone |
| |
| def update_zone(self, zone, domain=None, type=None, ttl=None, extra=None): |
| params = {"id": zone.id} |
| if domain: |
| params["name"] = domain |
| if type: |
| params["type"] = type |
| self.connection.set_context({"resource": "zone", "id": zone.id}) |
| self.connection.request( |
| API_ROOT + "/dns/zone/", data=json.dumps(params), method="PUT" |
| ).object |
| updated_zone = get_new_obj( |
| obj=zone, |
| klass=Zone, |
| attributes={"domain": domain, "type": type, "ttl": ttl, "extra": extra}, |
| ) |
| return updated_zone |
| |
| def create_record(self, name, zone, type, data, extra=None): |
| params = { |
| "name": name, |
| "type": self.RECORD_TYPE_MAP[type], |
| "domain_id": zone.id, |
| "content": data, |
| } |
| merged = merge_valid_keys(params=params, valid_keys=VALID_RECORD_EXTRA_PARAMS, extra=extra) |
| self.connection.set_context({"resource": "zone", "id": zone.id}) |
| result = self.connection.request( |
| API_ROOT + "/dns/record/", data=json.dumps(params), method="POST" |
| ).object |
| record = Record( |
| id=result["id"], |
| name=name, |
| type=type, |
| data=data, |
| extra=merged, |
| zone=zone, |
| ttl=merged.get("ttl", None), |
| driver=self, |
| ) |
| return record |
| |
| def update_record(self, record, name=None, type=None, data=None, extra=None): |
| params = {"domain_id": record.zone.id, "record_id": record.id} |
| if name: |
| params["name"] = name |
| if data: |
| params["content"] = data |
| if type is not None: |
| params["type"] = self.RECORD_TYPE_MAP[type] |
| merged = merge_valid_keys( |
| params=params, valid_keys=VALID_RECORD_EXTRA_PARAMS, extra=extra |
| ) |
| self.connection.set_context({"resource": "record", "id": record.id}) |
| self.connection.request( |
| API_ROOT + "/dns/record/", data=json.dumps(params), method="PUT" |
| ).object |
| updated_record = get_new_obj( |
| obj=record, |
| klass=Record, |
| attributes={"name": name, "data": data, "type": type, "extra": merged}, |
| ) |
| return updated_record |