#!/usr/bin/env python

import argparse
import syslog
import os.path
import errno
import dimclient
import configparser
import string
import subprocess


def pid_exists(pid):
    if pid < 0:
        return False
    try:
        os.kill(pid, 0)
    except OSError as e:
        return e.errno == errno.EPERM
    else:
        return True


def check_pidfile(pidfile):
    if os.path.isfile(pidfile):
        try:
            pid = int(open(pidfile).readline())
        except:
            remove_pidfile(pidfile)
        else:
            if pid_exists(pid):
                syslog.syslog(syslog.LOG_ERR, "Another instance is running according to pidfile: %s" % pidfile)
                exit(2)
    open(pidfile, 'w+').write(str(os.getpid()))


def remove_pidfile(pidfile):
    os.remove(pidfile)


def write_bind_file(zonefiledir, zone):
    syslog.syslog('Writing BIND file %s' % zone['name'])
    final_path = os.path.join(zonefiledir, zone['name'])
    with open(final_path, 'w') as f:
        f.write(server.zone_dump(zone['name'], view=zone['view']))


def handle_output(output, zonefiledir, includefile, cmds):
    old_zonefiles = set(os.listdir(zonefiledir))
    zones = {}
    for zg in (v['zone_group'] for v in server.output_get_groups(output)):
        for v in server.zone_group_get_views(zg):
            zones[v['zone']] = dict(name=v['zone'],
                                    view=v['view'],
                                    updated=False)
    updates = server.output_update_list(output=output)
    for update in updates:
        zone = update['zone_name']
        if zone in zones:
            zones[zone]['updated'] = True
    # Write zonefiles for changed zones
    for zone in list(zones.values()):
        if zone['name'] not in old_zonefiles or zone['updated']:
            write_bind_file(zonefiledir, zone)
    # Write the includefile
    with open(includefile, 'w') as include:
        for zone in list(zones.keys()):
            zonefile_path = os.path.join(zonefiledir, zone)
            include.write('zone "%(zone)s"  IN { type master; file "%(path)s"; };\n' %
                          dict(zone=zone, path=zonefile_path))
    # Delete updates
    run_command(cmds['delete'], zone)
    server.output_update_delete([u['id'] for u in updates])
    # Delete old zonefiles
    for fname in old_zonefiles - set(zones.keys()):
        syslog.syslog('Removing zonefile %s' % fname)
        os.remove(os.path.join(zonefiledir, fname))

    # when done with the files, run the commands to reload the files
    for zone in list(zones.values()):
        if zone['name'] not in old_zonefiles:
            run_command(cmds['create'], zone)
        else:
            run_command(cmds['updated'], zone)
    for zone in old_zonefiles - set(zones.keys()):
        run_command(cmds['delete'], zone)

def run_command(cmd, zone):
    if len(cmd) == 0:
        return
    cmds = string.split(cmd.format(zone), ' ')
    res = subprocess.call(cmds)


def load_config(params):
    result = {}
    if params.config:
        c = configparser.ConfigParser()
        c.read_file(open(params.config))
        return c['default']
    return params


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('-c', '--config', default='dim-bind-file-agent.conf', help='Set the path to a config file. If not set, all other options are required.')
    params = parser.parse_args()

    config = load_config(params)
    cmds = {
            'create': config['on_create'],
            'update': config['on_update'],
            'delete': config['on_delete']
            }
    for cmd in cmds:
        if len(cmds[cmd]) == 0:
            continue
        parts = string.split(cmds[cmd], ' ')
        try:
            res = subprocess.check_call(["which", parts[0]])
        except subprocess.CalledProcessError as e:
            print(("could not find '{}' for on_{}".format(parts[0], cmd)))
            exit(1)

    if config['pidfile'] is not None:
        check_pidfile(config['pidfile'])
    try:
        server = dimclient.DimClient(config['server'])
        server.login(config['username'], config['password'])
        output_type = server.output_get_attrs(config['output'])['type']
        if output_type != 'bind':
            syslog.syslog(syslog.LOG_WARN, 'Output %s has type %s but it must be bind' % (output, output_type))
            exit(1)
        handle_output(config['output'], config['zonefiledir'], config['includefile'], cmds)
    except Exception as e:
        syslog.syslog(syslog.LOG_ERR, str(e))
        exit(1)
    finally:
        if config['pidfile'] is not None:
            remove_pidfile(config['pidfile'])
