add support for specifying DB parameters via environment variables. sqlite or mysql.
updated README to focus on developing/running via docker
This commit is contained in:
parent
6ecae8b772
commit
6704469b9d
166
README.markdown
166
README.markdown
|
@ -5,81 +5,97 @@
|
||||||
|
|
||||||
A Django web application for viewing and editing BIND DNS zone records.
|
A Django web application for viewing and editing BIND DNS zone records.
|
||||||
|
|
||||||
Binder supports adding and deleting DNS records (and eventually editing in place). TSIG-authenticated transfers and updates are supported.
|
## Download ##
|
||||||
|
|
||||||
The Binder repository is housed in a [Github](http://github.com/jforman/binder) repository. The repo containts all the Django code and example configuration data for running Binder both in development and production.
|
```
|
||||||
|
git clone https://github.com/jforman/binder.git
|
||||||
|
```
|
||||||
|
|
||||||
## Installation ##
|
## Requirements ##
|
||||||
|
|
||||||
There are some build dependencies for the Python mondules, on apt based systems these can be installed with
|
The requirements.txt file has the necessary dependencies.
|
||||||
|
|
||||||
apt-get install python-dev libxml2-dev libxslt-dev git
|
```
|
||||||
|
pip install -r requirements.txt
|
||||||
Initial checkout has to be performed with git
|
```
|
||||||
|
|
||||||
git clone https://github.com/jforman/binder.git
|
|
||||||
|
|
||||||
### Requirements ###
|
|
||||||
|
|
||||||
Once the git repository has been cloned these can be installed with one command
|
|
||||||
|
|
||||||
pip install -r requirements.txt
|
|
||||||
|
|
||||||
Packages installed:
|
|
||||||
|
|
||||||
* [Django](http://www.djangoproject.com) >=1.8
|
|
||||||
* Python Modules
|
|
||||||
* [pybindxml](https://pypi.python.org/pypi?name=pybindxml&:action=display): This is a shared library I wrote to scrape and stick into Python dict objects various server/zone data from a BIND DNS server.
|
|
||||||
* Beautifulsoup4: This library is included as a dependency of pybindmlx when you when you install pybindxml.
|
|
||||||
* [python-dnspython](http://www.dnspython.org/)
|
|
||||||
* [python-sqlite](http://docs.python.org/2/library/sqlite3.html) (If you will be using Sqlite for server and key storage)
|
|
||||||
|
|
||||||
Elsewhere you will need a [Bind DNS Server](http://www.isc.org/software/bind) running (at least version 9.5.x, which provides instrumentation for gathering process and zone statistics remotely).
|
|
||||||
|
|
||||||
To verify that required and optional dependencies are installed, execute [check-dependencies.py](https://github.com/jforman/binder/blob/master/check-dependencies.py). This script checks that various Python modules will import correctly.
|
|
||||||
|
|
||||||
Binder is intended to be installed into the /opt directory in /opt/binder. Forthcoming deb packages will provide for this easy installation and upgrades.
|
|
||||||
|
|
||||||
## Configuration ##
|
|
||||||
|
|
||||||
### binder/ ###
|
|
||||||
|
|
||||||
If you wish to override anything from settings.py it should be done in a new file
|
|
||||||
|
|
||||||
* local_settings.py: Local settings called by Binder templates for TTL choices, record types handled, etc.
|
|
||||||
|
|
||||||
### config/ ###
|
|
||||||
|
|
||||||
Provided under the config directory are various example configurations for runing Binder:
|
|
||||||
|
|
||||||
* binder-apache.conf.dist: Name-based virtual host configuration for running Binder under Apache.
|
|
||||||
* django.wsgi: WSGI configuration file called by Apache to run Binder.
|
|
||||||
* binder-nginx.conf.dist: Name-based virtual host configuration for running Binder under Nginx using fcgi.
|
|
||||||
* binder-upstart.conf.dist: Ubuntu Upstart configuration file for starting Binder upon machine startup.
|
|
||||||
|
|
||||||
These are not necesary for development but are useful once moving to production.
|
|
||||||
|
|
||||||
### Admin user ###
|
|
||||||
|
|
||||||
It is necesary to create an administrative user
|
|
||||||
|
|
||||||
python manage.py createsuperuser
|
|
||||||
|
|
||||||
## Running Binder ##
|
## Running Binder ##
|
||||||
The development server is run as most Django dev servers are run.
|
|
||||||
|
|
||||||
/opt/binder/manage.py migrate
|
Over the course of developing Binder, it has come to the fore that using a container makes development and runnin Binder much easier.
|
||||||
/opt/binder/manage.py runserver
|
|
||||||
|
|
||||||
Once you have the Django server up and running, you will want to configure at least one BIND server in the Django Admin app. This includes a hostname, TCP statistics port and a default TSIG transfer key to be used when doing AXFR actions (if necessary).
|
### Local Sqlite database ###
|
||||||
|
|
||||||
Keys should also be created, if needed. The name of the key should match the contents of the below noted key file. Along side the name, key data and type should also be specified.
|
```
|
||||||
|
docker run -e NODB=1 jforman/binder:latest
|
||||||
|
```
|
||||||
|
### Admin user ###
|
||||||
|
|
||||||
Once these two pieces of configuration are done, open up [http://yourserver:port/](http://yourserver:port) to access Binder and begin DNS zone management.
|
Default admin user for Binder is 'admin', and password is 'admin' as well.
|
||||||
|
|
||||||
## BIND DNS Server ##
|
### MySQL database ###
|
||||||
|
|
||||||
When Binder accesses your BIND DNS server, it first queries the statistics port to gather zone information. This includes zone name, view, and serial number. This is all configured by some of the following configuration examples.
|
```
|
||||||
|
docker run -e DJANGO_DB_HOST="XXXX",DJANGO_DB_PASSWORD="YYYY",DJANGO_DB_USER="binder" jforman/binder:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
The Django settings.py is configured to accept the following environment
|
||||||
|
variables when configuring a MySQL-based backend database.
|
||||||
|
|
||||||
|
* DJANGO_DB_HOST: IP address or Hostname of the MySQL database host.
|
||||||
|
* DJANGO_DB_NAME: Name of the MySQL database.
|
||||||
|
* DJANGO_DB_USER: Username to access the above database.
|
||||||
|
* DJANGO_DB_PASSWORD: Binder Database password
|
||||||
|
|
||||||
|
### Manually ###
|
||||||
|
|
||||||
|
Or you can run Binder directly on your host using the Django devserver.
|
||||||
|
|
||||||
|
```
|
||||||
|
export NODB=1
|
||||||
|
python manage.py migrate
|
||||||
|
python manage.py createsuperuser
|
||||||
|
python manage.py runserver
|
||||||
|
```
|
||||||
|
|
||||||
|
## Develop Binder
|
||||||
|
|
||||||
|
If you want to develop on Binder, I've tried to write down the steps I use.
|
||||||
|
|
||||||
|
`develop.sh` is a shell script that will start a Docker container based off the
|
||||||
|
same image as the one on Docker hub. Only this script will mount your
|
||||||
|
Binder code directory into /code in the container.
|
||||||
|
|
||||||
|
Before any development can commence, you will need to install the requirements.
|
||||||
|
|
||||||
|
From inside the container:
|
||||||
|
|
||||||
|
```
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### Generating a new initial_data.json
|
||||||
|
|
||||||
|
Certain versions of Django cause changes in the schema of the admin table.
|
||||||
|
In this case, I've found a (perhaps less than proper) workflow for creating
|
||||||
|
a new initial_data.json file. This uses a local Sqlite database file for
|
||||||
|
bootstrapping.
|
||||||
|
|
||||||
|
```
|
||||||
|
export NODB=1
|
||||||
|
python manage.py migrate
|
||||||
|
python manage.py createsuperuser
|
||||||
|
...
|
||||||
|
python manage.py dumpdata -o binder/fixtures/initial_data.json
|
||||||
|
```
|
||||||
|
|
||||||
|
## External configuration ##
|
||||||
|
|
||||||
|
Aside from the Binder application itself, other infrastructure is required
|
||||||
|
to make Binder useful.
|
||||||
|
|
||||||
|
### BIND DNS Server ###
|
||||||
|
|
||||||
|
When Binder accesses your BIND DNS server, it first queries the statistics port to gather zone information. This includes zone name, view, and serial number.
|
||||||
|
|
||||||
#### named.conf ####
|
#### named.conf ####
|
||||||
|
|
||||||
|
@ -93,7 +109,9 @@ We must provide server statistics from the BIND process itself. This allows Bind
|
||||||
inet * port 8053 allow { 10.10.0.0/24; };
|
inet * port 8053 allow { 10.10.0.0/24; };
|
||||||
};
|
};
|
||||||
|
|
||||||
This tells bind to start an HTTP server on port 8053 on all interfaces, allowing 10.10.0.0/24 to make requests on this interface, http://${bind_server}:8053/. You will most likely want to narrow down the subset of hosts or subnets that can query BIND for this data. This data can be viewed via your choice of Browser, or read by your favorite programming language and progamatically processed by your choice of XML library.
|
This tells bind to start an HTTP server on port 8053 on all interfaces, allowing 10.10.0.0/24 to make requests on this interface, http://${bind_server}:8053/. You will most likely want to narrow down list of source hosts/IPs who can query BIND for this data.
|
||||||
|
|
||||||
|
It is smart to include your TSIG key in a separate file. This way if you choose to have specific ACLs for your named.conf that are different from your TSIG key, this can be done.
|
||||||
|
|
||||||
include "/etc/bind/dynzone.key";
|
include "/etc/bind/dynzone.key";
|
||||||
|
|
||||||
|
@ -120,9 +138,8 @@ referenced as 'dynzone-key' in named.conf
|
||||||
|
|
||||||
For information on TSIG see http://www.cyberciti.biz/faq/unix-linux-bind-named-configuring-tsig/ .
|
For information on TSIG see http://www.cyberciti.biz/faq/unix-linux-bind-named-configuring-tsig/ .
|
||||||
|
|
||||||
### Related Configuration ###
|
|
||||||
|
|
||||||
#### Apache HTTPD ####
|
### Apache HTTPD ###
|
||||||
|
|
||||||
If you are using Apache to front-end your Binder Django app, the following two configuration files can be used as starting points.
|
If you are using Apache to front-end your Binder Django app, the following two configuration files can be used as starting points.
|
||||||
|
|
||||||
|
@ -130,10 +147,21 @@ If you are using Apache to front-end your Binder Django app, the following two c
|
||||||
|
|
||||||
[django.wsgi](https://github.com/jforman/binder/blob/master/config/django.wsgi): WSGI configuration file used by Apache to run the actual Django app.
|
[django.wsgi](https://github.com/jforman/binder/blob/master/config/django.wsgi): WSGI configuration file used by Apache to run the actual Django app.
|
||||||
|
|
||||||
#### Nginx ####
|
### Nginx ###
|
||||||
|
|
||||||
[binder-nginx.conf.dist](https://github.com/jforman/binder/blob/master/config/binder-nginx.conf.dist): Nginx virtual host configuraiton. This configuration expects Django to be running in fcgi mode on port 4001 on 127.0.0.1.
|
[binder-nginx.conf.dist](https://github.com/jforman/binder/blob/master/config/binder-nginx.conf.dist): Nginx virtual host configuraiton. This configuration expects Django to be running in fcgi mode on port 4001 on 127.0.0.1.
|
||||||
|
|
||||||
#### Ubuntu Upstart ####
|
#### MySQL ###
|
||||||
|
|
||||||
To have Binder start upon system boot, if you are running Ubuntu, I have provided an [example Upstart configurarton](https://github.com/jforman/binder/blob/master/config/binder-upstart.conf.dist) to be installed in /etc/init/.
|
If you choose to use MySQL as your backing datastore, the following commands
|
||||||
|
will help you get up and running quickly.
|
||||||
|
|
||||||
|
```
|
||||||
|
create database binder;
|
||||||
|
|
||||||
|
create user 'binder'@'%' identified by 'INSERTYOURPASSWORDHERE';
|
||||||
|
|
||||||
|
grant all privileges on binder.* to 'binder'@'%';
|
||||||
|
|
||||||
|
flush privileges;
|
||||||
|
```
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
[{"fields": {"model": "contenttype", "app_label": "contenttypes"}, "model": "contenttypes.contenttype", "pk": 1}, {"fields": {"model": "group", "app_label": "auth"}, "model": "contenttypes.contenttype", "pk": 2}, {"fields": {"model": "user", "app_label": "auth"}, "model": "contenttypes.contenttype", "pk": 3}, {"fields": {"model": "permission", "app_label": "auth"}, "model": "contenttypes.contenttype", "pk": 4}, {"fields": {"model": "session", "app_label": "sessions"}, "model": "contenttypes.contenttype", "pk": 5}, {"fields": {"model": "logentry", "app_label": "admin"}, "model": "contenttypes.contenttype", "pk": 6}, {"fields": {"model": "bindserver", "app_label": "binder"}, "model": "contenttypes.contenttype", "pk": 7}, {"fields": {"model": "key", "app_label": "binder"}, "model": "contenttypes.contenttype", "pk": 8}, {"fields": {"codename": "add_contenttype", "name": "Can add content type", "content_type": 1}, "model": "auth.permission", "pk": 1}, {"fields": {"codename": "change_contenttype", "name": "Can change content type", "content_type": 1}, "model": "auth.permission", "pk": 2}, {"fields": {"codename": "delete_contenttype", "name": "Can delete content type", "content_type": 1}, "model": "auth.permission", "pk": 3}, {"fields": {"codename": "add_permission", "name": "Can add permission", "content_type": 4}, "model": "auth.permission", "pk": 4}, {"fields": {"codename": "change_permission", "name": "Can change permission", "content_type": 4}, "model": "auth.permission", "pk": 5}, {"fields": {"codename": "delete_permission", "name": "Can delete permission", "content_type": 4}, "model": "auth.permission", "pk": 6}, {"fields": {"codename": "add_group", "name": "Can add group", "content_type": 2}, "model": "auth.permission", "pk": 7}, {"fields": {"codename": "change_group", "name": "Can change group", "content_type": 2}, "model": "auth.permission", "pk": 8}, {"fields": {"codename": "delete_group", "name": "Can delete group", "content_type": 2}, "model": "auth.permission", "pk": 9}, {"fields": {"codename": "add_user", "name": "Can add user", "content_type": 3}, "model": "auth.permission", "pk": 10}, {"fields": {"codename": "change_user", "name": "Can change user", "content_type": 3}, "model": "auth.permission", "pk": 11}, {"fields": {"codename": "delete_user", "name": "Can delete user", "content_type": 3}, "model": "auth.permission", "pk": 12}, {"fields": {"codename": "add_session", "name": "Can add session", "content_type": 5}, "model": "auth.permission", "pk": 13}, {"fields": {"codename": "change_session", "name": "Can change session", "content_type": 5}, "model": "auth.permission", "pk": 14}, {"fields": {"codename": "delete_session", "name": "Can delete session", "content_type": 5}, "model": "auth.permission", "pk": 15}, {"fields": {"codename": "add_logentry", "name": "Can add log entry", "content_type": 6}, "model": "auth.permission", "pk": 16}, {"fields": {"codename": "change_logentry", "name": "Can change log entry", "content_type": 6}, "model": "auth.permission", "pk": 17}, {"fields": {"codename": "delete_logentry", "name": "Can delete log entry", "content_type": 6}, "model": "auth.permission", "pk": 18}, {"fields": {"codename": "add_key", "name": "Can add key", "content_type": 8}, "model": "auth.permission", "pk": 19}, {"fields": {"codename": "change_key", "name": "Can change key", "content_type": 8}, "model": "auth.permission", "pk": 20}, {"fields": {"codename": "delete_key", "name": "Can delete key", "content_type": 8}, "model": "auth.permission", "pk": 21}, {"fields": {"codename": "add_bindserver", "name": "Can add bind server", "content_type": 7}, "model": "auth.permission", "pk": 22}, {"fields": {"codename": "change_bindserver", "name": "Can change bind server", "content_type": 7}, "model": "auth.permission", "pk": 23}, {"fields": {"codename": "delete_bindserver", "name": "Can delete bind server", "content_type": 7}, "model": "auth.permission", "pk": 24}, {"fields": {"username": "admin", "first_name": "", "last_name": "", "is_active": true, "is_superuser": true, "is_staff": true, "last_login": null, "groups": [], "user_permissions": [], "password": "pbkdf2_sha256$20000$7zHgdrWNLW4j$vjAbJsyiSd3UXo7+MItoh9amW/N+tD8hls/BsXdDDnI=", "email": "admin@example.org", "date_joined": "2016-12-05T08:44:01.676"}, "model": "auth.user", "pk": 1}]
|
[{"model": "contenttypes.contenttype", "pk": 1, "fields": {"app_label": "contenttypes", "model": "contenttype"}}, {"model": "contenttypes.contenttype", "pk": 2, "fields": {"app_label": "auth", "model": "group"}}, {"model": "contenttypes.contenttype", "pk": 3, "fields": {"app_label": "auth", "model": "user"}}, {"model": "contenttypes.contenttype", "pk": 4, "fields": {"app_label": "auth", "model": "permission"}}, {"model": "contenttypes.contenttype", "pk": 5, "fields": {"app_label": "sessions", "model": "session"}}, {"model": "contenttypes.contenttype", "pk": 6, "fields": {"app_label": "admin", "model": "logentry"}}, {"model": "contenttypes.contenttype", "pk": 7, "fields": {"app_label": "binder", "model": "key"}}, {"model": "contenttypes.contenttype", "pk": 8, "fields": {"app_label": "binder", "model": "bindserver"}}, {"model": "auth.permission", "pk": 1, "fields": {"name": "Can add content type", "content_type": 1, "codename": "add_contenttype"}}, {"model": "auth.permission", "pk": 2, "fields": {"name": "Can change content type", "content_type": 1, "codename": "change_contenttype"}}, {"model": "auth.permission", "pk": 3, "fields": {"name": "Can delete content type", "content_type": 1, "codename": "delete_contenttype"}}, {"model": "auth.permission", "pk": 4, "fields": {"name": "Can add permission", "content_type": 4, "codename": "add_permission"}}, {"model": "auth.permission", "pk": 5, "fields": {"name": "Can change permission", "content_type": 4, "codename": "change_permission"}}, {"model": "auth.permission", "pk": 6, "fields": {"name": "Can delete permission", "content_type": 4, "codename": "delete_permission"}}, {"model": "auth.permission", "pk": 7, "fields": {"name": "Can add user", "content_type": 3, "codename": "add_user"}}, {"model": "auth.permission", "pk": 8, "fields": {"name": "Can change user", "content_type": 3, "codename": "change_user"}}, {"model": "auth.permission", "pk": 9, "fields": {"name": "Can delete user", "content_type": 3, "codename": "delete_user"}}, {"model": "auth.permission", "pk": 10, "fields": {"name": "Can add group", "content_type": 2, "codename": "add_group"}}, {"model": "auth.permission", "pk": 11, "fields": {"name": "Can change group", "content_type": 2, "codename": "change_group"}}, {"model": "auth.permission", "pk": 12, "fields": {"name": "Can delete group", "content_type": 2, "codename": "delete_group"}}, {"model": "auth.permission", "pk": 13, "fields": {"name": "Can add session", "content_type": 5, "codename": "add_session"}}, {"model": "auth.permission", "pk": 14, "fields": {"name": "Can change session", "content_type": 5, "codename": "change_session"}}, {"model": "auth.permission", "pk": 15, "fields": {"name": "Can delete session", "content_type": 5, "codename": "delete_session"}}, {"model": "auth.permission", "pk": 16, "fields": {"name": "Can add log entry", "content_type": 6, "codename": "add_logentry"}}, {"model": "auth.permission", "pk": 17, "fields": {"name": "Can change log entry", "content_type": 6, "codename": "change_logentry"}}, {"model": "auth.permission", "pk": 18, "fields": {"name": "Can delete log entry", "content_type": 6, "codename": "delete_logentry"}}, {"model": "auth.permission", "pk": 19, "fields": {"name": "Can add bind server", "content_type": 8, "codename": "add_bindserver"}}, {"model": "auth.permission", "pk": 20, "fields": {"name": "Can change bind server", "content_type": 8, "codename": "change_bindserver"}}, {"model": "auth.permission", "pk": 21, "fields": {"name": "Can delete bind server", "content_type": 8, "codename": "delete_bindserver"}}, {"model": "auth.permission", "pk": 22, "fields": {"name": "Can add key", "content_type": 7, "codename": "add_key"}}, {"model": "auth.permission", "pk": 23, "fields": {"name": "Can change key", "content_type": 7, "codename": "change_key"}}, {"model": "auth.permission", "pk": 24, "fields": {"name": "Can delete key", "content_type": 7, "codename": "delete_key"}}, {"model": "auth.user", "pk": 1, "fields": {"password": "pbkdf2_sha256$30000$sVkOSp8Qn2pn$AufvgGtIh0fhvbcIInzikFgIgOfTuDGn+c8g/7pViuI=", "last_login": "2017-03-28T21:24:33.401", "is_superuser": true, "username": "admin", "first_name": "", "last_name": "", "email": "admin@admin.com", "is_staff": true, "is_active": true, "date_joined": "2017-03-28T21:24:12.167", "groups": [], "user_permissions": []}}, {"model": "sessions.session", "pk": "907txblmf4apiwsjahcbiep1ncypimyc", "fields": {"session_data": "MzJlMGQ1NzI2ZDcwNmZkN2RlOTJmODZlZTc0YTA3YjQwZDE4ZWI1Mjp7Il9hdXRoX3VzZXJfaGFzaCI6ImFiYzUyYjUxODJmYzIyMWYxNTU2Yjc2YjRjYWQyMzk3YjIxZGZlZjQiLCJfYXV0aF91c2VyX2JhY2tlbmQiOiJkamFuZ28uY29udHJpYi5hdXRoLmJhY2tlbmRzLk1vZGVsQmFja2VuZCIsIl9hdXRoX3VzZXJfaWQiOiIxIn0=", "expire_date": "2017-04-11T21:24:33.415"}}]
|
|
@ -9,16 +9,28 @@ ADMINS = (
|
||||||
)
|
)
|
||||||
|
|
||||||
MANAGERS = ADMINS
|
MANAGERS = ADMINS
|
||||||
DATABASES = {
|
|
||||||
|
ALLOWED_HOSTS=['*']
|
||||||
|
|
||||||
|
if os.getenv('NODB'):
|
||||||
|
print "Attempting to use Sqlite database at %s" % (os.path.join(SITE_ROOT, 'db') + '/binder.db')
|
||||||
|
DATABASES = {
|
||||||
'default': {
|
'default': {
|
||||||
'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
'NAME': os.path.join(SITE_ROOT, 'db') + '/binder.db', # Or path to database file if using sqlite3.
|
'NAME': os.path.join(SITE_ROOT, 'db') + '/binder.db',
|
||||||
'USER': '', # Not used with sqlite3.
|
}
|
||||||
'PASSWORD': '', # Not used with sqlite3.
|
}
|
||||||
'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
|
else:
|
||||||
'PORT': '', # Set to empty string for default. Not used with sqlite3.
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.mysql',
|
||||||
|
'NAME': os.environ.get('DJANGO_DB_NAME', 'binder'),
|
||||||
|
'USER': os.environ.get('DJANGO_DB_USER', ''),
|
||||||
|
'PASSWORD': os.environ.get('DJANGO_DB_PASSWORD', ''),
|
||||||
|
'HOST': os.environ.get('DJANGO_DB_HOST', ''),
|
||||||
|
'PORT': 3306,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
TIME_ZONE = 'America/New_York'
|
TIME_ZONE = 'America/New_York'
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
docker run -it --rm -e NODB=1 -v `pwd`:/code/ -w /code/ python:2.7 /bin/bash
|
|
@ -2,3 +2,4 @@ Django>=1.10
|
||||||
dnspython>=1.11
|
dnspython>=1.11
|
||||||
git+https://github.com/jforman/pybindxml@0.6.1
|
git+https://github.com/jforman/pybindxml@0.6.1
|
||||||
lxml
|
lxml
|
||||||
|
mysqlclient
|
||||||
|
|
Loading…
Reference in New Issue