#!/usr/bin/python
'''bcfg2-admin is a script that helps to administrate a bcfg2 deployment'''

import difflib, lxml.etree, os, socket, sys, ConfigParser
import Bcfg2.Server.Core, Bcfg2.Logging

usage = '''
bcfg2-admin [options]
init      initialize the bcfg2 repository( this is interactive; only run once )
pull <client> <entry type> <entry name>
'''

config = '''
[server]
repository = %s
structures = Bundler,Base
generators = SSHbase,Cfg,Pkgmgr,Svcmgr,Rules

[statistics]
sendmailpath = /usr/sbin/sendmail

[communication]
protocol = xmlrpc/ssl
password = %s
key = /etc/bcfg2.key

[components]
bcfg2 = %s
'''

groups = '''
<Groups version='3.0'>
   <Group profile='true' public='false' name='basic'>
      <Group name='%s'/>
   </Group>
   <Group name='ubuntu' toolset='debian'/>
   <Group name='debian' toolset='debian'/>
   <Group name='redhat' toolset='rh'/>
   <Group name='suse' toolset='rh'/>
   <Group name='mandrake' toolset='rh'/>
   <Group name='solaris' toolset='solaris'/>
</Groups>
'''
clients = '''
<Clients version="3.0">
   <Client profile="basic" pingable="Y" pingtime="0" name="%s"/>
</Clients>
'''

prompt = '''
please select which os your machine is running:
a. Redhat/Fedora/RHEL/RHAS/Centos
b. SUSE/SLES
c. Mandrake
d. Debian
e. Ubuntu
f. Solaris
'''

def err_exit(msg):
    print msg
    raise SystemExit, 1

#build bcfg2.conf file
def initialize_repo():
    '''Setup a new repo'''
    repo = raw_input( "location of bcfg2 repository [/var/lib/bcfg2]: " )
    if repo == '':
        repo = '/var/lib/bcfg2'

    password = ''
    while ( password == '' ):
        password = raw_input( "please provide the password used for communication verification: " )

    #get the hostname
    server = "https://%s:6789" % socket.getfqdn()
    uri = raw_input( "please provide the server location[%s]: " % server)
    if uri == '':
        uri = server

    open("/etc/bcfg2.conf","w").write(config % ( repo, password, uri ))

    #generate the ssl key
    print "Now we will generate the ssl key used for secure communitcation"
    os.popen('openssl req -x509 -nodes -days 1000 -newkey rsa:1024 -out /etc/bcfg2.key -keyout /etc/bcfg2.key')
    try:
        os.chmod('/etc/bcfg2.key','0600')
    except:
        pass
    
    #create the repo dirs
    for subdir in ['SSHbase', 'Cfg', 'Pkgmgr', 'Svcmgr', 'Rules', 'etc', 'Metadata' ]:
        path = "%s/%s" % (repo, subdir)
        newpath = ''
        for subdir in path.split('/'):
            newpath = newpath + subdir + '/'
            try:
                os.mkdir(newpath)
            except:
                pass
            
    #create the groups.xml file
    selection = ''
    while ( selection == '' ):
        print prompt
        selection = raw_input(" selection: ")
        if selection.lower() not in 'abcde':
            selection = ''
    if selection.lower() == 'a':
        selection = 'redhat'
    elif selection.lower() == 'b':
        selection = 'suse'
    elif selection.lower() == 'c':
        selection = 'mandrake'
    elif selection.lower() == 'd':
        selection = 'debian'
    elif selection.lower() == 'e':
        selection = 'ubuntu'
    elif selection.lower() == 'f':
        selection = 'solaris'

    open("%s/Metadata/groups.xml"%repo, "w").write(groups%selection)

    #now the clients file
    open("%s/Metadata/clients.xml"%repo, "w").write(clients%socket.getfqdn())

def update_file(path, diff):
    '''Update file at path using diff'''
    newdata = '\n'.join(difflib.restore(diff.split('\n'), 1))
    print "writing file, %s" % path
    open(path, 'w').write(newdata)

def do_pull(client, etype, ename):
    '''Make currently recorded client state correct for entry'''
    cfile = '/etc/bcfg2.conf'
    cfp = ConfigParser.ConfigParser()
    cfp.read(cfile)
    repo = cfp.get('server', 'repository')
    stats = lxml.etree.parse("%s/etc/statistics.xml" % (repo))
    hostent = stats.xpath('.//Node[@name="%s"]' % client)
    if not hostent:
        print "Could not find stats for client %s" % (client)
    sdata = hostent[0]
    if sdata.xpath('.//Statistics[@state="dirty"]'):
        state = 'dirty'
    else:
        state = 'clean'
    # need to pull entry out of statistics
    entry = sdata.xpath('.//Statistics[@state="%s"]/Bad/%s[@name="%s"]' % \
                        (state, etype, ename))
    if not entry:
        err_exit("Could not find state data for entry; rerun bcfg2 on client system")
        
    # fail for unsupported etypes
    if etype not in ['ConfigFile', 'Package']:
        err_exit("Unsupported entry type %s" % (etype))
    try:
        bcore = Bcfg2.Server.Core.Core({}, cfile)
    except Bcfg2.Server.Core.CoreInitError, msg:
        print "Core load failed because %s" % msg
        raise SystemExit, 1
    [bcore.fam.Service() for x in range(10)]
    while bcore.fam.Service():
        pass
    m = bcore.metadata.get_metadata(client)
    # find appropriate location in repo
    if etype == 'ConfigFile':
        rversion = lxml.etree.Element('ConfigFile', name=ename)
        bcore.Bind(rversion, m)
        current = rversion.text
        diff = entry[0].get('current_diff')[1:-1]
        basefile = [frag for frag in \
                    bcore.plugins['Cfg'].entries[ename].fragments \
                    if frag.applies(m)][-1]
        if ".H_%s" % (m.hostname) in basefile.name:
            answer = raw_input("Found host-specific file %s; Should it be updated (n/Y): ")
            if answer in 'Yy':
                update_file(basefile.name, diff)
            else:
                raise SystemExit, 1
        else:
            # there are two possibilities
            msg = "Should this change apply to this host of all hosts effected by file %s? (N/y): " % (basefile.name)
            choice = raw_input(msg)
            if choice in 'Yy':
                newname = basefile.name
            else:
                # figure out host-specific filename
                if '.G_' in basefile.name:
                    idx = basefile.name.find(".G_")
                    newname = basefile.name[:idx] + ".H_%s" % (m.hostname)
                else:
                    newname = basefile.name + ".H_%s" % (m.hostname)
            print "This file will be installed as file %s" % newname
            if raw_input("Should it be installed? (N/y): ") in 'Yy':
                update_file(newname, diff)
    else:
        err_exit("Don't support entry type %s yet" % etype)
    # svn commit if running under svn
    
if __name__ == '__main__':
    Bcfg2.Logging.setup_logging('bcfg2-admin', to_console=True)
    if sys.argv[1] == "init":
        initialize_repo()
    elif sys.argv[1] == 'pull':
        if len(sys.argv) != 5:
            print usage
            raise SystemExit, 1
        do_pull(sys.argv[2], sys.argv[3], sys.argv[4])
    else:
        print usage
        
