Make dhcp-sync and zone-sync management commands work. Add diff to emails sent to admins.
This commit is contained in:
1 parent
3b9ed91e0c
commit
1ef39a9c89
3 files changed
+160
-164
No files matched your search
@@ -2,7 +2,7 @@ A Django app for managing computer equipment.
|
||||
Generates DHCP configs and zone files.
|
||||
|
||||
Dependencies:
|
||||
sudo apt-get install libmysqlclient-dev python-dev libldap2-dev libsasl2-dev
|
||||
sudo apt-get install libmysqlclient-dev python-dev libldap2-dev libsasl2-dev bind9utils
|
||||
|
||||
Setup environment:
|
||||
mdb$ virtualenv venv
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
from __future__ import with_statement
|
||||
from contextlib import closing
|
||||
|
||||
import os
|
||||
import sys
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from difflib import context_diff
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.core.mail import mail_admins
|
||||
from optparse import make_option
|
||||
from commands import getstatusoutput
|
||||
@@ -23,7 +24,7 @@ class Command(BaseCommand):
|
||||
action='store_true',
|
||||
dest='force',
|
||||
default=False,
|
||||
help='Write to output even if serial is unchanged.'),
|
||||
help='Write zone files even if serial is unchanged.'),
|
||||
make_option('--restart',
|
||||
action='store_true',
|
||||
dest='restart',
|
||||
@@ -35,8 +36,7 @@ class Command(BaseCommand):
|
||||
output = options['output'] if options['output'] else sys.stdout
|
||||
force = options['force']
|
||||
restart = options['restart']
|
||||
|
||||
dhcpd_init = "/etc/init.d/dhcp3-server"
|
||||
restart_command = "/etc/init.d/dhcp3-server restart"
|
||||
|
||||
config = DhcpConfig.objects.filter(name="default").get()
|
||||
|
||||
@@ -45,27 +45,52 @@ class Command(BaseCommand):
|
||||
print "Nothing to do, serial is the same: %s" % config.serial
|
||||
sys.exit(0)
|
||||
|
||||
# write config
|
||||
# write to stdout
|
||||
if output == sys.stdout:
|
||||
print config.dhcpd_configuration()
|
||||
else:
|
||||
with closing(open(output, 'w')) as f:
|
||||
f.write(config.dhcpd_configuration())
|
||||
print 'Not writing to file, serial not updated'
|
||||
return
|
||||
|
||||
# read current file contents
|
||||
try:
|
||||
with open(output, 'r') as f:
|
||||
old = f.read()
|
||||
except IOError:
|
||||
old = ''
|
||||
|
||||
# write to file
|
||||
new = config.dhcpd_configuration()
|
||||
with open(output, 'w') as f:
|
||||
f.write(new)
|
||||
|
||||
# calculate diff
|
||||
diff = "\n".join(context_diff(
|
||||
a=old.splitlines(),
|
||||
b=new.splitlines(),
|
||||
fromfile=config.active_serial,
|
||||
tofile=config.serial,
|
||||
lineterm=''))
|
||||
|
||||
print diff
|
||||
|
||||
# update serial
|
||||
old_serial = config.active_serial
|
||||
config.active_serial = config.serial
|
||||
config.save()
|
||||
|
||||
# restart the dhcp server?
|
||||
if restart:
|
||||
status, output = getstatusoutput("%s restart" % dhcpd_init)
|
||||
status, output = getstatusoutput(restart_command)
|
||||
if status != 0:
|
||||
mail_admins(
|
||||
"Failed to restart dhcp3-server",
|
||||
"Failed to restart dhcp3-server, please inspect...\n\n" + output)
|
||||
"FAIL: dhcpd %s -> %s" % (old_serial, config.active_serial),
|
||||
"Failed to restart dhcp3-server, please inspect...\n\n",
|
||||
"%s@%s# %s" % (os.getlogin(), os.uname()[1], restart_command) +
|
||||
"\n%s\n\n%s" % (output, diff))
|
||||
sys.exit(1)
|
||||
else:
|
||||
mail_admins(
|
||||
"Update dhcpd configuration success!",
|
||||
"Updated dhcpd configuration to serial %d." % config.active_serial)
|
||||
"Success: dhcpd %s -> %s" % (old_serial, config.active_serial),
|
||||
"Updated dhcpd configuration to serial %d\n\n" % config.active_serial +
|
||||
"%s" % diff)
|
||||
sys.exit(0)
|
||||
@@ -1,9 +1,10 @@
|
||||
from __future__ import with_statement
|
||||
from contextlib import closing
|
||||
|
||||
import os
|
||||
import sys
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from itertools import chain
|
||||
from difflib import context_diff
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.core.mail import mail_admins
|
||||
from optparse import make_option
|
||||
from commands import getstatusoutput
|
||||
@@ -17,170 +18,140 @@ class Command(BaseCommand):
|
||||
option_list = BaseCommand.option_list + (
|
||||
make_option('--debug',
|
||||
action='store_true',
|
||||
dest='force',
|
||||
dest='debug',
|
||||
default=False,
|
||||
help='Debug mode.'),
|
||||
help='Debug mode. Writes to /tmp and does not reload.'),
|
||||
)
|
||||
|
||||
bind_bin = "/etc/init.d/bind9"
|
||||
reload_command = "%s reload" % bind_bin
|
||||
checkzone_bin = "/usr/sbin/named-checkzone"
|
||||
checkzone_dir = "/tmp/mdb-checkzone"
|
||||
debug = False
|
||||
debug_dir = '/tmp/mdb-debug'
|
||||
changes = {}
|
||||
|
||||
def handle(self, *args, **options):
|
||||
debug = options['debug']
|
||||
self.debug = debug = options['debug']
|
||||
|
||||
bind_init = "/etc/init.d/bind9 %s"
|
||||
# do the binaries exist?
|
||||
for f in (self.checkzone_bin, self.bind_bin):
|
||||
if not os.path.isfile(f):
|
||||
print "ERROR: no such file %s, exiting..." % f
|
||||
if not debug:
|
||||
sys.exit(1)
|
||||
|
||||
error_log_file = "/tmp/zonecheck/zonecheck-fail-%s-%s"
|
||||
# create directories if needed
|
||||
os.path.isdir(self.checkzone_dir) or os.mkdir(self.checkzone_dir)
|
||||
debug and (os.path.isdir(self.debug_dir) or os.mkdir(self.debug_dir))
|
||||
|
||||
zone_check_command = "/usr/sbin/named-checkzone %s %s"
|
||||
zone_check_temp_dir = "/tmp/zonecheck"
|
||||
zone_check_temp_file = "%s/%s" % (zone_check_temp_dir, "zone")
|
||||
|
||||
debug = False
|
||||
|
||||
if not os.path.isfile((zone_check_command % ("", "")).strip()):
|
||||
print "ERROR: cannot find zone checking tool, exiting..."
|
||||
if not debug:
|
||||
sys.exit(1)
|
||||
|
||||
if not os.path.isfile((bind_init % "").strip()):
|
||||
print "ERROR: cannot find bind init script, exiting..."
|
||||
if not debug:
|
||||
sys.exit(1)
|
||||
|
||||
if not os.path.isdir(zone_check_temp_dir):
|
||||
os.mkdir(zone_check_temp_dir)
|
||||
|
||||
reload_bind = False
|
||||
|
||||
for domain in Domain.objects.all():
|
||||
if domain.domain_serial == domain.domain_active_serial and not debug:
|
||||
# update zones with new serials
|
||||
for zone in chain(Domain.objects.all(),
|
||||
Ip4Subnet.objects.all(),
|
||||
Ip6Subnet.objects.all()):
|
||||
if zone.domain_serial == zone.domain_active_serial:
|
||||
continue
|
||||
self.update_zone(zone)
|
||||
|
||||
print "updating domain %s [%d -> %d]" % (
|
||||
domain.domain_name,
|
||||
domain.domain_active_serial,
|
||||
domain.domain_serial)
|
||||
# join errors and diffs
|
||||
errors = "\n\n\n".join(
|
||||
[v['errors'] for k, v in self.changes.items() if v['errors']])
|
||||
diffs = "\n\n\n".join(
|
||||
[v['diff'] for k, v in self.changes.items() if v['diff']])
|
||||
|
||||
sys.stdout.write("\t- validating...")
|
||||
zonecheck = open(zone_check_temp_file, "w")
|
||||
zonecheck.write(domain.zone_file_contents())
|
||||
zonecheck.close()
|
||||
|
||||
result = check_zone(domain.domain_name, zone_check_temp_file)
|
||||
if result['value'] != 0:
|
||||
sys.stdout.write("fail\n")
|
||||
log = open(error_log_file % (
|
||||
domain.domain_name,
|
||||
domain.domain_serial), "w")
|
||||
log.write(domain.zone_file_contents())
|
||||
log.close()
|
||||
continue
|
||||
|
||||
sys.stdout.write("ok\n")
|
||||
|
||||
sys.stdout.write("\t- writing zone file...")
|
||||
zonefile = open(domain.domain_filename, "w")
|
||||
zonefile.write(domain.zone_file_contents())
|
||||
zonefile.close()
|
||||
sys.stdout.write("ok\n")
|
||||
|
||||
reload_bind = True
|
||||
|
||||
if not debug:
|
||||
domain.domain_active_serial = domain.domain_serial
|
||||
domain.save()
|
||||
|
||||
for subnet in Ip4Subnet.objects.all():
|
||||
if subnet.domain_serial == subnet.domain_active_serial and not debug:
|
||||
continue
|
||||
print "updating subnet %s [%d -> %d]" % (
|
||||
subnet.domain_name,
|
||||
subnet.domain_active_serial,
|
||||
subnet.domain_serial)
|
||||
|
||||
sys.stdout.write("\t- validating...")
|
||||
zonecheck = open(zone_check_temp_file, "w")
|
||||
zonecheck.write(subnet.zone_file_contents())
|
||||
zonecheck.close()
|
||||
sys.stdout.write("ok\n")
|
||||
|
||||
result = check_zone(subnet.domain_name, zone_check_temp_file)
|
||||
if result['value'] != 0:
|
||||
sys.stdout.write("fail\n")
|
||||
log = open(error_log_file % (
|
||||
subnet.domain_name,
|
||||
subnet.domain_serial), "w")
|
||||
log.write(subnet.zone_file_contents())
|
||||
log.close()
|
||||
continue
|
||||
|
||||
sys.stdout.write("\t- writing zone file...")
|
||||
zonefile = open(subnet.domain_filename, "w")
|
||||
zonefile.write(subnet.zone_file_contents())
|
||||
zonefile.close()
|
||||
sys.stdout.write("ok\n")
|
||||
|
||||
reload_bind = True
|
||||
|
||||
if not debug:
|
||||
subnet.domain_active_serial = subnet.domain_serial
|
||||
subnet.save()
|
||||
|
||||
for subnet in Ip6Subnet.objects.all():
|
||||
if subnet.domain_serial == subnet.domain_active_serial and not debug:
|
||||
continue
|
||||
print "updating subnet %s [%d -> %d]" % (
|
||||
subnet.domain_name,
|
||||
subnet.domain_active_serial,
|
||||
subnet.domain_serial)
|
||||
|
||||
sys.stdout.write("\t- validating...")
|
||||
zonecheck = open(zone_check_temp_file, "w")
|
||||
zonecheck.write(subnet.zone_file_contents())
|
||||
zonecheck.close()
|
||||
sys.stdout.write("ok\n")
|
||||
|
||||
print subnet.zone_file_contents()
|
||||
|
||||
result = check_zone(subnet.domain_name, zone_check_temp_file)
|
||||
if result['value'] != 0:
|
||||
sys.stdout.write("fail\n")
|
||||
log = open(error_log_file % (
|
||||
subnet.domain_name,
|
||||
subnet.domain_serial), "w")
|
||||
log.write(subnet.zone_file_contents())
|
||||
log.close()
|
||||
continue
|
||||
|
||||
sys.stdout.write("\t- writing zone file...")
|
||||
zonefile = open(subnet.domain_filename, "w")
|
||||
zonefile.write(subnet.zone_file_contents())
|
||||
zonefile.close()
|
||||
sys.stdout.write("ok\n")
|
||||
|
||||
reload_bind = True
|
||||
|
||||
if not debug:
|
||||
subnet.domain_active_serial = subnet.domain_serial
|
||||
subnet.save()
|
||||
|
||||
if reload_bind and not debug:
|
||||
sys.stdout.write("restarting bind...")
|
||||
status, output = getstatusoutput(bind_init % "reload")
|
||||
# reload and send email with changes
|
||||
if self.changes and not debug:
|
||||
sys.stdout.write("reloading bind... ")
|
||||
status, output = getstatusoutput(self.reload_command)
|
||||
if status != 0:
|
||||
sys.stdout.write("fail\n")
|
||||
mail_admins(
|
||||
"Failed to reload bind",
|
||||
"Failed to reload bind, plase inspect...\n\n" + output)
|
||||
"FAIL: bind (%s changed zones)" % len(self.changes),
|
||||
"Failed to reload bind, please inspect...\n\n" +
|
||||
"%s@%s# %s" % (
|
||||
os.getlogin(), os.uname()[1], self.reload_command) +
|
||||
"\n%s\n\n\n%s\n\n\n%s" % (output, errors, diffs))
|
||||
sys.exit(1)
|
||||
else:
|
||||
sys.stdout.write("ok\n")
|
||||
mail_admins(
|
||||
"Successfully updated bind zone files",
|
||||
"Bind zone files has been updated")
|
||||
"Success: bind (%s changed zones)" % len(self.changes),
|
||||
"Successfully updated bind zone files\n\n" +
|
||||
"%s\n\n%s" % (errors, diffs))
|
||||
sys.exit(0)
|
||||
|
||||
def update_zone(self, zone):
|
||||
zonetype = getattr(zone._meta, 'verbose_name', None) \
|
||||
or zone._meta.model_name
|
||||
|
||||
def check_zone(zone, filename):
|
||||
status, output = getstatusoutput(zone_check_command % (zone, filename))
|
||||
self.changes[zone.domain_name] = {
|
||||
'old_serial': zone.domain_active_serial,
|
||||
'new_serial': zone.domain_serial,
|
||||
'success': False,
|
||||
'errors': None,
|
||||
'diff': None,
|
||||
}
|
||||
|
||||
if debug and status != 0:
|
||||
print output
|
||||
print "updating %s %s [%d -> %d]" % (
|
||||
zonetype,
|
||||
zone.domain_name,
|
||||
zone.domain_active_serial,
|
||||
zone.domain_serial)
|
||||
|
||||
return {'value': status, 'output': output}
|
||||
# generate zone file contents
|
||||
new = zone.zone_file_contents()
|
||||
|
||||
# read current file contents
|
||||
try:
|
||||
with open(zone.domain_filename, 'r') as f:
|
||||
old = f.read()
|
||||
except IOError:
|
||||
old = ''
|
||||
|
||||
# calculate diff
|
||||
diff = "\n".join(context_diff(
|
||||
a=old.splitlines(),
|
||||
b=new.splitlines(),
|
||||
fromfile=str(zone.domain_active_serial) + '-' + zone.domain_name,
|
||||
tofile=str(zone.domain_serial) + '-' + zone.domain_name,
|
||||
lineterm=''))
|
||||
self.changes[zone.domain_name]['diff'] = diff
|
||||
|
||||
# validate with zonecheck
|
||||
sys.stdout.write("\t- validating... ")
|
||||
status, output = self.check_zone(zone, new)
|
||||
if status != 0:
|
||||
sys.stdout.write("fail\n")
|
||||
self.changes[zone.domain_name]['errors'] = output
|
||||
return False
|
||||
sys.stdout.write("ok\n")
|
||||
|
||||
# write to zone file
|
||||
sys.stdout.write("\t- writing zone file... ")
|
||||
|
||||
if not self.debug:
|
||||
zonefile = zone.domain_filename
|
||||
else:
|
||||
zonefile = self.debug_dir + zone.domain_filename
|
||||
path = "/".join(zonefile.split('/')[:-1])
|
||||
os.path.isdir(path) or os.makedirs(path)
|
||||
|
||||
with open(zonefile, 'w') as f:
|
||||
f.write(new)
|
||||
|
||||
# update serial
|
||||
if not self.debug:
|
||||
zone.domain_active_serial = zone.domain_serial
|
||||
zone.save()
|
||||
|
||||
sys.stdout.write("ok\n")
|
||||
self.changes[zone.domain_name]['success'] = True
|
||||
return True
|
||||
|
||||
def check_zone(self, zone, contents):
|
||||
zonefile = self.checkzone_dir + '/' + zone.domain_name
|
||||
with open(zonefile, 'w') as f:
|
||||
f.write(contents)
|
||||
|
||||
return getstatusoutput("%s %s %s" % (
|
||||
self.checkzone_bin, zone.domain_name, zonefile))
|
||||
Reference in new issue
Block a user