armv7 build

This commit is contained in:
Fergal Moran
2020-11-14 21:38:13 +00:00
parent 04ffc26647
commit 7d242820cd
70 changed files with 970 additions and 183 deletions

View File

@@ -144,3 +144,4 @@ cython_debug/
app.db
bitchmin.db
.idea/
app/dashboard.cfg

View File

@@ -1 +1 @@
BitchMin
bitchmin-api

View File

@@ -0,0 +1,27 @@
FROM python:3.7-alpine
COPY ./requirements.txt /requirements.txt
RUN apk update && \
apk add --no-cache --virtual build-deps gcc python3-dev musl-dev bind-tools lapack libstdc++ && \
apk --no-cache add --virtual .builddeps g++ gcc gfortran musl-dev lapack-dev && \
apk add postgresql-dev && \
pip install scipy && apk del .builddeps && rm -rf /root/.cache && \
pip install -r requirements.txt
RUN pip install -r requirements.txt
RUN pip install gunicorn
COPY app /app
COPY migrations /migrations
COPY server.py boot.sh ./
RUN chmod +x boot.sh
ENV FLASK_ENV production
ENV FLASK_APP server.py
RUN chown -R bitchmin:bitchmin ./
USER bitchmin
EXPOSE 5000
ENTRYPOINT ["./boot.sh"]

View File

@@ -1,30 +1,22 @@
FROM python:3.7.4-alpine
FROM python:3.7
RUN adduser -D bitchmin
RUN mkdir /bitchmin
WORKDIR /bitchmin
COPY ./requirements.txt requirements.txt
COPY ./requirements.txt /requirements.txt
RUN apt update && \
apt install libblas-dev && \
pip install gunicorn
RUN pip install -r requirements.txt
RUN apk update && \
apk add --no-cache --virtual build-deps gcc python3-dev musl-dev bind-tools && \
apk add postgresql-dev && \
pip install -r requirements.txt
RUN python3.7 -m venv venv
RUN venv/bin/pip install -r requirements.txt
RUN venv/bin/pip install gunicorn
COPY app app
COPY migrations migrations
COPY app /app
COPY migrations /migrations
COPY server.py boot.sh ./
RUN chmod +x boot.sh
ENV FLASK_ENV production
ENV FLASK_APP server.py
RUN chown -R bitchmin:bitchmin ./
USER bitchmin
#RUN chown -R bitchmin:bitchmin ./
#USER bitchmin
EXPOSE 5000
ENTRYPOINT ["./boot.sh"]

View File

@@ -9,8 +9,9 @@ from flask_jwt_extended import JWTManager
from flask_mail import Mail
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy
import flask_monitoringdashboard as dashboard
from app.config import Config
from app.conf import load_config
from app.utils.encoders import IPAddressFieldEncoder
logger = logging.getLogger(__name__)
@@ -18,19 +19,20 @@ logger = logging.getLogger(__name__)
db = SQLAlchemy()
jwt = JWTManager()
mail = Mail()
migrate = Migrate()
CELERY_TASK_LIST = [
'app.tasks.hosts',
]
import flask_monitoringdashboard as dashboard
def create_app(app_name='bitchmin', config_class=Config):
def create_app(app_name='bitchmin'):
logger.info('Creating app {}'.format(app_name))
app = Flask(app_name)
app.config.from_object(config_class)
app.config.from_object(
load_config(os.environ.get('FLASK_ENV'))
)
logger.info('Creating database {}'.format(app.config['SQLALCHEMY_DATABASE_URI']))
db.init_app(app)
@@ -69,6 +71,8 @@ def create_app(app_name='bitchmin', config_class=Config):
app.logger.addHandler(file_handler)
app.json_encoder = IPAddressFieldEncoder
dashboard.bind(app)
dashboard.config.init_from(envvar='FLASK_MONITORING_DASHBOARD_CONFIG')
db.init_app(app)
@@ -95,6 +99,3 @@ def create_celery_app(app=None):
celery.Task = ContextTask
return celery
celery = create_celery_app()

View File

@@ -1,41 +1,38 @@
import logging
from datetime import timedelta
from flask import jsonify, request
from flask import jsonify, request, current_app
from flask_jwt_extended import (
create_access_token,
jwt_refresh_token_required, create_refresh_token,
get_current_user
jwt_refresh_token_required, get_current_user
)
from sqlalchemy.orm.exc import NoResultFound
from app import db, jwt
from app.api import api
from app.config import Config
from app.models.user import User
logger = logging.getLogger(__name__)
def __create_token(user_id):
expiry = timedelta(days=14) if Config.ISDEV else timedelta(minutes=15)
return create_access_token(
identity=user_id,
expires_delta=expiry,
fresh=True)
@jwt.user_loader_callback_loader
def user_loader_callback(identity):
return User.by_id(identity)
return db.session.query(User).get(identity)
@api.route('/auth/register/', methods=('POST',))
def register():
data = request.get_json()
user = User(**data)
db.session.add(user)
db.session.commit()
return jsonify(user.to_dict()), 201
try:
db.session.query(User).filter_by(email=user.email).one()
return jsonify({
'status': 'error',
'payload': 'User with email {} already exists.'.format(user.email)
}), 409
except NoResultFound:
db.session.add(user)
db.session.commit()
return jsonify(user.to_dict()), 201
@api.route('/auth/login/', methods=('POST',))
@@ -45,8 +42,8 @@ def login():
if not user:
return jsonify({'message': 'Invalid credentials', 'authenticated': False}), 401
access_token = __create_token(user.id)
refresh_token = create_refresh_token(user.id)
access_token = user.create_token(timedelta(days=14) if current_app.config['ISDEV'] else timedelta(minutes=15))
refresh_token = user.create_refresh_token()
return jsonify({
'accessToken': access_token,
@@ -61,8 +58,8 @@ def login():
@jwt_refresh_token_required
def token_refresh():
user = get_current_user()
access_token = __create_token(user.id)
refresh_token = create_refresh_token(user.id)
access_token = user.create_token()
refresh_token = user.create_refresh_token()
return jsonify({
'accessToken': access_token,

View File

@@ -1,3 +1,4 @@
import json
import logging
import os
@@ -17,9 +18,9 @@ from app.utils.iputils import is_valid_ip, is_valid_hostname
logger = logging.getLogger(__name__)
@api.route('/dns/hosts')
def get_hosts():
return DnsZone.get_delete_put_post()
@api.route('/dns/zones')
def get_zones():
return jsonify([zone.to_dict() for zone in db.session.query(DnsZone).all()]), 200
@api.route('/dns/refresh', methods=['POST'])

View File

@@ -0,0 +1,18 @@
import os
def load_config(mode=os.environ.get('FLASK_ENV')):
"""Load config."""
try:
if mode == 'production':
from .production import ProductionConfig
return ProductionConfig
elif mode == 'testing':
from .testing import TestingConfig
return TestingConfig
else:
from .development import DevelopmentConfig
return DevelopmentConfig
except ImportError:
from .config import Config
return Config

View File

@@ -4,17 +4,14 @@ from datetime import timedelta
from dotenv import load_dotenv
basedir = os.path.abspath(os.path.dirname(__file__))
load_dotenv(os.path.join(basedir, '../.env'))
load_dotenv(os.path.join(basedir, '../../.env'))
ISDEV = os.getenv('FLASK_ENV') == 'development'
DEBUG_CONNECTION = 'postgresql+psycopg2://bitchmin:bitchmin@localhost/bitchmin'
# DEBUG_CONNECTION = 'sqlite:///' + os.path.join(basedir, '../app.db')
class Config(object):
ISDEV = ISDEV
SECRET_KEY = os.getenv('SECRET_KEY') or 'you-will-never-guess'
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or DEBUG_CONNECTION
SQLALCHEMY_TRACK_MODIFICATIONS = False
LOG_TO_STDOUT = os.getenv('LOG_TO_STDOUT')
@@ -37,21 +34,3 @@ class Config(object):
'schedule': timedelta(minutes=15)
}
}
class ProductionConfig(Config):
DEBUG = False
class StagingConfig(Config):
DEVELOPMENT = True
DEBUG = True
class DevelopmentConfig(Config):
DEVELOPMENT = True
DEBUG = True
class TestingConfig(Config):
TESTING = True

View File

@@ -0,0 +1,6 @@
from .config import Config
class DevelopmentConfig(Config):
DEBUG = True
SQLALCHEMY_DATABASE_URI = 'postgresql+psycopg2://bitchmin:bitchmin@localhost/bitchmin'

View File

View File

@@ -0,0 +1,7 @@
import os
from .config import Config
class ProductionConfig(Config):
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL')

View File

@@ -0,0 +1,6 @@
from .config import Config
class TestingConfig(Config):
TESTING = True
SQLALCHEMY_DATABASE_URI = 'sqlite:////tmp/test.db'

View File

@@ -24,6 +24,12 @@ class DnsNameServer(db.Model, _BaseModelMixin, FlaskSerializeMixin):
self.host = host
self.ip = ip
def to_dict(self):
return {
'host': self.host,
'ip': self.ip
}
@dataclass
class DnsZone(db.Model, _BaseModelMixin, FlaskSerializeMixin):
@@ -48,6 +54,19 @@ class DnsZone(db.Model, _BaseModelMixin, FlaskSerializeMixin):
return self._create_serial()
def to_dict(self):
ret_data = {
'name': self.zone_name,
'serial': self.serial,
'admin': self.admin,
'nameservers': [ns.to_dict() for ns in self.nameservers],
'hosts': [host.to_dict() for host in self.hosts]
}
return ret_data
# TODO: FUCK!!
admin = 'Ferg@lMoran.me'
id = db.Column(db.Integer, primary_key=True)
zone_name = db.Column(db.String(253), unique=True, nullable=False)
serial = db.Column(db.Integer, default=_create_serial)
@@ -66,15 +85,24 @@ class DnsHost(db.Model, _BaseModelMixin, FlaskSerializeMixin):
host = db.Column(db.String(255), unique=True, nullable=False)
ip = db.Column(IPAddressType(255), nullable=False)
type = db.Column(db.String(10), nullable=False)
ttl = db.Column(db.Integer(), nullable=False)
def __init__(self, zone, host, ip):
def __init__(self, zone, host, ip, ttl=30, record_type='A'):
self.zone = zone
self.host = host
self.ip = ip
self.ttl = ttl
self.type = record_type
pass
def to_dict(self):
return dict(id=self.id, host=self.host, ip=self.ip)
return {
'name': self.host,
'type': self.type,
'ttl': self.ttl,
'ip': self.ip,
}
# @event.listens_for(DnsZone.hosts, 'append')

View File

@@ -1,6 +1,7 @@
from dataclasses import dataclass
from datetime import datetime
from datetime import datetime, timedelta
from flask_jwt_extended import create_access_token, create_refresh_token
from sqlalchemy.orm import relationship
from werkzeug.security import check_password_hash, generate_password_hash
@@ -49,6 +50,15 @@ class User(db.Model, _BaseModelMixin):
return user
def create_refresh_token(self):
return create_refresh_token(self.id)
def create_token(self, expiry=timedelta(days=14)):
return create_access_token(
identity=self.id,
expires_delta=expiry,
fresh=True)
@classmethod
def by_id(cls, user_id):
return cls.query.get(user_id)

View File

@@ -0,0 +1,32 @@
import pytest
from app import create_app
from app import db as _db
from seeder import DbSeeder
@pytest.fixture()
def app(request):
app = create_app()
ctx = app.app_context()
ctx.push()
def teardown():
ctx.pop()
request.addfinalizer(teardown)
return app
@pytest.fixture()
def db(app, request):
_db.app = app
seeder = DbSeeder(_db)
seeder.seed()
def teardown():
seeder.teardown()
request.addfinalizer(teardown)
return _db

View File

@@ -0,0 +1,12 @@
from app import create_app
from seeder import DbSeeder
if __name__ == '__main__':
app = create_app()
ctx = app.app_context()
ctx.push()
from app import db
db.app = app
DbSeeder(db).seed()

View File

@@ -1,19 +1,10 @@
import unittest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.pool import StaticPool
from app import db
from app.models import DnsZone, User, DnsHost, DnsNameServer
class Scaffolder(object):
engine = create_engine(
'postgresql+psycopg2://bitchmin:bitchmin@localhost/bitchmin',
poolclass=StaticPool)
Session = sessionmaker(bind=engine)
session = Session()
class DbSeeder(object):
def __init__(self, database):
self._db = database
def _create_user(self):
user = User(
@@ -21,11 +12,11 @@ class Scaffolder(object):
'Fergal Moran',
'topsecret'
)
self.session.add(user)
self._db.session.add(user)
def _create_zone(self):
zone = DnsZone('bitchmints.com')
self.session.add(zone)
self._db.session.add(zone)
return zone
def _create_nameservers(self, zone):
@@ -36,7 +27,7 @@ class Scaffolder(object):
'10.1.1.10{}'.format(i)
)
self.session.add(ns)
self._db.session.add(ns)
def _create_hosts(self, zone):
for i in range(1, 11):
@@ -45,24 +36,20 @@ class Scaffolder(object):
'host-{}'.format(i),
'10.1.1.{}'.format(i)
)
self.session.add(host)
self._db.session.add(host)
def scaffold(self):
db.metadata.drop_all(self.engine)
db.metadata.create_all(self.engine)
def seed(self):
self._db.metadata.drop_all(self._db.engine)
self._db.metadata.create_all(self._db.engine)
self._create_user()
z = self._create_zone()
self._create_nameservers(z)
self._create_hosts(z)
self.session.commit()
self.session.flush()
self._db.session.commit()
self._db.session.flush()
def teardown(self):
db.metadata.drop_all(self.engine)
self.engine.dispose()
if __name__ == '__main__':
Scaffolder().scaffold()
self._db.metadata.drop_all(self._db.engine)
self._db.engine.dispose()

View File

@@ -0,0 +1,51 @@
from datetime import timedelta
from app.models import User
class TestAuthRoutes:
def test_no_duplicate_registration(self, db, app) -> None:
client = app.test_client()
request = {
'email': 'fergal.moran@gmail.com',
'full_name': 'Fergal Moran',
'password': 'topsecret'
}
rv = client.post(f"/auth/register/", json=request)
assert "409" in rv.status
def test_registration(self, db, app) -> None:
client = app.test_client()
request = {
'email': 'fergal.moran+testuser@gmail.com',
'full_name': 'Fergal Moran Test User',
'password': 'topsecret2'
}
rv = client.post(f"/auth/register/", json=request)
assert "201" in rv.status
def test_login_fails(self, db, app) -> None:
client = app.test_client()
request = {
'email': 'fergal.moran@gmail.com',
'password': 'asdasda'
}
rv = client.post(f"/auth/login/", json=request)
assert "401" in rv.status
def test_login_succeeds(self, db, app) -> None:
client = app.test_client()
request = {
'email': 'fergal.moran@gmail.com',
'password': 'topsecret'
}
rv = client.post(f"/auth/login/", json=request)
assert "200" in rv.status

View File

@@ -1,37 +1,22 @@
import uuid
import pytest
from sqlalchemy import func
from app.models import User, DnsZone, DnsNameServer, DnsHost
from scaffolder import Scaffolder
@pytest.fixture(scope="function") # or "module" (to teardown at a module level)
def db():
scaffolder = Scaffolder()
scaffolder.teardown()
scaffolder.scaffold()
yield scaffolder.session
scaffolder.teardown()
class TestDatabaseUpdate:
def test_load(self, db) -> None:
assert db.query(User).count() == 1
assert db.query(DnsZone).count() == 1
assert db.query(DnsNameServer).count() == 2
assert db.query(DnsHost).count() == 10
assert db.session.query(User).count() == 1
assert db.session.query(DnsZone).count() == 1
assert db.session.query(DnsNameServer).count() == 2
assert db.session.query(DnsHost).count() == 10
def test_serial_on_create(self, db) -> None:
zone = db.query(DnsZone).first()
zone = db.session.query(DnsZone).first()
assert len(str(zone.serial)) == 10
assert zone.get_serial_increment() == 10
def test_serial_on_add_host(self, db) -> None:
zone = db.query(DnsZone).first()
zone = db.session.query(DnsZone).first()
serial = zone.get_serial_increment()
DnsHost(
@@ -39,7 +24,7 @@ class TestDatabaseUpdate:
host=str(uuid.uuid4()),
ip='99.99.99.99'
)
db.commit()
db.session.commit()
"""
Probably better to test that the new serial is > old serial...
Not hugely important that it's only ONE more than it
@@ -47,11 +32,10 @@ class TestDatabaseUpdate:
assert zone.get_serial_increment() > serial
def test_serial_on_update_host(self, db) -> None:
host = db.query(DnsHost).get(4)
host = db.session.query(DnsHost).get(4)
serial = host.zone.get_serial_increment()
host.ip = '99.99.99.99'
db.commit()
db.session.commit()
assert host.zone.get_serial_increment() > serial

View File

@@ -0,0 +1,20 @@
import json
from datetime import timedelta
from app.models import User
class TestUserRoutes:
def test_get_hosts(self, db, app) -> None:
client = app.test_client()
rv = client.get(f"/dns/zones")
assert "200" in rv.status
zones = json.loads(rv.data)
assert len(zones) == 1
assert zones[0]['name'] == 'bitchmints.com'
assert len(zones[0]['nameservers']) == 2
assert zones[0]['nameservers'][0]['host'] == 'host-1'
assert zones[0]['nameservers'][1]['host'] == 'host-2'
assert zones[0]['nameservers'][0]['ip'] == '10.1.1.101'
assert zones[0]['nameservers'][1]['ip'] == '10.1.1.102'

View File

@@ -0,0 +1,12 @@
from datetime import timedelta
from app.models import User
class TestUserRoutes:
def test_user_get(self, db, app) -> None:
client = app.test_client()
rv = client.get(f"/ping")
assert "200" in rv.status
assert "pong!" in str(rv.data)

View File

@@ -0,0 +1,14 @@
from datetime import timedelta
from app.models import User
class TestUserRoutes:
def test_user_get(self, db, app) -> None:
client = app.test_client()
user = db.session.query(User).first()
token = user.create_token(timedelta(seconds=60))
rv = client.get(f"/user", headers={"Authorization": "Bearer {}".format(token)})
assert "200" in rv.status

View File

@@ -1,4 +1,3 @@
#!/bin/sh
source venv/bin/activate
flask db upgrade
exec gunicorn -b :5000 --access-logfile - --error-logfile - server:app

View File

@@ -2,5 +2,5 @@
docker buildx build \
--push \
--platform linux/arm/v7,linux/amd64 \
--platform linux/arm/v7 \
-t fergalmoran/bitchmin-api .

Binary file not shown.

View File

@@ -24,8 +24,8 @@ logger = logging.getLogger('alembic.env')
from flask import current_app
config.set_main_option(
'sqlalchemy.url',
str(current_app.extensions['migrate'].db.engine.url).replace('%', '%%'))
target_metadata = current_app.extensions['migrate'].db.metadata
str(current_app.extensions['migrate']._db.engine.url).replace('%', '%%'))
target_metadata = current_app.extensions['migrate']._db.metadata
# other values from the config, defined by the needs of env.py,
# can be acquired:

View File

@@ -1,6 +0,0 @@
ls
rm -rf migrations || \
rm bitchmin/bitchmin.db || \
flask db init || \
flask db migrate -m "Initial" || \
flask db upgrade

View File

@@ -20,7 +20,9 @@ sqlalchemy
requests
IPy
pydig
tinder
twilio
flask_monitoringdashboard
pytest
jinja2==2.11.1
pytest
pytest-flask
pytest-flask-sqlalchemy

View File

@@ -1,7 +1,9 @@
from flask_migrate import Migrate, upgrade
from app import create_app, db
from app import create_app, db, create_celery_app
app = create_app()
celery = create_celery_app()
migrate = Migrate(app, db)

View File

@@ -1 +0,0 @@
BitchMin

View File

@@ -0,0 +1,17 @@
FROM python:3.7-alpine
RUN apk add --update alpine-sdk
COPY requirements.txt /
RUN pip install -r /requirements.txt
COPY . /app
WORKDIR /app
ENV DNS_PORT 53
ENV WORKER_PORT 10054
EXPOSE 53
EXPOSE 10054
CMD [ "python", "./server.py" ]

View File

View File

@@ -0,0 +1,69 @@
import json
from types import SimpleNamespace
class Zone(object):
def __init__(self, serial, name, admin, hosts):
self._serial = serial
self._name = name
self._admin = admin
self._hosts = hosts
@staticmethod
def from_json(data):
try:
zones = json.loads(data, object_hook=lambda d: SimpleNamespace(**d))
return [Zone(
x.serial,
x.name,
x.admin,
{h.name: Host(
h.ip,
h.name,
h.ttl,
h.type
) for h in x.hosts}
) for x in zones]
except Exception as e:
print(e)
@property
def name(self):
return self._name
@property
def admin(self):
return self._admin
@property
def serial(self):
return self._serial
@property
def hosts(self):
return self._hosts
class Host(object):
def __init__(self, ip, name, ttl, record_type):
self._ip = ip
self._name = name
self._ttl = ttl
self._record_type = record_type
@property
def ip(self):
return self._ip
@property
def name(self):
return self._name
@property
def ttl(self):
return self._ttl
@property
def record_type(self):
return self._record_type

View File

@@ -1 +1,4 @@
twisted
twisted
pytest
dnspython
requests

View File

@@ -1,3 +1,4 @@
import requests
from twisted.internet import defer
from twisted.names import dns, error
import logging
@@ -8,7 +9,8 @@ class InvalidZoneException(Exception):
class Resolver:
def _build_host(self, record_type, host, ip, ttl):
@staticmethod
def _build_host(record_type, host, ip, ttl):
return {
host: {
'type': record_type,
@@ -68,7 +70,7 @@ class MemoryResolver(Resolver):
"""
try:
host = self._parse_host(zone, str(query.name))
record = self._zones[zone]['hosts'][host]
record = self._zones[zone].hosts[host]
if query.type == dns.NS:
payload = dns.Record_NS(
@@ -77,14 +79,14 @@ class MemoryResolver(Resolver):
)
else:
payload = dns.Record_A(
address=record['ip'],
ttl=record['ttl']
address=record.ip,
ttl=record.ttl
)
answer = dns.RRHeader(
name=zone,
payload=payload,
ttl=record['ttl']
ttl=record.ttl
)
answers = [answer]

View File

@@ -1,56 +1,40 @@
import json
import logging
import os
import requests
from twisted.internet import reactor
from twisted.internet.endpoints import TCP4ServerEndpoint
from twisted.names import client, dns, server
from models.zone import Zone
from resolvers.memory_resolver import MemoryResolver
from servers.worker_server import WorkerServerFactory
logging.basicConfig(level=logging.DEBUG)
PORT = 10053
WORKER_PORT = 10054
PORT = os.environ.get('DNS_PORT') or 10053
WORKER_PORT = os.environ.get('WORKER_PORT') or 10054
API_HOST = os.environ.get('API_HOST') or 'http://localhost:5000/dns/zones'
# TODO: This smells
def get_zones():
response = requests.get(API_HOST)
zones = Zone.from_json(response.text)
return {
zone.name: zone
for zone in zones}
def main():
zones = {
'bitchmints.com': {
'serial': 'BOOO',
'admin': 'Ferg@lMoran.me',
'nameservers': [
'ns1.bitchmints.com',
'ns2.bitchmints.com'
],
'hosts': {
'ns1': {'type': 'A', 'ttl': 30, 'ip': '10.1.33.7'},
'ns2': {'type': 'A', 'ttl': 30, 'ip': '10.1.33.8'},
'host-1': {'type': 'A', 'ttl': 30, 'ip': '10.1.33.1'},
'host-2': {'type': 'A', 'ttl': 30, 'ip': '10.1.33.1'},
'host-3': {'type': 'A', 'ttl': 30, 'ip': '10.1.33.1'},
'host-4': {'type': 'A', 'ttl': 30, 'ip': '10.1.33.1'},
}
},
'fergl.ie': {
'serial': 'BOOO',
'admin': 'Ferg@lMoran.me',
'nameservers': [
'ns1.bitchmints.com',
'ns2.bitchmints.com'
],
'hosts': {
'farts': {'type': 'A', 'ttl': 30, 'ip': '10.1.33.8'},
}
}
}
zones = get_zones()
memory_resolver = MemoryResolver(zones)
dns_factory = server.DNSServerFactory(
clients=[
memory_resolver,
client.Resolver(resolv='/etc/resolv.conf')]
)
protocol = dns.DNSDatagramProtocol(controller=dns_factory)

View File

View File

@@ -0,0 +1,14 @@
import pytest
@pytest.fixture()
def resolver():
import dns.resolver
r = dns.resolver.Resolver()
r.nameservers = ['127.0.0.1']
r.nameserver_ports = {
'127.0.0.1': 10053
}
return r

View File

@@ -0,0 +1,4 @@
def test_zones_exist(resolver) -> None:
answer = resolver.resolve('host-1.bitchmints.com')
assert answer.address == '10.1.1.1'

View File

@@ -0,0 +1,153 @@
###############################
# Core EditorConfig Options #
###############################
root = true
# All files
[*]
indent_style = space
indent_size = 4
# Code files
[*.{cs,csx,vb,vbx}]
indent_size = 4
insert_final_newline = true
charset = utf-8-bom
###############################
# .NET Coding Conventions #
###############################
[*.{cs,vb}]
# Organize usings
dotnet_sort_system_directives_first = true
dotnet_separate_import_directive_groups = false
# this. preferences
dotnet_style_qualification_for_field = false:silent
dotnet_style_qualification_for_property = false:silent
dotnet_style_qualification_for_method = false:silent
dotnet_style_qualification_for_event = false:silent
# Language keywords vs BCL types preferences
dotnet_style_predefined_type_for_locals_parameters_members = true:silent
dotnet_style_predefined_type_for_member_access = true:silent
# Parentheses preferences
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
# Modifier preferences
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
dotnet_style_readonly_field = true:suggestion
# Expression-level preferences
dotnet_style_object_initializer = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
dotnet_style_prefer_inferred_tuple_names = true:suggestion
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_auto_properties = true:silent
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
dotnet_style_prefer_conditional_expression_over_return = true:silent
###############################
# Naming Conventions #
###############################
# Style Definitions
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
# Use PascalCase for constant fields
dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
dotnet_naming_symbols.constant_fields.applicable_kinds = field
dotnet_naming_symbols.constant_fields.applicable_accessibilities = *
dotnet_naming_symbols.constant_fields.required_modifiers = const
dotnet_naming_rule.private_members_with_underscore.symbols = private_fields
dotnet_naming_rule.private_members_with_underscore.style = prefix_underscore
dotnet_naming_rule.private_members_with_underscore.severity = suggestion
dotnet_naming_symbols.private_fields.applicable_kinds = field
dotnet_naming_symbols.private_fields.applicable_accessibilities = private
dotnet_naming_style.prefix_underscore.capitalization = camel_case
dotnet_naming_style.prefix_underscore.required_prefix = _
###############################
# C# Code Style Rules #
###############################
[*.cs]
# var preferences
csharp_style_var_for_built_in_types = true:silent
csharp_style_var_when_type_is_apparent = true:silent
csharp_style_var_elsewhere = true:silent
# Expression-bodied members
csharp_style_expression_bodied_methods = false:silent
csharp_style_expression_bodied_constructors = false:silent
csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_properties = true:silent
csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_accessors = true:silent
# Pattern-matching preferences
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
# Null-checking preferences
csharp_style_throw_expression = true:suggestion
csharp_style_conditional_delegate_call = true:suggestion
# Modifier preferences
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion
# Expression-level preferences
csharp_prefer_braces = true:silent
csharp_style_deconstructed_variable_declaration = true:suggestion
csharp_prefer_simple_default_expression = true:suggestion
csharp_style_pattern_local_over_anonymous_function = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
###############################
# C# Formatting Rules #
###############################
# New line preferences
csharp_new_line_before_open_brace = false
csharp_new_line_before_else = false
csharp_new_line_before_catch = false
csharp_new_line_before_finally = false
csharp_new_line_before_members_in_object_initializers = false
csharp_new_line_before_members_in_anonymous_types = false
csharp_new_line_between_query_expression_clauses = false
# Indentation preferences
csharp_indent_case_contents = true
csharp_indent_switch_labels = true
csharp_indent_labels = flush_left
# Space preferences
csharp_space_after_cast = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_around_binary_operators = before_and_after
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
# Wrapping preferences
csharp_preserve_single_line_statements = true
csharp_preserve_single_line_blocks = true

View File

@@ -0,0 +1,27 @@
using System;
using System.Linq;
using System.Net;
using DnsClient;
using DnsClient.Protocol;
Console.WriteLine("Testing lookup");
var endpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 10053);
var client = new LookupClient(endpoint);
for (int i = 1; i <= 10; i++) {
var host = $"host-{i}.bitchmints.com";
Console.WriteLine($"Performing lookup for {host}");
var results = client
.Query(host, QueryType.A)
.Answers
.ARecords();
if (results.Any()) {
foreach (var aRecord in results) {
Console.WriteLine($"\tDomain: {aRecord.DomainName} resolved to {aRecord.Address}");
}
} else {
Console.WriteLine($"\tDomain: {host} was not found");
}
}

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,41 @@
{
"runtimeTarget": {
"name": ".NETCoreApp,Version=v5.0",
"signature": ""
},
"compilationOptions": {},
"targets": {
".NETCoreApp,Version=v5.0": {
"dns-test/1.0.0": {
"dependencies": {
"DnsClient": "1.3.2"
},
"runtime": {
"dns-test.dll": {}
}
},
"DnsClient/1.3.2": {
"runtime": {
"lib/netstandard2.1/DnsClient.dll": {
"assemblyVersion": "1.3.2.0",
"fileVersion": "1.3.2.0"
}
}
}
}
},
"libraries": {
"dns-test/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"DnsClient/1.3.2": {
"type": "package",
"serviceable": true,
"sha512": "sha512-+3pku+O8VzuAirIezOhOa0rz0bK5SDtDgYUzVDbKxzFj9/zsMjvUAqS6kkxnUyX4d3L5d5MRR7gF2wLudODBYg==",
"path": "dnsclient/1.3.2",
"hashPath": "dnsclient.1.3.2.nupkg.sha512"
}
}
}

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,8 @@
{
"runtimeOptions": {
"additionalProbingPaths": [
"/home/fergalm/.dotnet/store/|arch|/|tfm|",
"/home/fergalm/.nuget/packages"
]
}
}

View File

@@ -0,0 +1,9 @@
{
"runtimeOptions": {
"tfm": "net5.0",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "5.0.0"
}
}
}

Binary file not shown.

View File

@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<RootNamespace>dns_test</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DnsClient" Version="1.3.2" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,4 @@
// <autogenerated />
using System;
using System.Reflection;
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v5.0", FrameworkDisplayName = "")]

Binary file not shown.

View File

@@ -0,0 +1,23 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("dns-test")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")]
[assembly: System.Reflection.AssemblyProductAttribute("dns-test")]
[assembly: System.Reflection.AssemblyTitleAttribute("dns-test")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
// Generated by the MSBuild WriteCodeFragment class.

View File

@@ -0,0 +1 @@
9f361857c6c60ffd4891f6a8e0304ff6fe4b537e

View File

@@ -0,0 +1,8 @@
is_global = true
build_property.TargetFramework = net5.0
build_property.TargetPlatformMinVersion =
build_property.UsingMicrosoftNETSdkWeb =
build_property.ProjectTypeGuids =
build_property.PublishSingleFile =
build_property.IncludeAllContentForSelfExtract =
build_property._SupportedPlatformList = Android,iOS,Linux,macOS,Windows

View File

@@ -0,0 +1 @@
70cfeda8d1e9e2eb58e13e423a234b2e09b3c59b

View File

@@ -0,0 +1,18 @@
/home/fergalm/dev/BitchMin/working/dns-test/bin/Debug/net5.0/dns-test
/home/fergalm/dev/BitchMin/working/dns-test/bin/Debug/net5.0/dns-test.deps.json
/home/fergalm/dev/BitchMin/working/dns-test/bin/Debug/net5.0/dns-test.runtimeconfig.json
/home/fergalm/dev/BitchMin/working/dns-test/bin/Debug/net5.0/dns-test.runtimeconfig.dev.json
/home/fergalm/dev/BitchMin/working/dns-test/bin/Debug/net5.0/dns-test.dll
/home/fergalm/dev/BitchMin/working/dns-test/bin/Debug/net5.0/ref/dns-test.dll
/home/fergalm/dev/BitchMin/working/dns-test/bin/Debug/net5.0/dns-test.pdb
/home/fergalm/dev/BitchMin/working/dns-test/obj/Debug/net5.0/dns-test.csprojAssemblyReference.cache
/home/fergalm/dev/BitchMin/working/dns-test/obj/Debug/net5.0/dns-test.GeneratedMSBuildEditorConfig.editorconfig
/home/fergalm/dev/BitchMin/working/dns-test/obj/Debug/net5.0/dns-test.AssemblyInfoInputs.cache
/home/fergalm/dev/BitchMin/working/dns-test/obj/Debug/net5.0/dns-test.AssemblyInfo.cs
/home/fergalm/dev/BitchMin/working/dns-test/obj/Debug/net5.0/dns-test.csproj.CoreCompileInputs.cache
/home/fergalm/dev/BitchMin/working/dns-test/obj/Debug/net5.0/dns-test.dll
/home/fergalm/dev/BitchMin/working/dns-test/obj/Debug/net5.0/ref/dns-test.dll
/home/fergalm/dev/BitchMin/working/dns-test/obj/Debug/net5.0/dns-test.pdb
/home/fergalm/dev/BitchMin/working/dns-test/obj/Debug/net5.0/dns-test.genruntimeconfig.cache
/home/fergalm/dev/BitchMin/working/dns-test/bin/Debug/net5.0/DnsClient.dll
/home/fergalm/dev/BitchMin/working/dns-test/obj/Debug/net5.0/dns-test.csproj.CopyComplete

Binary file not shown.

View File

@@ -0,0 +1 @@
1e5a2e58d8a5d8f0bca0aaa4ee624df8facbe5c3

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,66 @@
{
"format": 1,
"restore": {
"/home/fergalm/dev/BitchMin/working/dns-test/dns-test.csproj": {}
},
"projects": {
"/home/fergalm/dev/BitchMin/working/dns-test/dns-test.csproj": {
"version": "1.0.0",
"restore": {
"projectUniqueName": "/home/fergalm/dev/BitchMin/working/dns-test/dns-test.csproj",
"projectName": "dns-test",
"projectPath": "/home/fergalm/dev/BitchMin/working/dns-test/dns-test.csproj",
"packagesPath": "/home/fergalm/.nuget/packages/",
"outputPath": "/home/fergalm/dev/BitchMin/working/dns-test/obj/",
"projectStyle": "PackageReference",
"configFilePaths": [
"/home/fergalm/.nuget/NuGet/NuGet.Config"
],
"originalTargetFrameworks": [
"net5.0"
],
"sources": {
"https://api.nuget.org/v3/index.json": {}
},
"frameworks": {
"net5.0": {
"targetAlias": "net5.0",
"projectReferences": {}
}
},
"warningProperties": {
"warnAsError": [
"NU1605"
]
}
},
"frameworks": {
"net5.0": {
"targetAlias": "net5.0",
"dependencies": {
"DnsClient": {
"target": "Package",
"version": "[1.3.2, )"
}
},
"imports": [
"net461",
"net462",
"net47",
"net471",
"net472",
"net48"
],
"assetTargetFallback": true,
"warn": true,
"frameworkReferences": {
"Microsoft.NETCore.App": {
"privateAssets": "all"
}
},
"runtimeIdentifierGraphPath": "/home/fergalm/dotnet/sdk/5.0.100/RuntimeIdentifierGraph.json"
}
}
}
}
}

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess>
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile>
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">/home/fergalm/.nuget/packages/</NuGetPackageRoot>
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">/home/fergalm/.nuget/packages/</NuGetPackageFolders>
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">5.8.0</NuGetToolVersion>
</PropertyGroup>
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<SourceRoot Include="$([MSBuild]::EnsureTrailingSlash($(NuGetPackageFolders)))" />
</ItemGroup>
<PropertyGroup>
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,106 @@
{
"version": 3,
"targets": {
"net5.0": {
"DnsClient/1.3.2": {
"type": "package",
"compile": {
"lib/netstandard2.1/DnsClient.dll": {}
},
"runtime": {
"lib/netstandard2.1/DnsClient.dll": {}
}
}
}
},
"libraries": {
"DnsClient/1.3.2": {
"sha512": "+3pku+O8VzuAirIezOhOa0rz0bK5SDtDgYUzVDbKxzFj9/zsMjvUAqS6kkxnUyX4d3L5d5MRR7gF2wLudODBYg==",
"type": "package",
"path": "dnsclient/1.3.2",
"files": [
".nupkg.metadata",
".signature.p7s",
"dnsclient.1.3.2.nupkg.sha512",
"dnsclient.nuspec",
"icon.png",
"lib/net45/DnsClient.dll",
"lib/net45/DnsClient.xml",
"lib/net471/DnsClient.dll",
"lib/net471/DnsClient.xml",
"lib/netstandard1.3/DnsClient.dll",
"lib/netstandard1.3/DnsClient.xml",
"lib/netstandard2.0/DnsClient.dll",
"lib/netstandard2.0/DnsClient.xml",
"lib/netstandard2.1/DnsClient.dll",
"lib/netstandard2.1/DnsClient.xml"
]
}
},
"projectFileDependencyGroups": {
"net5.0": [
"DnsClient >= 1.3.2"
]
},
"packageFolders": {
"/home/fergalm/.nuget/packages/": {}
},
"project": {
"version": "1.0.0",
"restore": {
"projectUniqueName": "/home/fergalm/dev/BitchMin/working/dns-test/dns-test.csproj",
"projectName": "dns-test",
"projectPath": "/home/fergalm/dev/BitchMin/working/dns-test/dns-test.csproj",
"packagesPath": "/home/fergalm/.nuget/packages/",
"outputPath": "/home/fergalm/dev/BitchMin/working/dns-test/obj/",
"projectStyle": "PackageReference",
"configFilePaths": [
"/home/fergalm/.nuget/NuGet/NuGet.Config"
],
"originalTargetFrameworks": [
"net5.0"
],
"sources": {
"https://api.nuget.org/v3/index.json": {}
},
"frameworks": {
"net5.0": {
"targetAlias": "net5.0",
"projectReferences": {}
}
},
"warningProperties": {
"warnAsError": [
"NU1605"
]
}
},
"frameworks": {
"net5.0": {
"targetAlias": "net5.0",
"dependencies": {
"DnsClient": {
"target": "Package",
"version": "[1.3.2, )"
}
},
"imports": [
"net461",
"net462",
"net47",
"net471",
"net472",
"net48"
],
"assetTargetFallback": true,
"warn": true,
"frameworkReferences": {
"Microsoft.NETCore.App": {
"privateAssets": "all"
}
},
"runtimeIdentifierGraphPath": "/home/fergalm/dotnet/sdk/5.0.100/RuntimeIdentifierGraph.json"
}
}
}
}

View File

@@ -0,0 +1,10 @@
{
"version": 2,
"dgSpecHash": "eyFn1mF0Ec0RODelM//pl87ne40rmd2/fm30g+WxWQ97TpqhUsZ79YryHC9MGUYH+wDk/epuUARQ5ofTUycO6w==",
"success": true,
"projectFilePath": "/home/fergalm/dev/BitchMin/working/dns-test/dns-test.csproj",
"expectedPackageFiles": [
"/home/fergalm/.nuget/packages/dnsclient/1.3.2/dnsclient.1.3.2.nupkg.sha512"
],
"logs": []
}