#!/usr/bin/python3


import argparse
import os
import sys
path = os.path.dirname(os.path.realpath(__file__))
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
if path.startswith('/opt'):
    sys.path.append(path)

import confluent.client as client
import confluent.sortutil as sortutil

def cleararm(nr, cli):
    nodes = set([])
    for rsp in cli.read('/noderange/{0}/attributes/current'.format(nr)):
        for node in rsp.get('databynode', {}):
            nodeinfo = rsp['databynode'][node]
            for attr in nodeinfo:
                if attr == 'deployment.apiarmed':
                    curr = nodeinfo[attr].get('value', '')
                    if curr == 'continuous':
                        nodes.add(node)
    noderange = nr
    if nodes:
        noderange += ',-({0})'.format(','.join(nodes))
    for rsp in cli.update('/noderange/{0}/attributes/current'.format(noderange),
                          {'deployment.apiarmed': ''}):
        pass


def armonce(nr, cli):
    nodes = set([])
    for rsp in cli.read('/noderange/{0}/attributes/current'.format(nr)):
        for node in rsp.get('databynode', {}):
            nodeinfo = rsp['databynode'][node]
            for attr in nodeinfo:
                if attr == 'deployment.apiarmed':
                    curr = nodeinfo[attr].get('value', '')
                    if curr == 'continuous':
                        nodes.add(node)
    noderange = nr
    if nodes:
        noderange += ',-({0})'.format(','.join(nodes))
    for rsp in cli.update('/noderange/{0}/attributes/current'.format(noderange),
                          {'deployment.apiarmed': 'once'}):
        pass


def setpending(nr, profile, cli):
    args = {'deployment.pendingprofile': profile}
    if not profile.startswith('genesis-'):
        args['deployment.stagedprofile'] = ''
        args['deployment.profile'] = ''
    for rsp in cli.update('/noderange/{0}/attributes/current'.format(nr),
                          args):
        pass


def clearpending(nr, cli):
    for rsp in cli.update('/noderange/{0}/attributes/current'.format(nr),
                          {'deployment.pendingprofile': ''}):
        pass

def main(args):
    ap = argparse.ArgumentParser(description='Deploy OS to nodes')
    ap.add_argument('-c', '--clear', help='Clear any pending deployment action', action='store_true')
    ap.add_argument('-n', '--network', help='Initiate deployment over PXE/HTTP', action='store_true')
    ap.add_argument('-p', '--prepareonly', help='Prepare only, skip any interaction with a BMC associated with this deployment action', action='store_true')
    ap.add_argument('-m', '--maxnodes', help='Specifiy a maximum nodes to be deployed')
    ap.add_argument('noderange', help='Set of nodes to deploy')
    ap.add_argument('profile', nargs='?', help='Profile name to deploy')
    args, extra = ap.parse_known_args(args)
    if args.profile is None and len(extra) == 1:
        args.profile = extra[0]
        extra = extra[1:]
    if args.profile and not args.network:
        sys.stderr.write('-n is a required argument currently to perform an install, optionally with -p\n')
        return 1
    if not args.profile and args.network:
        sys.stderr.write('Both noderange and a profile name are required arguments to request a network deployment\n')
        return 1
    if extra:
        sys.stderr.write('Unrecognized arguments: ' + repr(extra) + '\n')
    c = client.Command()
    c.stop_if_noderange_over(args.noderange, args.maxnodes)
    if args.clear:
        cleararm(args.noderange, c)
        clearpending(args.noderange, c)
    elif args.profile:
        profnames = []
        for prof in c.read('/deployment/profiles/'):
            profname = prof.get('item', {}).get('href', None)
            if profname:
                profname = profname.replace('/', '')
                profnames.append(profname)
                if profname == args.profile:
                    break
        else:
            sys.stderr.write('The specified profile "{}" is not an available profile\n'.format(args.profile))
            if profnames:
                sys.stderr.write('The following profiles are available:\n')
                for profname in profnames:
                    sys.stderr.write('    ' + profname + '\n')
            else:
                sys.stderr.write('No deployment profiles available, try osdeploy fiimport or imgutil capture\n')
            sys.exit(1)
        armonce(args.noderange, c)
        setpending(args.noderange, args.profile, c)
    else:
        databynode = {}
        for r in c.read('/noderange/{0}/attributes/current'.format(args.noderange)):
            dbn = r.get('databynode', {})
            for node in dbn:
                if node not in databynode:
                    databynode[node] = {}
                for attr in dbn[node]:
                    if attr in ('deployment.pendingprofile', 'deployment.apiarmed', 'deployment.stagedprofile', 'deployment.profile'):
                        databynode[node][attr] = dbn[node][attr].get('value', '')
        for node in sortutil.natural_sort(databynode):
            profile = databynode[node].get('deployment.pendingprofile', '')
            if profile:
                profile = 'pending: {}'.format(profile)
            else:
                profile = databynode[node].get('deployment.stagedprofile', '')
                if profile:
                    profile = 'staged: {}'.format(profile)
                else:
                    profile = databynode[node].get('deployment.profile', '')
                    if profile:
                        profile = 'completed: {}'.format(profile)
                    else:
                        profile= 'No profile pending or applied'
            armed = databynode[node].get('deployment.apiarmed', '')
            if armed in ('once', 'continuous'):
                armed = ' (node authentication armed)'
            else:
                armed = ''
            print('{0}: {1}{2}'.format(node, profile, armed))
        sys.exit(0)
    errnodes = set([])
    if args.network and not args.prepareonly:
        rc = c.simple_noderange_command(args.noderange, '/boot/nextdevice', 'network',
                                        bootmode='uefi',
                                        persistent=False,
                                        errnodes=errnodes)
        if errnodes:
            sys.stderr.write(
                'Unable to set boot device for following nodes: {0}\n'.format(
                    ','.join(errnodes)))
            return 1
        rc |= c.simple_noderange_command(args.noderange, '/power/state', 'boot')
        return rc
    return 0

if __name__ == '__main__':
    sys.exit(main(sys.argv[1:]))