Skip to content

Commit

Permalink
Initial extraction of EtcHostsProvider from octoDNS core
Browse files Browse the repository at this point in the history
  • Loading branch information
ross committed Jan 9, 2022
1 parent 84f3757 commit ed48524
Show file tree
Hide file tree
Showing 7 changed files with 323 additions and 21 deletions.
6 changes: 2 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
## TODO: v0.0.1 - 20??-??-?? - Moving
## v0.0.1 - 2022-01-09 - Moving

#### Nothworthy Changes

* Initial extraction of EtcHostsProvider from octoDNS core

TODO: anything else

#### Stuff

TODO: anything else
Nothing
13 changes: 5 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
TODO: Review this README and add or modify as necessary.

## /etc/hosts provider for octoDNS

An [octoDNS](https://github.com/octodns/octodns/) provider that targets [/etc/hosts](https://github.com/octodns/octodns-etchosts/).
An [octoDNS](https://github.com/octodns/octodns/) 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.

### Installation

Expand Down Expand Up @@ -38,21 +36,20 @@ octodns_etchosts==0.0.1
providers:
etchosts:
class: octodns_etchosts.EtcHostsProvider
# TODO
# The output director for the hosts file <zone>.hosts
directory: ./hosts
```
### Support Information
#### Records
TODO: All octoDNS record types are supported.
EtcHostsProvider supports A and AAAA, and has partial support for tracing ALIAS and CNAME records when they can be resolved within the zone.
#### Dynamic
TODO: EtcHostsProvider does not support dynamic records.
EtcHostsProvider does not support dynamic records.
### Developement
See the [/script/](/script/) directory for some tools to help with the development process. They generally follow the [Script to rule them all](https://github.com/github/scripts-to-rule-them-all) pattern. Most useful is `./script/bootstrap` which will create a venv and install both the runtime and development related requirements. It will also hook up a pre-commit hook that covers most of what's run by CI.

TODO: any provider specific setup, a docker compose to run things locally etc?
97 changes: 94 additions & 3 deletions octodns_etchosts/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,102 @@
#
#

from os import makedirs, path
from os.path import isdir
from logging import getLogger

from octodns.provider.base import BaseProvider

__VERSION__ = '0.0.1'


class EtcHostsProvider(BaseProvider)
# TODO: implement things
pass
class EtcHostsProvider(BaseProvider):
SUPPORTS_GEO = False
SUPPORTS_DYNAMIC = False
SUPPORTS = set(('A', 'AAAA', 'ALIAS', 'CNAME'))

def __init__(self, id, directory, *args, **kwargs):
self.log = getLogger(f'EtcHostsProvider[{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)

filepath = path.join(self.directory, desired.name)
filename = f'{filepath}hosts'
self.log.info('_apply: filename=%s', filename)
with open(filename, 'w') as fh:
fh.write('##################################################\n')
fh.write(f'# octoDNS {self.id} {desired.name}\n')
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(f'{value}\t{fqdn}\n\n')

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(f'# {fqdn} -> {value}\n')
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(f'{value}\t{fqdn}\n')
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(f'# {orig} -> {value} **loop**\n')
break
else:
fh.write(f'# {orig} -> {value}\n')
else:
# Don't have anywhere else to go
fh.write(f'# {orig} -> **unknown**\n')
break

fh.write('\n')
15 changes: 14 additions & 1 deletion requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
Pygments==2.11.2
bleach==4.1.0
build==0.7.0
colorama==0.4.4
docutils==0.18.1
importlib-metadata==4.10.0
keyring==23.5.0
nose-no-network==0.0.1
nose-timer==1.0.1
nose==1.3.7
pep517==0.12.0
pkginfo==1.8.2
pycodestyle==2.6.0
pyflakes==2.2.0
readme-renderer==32.0
requests-toolbelt==0.9.1
rfc3986==1.5.0
tqdm==4.62.3
twine==3.7.1
# TODO: add any other test-time requirements, these should be exhaustive and pinned
webencodings==0.5.1
zipp==3.7.0
28 changes: 27 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,28 @@
PyYAML==6.0
attrs==21.4.0
certifi==2021.10.8
charset-normalizer==2.0.10
coverage==6.2
dnspython==2.1.0
fqdn==1.5.1
idna==3.3
iniconfig==1.1.1
natsort==8.0.2
octodns==0.9.14
# TODO: add any other runtime requirements, these should be exhaustive and pinned
packaging==21.3
pluggy==1.0.0
pprintpp==0.4.0
py==1.11.0
pycountry-convert==0.7.2
pycountry==20.7.3
pyparsing==3.0.6
pytest-cov==3.0.0
pytest-mock==3.6.1
pytest==6.2.5
python-dateutil==2.8.2
repoze.lru==0.7
requests==2.27.1
six==1.16.0
toml==0.10.2
tomli==2.0.0
urllib3==1.26.8
3 changes: 1 addition & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,11 @@ def version():
name='octodns-etchosts',
packages=('octodns_etchosts',),
python_requires='>=3.6',
install_requires=('octodns>=0.9.14', 'TODO: other requirements'),
install_requires=('octodns>=0.9.14'),
url='https://github.com/octodns/octodns-etchosts',
version=version(),
tests_require=(
'nose',
'nose-no-network',
'TODO: other test-time requirements'
),
)
Loading

0 comments on commit ed48524

Please sign in to comment.