#!/usr/bin/env python3

"""
Runs appropriate onenv_* script in docker.
"""

__author__ = "Michal Cwiertnia"
__copyright__ = "Copyright (C) 2019 ACK CYFRONET AGH"
__license__ = "This software is released under the MIT license cited in " \
              "LICENSE.txt"


import os
import re
import sys
import argparse
import webbrowser
from typing import List
import subprocess as sp

import yaml

from scripts import docker


SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
ONE_ENV_CONTAINER_NAME = 'one-env'
ONE_ENV_CONFIG_FILE = 'one_env_config.yaml'
OPENING_URL_INFO = 'Opening URL: '


def get_environment_vars() -> List[docker.EnvVar]:
    env_variables = []
    env_variables_names = ['HOME', 'SSH_AUTH_SOCK', 'ARTIFACT_REPO_HOST',
                           'ARTIFACT_REPO_PORT', 'bamboo_crossSupportHelmChartVersion', 
                           'bamboo_planRepository_branchName']

    for variable_name in env_variables_names:
        value = os.environ.get(variable_name)
        if value:
            env_variables.append(docker.EnvVar(variable_name, value))

    env_variables.append(docker.EnvVar('ONENV_DIR', SCRIPT_DIR))
    env_variables.append(docker.EnvVar('DEFAULT_CHARTS_VERSION', read_one_env_config('defaultChartsVersion')))

    return env_variables


def get_volumes(path_to_sources: str) -> List[docker.Volume]:
    volumes = [docker.Volume(os.environ.get('HOME'), os.environ.get('HOME'), 'delegated'),
               docker.Volume('/etc/hosts', '/etc/hosts'),
               docker.Volume('/var/run/docker.sock', '/var/run/docker.sock')]

    ssh_auth_sock = os.environ.get('SSH_AUTH_SOCK')
    if ssh_auth_sock:
        volumes.append(docker.Volume(ssh_auth_sock, ssh_auth_sock))

    # this is necessary for builds on bamboo. All builds run in the /mnt
    # directory not in the /home directory so to make any configs and the
    # like present in one-env docker, volume with path_to_sources is created
    if not path_to_sources.startswith(os.path.expanduser('~')):
        volumes.append(docker.Volume(path_to_sources, path_to_sources, 'delegated'))

    return volumes


def run_one_env_docker(path_to_sources: str) -> str:
    # make sure the current user is a member of the docker group so that
    # he can run docker commands inside the container - checking the
    # gid of the docker.sock always returns the same value, regardless
    # if on the host or any container where it was mounted
    try:
        groups = [str(os.stat('/var/run/docker.sock').st_gid)]
    except KeyError:
        # on OSX there is no docker group so pass empty list if cannot get
        # gid of docker group
        groups = []

    try:
        container = docker.run(
            read_one_env_config('onenvImage'),
            work_dir=SCRIPT_DIR,
            name=ONE_ENV_CONTAINER_NAME,
            envs=get_environment_vars(),
            volumes=get_volumes(path_to_sources),
            interactive=True,
            tty=True,
            detach=True,
            network='host',
            groups=groups,
            output=True
        )
    except sp.CalledProcessError as ex:
        print(ex.stdout.decode('utf-8').strip())
        sys.exit(1)
    else:
        exec_cmd_in_one_env_docker(container, ['init'], 'init')
        return container


def exec_cmd_in_one_env_docker(container: str, args: List[str],
                               one_env_command: str) -> int:
    try:
        print_output = one_env_command == 'gui'

        if one_env_command == 'hosts':
            # the 'hosts' command should be run as root as it modifies the /etc/hosts file
            # (root will be used by default if no user is provided)
            user = None
        else:
            # run other commands as the current user so that the artifacts that are
            # created by the process (e.g. one-env config in user home, deployment
            # data or downloaded build artifacts) belong to the current user and
            # then can easily be cleaned by him (otherwise, they would belong to root)
            user = docker.User(str(os.geteuid()), str(os.getegid()))

        res = docker.execute(
            container,
            tty=True,
            interactive=True,
            work_dir=os.getcwd(),
            user=user,
            envs=get_environment_vars(),
            command=['bash', '-c', 'onenv ' + ' '.join(args)],
            output=print_output
        )

        if print_output:
            print(res)
            if OPENING_URL_INFO in res:
                url = re.search(r'{}(.*)'.format(OPENING_URL_INFO),
                                res).group(1)
                webbrowser.open(url)
            return 0
        else:
            return res
    except sp.CalledProcessError as ex:
        print(ex.stdout.decode('utf-8').strip())
        sys.exit(1)


def get_one_env_container(path_to_sources: str) -> str:
    name_filter = docker.Filter('name', ONE_ENV_CONTAINER_NAME)
    container = docker.ps(all_containers=True, quiet=True, output=True,
                          filters=[name_filter])
    if container:
        status = docker.get_container_status(container)
        if status.lower() == docker.RUNNING_STATUS:
            return container
        docker.rm(container, force=True)
    return run_one_env_docker(path_to_sources)


def run_one_env_cmd_in_docker(path_to_sources: str, args: List[str]) -> int:
    one_env_command = args[0] if args else '-h'
    container = get_one_env_container(path_to_sources)
    return exec_cmd_in_one_env_docker(container, args, one_env_command)


def read_one_env_config(key: str) -> str:
    with open(os.path.join(SCRIPT_DIR, ONE_ENV_CONFIG_FILE)) as f:
        one_env_cfg = yaml.load(f, yaml.Loader)
        return one_env_cfg.get(key)


def main() -> None:
    parser = argparse.ArgumentParser(
        prog='onenv',
        add_help=False,
        description='Wrapper responsible for starting appropriate onenv_* '
                    'command in one-env docker container.'
    )

    parser.add_argument(
        '--path-to-sources',
        help='path to sources to be mounted on onenv container',
        default=os.getcwd(),
        dest='path'
    )

    (args, rest) = parser.parse_known_args()
    path_to_sources = os.path.normpath(os.path.join(os.getcwd(), args.path))
    ret = run_one_env_cmd_in_docker(path_to_sources, rest)
    sys.exit(ret)


if __name__ == '__main__':
    main()
