Removed slaves domain settings, now generated on the fly
This commit is contained in:
parent
9ecfc8d4dd
commit
6a3e3f637c
|
@ -15,7 +15,7 @@ Note `*` _for sustancial progress_
|
||||||
4. [ ] Data model, crazy input validation, admin and REST interfaces, permissions, unit and functional tests, service management, migration scripts and documentation of:
|
4. [ ] Data model, crazy input validation, admin and REST interfaces, permissions, unit and functional tests, service management, migration scripts and documentation of:
|
||||||
1. [x] PHP/static Web applications
|
1. [x] PHP/static Web applications
|
||||||
1. [x] Websites with Apache
|
1. [x] Websites with Apache
|
||||||
2. [ ] *FTP/rsync/scp/shell system accounts
|
2. [x] FTP/rsync/scp/shell system accounts
|
||||||
2. [ ] *Databases and database users with MySQL
|
2. [ ] *Databases and database users with MySQL
|
||||||
1. [ ] *Mail accounts, aliases, forwards with Postfix and Dovecot
|
1. [ ] *Mail accounts, aliases, forwards with Postfix and Dovecot
|
||||||
1. [x] DNS with Bind
|
1. [x] DNS with Bind
|
||||||
|
@ -63,4 +63,4 @@ Note `*` _for sustancial progress_
|
||||||
2. [ ] REST API functionality for superusers
|
2. [ ] REST API functionality for superusers
|
||||||
3. [ ] Responsive user interface, based on a JS framework.
|
3. [ ] Responsive user interface, based on a JS framework.
|
||||||
4. [ ] Full documentation
|
4. [ ] Full documentation
|
||||||
5. [ ] [http://www.ansible.com/home](Ansible) orchestration method, which synchronize the whole service config everytime instead of incremental changes.
|
5. [ ] [Ansible](http://www.ansible.com/home) orchestration method, which synchronizes the whole service config everytime instead of incremental changes.
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
|
import textwrap
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from . import settings
|
|
||||||
|
|
||||||
from orchestra.apps.orchestration import ServiceController
|
from orchestra.apps.orchestration import ServiceController
|
||||||
|
|
||||||
|
from . import settings
|
||||||
|
|
||||||
|
|
||||||
class Bind9MasterDomainBackend(ServiceController):
|
class Bind9MasterDomainBackend(ServiceController):
|
||||||
verbose_name = _("Bind9 master domain")
|
verbose_name = _("Bind9 master domain")
|
||||||
|
@ -52,20 +54,31 @@ class Bind9MasterDomainBackend(ServiceController):
|
||||||
""" reload bind if needed """
|
""" reload bind if needed """
|
||||||
self.append('[[ $UPDATED == 1 ]] && service bind9 reload')
|
self.append('[[ $UPDATED == 1 ]] && service bind9 reload')
|
||||||
|
|
||||||
|
def get_servers(self, domain, backend):
|
||||||
|
from orchestra.apps.orchestration.models import Route, BackendOperation as Operation
|
||||||
|
operation = Operation(backend=backend, action='save', instance=domain)
|
||||||
|
servers = []
|
||||||
|
for server in Route.get_servers(operation):
|
||||||
|
servers.append(server.get_ip())
|
||||||
|
return servers
|
||||||
|
|
||||||
def get_context(self, domain):
|
def get_context(self, domain):
|
||||||
context = {
|
context = {
|
||||||
'name': domain.name,
|
'name': domain.name,
|
||||||
'zone_path': settings.DOMAINS_ZONE_PATH % {'name': domain.name},
|
'zone_path': settings.DOMAINS_ZONE_PATH % {'name': domain.name},
|
||||||
'subdomains': domain.subdomains.all(),
|
'subdomains': domain.subdomains.all(),
|
||||||
'banner': self.get_banner(),
|
'banner': self.get_banner(),
|
||||||
|
'slaves': '; '.join(self.get_servers(domain, Bind9SlaveDomainBackend)),
|
||||||
}
|
}
|
||||||
context.update({
|
context.update({
|
||||||
'conf_path': settings.DOMAINS_MASTERS_PATH,
|
'conf_path': settings.DOMAINS_MASTERS_PATH,
|
||||||
'conf': 'zone "%(name)s" {\n'
|
'conf': textwrap.dedent("""
|
||||||
' // %(banner)s\n'
|
zone "%(name)s" {
|
||||||
' type master;\n'
|
// %(banner)s
|
||||||
' file "%(zone_path)s";\n'
|
type master;
|
||||||
'};\n' % context
|
file "%(zone_path)s";
|
||||||
|
allow-transfer { %(slaves)s; };
|
||||||
|
};""" % context)
|
||||||
})
|
})
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
@ -91,15 +104,19 @@ class Bind9SlaveDomainBackend(Bind9MasterDomainBackend):
|
||||||
def get_context(self, domain):
|
def get_context(self, domain):
|
||||||
context = {
|
context = {
|
||||||
'name': domain.name,
|
'name': domain.name,
|
||||||
'masters': '; '.join(settings.DOMAINS_MASTERS),
|
'banner': self.get_banner(),
|
||||||
'subdomains': domain.subdomains.all()
|
'subdomains': domain.subdomains.all(),
|
||||||
|
'masters': '; '.join(self.get_servers(domain, Bind9MasterDomainBackend)),
|
||||||
}
|
}
|
||||||
context.update({
|
context.update({
|
||||||
'conf_path': settings.DOMAINS_SLAVES_PATH,
|
'conf_path': settings.DOMAINS_SLAVES_PATH,
|
||||||
'conf': 'zone "%(name)s" {\n'
|
'conf': textwrap.dedent("""
|
||||||
' type slave;\n'
|
zone "%(name)s" {
|
||||||
' file "%(name)s";\n'
|
// %(banner)s
|
||||||
' masters { %(masters)s; };\n'
|
type slave;
|
||||||
'};\n' % context
|
file "%(name)s";
|
||||||
|
masters { %(masters)s; };
|
||||||
|
allow-notify { %(masters)s; };
|
||||||
|
};""" % context)
|
||||||
})
|
})
|
||||||
return context
|
return context
|
||||||
|
|
|
@ -124,6 +124,7 @@ class Domain(models.Model):
|
||||||
top = self.get_top()
|
top = self.get_top()
|
||||||
if top:
|
if top:
|
||||||
self.top = top
|
self.top = top
|
||||||
|
self.account_id = self.account_id or top.account_id
|
||||||
else:
|
else:
|
||||||
update = True
|
update = True
|
||||||
super(Domain, self).save(*args, **kwargs)
|
super(Domain, self).save(*args, **kwargs)
|
||||||
|
@ -132,7 +133,7 @@ class Domain(models.Model):
|
||||||
for domain in domains.filter(name__endswith=self.name):
|
for domain in domains.filter(name__endswith=self.name):
|
||||||
domain.top = self
|
domain.top = self
|
||||||
domain.save(update_fields=['top'])
|
domain.save(update_fields=['top'])
|
||||||
self.subdomains.update(account=self.account)
|
self.subdomains.update(account_id=self.account_id)
|
||||||
|
|
||||||
def get_top(self):
|
def get_top(self):
|
||||||
split = self.name.split('.')
|
split = self.name.split('.')
|
||||||
|
|
|
@ -4,48 +4,61 @@ from django.conf import settings
|
||||||
DOMAINS_DEFAULT_NAME_SERVER = getattr(settings, 'DOMAINS_DEFAULT_NAME_SERVER',
|
DOMAINS_DEFAULT_NAME_SERVER = getattr(settings, 'DOMAINS_DEFAULT_NAME_SERVER',
|
||||||
'ns.orchestra.lan')
|
'ns.orchestra.lan')
|
||||||
|
|
||||||
|
|
||||||
DOMAINS_DEFAULT_HOSTMASTER = getattr(settings, 'DOMAINS_DEFAULT_HOSTMASTER',
|
DOMAINS_DEFAULT_HOSTMASTER = getattr(settings, 'DOMAINS_DEFAULT_HOSTMASTER',
|
||||||
'hostmaster@orchestra.lan')
|
'hostmaster@orchestra.lan')
|
||||||
|
|
||||||
|
|
||||||
DOMAINS_DEFAULT_TTL = getattr(settings, 'DOMAINS_DEFAULT_TTL', '1h')
|
DOMAINS_DEFAULT_TTL = getattr(settings, 'DOMAINS_DEFAULT_TTL', '1h')
|
||||||
|
|
||||||
|
|
||||||
DOMAINS_DEFAULT_REFRESH = getattr(settings, 'DOMAINS_DEFAULT_REFRESH', '1d')
|
DOMAINS_DEFAULT_REFRESH = getattr(settings, 'DOMAINS_DEFAULT_REFRESH', '1d')
|
||||||
|
|
||||||
|
|
||||||
DOMAINS_DEFAULT_RETRY = getattr(settings, 'DOMAINS_DEFAULT_RETRY', '2h')
|
DOMAINS_DEFAULT_RETRY = getattr(settings, 'DOMAINS_DEFAULT_RETRY', '2h')
|
||||||
|
|
||||||
|
|
||||||
DOMAINS_DEFAULT_EXPIRATION = getattr(settings, 'DOMAINS_DEFAULT_EXPIRATION', '4w')
|
DOMAINS_DEFAULT_EXPIRATION = getattr(settings, 'DOMAINS_DEFAULT_EXPIRATION', '4w')
|
||||||
|
|
||||||
|
|
||||||
DOMAINS_DEFAULT_MIN_CACHING_TIME = getattr(settings, 'DOMAINS_DEFAULT_MIN_CACHING_TIME', '1h')
|
DOMAINS_DEFAULT_MIN_CACHING_TIME = getattr(settings, 'DOMAINS_DEFAULT_MIN_CACHING_TIME', '1h')
|
||||||
|
|
||||||
|
|
||||||
DOMAINS_ZONE_PATH = getattr(settings, 'DOMAINS_ZONE_PATH', '/etc/bind/master/%(name)s')
|
DOMAINS_ZONE_PATH = getattr(settings, 'DOMAINS_ZONE_PATH', '/etc/bind/master/%(name)s')
|
||||||
|
|
||||||
|
|
||||||
DOMAINS_MASTERS_PATH = getattr(settings, 'DOMAINS_MASTERS_PATH', '/etc/bind/named.conf.local')
|
DOMAINS_MASTERS_PATH = getattr(settings, 'DOMAINS_MASTERS_PATH', '/etc/bind/named.conf.local')
|
||||||
|
|
||||||
|
|
||||||
DOMAINS_SLAVES_PATH = getattr(settings, 'DOMAINS_SLAVES_PATH', '/etc/bind/named.conf.local')
|
DOMAINS_SLAVES_PATH = getattr(settings, 'DOMAINS_SLAVES_PATH', '/etc/bind/named.conf.local')
|
||||||
|
|
||||||
DOMAINS_MASTERS = getattr(settings, 'DOMAINS_MASTERS', ['10.0.3.13'])
|
|
||||||
|
|
||||||
DOMAINS_CHECKZONE_BIN_PATH = getattr(settings, 'DOMAINS_CHECKZONE_BIN_PATH',
|
DOMAINS_CHECKZONE_BIN_PATH = getattr(settings, 'DOMAINS_CHECKZONE_BIN_PATH',
|
||||||
'/usr/sbin/named-checkzone -i local')
|
'/usr/sbin/named-checkzone -i local')
|
||||||
|
|
||||||
DOMAINS_CHECKZONE_PATH = getattr(settings, 'DOMAINS_CHECKZONE_PATH', '/dev/shm')
|
DOMAINS_CHECKZONE_PATH = getattr(settings, 'DOMAINS_CHECKZONE_PATH', '/dev/shm')
|
||||||
|
|
||||||
|
|
||||||
DOMAINS_DEFAULT_A = getattr(settings, 'DOMAINS_DEFAULT_A', '10.0.3.13')
|
DOMAINS_DEFAULT_A = getattr(settings, 'DOMAINS_DEFAULT_A', '10.0.3.13')
|
||||||
|
|
||||||
|
|
||||||
DOMAINS_DEFAULT_MX = getattr(settings, 'DOMAINS_DEFAULT_MX', (
|
DOMAINS_DEFAULT_MX = getattr(settings, 'DOMAINS_DEFAULT_MX', (
|
||||||
'10 mail.orchestra.lan.',
|
'10 mail.orchestra.lan.',
|
||||||
'10 mail2.orchestra.lan.',
|
'10 mail2.orchestra.lan.',
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
DOMAINS_DEFAULT_NS = getattr(settings, 'DOMAINS_DEFAULT_NS', (
|
DOMAINS_DEFAULT_NS = getattr(settings, 'DOMAINS_DEFAULT_NS', (
|
||||||
'ns1.orchestra.lan.',
|
'ns1.orchestra.lan.',
|
||||||
'ns2.orchestra.lan.',
|
'ns2.orchestra.lan.',
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
DOMAINS_FORBIDDEN = getattr(settings, 'DOMAINS_FORBIDDEN',
|
DOMAINS_FORBIDDEN = getattr(settings, 'DOMAINS_FORBIDDEN',
|
||||||
# This setting prevents users from providing random domain names, i.e. google.com
|
# This setting prevents users from providing random domain names, i.e. google.com
|
||||||
# You can generate a 5K forbidden domains list from Alexa's top 1M
|
# You can generate a 5K forbidden domains list from Alexa's top 1M
|
||||||
# wget http://s3.amazonaws.com/alexa-static/top-1m.csv.zip -O /tmp/top-1m.csv.zip
|
# wget http://s3.amazonaws.com/alexa-static/top-1m.csv.zip -O /tmp/top-1m.csv.zip
|
||||||
# unzip -p /tmp/top-1m.csv.zip | head -n 5000 | sed "s/^.*,//" > forbidden_domains.list
|
# unzip -p /tmp/top-1m.csv.zip | head -n 5000 | sed "s/^.*,//" > forbidden_domains.list
|
||||||
|
|
||||||
# '%(site_root)s/forbidden_domains.list')
|
# '%(site_root)s/forbidden_domains.list')
|
||||||
'')
|
'')
|
||||||
|
|
|
@ -26,7 +26,6 @@ class DomainTestMixin(object):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
djsettings.DEBUG = True
|
djsettings.DEBUG = True
|
||||||
settings.DOMAINS_MASTERS = [self.MASTER_SERVER_ADDR]
|
|
||||||
super(DomainTestMixin, self).setUp()
|
super(DomainTestMixin, self).setUp()
|
||||||
self.domain_name = 'orchestra%s.lan' % random_ascii(10)
|
self.domain_name = 'orchestra%s.lan' % random_ascii(10)
|
||||||
self.domain_records = (
|
self.domain_records = (
|
||||||
|
|
|
@ -1,17 +1,18 @@
|
||||||
from django.test import TestCase
|
from orchestra.utils.tests import BaseTestCase
|
||||||
|
|
||||||
from ..models import Domain
|
from ..models import Domain
|
||||||
|
|
||||||
|
|
||||||
class DomainTests(TestCase):
|
class DomainTest(BaseTestCase):
|
||||||
def setUp(self):
|
def test_top_relation(self):
|
||||||
self.domain = Domain.objects.create(name='rostrepalid.org')
|
account = self.create_account()
|
||||||
|
domain = Domain.objects.create(name='rostrepalid.org', account=account)
|
||||||
Domain.objects.create(name='www.rostrepalid.org')
|
Domain.objects.create(name='www.rostrepalid.org')
|
||||||
Domain.objects.create(name='mail.rostrepalid.org')
|
Domain.objects.create(name='mail.rostrepalid.org')
|
||||||
|
self.assertEqual(2, len(domain.subdomains.all()))
|
||||||
def test_top_relation(self):
|
|
||||||
self.assertEqual(2, len(self.domain.subdomains.all()))
|
|
||||||
|
|
||||||
def test_render_zone(self):
|
def test_render_zone(self):
|
||||||
print self.domain.render_zone()
|
account = self.create_account()
|
||||||
|
domain = Domain.objects.create(name='rostrepalid.org', account=account)
|
||||||
|
domain.render_zone()
|
||||||
|
|
||||||
|
|
|
@ -88,18 +88,16 @@ class BackendLogAdmin(admin.ModelAdmin):
|
||||||
)
|
)
|
||||||
list_display_links = ('id', 'backend')
|
list_display_links = ('id', 'backend')
|
||||||
list_filter = ('state', 'backend')
|
list_filter = ('state', 'backend')
|
||||||
date_hierarchy = 'updated_at'
|
|
||||||
inlines = [BackendOperationInline]
|
inlines = [BackendOperationInline]
|
||||||
fields = [
|
fields = [
|
||||||
'backend', 'server_link', 'state', 'mono_script', 'mono_stdout',
|
'backend', 'server_link', 'state', 'mono_script', 'mono_stdout',
|
||||||
'mono_stderr', 'mono_traceback', 'exit_code', 'task_id', 'display_created',
|
'mono_stderr', 'mono_traceback', 'exit_code', 'task_id', 'display_created',
|
||||||
'display_updated', 'execution_time'
|
'execution_time'
|
||||||
]
|
]
|
||||||
readonly_fields = fields
|
readonly_fields = fields
|
||||||
|
|
||||||
server_link = admin_link('server')
|
server_link = admin_link('server')
|
||||||
display_updated = admin_date('updated_at')
|
display_created = admin_date('created_at', short_description=_("Created"))
|
||||||
display_created = admin_date('created_at')
|
|
||||||
display_state = admin_colored('state', colors=STATE_COLORS)
|
display_state = admin_colored('state', colors=STATE_COLORS)
|
||||||
mono_script = display_mono('script')
|
mono_script = display_mono('script')
|
||||||
mono_stdout = display_mono('stdout')
|
mono_stdout = display_mono('stdout')
|
||||||
|
|
|
@ -52,9 +52,10 @@ def message_user(request, logs):
|
||||||
_('{errors} out of {total} <a href="{url}">banckends</a> has fail to execute.'),
|
_('{errors} out of {total} <a href="{url}">banckends</a> has fail to execute.'),
|
||||||
_('{errors} out of {total} <a href="{url}">banckends</a> have fail to execute.'),
|
_('{errors} out of {total} <a href="{url}">banckends</a> have fail to execute.'),
|
||||||
errors)
|
errors)
|
||||||
|
messages.error(request, mark_safe(msg.format(errors=errors, total=total, url=url)))
|
||||||
else:
|
else:
|
||||||
msg = ungettext(
|
msg = ungettext(
|
||||||
_('{total} <a href="{url}">banckend</a> has been executed.'),
|
_('{total} <a href="{url}">banckend</a> has been executed.'),
|
||||||
_('{total} <a href="{url}">banckends</a> have been executed.'),
|
_('{total} <a href="{url}">banckends</a> have been executed.'),
|
||||||
total)
|
total)
|
||||||
messages.warning(request, mark_safe(msg.format(errors=errors, total=total, url=url)))
|
messages.success(request, mark_safe(msg.format(total=total, url=url)))
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
|
import socket
|
||||||
|
|
||||||
from django.contrib.contenttypes import generic
|
from django.contrib.contenttypes import generic
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from orchestra.core.validators import validate_ip_address, ValidationError
|
||||||
from orchestra.models.fields import NullableCharField
|
from orchestra.models.fields import NullableCharField
|
||||||
from orchestra.utils.apps import autodiscover
|
from orchestra.utils.apps import autodiscover
|
||||||
|
|
||||||
|
@ -28,6 +31,16 @@ class Server(models.Model):
|
||||||
return self.address
|
return self.address
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
def get_ip(self):
|
||||||
|
if self.address:
|
||||||
|
return self.address
|
||||||
|
try:
|
||||||
|
validate_ip_address(self.name)
|
||||||
|
except ValidationError:
|
||||||
|
return socket.gethostbyname(self.name)
|
||||||
|
else:
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class BackendLog(models.Model):
|
class BackendLog(models.Model):
|
||||||
RECEIVED = 'RECEIVED'
|
RECEIVED = 'RECEIVED'
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import ftplib
|
import ftplib
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import socket
|
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
import paramiko
|
import paramiko
|
||||||
|
|
|
@ -159,7 +159,8 @@ function install_requirements () {
|
||||||
selenium \
|
selenium \
|
||||||
xvfbwrapper \
|
xvfbwrapper \
|
||||||
freezegun \
|
freezegun \
|
||||||
coverage"
|
coverage \
|
||||||
|
orchestra-orm==dev"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Make sure locales are in place before installing postgres
|
# Make sure locales are in place before installing postgres
|
||||||
|
|
|
@ -29,6 +29,14 @@ def validate_ipv6_address(value):
|
||||||
raise ValidationError(msg)
|
raise ValidationError(msg)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_ip_address(value):
|
||||||
|
msg = _("%s is not a valid IP address") % value
|
||||||
|
try:
|
||||||
|
ip = IP(value)
|
||||||
|
except:
|
||||||
|
raise ValidationError(msg)
|
||||||
|
|
||||||
|
|
||||||
def validate_name(value):
|
def validate_name(value):
|
||||||
"""
|
"""
|
||||||
A single non-empty line of free-form text with no whitespace.
|
A single non-empty line of free-form text with no whitespace.
|
||||||
|
|
|
@ -12,13 +12,3 @@ Bind9 Master and Slave
|
||||||
mkdir /etc/bind/master
|
mkdir /etc/bind/master
|
||||||
chown bind.bind /etc/bind/master
|
chown bind.bind /etc/bind/master
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Allow zone transfer on master by adding the following line to `named.conf.options`
|
|
||||||
```bash
|
|
||||||
allow-transfer { slave-ip; };
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Addlow notifications on the slave server by adding the following line to `named.conf.options`
|
|
||||||
```bash
|
|
||||||
allow-notify { master-ip; };
|
|
||||||
```
|
|
||||||
|
|
|
@ -1 +1,4 @@
|
||||||
|
MySQL
|
||||||
|
=====
|
||||||
|
|
||||||
apt-get install mysql-server
|
apt-get install mysql-server
|
Loading…
Reference in a new issue