| # 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.utils.py3 import httplib |
| from libcloud.utils.misc import merge_valid_keys, get_new_obj |
| from libcloud.common.hostvirtual import HostVirtualResponse |
| from libcloud.common.hostvirtual import HostVirtualConnection |
| from libcloud.compute.drivers.hostvirtual import API_ROOT |
| from libcloud.dns.types import Provider, RecordType |
| from libcloud.dns.types import ZoneDoesNotExistError, RecordDoesNotExistError |
| from libcloud.dns.base import DNSDriver, Zone, Record |
| |
| 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(HostVirtualDNSResponse, self).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(HostVirtualDNSDriver, self).__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 |