#!/usr/bin/env python3

# scripts/config_docker.py
#
# Import/Export script for Docker.io.
#
# @author Andrea Dainese <andrea.dainese@gmail.com>
# @author Alain Degreffe <eczema@ecze.com>
# @copyright 2014-2016 Andrea Dainese
# @copyright 2017-2018 Alain Degreffe
# @license BSD-3-Clause https://github.com/dainok/unetlab/blob/master/LICENSE
# @link http://www.eve-ng.net/
# @version 20181203

import getopt, multiprocessing, os, pexpect, re, subprocess, sys, time

username = 'cisco'
password = 'cisco'
secret = 'cisco'
conntimeout = 3     # Maximum time for console connection
expctimeout = 3     # Maximum time for each short expect
longtimeout = 30    # Maximum time for each long expect
timeout = 60        # Maximum run time (conntimeout is included)

def config_put(docker_pid, config):
    for line in config.split(os.linesep):
        m = re.match(r'^ip ', line, re.M|re.I)
        if m:
            # use nsenter for ip configuration
            cmd = "/opt/unetlab/wrappers/nsenter -t %s -n %s" %(docker_pid, line.replace("'",''))
            p1 = subprocess.Popen([cmd], stdout=subprocess.PIPE, shell=True)
            p1.wait()
            if p1.returncode != 0:
                print('ERROR: error executing "%s".' %(cmd))
                #return False
    return True

def usage():
    print('Usage: %s <standard options>' %(sys.argv[0]));
    print('Standard Options:');
    print('-a <s>    *Action can be:')
    print('           - get: get the startup-configuration and push it to a file')
    print('           - put: put the file as startup-configuration')
    print('-f <s>    *File');
    print('-i <n>    *Docker instance ID');
    print('-t <n>     Timeout (default = %i)' %(timeout));
    print('* Mandatory option')

def now():
    # Return current UNIX time in milliseconds
    return int(round(time.time() * 1000))

def main(action, filename, docker_id):
    try:
        if action == 'put':
            try:
                fd = open(filename, 'r')
                config = fd.read()
                fd.close()
            except:
                print('ERROR: cannot read config from file.')
                sys.exit(1)

            p1 = subprocess.Popen(["/usr/bin/docker -H=tcp://127.0.0.1:4243 inspect --format '{{ .State.Pid }}' %s" %(docker_id)], stdout=subprocess.PIPE, shell=True)
            p1.wait()
            output, error = p1.communicate()
            docker_pid = output.decode("utf-8").rstrip()
            rc = config_put(docker_pid, config)
            if rc != True:
                print('ERROR: failed to push config.')
                sys.exit(1)
            # copy config file into docker
            p1 = subprocess.Popen(["/usr/bin/docker -H=tcp://127.0.0.1:4243 cp %s %s:/startup.sh" %(filename,docker_id)], stdout=subprocess.PIPE, shell=True)
            p1.wait()
            if p1.returncode != 0:
               print('ERROR: error copy startup script')
               sys.exit(1)
            # Execute script
            p1 = subprocess.Popen(["/usr/bin/docker -H=tcp://127.0.0.1:4243 exec -u root %s /bash-static -e /startup.sh" %(docker_id)], stdout=subprocess.PIPE, shell=True)
            p1.wait()
            if p1.returncode != 0:
               print('ERROR: error executing startup script')
               sys.exit(1)

            # Remove lock file
            lock = '%s/.lock' %(os.path.dirname(filename))

            if os.path.exists(lock):
                os.remove(lock)

            # Mark as configured
            configured = '%s/.configured' %(os.path.dirname(filename))
            if not os.path.exists(configured):
                open(configured, 'a').close()

        sys.exit(0)

    except Exception as e:
        print('ERROR: got an exception')
        print(type(e))  # the exception instance
        print(e.args)   # arguments stored in .args
        print(e)        # __str__ allows args to be printed directly,
        return False

if __name__ == "__main__":
    action = None
    filename = None
    port = None

    # Getting parameters from command line
    try:
        opts, args = getopt.getopt(sys.argv[1:], 'a:i:t:f:', ['action=', 'port=', 'timeout=', 'file='])
    except getopt.GetoptError as e:
        usage()
        sys.exit(3)

    for o, a in opts:
        if o in ('-a', '--action'):
            action = a
        elif o in ('-f', '--file'):
            filename = a
        elif o in ('-i', '--id'):
            docker_id = a
        elif o in ('-t', '--timeout'):
            try:
                timeout = int(a)
            except:
                timeout = -1
        else:
            print('ERROR: invalid parameter.')

    # Checking mandatory parameters
    if action == None or docker_id == None or filename == None:
        usage()
        print('ERROR: missing mandatory parameters.')
        sys.exit(1)
    if action not in ['get', 'put']:
        usage()
        print('ERROR: invalid action.')
        sys.exit(1)
    if timeout < 0:
        usage()
        print('ERROR: timeout must be 0 or higher.')
        sys.exit(1)
    if action == 'get' and os.path.exists(filename):
        usage()
        print('ERROR: destination file already exists.')
        sys.exit(1)
    if action == 'put' and not os.path.exists(filename):
        usage()
        print('ERROR: source file does not already exist.')
        sys.exit(1)
    if action == 'put':
        try:
            fd = open(filename, 'r')
            config = fd.read()
            fd.close()
        except:
            usage()
            print('ERROR: cannot read from file.')
            sys.exit(1)

    # Backgrounding the script
    end_before = now() + timeout * 1000
    p = multiprocessing.Process(target=main, name="Main", args=(action, filename, docker_id))
    p.start()

    while (p.is_alive() and now() < end_before):
        # Waiting for the child process to end
        time.sleep(1)

    if p.is_alive():
        # Timeout occurred
        print('ERROR: timeout occurred.')
        p.terminate()
        sys.exit(127)

    if p.exitcode != 0:
        sys.exit(127)

    sys.exit(0)
