#!/usr/bin/env python3

# pylint: disable=c0111

"""
update local configuration files from PLC
"""

import os
import time
import pwd
import grp
from hashlib import sha1 as sha
import xmlrpc.client

import curlwrapper
import logger
import tools
from config import Config

# right after net
priority = 2

class ConfFiles:
    def __init__(self, noscripts=False):
        self.config = Config()
        self.noscripts = noscripts
        self.data = None

    def checksum(self, path):
        try:
            with open(path) as feed:
                return sha(feed.read().encode()).digest()
        except IOError:
            return None

    def system(self, cmd):
        if not self.noscripts and cmd:
            logger.verbose('conf_files: running command %s' % cmd)
            return tools.fork_as(None, os.system, cmd)
        else: return 0

    def update_conf_file(self, cf_rec):
        if not cf_rec['enabled']:
            return
        dest = cf_rec['dest']
        err_cmd = cf_rec['error_cmd']
        mode = int(cf_rec['file_permissions'], base=8)
        try:
            uid = pwd.getpwnam(cf_rec['file_owner'])[2]
        except:
            logger.log('conf_files: cannot find user %s -- %s not updated'
                       %(cf_rec['file_owner'], dest))
            return
        try:
            gid = grp.getgrnam(cf_rec['file_group'])[2]
        except:
            logger.log('conf_files: cannot find group %s -- %s not updated'
                       %(cf_rec['file_group'], dest))
            return
        url = 'https://%s/%s' % (self.config.PLC_BOOT_HOST, cf_rec['source'])
        # set node_id at the end of the request - hacky
        if tools.node_id():
            if url.find('?') > 0:
                url += '&'
            else:
                url += '?'
            url += "node_id=%d"%tools.node_id()
        else:
            logger.log('conf_files: %s -- WARNING, cannot add node_id to request'
                       % dest)
        try:
            logger.verbose("conf_files: retrieving URL=%s"%url)
            contents = curlwrapper.retrieve(url, self.config.cacert)
        except xmlrpc.client.ProtocolError as e:
            logger.log('conf_files: failed to retrieve %s from %s, skipping' % (dest, url))
            return
        if not cf_rec['always_update'] and sha(contents).digest() == self.checksum(dest):
            return
        if self.system(cf_rec['preinstall_cmd']):
            self.system(err_cmd)
            if not cf_rec['ignore_cmd_errors']:
                return
        logger.log('conf_files: installing file %s from %s' % (dest, url))
        try:
            os.makedirs(os.path.dirname(dest))
        except OSError:
            pass
        tools.write_file(dest, lambda f: f.write(contents.decode()),
                         mode=mode, uidgid=(uid, gid))
        if self.system(cf_rec['postinstall_cmd']):
            self.system(err_cmd)

    def run_once(self, data):
        if "conf_files" in data:
            for file in data['conf_files']:
                try:
                    self.update_conf_file(file)
                except:
                    logger.log_exc("conf_files: failed to update conf_file")
        else:
            logger.log_missing_data("conf_files.run_once", 'conf_files')


def start():
    pass


def GetSlivers(data, config=None, plc=None):
    logger.log("conf_files: Running.")
    instance = ConfFiles()
    instance.run_once(data)
    logger.log("conf_files: Done.")


def main():
    from argparse import ArgumentParser
    from plcapi import PLCAPI

    parser = ArgumentParser()
    parser.add_argument('-f', '--config', action='store', dest='config',
                        default='/etc/planetlab/plc_config',
                        help='PLC configuration file')
    parser.add_argument('-k', '--session', action='store', dest='session',
                        default='/etc/planetlab/session',
                        help='API session key (or file)')
    parser.add_argument('--noscripts', action='store_true', dest='noscripts',
                        default=False,
                        help='Do not run pre- or post-install scripts')
    parser.add_argument('--max-attempts', action='store', dest='max_attempts',
                        default=10,
                        help='Max number of attempts')
    parser.add_argument('--period', action='store', dest='period',
                        help='Time in seconds to wait between attempts')
    args = parser.parse_args()

    # Load /etc/planetlab/plc_config
    config = Config(args.config)

    # Load /etc/planetlab/session
    if os.path.exists(args.session):
        with open(args.session) as feed:
            session = feed.read().strip()
    else:
        session = args.session

    # loop until it succeeds once
    # this is a change that comes with python3/fedora29 in late 2018,
    # because although the conf_files service is defined to systemd
    # as a dependency of the network, it triggers too early
    # at a point where eth0 is not ready

    # Initialize XML-RPC client
    attempts = 0
    while True:
        try:
            plc = PLCAPI(config.plc_api_uri, config.cacert, auth=session)
            data = plc.GetSlivers()
            instance = ConfFiles(args.noscripts)
            instance.run_once(data)
            return 0
        except Exception as exc:
            logger.log_exc("Could not receive GetSlivers() from PLC")
            attempts += 1
            if attempts >= args.max_attempts:
                return 1
            logger.log("Waiting for {}s before trying again".format(args.period))
            time.sleep(args.period)


if __name__ == '__main__':
    main()
