Initial commit
commit
0c238bdb65
|
@ -0,0 +1,3 @@
|
||||||
|
*.egg-info/
|
||||||
|
__pycache__/
|
||||||
|
*.py[co]
|
|
@ -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...')
|
|
@ -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',
|
||||||
|
],
|
||||||
|
)
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
|
@ -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)
|
Reference in New Issue