mirror of
https://github.com/github/octodns.git
synced 2024-05-11 05:55:00 +00:00
Revert deletion of transip code
This commit is contained in:
354
octodns/provider/transip.py
Normal file
354
octodns/provider/transip.py
Normal file
@@ -0,0 +1,354 @@
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
from __future__ import absolute_import, division, print_function, \
|
||||
unicode_literals
|
||||
|
||||
from suds import WebFault
|
||||
|
||||
from collections import defaultdict
|
||||
from . import ProviderException
|
||||
from .base import BaseProvider
|
||||
from logging import getLogger
|
||||
from ..record import Record
|
||||
from transip.service.domain import DomainService
|
||||
from transip.service.objects import DnsEntry
|
||||
|
||||
|
||||
class TransipException(ProviderException):
|
||||
pass
|
||||
|
||||
|
||||
class TransipConfigException(TransipException):
|
||||
pass
|
||||
|
||||
|
||||
class TransipNewZoneException(TransipException):
|
||||
pass
|
||||
|
||||
|
||||
class TransipProvider(BaseProvider):
|
||||
'''
|
||||
Transip DNS provider
|
||||
|
||||
transip:
|
||||
class: octodns.provider.transip.TransipProvider
|
||||
# Your Transip account name (required)
|
||||
account: yourname
|
||||
# Path to a private key file (required if key is not used)
|
||||
key_file: /path/to/file
|
||||
# The api key as string (required if key_file is not used)
|
||||
key: |
|
||||
\'''
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
...
|
||||
-----END PRIVATE KEY-----
|
||||
\'''
|
||||
# if both `key_file` and `key` are presented `key_file` is used
|
||||
|
||||
'''
|
||||
SUPPORTS_GEO = False
|
||||
SUPPORTS_DYNAMIC = False
|
||||
SUPPORTS = set(('A', 'AAAA', 'CNAME', 'MX', 'NS', 'SRV', 'SPF', 'TXT',
|
||||
'SSHFP', 'CAA'))
|
||||
# unsupported by OctoDNS: 'TLSA'
|
||||
MIN_TTL = 120
|
||||
TIMEOUT = 15
|
||||
ROOT_RECORD = '@'
|
||||
|
||||
def __init__(self, id, account, key=None, key_file=None, *args, **kwargs):
|
||||
self.log = getLogger('TransipProvider[{}]'.format(id))
|
||||
self.log.debug('__init__: id=%s, account=%s, token=***', id,
|
||||
account)
|
||||
super(TransipProvider, self).__init__(id, *args, **kwargs)
|
||||
|
||||
if key_file is not None:
|
||||
self._client = self._domain_service(account,
|
||||
private_key_file=key_file)
|
||||
elif key is not None:
|
||||
self._client = self._domain_service(account, private_key=key)
|
||||
else:
|
||||
raise TransipConfigException(
|
||||
'Missing `key` or `key_file` parameter in config'
|
||||
)
|
||||
|
||||
self._currentZone = {}
|
||||
|
||||
def _domain_service(self, *args, **kwargs):
|
||||
'This exists only for mocking purposes'
|
||||
return DomainService(*args, **kwargs)
|
||||
|
||||
def populate(self, zone, target=False, lenient=False):
|
||||
|
||||
exists = False
|
||||
self._currentZone = zone
|
||||
self.log.debug('populate: name=%s, target=%s, lenient=%s', zone.name,
|
||||
target, lenient)
|
||||
|
||||
before = len(zone.records)
|
||||
try:
|
||||
zoneInfo = self._client.get_info(zone.name[:-1])
|
||||
except WebFault as e:
|
||||
if e.fault.faultcode == '102' and target is False:
|
||||
# Zone not found in account, and not a target so just
|
||||
# leave an empty zone.
|
||||
return exists
|
||||
elif e.fault.faultcode == '102' and target is True:
|
||||
self.log.warning('populate: Transip can\'t create new zones')
|
||||
raise TransipNewZoneException(
|
||||
('populate: ({}) Transip used ' +
|
||||
'as target for non-existing zone: {}').format(
|
||||
e.fault.faultcode, zone.name))
|
||||
else:
|
||||
self.log.error('populate: (%s) %s ', e.fault.faultcode,
|
||||
e.fault.faultstring)
|
||||
raise e
|
||||
|
||||
self.log.debug('populate: found %s records for zone %s',
|
||||
len(zoneInfo.dnsEntries), zone.name)
|
||||
exists = True
|
||||
if zoneInfo.dnsEntries:
|
||||
values = defaultdict(lambda: defaultdict(list))
|
||||
for record in zoneInfo.dnsEntries:
|
||||
name = zone.hostname_from_fqdn(record['name'])
|
||||
if name == self.ROOT_RECORD:
|
||||
name = ''
|
||||
|
||||
if record['type'] in self.SUPPORTS:
|
||||
values[name][record['type']].append(record)
|
||||
|
||||
for name, types in values.items():
|
||||
for _type, records in types.items():
|
||||
data_for = getattr(self, '_data_for_{}'.format(_type))
|
||||
record = Record.new(zone, name, data_for(_type, records),
|
||||
source=self, lenient=lenient)
|
||||
zone.add_record(record, lenient=lenient)
|
||||
self.log.info('populate: found %s records, exists = %s',
|
||||
len(zone.records) - before, exists)
|
||||
|
||||
self._currentZone = {}
|
||||
return exists
|
||||
|
||||
def _apply(self, plan):
|
||||
desired = plan.desired
|
||||
changes = plan.changes
|
||||
self.log.debug('apply: zone=%s, changes=%d', desired.name,
|
||||
len(changes))
|
||||
|
||||
self._currentZone = plan.desired
|
||||
try:
|
||||
self._client.get_info(plan.desired.name[:-1])
|
||||
except WebFault as e:
|
||||
self.log.exception('_apply: get_info failed')
|
||||
raise e
|
||||
|
||||
_dns_entries = []
|
||||
for record in plan.desired.records:
|
||||
entries_for = getattr(self, '_entries_for_{}'.format(record._type))
|
||||
|
||||
# Root records have '@' as name
|
||||
name = record.name
|
||||
if name == '':
|
||||
name = self.ROOT_RECORD
|
||||
|
||||
_dns_entries.extend(entries_for(name, record))
|
||||
|
||||
try:
|
||||
self._client.set_dns_entries(plan.desired.name[:-1], _dns_entries)
|
||||
except WebFault as e:
|
||||
self.log.warning(('_apply: Set DNS returned ' +
|
||||
'one or more errors: {}').format(
|
||||
e.fault.faultstring))
|
||||
raise TransipException(200, e.fault.faultstring)
|
||||
|
||||
self._currentZone = {}
|
||||
|
||||
def _entries_for_multiple(self, name, record):
|
||||
_entries = []
|
||||
|
||||
for value in record.values:
|
||||
_entries.append(DnsEntry(name, record.ttl, record._type, value))
|
||||
|
||||
return _entries
|
||||
|
||||
def _entries_for_single(self, name, record):
|
||||
|
||||
return [DnsEntry(name, record.ttl, record._type, record.value)]
|
||||
|
||||
_entries_for_A = _entries_for_multiple
|
||||
_entries_for_AAAA = _entries_for_multiple
|
||||
_entries_for_NS = _entries_for_multiple
|
||||
_entries_for_SPF = _entries_for_multiple
|
||||
_entries_for_CNAME = _entries_for_single
|
||||
|
||||
def _entries_for_MX(self, name, record):
|
||||
_entries = []
|
||||
|
||||
for value in record.values:
|
||||
content = "{} {}".format(value.preference, value.exchange)
|
||||
_entries.append(DnsEntry(name, record.ttl, record._type, content))
|
||||
|
||||
return _entries
|
||||
|
||||
def _entries_for_SRV(self, name, record):
|
||||
_entries = []
|
||||
|
||||
for value in record.values:
|
||||
content = "{} {} {} {}".format(value.priority, value.weight,
|
||||
value.port, value.target)
|
||||
_entries.append(DnsEntry(name, record.ttl, record._type, content))
|
||||
|
||||
return _entries
|
||||
|
||||
def _entries_for_SSHFP(self, name, record):
|
||||
_entries = []
|
||||
|
||||
for value in record.values:
|
||||
content = "{} {} {}".format(value.algorithm,
|
||||
value.fingerprint_type,
|
||||
value.fingerprint)
|
||||
_entries.append(DnsEntry(name, record.ttl, record._type, content))
|
||||
|
||||
return _entries
|
||||
|
||||
def _entries_for_CAA(self, name, record):
|
||||
_entries = []
|
||||
|
||||
for value in record.values:
|
||||
content = "{} {} {}".format(value.flags, value.tag,
|
||||
value.value)
|
||||
_entries.append(DnsEntry(name, record.ttl, record._type, content))
|
||||
|
||||
return _entries
|
||||
|
||||
def _entries_for_TXT(self, name, record):
|
||||
_entries = []
|
||||
|
||||
for value in record.values:
|
||||
value = value.replace('\\;', ';')
|
||||
_entries.append(DnsEntry(name, record.ttl, record._type, value))
|
||||
|
||||
return _entries
|
||||
|
||||
def _parse_to_fqdn(self, value):
|
||||
|
||||
# Enforce switch from suds.sax.text.Text to string
|
||||
value = str(value)
|
||||
|
||||
# TransIP allows '@' as value to alias the root record.
|
||||
# this provider won't set an '@' value, but can be an existing record
|
||||
if value == self.ROOT_RECORD:
|
||||
value = self._currentZone.name
|
||||
|
||||
if value[-1] != '.':
|
||||
self.log.debug('parseToFQDN: changed %s to %s', value,
|
||||
'{}.{}'.format(value, self._currentZone.name))
|
||||
value = '{}.{}'.format(value, self._currentZone.name)
|
||||
|
||||
return value
|
||||
|
||||
def _get_lowest_ttl(self, records):
|
||||
_ttl = 100000
|
||||
for record in records:
|
||||
_ttl = min(_ttl, record['expire'])
|
||||
return _ttl
|
||||
|
||||
def _data_for_multiple(self, _type, records):
|
||||
|
||||
_values = []
|
||||
for record in records:
|
||||
# Enforce switch from suds.sax.text.Text to string
|
||||
_values.append(str(record['content']))
|
||||
|
||||
return {
|
||||
'ttl': self._get_lowest_ttl(records),
|
||||
'type': _type,
|
||||
'values': _values
|
||||
}
|
||||
|
||||
_data_for_A = _data_for_multiple
|
||||
_data_for_AAAA = _data_for_multiple
|
||||
_data_for_NS = _data_for_multiple
|
||||
_data_for_SPF = _data_for_multiple
|
||||
|
||||
def _data_for_CNAME(self, _type, records):
|
||||
return {
|
||||
'ttl': records[0]['expire'],
|
||||
'type': _type,
|
||||
'value': self._parse_to_fqdn(records[0]['content'])
|
||||
}
|
||||
|
||||
def _data_for_MX(self, _type, records):
|
||||
_values = []
|
||||
for record in records:
|
||||
preference, exchange = record['content'].split(" ", 1)
|
||||
_values.append({
|
||||
'preference': preference,
|
||||
'exchange': self._parse_to_fqdn(exchange)
|
||||
})
|
||||
return {
|
||||
'ttl': self._get_lowest_ttl(records),
|
||||
'type': _type,
|
||||
'values': _values
|
||||
}
|
||||
|
||||
def _data_for_SRV(self, _type, records):
|
||||
_values = []
|
||||
for record in records:
|
||||
priority, weight, port, target = record['content'].split(' ', 3)
|
||||
_values.append({
|
||||
'port': port,
|
||||
'priority': priority,
|
||||
'target': self._parse_to_fqdn(target),
|
||||
'weight': weight
|
||||
})
|
||||
|
||||
return {
|
||||
'type': _type,
|
||||
'ttl': self._get_lowest_ttl(records),
|
||||
'values': _values
|
||||
}
|
||||
|
||||
def _data_for_SSHFP(self, _type, records):
|
||||
_values = []
|
||||
for record in records:
|
||||
algorithm, fp_type, fingerprint = record['content'].split(' ', 2)
|
||||
_values.append({
|
||||
'algorithm': algorithm,
|
||||
'fingerprint': fingerprint.lower(),
|
||||
'fingerprint_type': fp_type
|
||||
})
|
||||
|
||||
return {
|
||||
'type': _type,
|
||||
'ttl': self._get_lowest_ttl(records),
|
||||
'values': _values
|
||||
}
|
||||
|
||||
def _data_for_CAA(self, _type, records):
|
||||
_values = []
|
||||
for record in records:
|
||||
flags, tag, value = record['content'].split(' ', 2)
|
||||
_values.append({
|
||||
'flags': flags,
|
||||
'tag': tag,
|
||||
'value': value
|
||||
})
|
||||
|
||||
return {
|
||||
'type': _type,
|
||||
'ttl': self._get_lowest_ttl(records),
|
||||
'values': _values
|
||||
}
|
||||
|
||||
def _data_for_TXT(self, _type, records):
|
||||
_values = []
|
||||
for record in records:
|
||||
_values.append(record['content'].replace(';', '\\;'))
|
||||
|
||||
return {
|
||||
'type': _type,
|
||||
'ttl': self._get_lowest_ttl(records),
|
||||
'values': _values
|
||||
}
|
||||
Reference in New Issue
Block a user