2014-10-17 20:03:41 +00:00
|
|
|
import re
|
2014-10-03 17:37:36 +00:00
|
|
|
import textwrap
|
2014-05-08 16:59:35 +00:00
|
|
|
|
2014-10-03 17:37:36 +00:00
|
|
|
from django.utils.translation import ugettext_lazy as _
|
2014-05-08 16:59:35 +00:00
|
|
|
|
2015-04-05 18:02:36 +00:00
|
|
|
from orchestra.contrib.orchestration import ServiceController, replace
|
2015-04-07 15:14:49 +00:00
|
|
|
from orchestra.contrib.orchestration import Operation
|
2014-05-08 16:59:35 +00:00
|
|
|
|
2014-10-03 17:37:36 +00:00
|
|
|
from . import settings
|
|
|
|
|
2014-05-08 16:59:35 +00:00
|
|
|
|
2014-07-09 16:17:43 +00:00
|
|
|
class Bind9MasterDomainBackend(ServiceController):
|
2014-05-08 16:59:35 +00:00
|
|
|
verbose_name = _("Bind9 master domain")
|
|
|
|
model = 'domains.Domain'
|
|
|
|
related_models = (
|
|
|
|
('domains.Record', 'domain__origin'),
|
|
|
|
('domains.Domain', 'origin'),
|
|
|
|
)
|
2014-10-10 14:39:46 +00:00
|
|
|
ignore_fields = ['serial']
|
2015-04-16 15:46:26 +00:00
|
|
|
CONF_PATH = settings.DOMAINS_MASTERS_PATH
|
2014-05-08 16:59:35 +00:00
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def is_main(cls, obj):
|
|
|
|
""" work around Domain.top self relationship """
|
|
|
|
if super(Bind9MasterDomainBackend, cls).is_main(obj):
|
|
|
|
return not obj.top
|
|
|
|
|
|
|
|
def save(self, domain):
|
|
|
|
context = self.get_context(domain)
|
|
|
|
domain.refresh_serial()
|
2015-04-16 15:46:26 +00:00
|
|
|
self.update_zone(domain, context)
|
|
|
|
self.update_conf(context)
|
|
|
|
|
|
|
|
def update_zone(self, domain, context):
|
2014-05-08 16:59:35 +00:00
|
|
|
context['zone'] = ';; %(banner)s\n' % context
|
2015-04-05 18:02:36 +00:00
|
|
|
context['zone'] += domain.render_zone().replace("'", '"')
|
2014-10-17 13:41:08 +00:00
|
|
|
self.append(textwrap.dedent("""\
|
2014-10-17 20:03:41 +00:00
|
|
|
echo -e '%(zone)s' > %(zone_path)s.tmp
|
|
|
|
diff -N -I'^\s*;;' %(zone_path)s %(zone_path)s.tmp || UPDATED=1
|
2015-03-11 20:01:08 +00:00
|
|
|
# Because bind reload will not display any fucking error
|
|
|
|
named-checkzone -k fail -n fail %(name)s %(zone_path)s.tmp
|
2015-03-10 11:46:48 +00:00
|
|
|
mv %(zone_path)s.tmp %(zone_path)s
|
2015-03-10 16:57:23 +00:00
|
|
|
""") % context
|
|
|
|
)
|
2014-05-08 16:59:35 +00:00
|
|
|
|
|
|
|
def update_conf(self, context):
|
2014-10-17 13:41:08 +00:00
|
|
|
self.append(textwrap.dedent("""\
|
2015-04-07 15:14:49 +00:00
|
|
|
conf='%(conf)s'
|
|
|
|
sed '/zone "%(name)s".*/,/^\s*};\s*$/!d' %(conf_path)s | diff -B -I"^\s*//" - <(echo "${conf}") || {
|
2014-10-20 15:51:24 +00:00
|
|
|
sed -i -e '/zone\s\s*"%(name)s".*/,/^\s*};/d' \\
|
2014-10-27 15:15:22 +00:00
|
|
|
-e 'N; /^\s*\\n\s*$/d; P; D' %(conf_path)s
|
2015-04-07 15:14:49 +00:00
|
|
|
echo "${conf}" >> %(conf_path)s
|
2014-10-17 13:41:08 +00:00
|
|
|
UPDATED=1
|
2015-03-10 16:57:23 +00:00
|
|
|
}""") % context
|
|
|
|
)
|
2014-10-17 20:03:41 +00:00
|
|
|
# Delete ex-top-domains that are now subdomains
|
|
|
|
self.append(textwrap.dedent("""\
|
2014-10-20 15:51:24 +00:00
|
|
|
sed -i -e '/zone\s\s*".*\.%(name)s".*/,/^\s*};\s*$/d' \\
|
2015-03-10 16:57:23 +00:00
|
|
|
-e 'N; /^\s*\\n\s*$/d; P; D' %(conf_path)s""") % context
|
|
|
|
)
|
2014-10-17 20:03:41 +00:00
|
|
|
if 'zone_path' in context:
|
|
|
|
context['zone_subdomains_path'] = re.sub(r'^(.*/)', r'\1*.', context['zone_path'])
|
|
|
|
self.append('rm -f %(zone_subdomains_path)s' % context)
|
2014-05-08 16:59:35 +00:00
|
|
|
|
|
|
|
def delete(self, domain):
|
|
|
|
context = self.get_context(domain)
|
|
|
|
self.append('rm -f %(zone_path)s;' % context)
|
|
|
|
self.delete_conf(context)
|
|
|
|
|
|
|
|
def delete_conf(self, context):
|
2014-10-17 13:09:56 +00:00
|
|
|
if context['name'][0] in ('*', '_'):
|
|
|
|
# These can never be top level domains
|
|
|
|
return
|
2014-10-17 13:41:08 +00:00
|
|
|
self.append(textwrap.dedent("""\
|
2014-10-20 15:51:24 +00:00
|
|
|
sed -e '/zone\s\s*"%(name)s".*/,/^\s*};\s*$/d' \\
|
2015-03-10 16:57:23 +00:00
|
|
|
-e 'N; /^\s*\\n\s*$/d; P; D' %(conf_path)s > %(conf_path)s.tmp""") % context
|
|
|
|
)
|
2014-10-17 13:57:34 +00:00
|
|
|
self.append('diff -B -I"^\s*//" %(conf_path)s.tmp %(conf_path)s || UPDATED=1' % context)
|
2014-05-08 16:59:35 +00:00
|
|
|
self.append('mv %(conf_path)s.tmp %(conf_path)s' % context)
|
|
|
|
|
|
|
|
def commit(self):
|
|
|
|
""" reload bind if needed """
|
2015-03-10 16:57:23 +00:00
|
|
|
self.append('if [[ $UPDATED == 1 ]]; then service bind9 reload; fi')
|
2014-05-08 16:59:35 +00:00
|
|
|
|
2014-10-03 17:37:36 +00:00
|
|
|
def get_servers(self, domain, backend):
|
2015-03-04 21:06:16 +00:00
|
|
|
""" Get related server IPs from registered backend routes """
|
2015-04-05 10:46:24 +00:00
|
|
|
from orchestra.contrib.orchestration.manager import router
|
2015-04-07 15:14:49 +00:00
|
|
|
operation = Operation(backend, domain, Operation.SAVE)
|
2014-10-03 17:37:36 +00:00
|
|
|
servers = []
|
2015-03-04 21:06:16 +00:00
|
|
|
for server in router.get_servers(operation):
|
2014-10-03 17:37:36 +00:00
|
|
|
servers.append(server.get_ip())
|
|
|
|
return servers
|
|
|
|
|
2014-10-04 09:29:18 +00:00
|
|
|
def get_slaves(self, domain):
|
2015-04-16 15:46:26 +00:00
|
|
|
return set(settings.DOMAINS_SLAVES).union(
|
|
|
|
set(self.get_servers(domain, Bind9SlaveDomainBackend))
|
|
|
|
)
|
2014-10-04 09:29:18 +00:00
|
|
|
|
2014-05-08 16:59:35 +00:00
|
|
|
def get_context(self, domain):
|
2014-10-27 13:29:02 +00:00
|
|
|
slaves = self.get_slaves(domain)
|
2014-05-08 16:59:35 +00:00
|
|
|
context = {
|
|
|
|
'name': domain.name,
|
|
|
|
'zone_path': settings.DOMAINS_ZONE_PATH % {'name': domain.name},
|
2014-10-03 14:02:11 +00:00
|
|
|
'subdomains': domain.subdomains.all(),
|
2014-05-08 16:59:35 +00:00
|
|
|
'banner': self.get_banner(),
|
2014-10-27 13:29:02 +00:00
|
|
|
'slaves': '; '.join(slaves) or 'none',
|
|
|
|
'also_notify': '; '.join(slaves) + ';' if slaves else '',
|
2015-04-16 15:46:26 +00:00
|
|
|
'conf_path': self.CONF_PATH,
|
2015-04-05 18:02:36 +00:00
|
|
|
}
|
|
|
|
context['conf'] = textwrap.dedent("""
|
|
|
|
zone "%(name)s" {
|
|
|
|
// %(banner)s
|
|
|
|
type master;
|
|
|
|
file "%(zone_path)s";
|
|
|
|
allow-transfer { %(slaves)s; };
|
|
|
|
also-notify { %(also_notify)s };
|
|
|
|
notify yes;
|
|
|
|
};""") % context
|
|
|
|
return replace(context, "'", '"')
|
2014-05-08 16:59:35 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Bind9SlaveDomainBackend(Bind9MasterDomainBackend):
|
|
|
|
verbose_name = _("Bind9 slave domain")
|
2014-09-24 20:09:41 +00:00
|
|
|
related_models = (
|
|
|
|
('domains.Domain', 'origin'),
|
|
|
|
)
|
2015-04-16 15:46:26 +00:00
|
|
|
CONF_PATH = settings.DOMAINS_SLAVES_PATH
|
2014-05-13 13:46:40 +00:00
|
|
|
|
2014-05-08 16:59:35 +00:00
|
|
|
def save(self, domain):
|
|
|
|
context = self.get_context(domain)
|
|
|
|
self.update_conf(context)
|
|
|
|
|
|
|
|
def delete(self, domain):
|
|
|
|
context = self.get_context(domain)
|
|
|
|
self.delete_conf(context)
|
2014-10-04 09:29:18 +00:00
|
|
|
|
2014-05-08 16:59:35 +00:00
|
|
|
def commit(self):
|
|
|
|
""" ideally slave should be restarted after master """
|
2015-03-10 16:57:23 +00:00
|
|
|
self.append('if [[ $UPDATED == 1 ]]; then { sleep 1 && service bind9 reload; } & fi')
|
2014-05-08 16:59:35 +00:00
|
|
|
|
2014-10-04 09:29:18 +00:00
|
|
|
def get_masters(self, domain):
|
2015-04-16 15:46:26 +00:00
|
|
|
return set(settings.DOMAINS_MASTERS).union(
|
|
|
|
set(self.get_servers(domain, Bind9MasterDomainBackend))
|
|
|
|
)
|
2014-10-04 09:29:18 +00:00
|
|
|
|
2014-05-08 16:59:35 +00:00
|
|
|
def get_context(self, domain):
|
|
|
|
context = {
|
|
|
|
'name': domain.name,
|
2014-10-03 17:37:36 +00:00
|
|
|
'banner': self.get_banner(),
|
|
|
|
'subdomains': domain.subdomains.all(),
|
2014-10-23 21:25:44 +00:00
|
|
|
'masters': '; '.join(self.get_masters(domain)) or 'none',
|
2015-04-16 15:46:26 +00:00
|
|
|
'conf_path': self.CONF_PATH,
|
2015-04-05 18:02:36 +00:00
|
|
|
}
|
|
|
|
context['conf'] = textwrap.dedent("""
|
|
|
|
zone "%(name)s" {
|
|
|
|
// %(banner)s
|
|
|
|
type slave;
|
|
|
|
file "%(name)s";
|
|
|
|
masters { %(masters)s; };
|
|
|
|
allow-notify { %(masters)s; };
|
|
|
|
};""") % context
|
|
|
|
return replace(context, "'", '"')
|