mirror of
https://github.com/github/octodns.git
synced 2024-05-11 05:55:00 +00:00
117 lines
4.7 KiB
Python
117 lines
4.7 KiB
Python
#
|
|
#
|
|
#
|
|
|
|
from __future__ import absolute_import, division, print_function, \
|
|
unicode_literals
|
|
|
|
from os import makedirs, path
|
|
from os.path import isdir
|
|
import logging
|
|
|
|
from .base import BaseProvider
|
|
|
|
|
|
class EtcHostsProvider(BaseProvider):
|
|
'''
|
|
Provider that creates a "best effort" static/emergency content that can be
|
|
used in /etc/hosts to resolve things. A, AAAA records are supported and
|
|
ALIAS and CNAME records will be included when they can be mapped within the
|
|
zone.
|
|
|
|
config:
|
|
class: octodns.provider.etc_hosts.EtcHostsProvider
|
|
# The output director for the hosts file <zone>.hosts
|
|
directory: ./hosts
|
|
'''
|
|
SUPPORTS_GEO = False
|
|
SUPPORTS_DYNAMIC = False
|
|
SUPPORTS = set(('A', 'AAAA', 'ALIAS', 'CNAME'))
|
|
|
|
def __init__(self, id, directory, *args, **kwargs):
|
|
self.log = logging.getLogger('EtcHostsProvider[{}]'.format(id))
|
|
self.log.debug('__init__: id=%s, directory=%s', id, directory)
|
|
super(EtcHostsProvider, self).__init__(id, *args, **kwargs)
|
|
self.directory = directory
|
|
|
|
def populate(self, zone, target=False, lenient=False):
|
|
self.log.debug('populate: name=%s, target=%s, lenient=%s', zone.name,
|
|
target, lenient)
|
|
|
|
# We never act as a source, at least for now, if/when we do we still
|
|
# need to noop `if target`
|
|
return False
|
|
|
|
def _apply(self, plan):
|
|
desired = plan.desired
|
|
changes = plan.changes
|
|
self.log.debug('_apply: zone=%s, len(changes)=%d', desired.name,
|
|
len(changes))
|
|
cnames = {}
|
|
values = {}
|
|
for record in sorted([c.new for c in changes]):
|
|
# Since we don't have existing we'll only see creates
|
|
fqdn = record.fqdn[:-1]
|
|
if record._type in ('ALIAS', 'CNAME'):
|
|
# Store cnames so we can try and look them up in a minute
|
|
cnames[fqdn] = record.value[:-1]
|
|
elif record._type == 'AAAA' and fqdn in values:
|
|
# We'll prefer A over AAAA, skipping rather than replacing an
|
|
# existing A
|
|
pass
|
|
else:
|
|
# If we're here it's and A or AAAA and we want to record it's
|
|
# value (maybe replacing if it's an A and we have a AAAA
|
|
values[fqdn] = record.values[0]
|
|
|
|
if not isdir(self.directory):
|
|
makedirs(self.directory)
|
|
|
|
filename = '{}hosts'.format(path.join(self.directory, desired.name))
|
|
self.log.info('_apply: filename=%s', filename)
|
|
with open(filename, 'w') as fh:
|
|
fh.write('##################################################\n')
|
|
fh.write('# octoDNS {} {}\n'.format(self.id, desired.name))
|
|
fh.write('##################################################\n\n')
|
|
if values:
|
|
fh.write('## A & AAAA\n\n')
|
|
for fqdn, value in sorted(values.items()):
|
|
if fqdn[0] == '*':
|
|
fh.write('# ')
|
|
fh.write('{}\t{}\n\n'.format(value, fqdn))
|
|
|
|
if cnames:
|
|
fh.write('\n## CNAME (mapped)\n\n')
|
|
for fqdn, value in sorted(cnames.items()):
|
|
# Print out a comment of the first level
|
|
fh.write('# {} -> {}\n'.format(fqdn, value))
|
|
seen = set()
|
|
while True:
|
|
seen.add(value)
|
|
try:
|
|
value = values[value]
|
|
# If we're here we've found the target, print it
|
|
# and break the loop
|
|
fh.write('{}\t{}\n'.format(value, fqdn))
|
|
break
|
|
except KeyError:
|
|
# Try and step down one level
|
|
orig = value
|
|
value = cnames.get(value, None)
|
|
# Print out this step
|
|
if value:
|
|
if value in seen:
|
|
# We'd loop here, break it
|
|
fh.write('# {} -> {} **loop**\n'
|
|
.format(orig, value))
|
|
break
|
|
else:
|
|
fh.write('# {} -> {}\n'
|
|
.format(orig, value))
|
|
else:
|
|
# Don't have anywhere else to go
|
|
fh.write('# {} -> **unknown**\n'.format(orig))
|
|
break
|
|
|
|
fh.write('\n')
|