def find_config():
    '''Search up the directory hierarchy for .scuba.yml

    Returns: path, rel, config on success, or None if not found
        path    The absolute path of the directory where .scuba.yml was found
        rel     The relative path from the directory where .scuba.yml was found
                to the current directory
        config  The loaded configuration
    '''
    cross_fs = 'SCUBA_DISCOVERY_ACROSS_FILESYSTEM' in os.environ
    path = os.getcwd()

    rel = ''
    while True:
        cfg_path = os.path.join(path, SCUBA_YML)
        if os.path.exists(cfg_path):
            return path, rel, load_config(cfg_path)

        if not cross_fs and os.path.ismount(path):
            msg = '{} not found here or any parent up to mount point {}'.format(SCUBA_YML, path) \
                   + '\nStopping at filesystem boundary (SCUBA_DISCOVERY_ACROSS_FILESYSTEM not set).'
            raise ConfigNotFoundError(msg)

        # Traverse up directory hierarchy
        path, rest = os.path.split(path)
        if not rest:
            raise ConfigNotFoundError('{} not found here or any parent directories'.format(SCUBA_YML))

        # Accumulate the relative path back to where we started
        rel = os.path.join(rest, rel) class ScubaConfig:
    def __init__(self, **data):
        optional_nodes = (
            'image', 'aliases', 'hooks', 'entrypoint', 'environment', 'shell',
            'docker_args', 'volumes',
        )

        # Check for unrecognized nodes
        extra = [n for n in data if not n in optional_nodes]
        if extra:
            raise ConfigError('{}: Unrecognized node{}: {}'.format(SCUBA_YML,
                    's' if len(extra) > 1 else '', ', '.join(extra)))

        self._image = data.get('image')
        self.shell = data.get('shell', DEFAULT_SHELL)
        self.entrypoint = _get_entrypoint(data)
        self.docker_args = _get_docker_args(data)
        self.volumes = _get_volumes(data)
        self._load_aliases(data)
        self._load_hooks(data)
        self._load_environment(data)


    def _load_aliases(self, data):
        self.aliases = {}

        for name, node in data.get('aliases', {}).items():
            if ' ' in name:
                raise ConfigError('Alias names cannot contain spaces')
            self.aliases[name] = ScubaAlias.from_dict(name, node)

    def _load_hooks(self, data):
        self.hooks = {}

        for name in ('user', 'root',):
            node = data.get('hooks', {}).get(name)
            if node:
                hook = _process_script_node(node, name)
                self.hooks[name] = hook

    def _load_environment(self, data):
        self.environment = _process_environment(data.get('environment'), 'environment')


    @property
    def image(self):
        if not self._image:
            raise ConfigError("Top-level 'image' not set")
        return self._image class OverrideMixin:
    '''
    A mixin class that indicates an instance's value should override something

    This class is mixed into objects loaded from YAML with an !override tag,
    and any object can be checked if it is an OverrideMixin using isinstance().
    '''
    pass class ConfigError(Exception):
    pass class ConfigNotFoundError(ConfigError):
    pass def get_image_command(image):
    '''Gets the default command for an image'''
    info = docker_inspect_or_pull(image)
    try:
        return info['Config']['Cmd']
    except KeyError as ke:
        raise DockerError('Failed to inspect image: JSON result missing key {}'.format(ke)) def get_image_entrypoint(image):
    '''Gets the image entrypoint'''
    info = docker_inspect_or_pull(image)
    try:
        return info['Config']['Entrypoint']
    except KeyError as ke:
        raise DockerError('Failed to inspect image: JSON result missing key {}'.format(ke)) def make_vol_opt(hostdir, contdir, options=None):
    '''Generate a docker volume option'''
    vol = '--volume={}:{}'.format(hostdir, contdir)
    if options != None:
        if isinstance(options, str):
            options = (options,)
        vol += ':' + ','.join(options)
    return vol def shell_quote_cmd(cmdlist):
    return ' '.join(map(shell_quote, cmdlist)) def flatten_list(x):
    if not isinstance(x, list):
        raise ValueError("argument is not a list")
    result = []
    for i in x:
        if isinstance(i, list):
            for j in flatten_list(i):
                result.append(j)
        else:
            result.append(i)
    return result def get_umask():
    # Same logic as bash/builtins/umask.def
    val = os.umask(0o22)
    os.umask(val)
    return val def writeln(f, line):
    f.write(line + '\n')
import os
import shutil
import sys
import tempfile
from collections.abc import Mapping
from grp import getgrgid
from io import StringIO
from pwd import getpwuid
from .config import find_config, ScubaConfig, OverrideMixin
from .config import ConfigError, ConfigNotFoundError
from .dockerutil import get_image_command
from .dockerutil import get_image_entrypoint
from .dockerutil import make_vol_opt
from .utils import shell_quote_cmd, flatten_list, get_umask, writeln

        Returns the container-path of the copied file
        '''
        dest = os.path.join(self.__scubadir_hostpath, name)
        assert not os.path.exists(dest)
        shutil.copy2(source, dest)

        return os.path.join(self.__scubadir_contpath, name)


    def __generate_hook_script(self, name, shell):
        script = self.config.hooks.get(name)
        if not script:
            return

        # Generate the hook script, mount it into the container, and tell scubainit
        with self.open_scubadir_file('hooks/{}.sh'.format(name), 'wt') as f:

            self.add_env('SCUBAINIT_HOOK_{}'.format(name.upper()), f.container_path)

            writeln(f, '#!{}'.format(shell))
            writeln(f, '# Auto-generated from .scuba.yml')
            writeln(f, 'set -e')
            for cmd in script:
                writeln(f, cmd)

    def __get_vol_opts(self):
        for hostpath, contpath, options in self.volumes:
            yield hostpath, contpath, options + self.vol_opts

    def get_docker_cmdline(self):
        args = ['docker', 'run',
            # interactive: keep STDIN open
            '-i',

            # remove container after exit
            '--rm',
        ]

        for name,val in self.env_vars.items():
            args.append('--env={}={}'.format(name, val))

        for hostpath, contpath, options in self.__get_vol_opts():
            args.append(make_vol_opt(hostpath, contpath, options))

        for _, vol in self.context.volumes.items():
            args.append(vol.get_vol_opt())

        if self.workdir:
            args += ['-w', self.workdir]

        args += self.options

        # .scuba.yml (top-level or alias)
        if self.context.docker_args is not None:
            args += self.context.docker_args

        # Command-line -d
        if self.docker_args:
            args += self.docker_args

        # Docker image
        args.append(self.context.image)

        # Command to run in container
        args += self.docker_cmd

        return args


class ScubaContext:
    def __init__(self, image=None, script=None, entrypoint=None, environment=None, shell=None, docker_args=None, volumes=None):
        self.image = image
        self.script = script
        self.as_root = False
        self.entrypoint = entrypoint
        self.environment = environment
        self.shell = shell
        self.docker_args = docker_args
        self.volumes = volumes or {}

    @classmethod
    def process_command(cls, cfg, command, image=None, shell=None):
        '''Processes a user command using aliases

        Arguments:
            cfg         ScubaConfig object
            command     A user command list (e.g. argv)
            image       Override the image from .scuba.yml
            shell       Override the shell from .scuba.yml

        Returns: A ScubaContext object with the following attributes:
            script: a list of command line strings
            image: the docker image name to use
        '''
        result = cls(
                entrypoint = cfg.entrypoint,
                environment = cfg.environment.copy(),
                shell = cfg.shell,
                docker_args = cfg.docker_args,
                volumes = cfg.volumes,
                )

        if command:
            alias = cfg.aliases.get(command[0])
            if not alias:
                # Command is not an alias; use it as-is.
                result.script = [shell_quote_cmd(command)]
            else:
                # Using an alias
                # Does this alias override the image and/or entrypoint?
                if alias.image:
                    result.image = alias.image
                if alias.entrypoint is not None:
                    result.entrypoint = alias.entrypoint
                if alias.shell is not None:
                    result.shell = alias.shell
                if alias.as_root:
                    result.as_root = True


                if isinstance(alias.docker_args, OverrideMixin) or result.docker_args is None: