#!/usr/bin/env python

"""Simple console deployment utility.
"""

import Tartarus, Ice
import os, sys, socket, traceback, re
from Tartarus import system
import Tartarus.system.resolv as resolv
import Tartarus.system.consdialog as ask
from Tartarus.system import service
import Tartarus.deploy.client

class Failure(RuntimeError):
    pass

def fail(msg):
    raise Failure(msg)

def fail_unless(expr, msg):
    if not expr:
        fail(msg)
    return expr

def check_deployment(comm, svc, force_name, opts):
    prx = comm.propertyToProxy("Tartarus.deployPrx.%sService" % svc)
    s = Tartarus.iface.core.ServicePrx.checkedCast(prx)
    if not s:
        raise Tartarus.iface.core.ValueError(
                'Bad proxy for %s service' % svc)
    if not s.isConfigured():
        return
    prompt = "%s configuration already exists. Force reinitialization?"
    if not ask.force(prompt % s.getName()):
        fail('Deployment canceled')
    opts[force_name] = 'force'

def check_availability(comm, svc):
    prx = comm.propertyToProxy("Tartarus.deployPrx.%sService" % svc)
    try:
        s = Tartarus.iface.core.ServicePrx.checkedCast(prx)
        if s:
            return
    except Exception:
        pass
    fail("Service not available: Tartarus.%s. "
         "Maybe Tartarus-%s is not installed or "
         "Tartarus service is not started in deployment mode."
         % (svc, svc))



GOOD_USER_NAME = "[A-Za-z]\w{2,30}$"

def ask_user(prompt, default):
    good_name = re.compile(GOOD_USER_NAME)
    while True:
        ans = ask.ask(prompt, default)
        if not good_name.match(ans):
            print 'User name should match ' + repr(GOOD_USER_NAME)
            continue
        return ans

def _is_valid_ip(s):
    l = s.split('.')
    if len(l) != 4:
        return False
    for x in l:
        try:
            i = int(x)
            if i < 0 or i > 255:
                return False
        except ValueError:
            return False
    return True


def configure_recursor(opts):
    if not ask.yesno("Configure DNS server to use upper-level DNS?"):
        return
    ns = resolv.get_nameservers()
    ns = [x for x in ns
          if not x.startswith('127.') and not x.startswith('localhost')]
    while True:
        r = ask.choice("Address of upper-level DNS server:", ns)
        if not isinstance(r, basestring):
            continue
        if not _is_valid_ip(r):
            print "This is not valid IPv4 address."
            continue
        try:
            if socket.inet_aton(r) == socket.inet_aton(opts['ip']):
                print "This address should not coinside with your server"
                continue
        except socket.error:
            continue
        opts['recursor'] = r
        opts['allow_recursion'] = '127.0.0.1/8 %s/%s' % (
                opts['ip'], opts['mask'])
        break

def configure_resolv(opts):
    if ask.yesno("Use local DNS server on this host?"):
        opts['dns_use_self'] = 'true'
    if 'recursor' in opts:
        if ask.yesno("Use %s on this host as a secondary nameserver?"
                     % opts['recursor']):
            opts['dns_use_recursor'] = 'true'


SUPPORTED_MASKS = ['8', '16', '24']

def deploy_options():
    if os.getuid() != 0:
        fail("Only root can do this")
    opts = {}
    opts['domain'] = system.hostname.getdomain()
    if not ask.yesno("This utility will deploy "
                     "Tartarus domain for this server\n"
                     "Domain name: %s\n"
                     "Do you want to proceed?"
                     % opts['domain']):
        sys.exit(0)
    try:
        opts['hostname'] = ".".join([system.hostname.getname(),
                                     opts['domain']])
        addr2mask = dict(system.hostname.getsystemnets())
    except socket.error, e:
        fail("Faild to get address information for this host.\n"
             "The error was: %s\n"
             "Please check your network configuration."
             % e.args[1])

    ip = ask.choice("The ip address of server will be", addr2mask.keys())
    fail_unless(ip, "Deployment canceled!")
    opts['ip'] = ip
    try:
        idx = SUPPORTED_MASKS.index(addr2mask[ip])
    except ValueError:
        idx = SUPPORTED_MASKS.index('24')
    opts['mask'] = ask.choice("The network mask for server will be",
                              SUPPORTED_MASKS, idx)
    fail_unless(opts['mask'], "Deployment canceled!")
    configure_recursor(opts)
    configure_resolv(opts)
    opts['name'] = ask_user("User name for system administrator", "sysadmin")
    opts['password'] = ask.password(
            "Please enter password for system administrator.")
    return opts


def init():
    if len(sys.argv) > 2:
        fail("Too many arguments.\nUsage: %s [config]" % sys.argv[0])
    if len(sys.argv) == 2:
        cfgfile = sys.argv[1]
    else:
        cfgfile = '/etc/Tartarus/clients/deploy.conf'
    p = Ice.createProperties()
    p.load(cfgfile)
    d = Ice.InitializationData()
    d.properties = p
    sp = p.getPropertiesForPrefix("Tartarus.addSlicePath.")
    Tartarus.slices.path += sp.values()
    return Ice.initialize(d)

def _main():
    service.tartarus_start_deploy()
    comm = init()
    services = [
            ('DNS',      'dns_force',   Tartarus.deploy.deploy_sysdb),
            ('SysDB',    'sysdb_force', Tartarus.deploy.deploy_dns),
            ('Kerberos', 'krb_force',   Tartarus.deploy.deploy_kadmin)]
    for svc, _, _ in services:
        check_availability(comm, svc)
    opts = deploy_options()
    for svc, force, _ in services:
        check_deployment(comm, svc, force, opts)
    for svc, _, proc in services:
        print "Configuring %s..." % svc
        proc(comm, opts)
    nameservers = []
    if 'dns_use_self' in opts:
        nameservers.append('127.0.0.1')
    if 'dns_use_recursor' in opts:
        nameservers.append(opts['recursor'])
    if len(nameservers):
        resolv.set_nameservers(nameservers)
    comm.destroy()
    Tartarus.deploy.client.deploy_client_for_server(opts)


def _format_exception():
    et, ev, _  = sys.exc_info()

    if et is Failure:
        msg = ev.message
        code = -1
    elif et is Ice.InitializationException:
        code = -1
        msg = "Failed to initialize runtime: %s" % ev.reason
    elif et is OSError:
        code =  ev.errno
        msg = "OS Error: %s" % ev.strerror
    else:
        code =  -1
        msg = str().join(traceback. format_exception(et, ev, None))

    if not msg.endswith('\n'):
        msg += '\n'

    return (code, msg)

def main():
    try:
        _main()
        sys.exit(0)
    except KeyboardInterrupt:
        msg = "\n\nDeployment canceled!\n"
        code = -1
    except Exception:
        code, msg = _format_exception()
    sys.stderr.write(msg)
    sys.exit(code)

if __name__ == '__main__':
    main()

