Initial commit

master
Dustin 2015-02-15 14:51:39 -06:00
commit 0c238bdb65
7 changed files with 209 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
*.egg-info/
__pycache__/
*.py[co]

19
debug.py Normal file
View File

@ -0,0 +1,19 @@
from werkzeug import serving
import dyns
import os.path
app = dyns.make_app()
try:
serving.run_simple(
hostname='::',
port=8080,
application=app,
static_files={
'/static': os.path.join(os.path.dirname(__file__), 'static'),
},
)
except KeyboardInterrupt:
print('Exiting...')

16
setup.py Normal file
View File

@ -0,0 +1,16 @@
from setuptools import find_packages, setup
setup(
name='DyNS',
version='0.1',
description='Dynamic Nameserver Configuration UI',
author='Dustin C. Hatch',
author_email='dustin@hatch.name',
url='https://bitbucket.org/AdmiralNemo/dyns',
license='GPL-3',
packages=find_packages('src'),
package_dir={'': 'src'},
install_requires=[
'Milla',
],
)

22
src/dyns/__init__.py Normal file
View File

@ -0,0 +1,22 @@
import sqlalchemy
import milla
import milla.util
DEFAULT_CONFIG = {
'sqlalchemy.url': 'postgresql:///named',
}
def make_app(filename=None):
from . import routes, model
app = milla.Application(routes.router)
app.config.update(DEFAULT_CONFIG)
if filename:
app.config.update(milla.util.read_config(filename))
engine = sqlalchemy.engine_from_config(app.config, 'sqlalchemy.')
model.Session.configure(bind=engine)
return app

89
src/dyns/controllers.py Normal file
View File

@ -0,0 +1,89 @@
from . import model
import json
import milla
@milla.allow('GET', 'HEAD', 'POST')
def all_zones(request):
response = request.ResponseClass()
response.content_type = 'application/json'
session = model.Session()
if request.method == 'GET':
zones = map(model.Zone.as_dict, session.query(model.Zone))
json.dump(list(zones), response.body_file)
session.rollback()
elif request.method == 'POST':
data = json.loads(request.text)
zone = model.Zone(**data)
session.add(zone)
session.commit()
response.status_int = 201
return response
@milla.allow('GET', 'HEAD', 'POST', 'PUT', 'DELETE')
def zone(request, name):
response = request.ResponseClass()
response.content_type = 'application/json'
session = model.Session()
zone = session.query(model.Zone).get(name)
if not zone:
raise milla.HTTPNotFound
if request.method == 'GET':
zone_d = zone.as_dict()
zone_d['records'] = list(map(model.Record.as_dict, zone.records))
json.dump(zone_d, response.body_file)
session.rollback()
elif request.method == 'PUT':
data = json.loads(request.text)
for k, v in data.items():
assert k != 'records'
assert hasattr(zone, k)
setattr(zone, k, v)
session.commit()
elif request.method == 'POST':
data = json.loads(request.text)
zone_name = data.pop('zone', zone.name)
assert zone_name == zone.name
record = model.Record(zone=zone_name, **data)
session.add(record)
session.commit()
response.status_int = 201
response.location = request.create_href_full(
'/records/{}'.format(record.id)
)
elif request.method == 'DELETE':
session.delete(zone)
session.commit()
else:
session.rollback()
return response
@milla.allow('GET', 'HEAD', 'PUT', 'DELETE')
def record(request, id):
response = request.ResponseClass()
response.content_type = 'application/json'
session = model.Session()
record = session.query(model.Record).get(id)
if not record:
raise milla.HTTPNotFound
if request.method == 'GET':
json.dump(record.as_dict(), response.body_file)
session.rollback()
elif request.method == 'DELETE':
session.delete(record)
session.commit()
elif request.method == 'PUT':
data =json.loads(request.text)
for k, v in data.items():
assert hasattr(record, k)
setattr(record, k, v)
session.commit()
else:
session.rollback()
return response

52
src/dyns/model.py Normal file
View File

@ -0,0 +1,52 @@
from __future__ import unicode_literals
from sqlalchemy import schema, types, orm
from sqlalchemy.ext import declarative
Base = declarative.declarative_base()
Session = orm.session.sessionmaker()
NoResultFound = orm.exc.NoResultFound
class Serializable(object):
def as_dict(self):
def serialize(obj):
for col in obj.__table__.columns:
value = getattr(obj, col.name)
if isinstance(value, Serializable):
value = dict(serialize(value))
yield (col.name, value)
return dict(serialize(self))
class Zone(Base, Serializable):
__tablename__ = 'zones'
name = schema.Column(types.Unicode(254), primary_key=True)
ttl = schema.Column(types.Integer, nullable=False, default=3600)
source = schema.Column(types.Unicode(254), nullable=False)
contact = schema.Column(types.Unicode(254), nullable=False)
serial = schema.Column(types.Integer, nullable=False, default=1024)
refresh = schema.Column(types.Integer, nullable=False, default=900)
retry = schema.Column(types.Integer, nullable=False, default=600)
expire = schema.Column(types.Integer, nullable=False, default=86400)
minimum = schema.Column(types.Integer, nullable=False, default=3600)
records = orm.relationship('Record', cascade='delete')
class Record(Base, Serializable):
__tablename__ = 'records'
id = schema.Column(types.Integer, autoincrement=True, primary_key=True)
zone = schema.Column(types.Unicode(254), schema.ForeignKey(Zone.name),
nullable=False)
host = schema.Column(types.Unicode(254), nullable=False)
ttl = schema.Column(types.Integer, nullable=False, default=3600)
rdtype = schema.Column(types.Unicode(9), nullable=False)
mx_prio = schema.Column(types.Integer)
data = schema.Column(types.UnicodeText)

8
src/dyns/routes.py Normal file
View File

@ -0,0 +1,8 @@
from . import controllers
from milla.dispatch import routing
router = routing.Router()
router.add_route('/zones/', controllers.all_zones)
router.add_route('/zones/{name}', controllers.zone)
router.add_route('/records/{id}', controllers.record)