from __future__ import print_function

import json
import logging
import os
import subprocess
import sys
import tempfile
import yaml
from distutils import dir_util
from six.moves import urllib

from segmind.entities import RunStatus, SourceType
from segmind.exceptions import ExecutionException, MlflowException
from segmind.projects import _project_spec
from segmind.projects.submitted_run import LocalSubmittedRun, SubmittedRun
from segmind.projects.utils import (_GIT_URI_REGEX, _expand_uri,
                                    _get_git_repo_url, _get_storage_dir,
                                    _is_file_uri, _is_local_uri,
                                    _is_valid_branch_name, _is_zip_uri,
                                    _parse_subdirectory)
from segmind.tracking import MlflowClient, _tracking_service, _RUN_ID_ENV_VAR, _TRACKING_URI_ENV_VAR, \
    _EXPERIMENT_ID_ENV_VAR, get_tracking_uri
from segmind.tracking.context.default_context import _get_user
from segmind.tracking.context.git_context import _get_git_commit
from segmind.tracking.fluent import _get_experiment_id, active_run
from segmind.utils.file_utils import (path_to_local_file_uri,
                                      path_to_local_sqlite_uri)
from segmind.utils.mlflow_tags import (LEGACY_MLFLOW_GIT_BRANCH_NAME,
                                       LEGACY_MLFLOW_GIT_REPO_URL,
                                       MLFLOW_GIT_BRANCH, MLFLOW_GIT_COMMIT,
                                       MLFLOW_GIT_REPO_URL,
                                       MLFLOW_PARENT_RUN_ID,
                                       MLFLOW_PROJECT_BACKEND,
                                       MLFLOW_PROJECT_ENTRY_POINT,
                                       MLFLOW_SOURCE_NAME, MLFLOW_SOURCE_TYPE,
                                       MLFLOW_USER)

# "conda" if unset
MLFLOW_CONDA_HOME = 'MLFLOW_CONDA_HOME'
_GENERATED_DOCKERFILE_NAME = 'Dockerfile.mlflow-autogenerated'
_PROJECT_TAR_ARCHIVE_NAME = 'mlflow-project-docker-build-context'
_MLFLOW_DOCKER_TRACKING_DIR_PATH = '/mlflow/tmp/mlruns'
_MLFLOW_DOCKER_WORKDIR_PATH = '/mlflow/projects/code/'

_logger = logging.getLogger(__name__)


def _resolve_experiment_id(experiment_name=None, experiment_id=None):
    """Resolve experiment.

    Verifies either one or other is specified - cannot be both selected.

    If ``experiment_name`` is provided and does not exist, an experiment
    of that name is created and its id is returned.

    :param experiment_name: Name of experiment under which to launch the run.
    :param experiment_id: ID of experiment under which to launch the run.
    :return: str
    """

    if experiment_name and experiment_id:
        raise MlflowException(
            "Specify only one of 'experiment_name' or 'experiment_id'.")

    if experiment_id:
        return str(experiment_id)

    if experiment_name:
        client = MlflowClient()
        exp = client.get_experiment_by_name(experiment_name)
        if exp:
            return exp.experiment_id
        else:
            print(
                "INFO: '{}' does not exist. Creating a new experiment".format(
                    experiment_name))
            return client.create_experiment(experiment_name)

    return _get_experiment_id()


def _run(uri,
         experiment_id,
         entry_point='main',
         version=None,
         parameters=None,
         backend=None,
         backend_config=None,
         use_conda=True,
         storage_dir=None,
         synchronous=True,
         run_id=None):
    """Helper that delegates to the project-running method corresponding to the
    passed-in backend.

    Returns a ``SubmittedRun`` corresponding to the project run.
    """

    parameters = parameters or {}
    work_dir = _fetch_project(uri=uri, force_tempdir=False, version=version)
    project = _project_spec.load_project(work_dir)
    _validate_execution_environment(project, backend)  # noqa
    project.get_entry_point(entry_point)._validate_parameters(parameters)
    if run_id:
        active_run = MlflowClient().get_run(run_id)
    else:
        active_run = _create_run(uri, experiment_id, work_dir, entry_point)

    # Consolidate parameters for logging.
    # `storage_dir` is `None` since we want to log actual path not downloaded
    # local path
    entry_point_obj = project.get_entry_point(entry_point)
    final_params, extra_params = entry_point_obj.compute_parameters(
        parameters, storage_dir=None)
    for key, value in (list(final_params.items()) +
                       list(extra_params.items())):
        MlflowClient().log_param(active_run.info.run_id, key, value)

    repo_url = _get_git_repo_url(work_dir)
    if repo_url is not None:
        for tag in [MLFLOW_GIT_REPO_URL, LEGACY_MLFLOW_GIT_REPO_URL]:
            MlflowClient().set_tag(active_run.info.run_id, tag,
                                            repo_url)

    # Add branch name tag if a branch is specified through -version
    if _is_valid_branch_name(work_dir, version):
        for tag in [MLFLOW_GIT_BRANCH, LEGACY_MLFLOW_GIT_BRANCH_NAME]:
            MlflowClient().set_tag(active_run.info.run_id, tag,
                                            version)

    if backend == 'local' or backend is None:
        MlflowClient().set_tag(active_run.info.run_id,
                                        MLFLOW_PROJECT_BACKEND, 'local')
        command_args = []
        command_separator = ' '
        # If a docker_env attribute is defined in MLproject then it takes
        # precedence over conda yaml environments, so the project will be
        # executed inside a docker container.
        if project.docker_env:
            pass
        # MlflowClient().set_tag(active_run.info.run_id,
        #                                 MLFLOW_PROJECT_ENV, 'docker')
        # _validate_docker_env(project)
        # _validate_docker_installation()
        # image = _build_docker_image(
        #     work_dir=work_dir,
        #     repository_uri=project.name,
        #     base_image=project.docker_env.get('image'),
        #     run_id=active_run.info.run_id)
        # command_args += _get_docker_command(
        #     image=image,
        #     active_run=active_run,
        #     volumes=project.docker_env.get('volumes'),
        #     user_env_vars=project.docker_env.get('environment'))
        # Synchronously create a conda environment (even though this may take
        # some time) to avoid failures due to multiple concurrent attempts to
        # create the same conda env.
        # elif use_conda:
        #     MlflowClient().set_tag(active_run.info.run_id,
        #                                     MLFLOW_PROJECT_ENV, 'conda')
        #     command_separator = ' && '
        #     conda_env_name = _get_or_create_conda_env(project.conda_env_path)
        #     command_args += _get_conda_command(conda_env_name)
        # In synchronous mode, run the entry point command in a blocking
        # fashion, sending status updates to the tracking server when finished
        # . Note that the run state may not be persisted to the tracking server
        # if interrupted
        if synchronous:
            command_args += _get_entry_point_command(project, entry_point,
                                                     parameters, storage_dir)
            command_str = command_separator.join(command_args)
            return _run_entry_point(
                command_str,
                work_dir,
                experiment_id,
                run_id=active_run.info.run_id)
        # Otherwise, invoke `mlflow run` in a subprocess
        return _invoke_mlflow_run_subprocess(
            work_dir=work_dir,
            entry_point=entry_point,
            parameters=parameters,
            experiment_id=experiment_id,
            use_conda=use_conda,
            storage_dir=storage_dir,
            run_id=active_run.info.run_id)
    # elif backend == 'kubernetes':
    # from segmind.projects import kubernetes as kb
    # MlflowClient().set_tag(active_run.info.run_id,
    #                                 MLFLOW_PROJECT_ENV, 'docker')
    # MlflowClient().set_tag(active_run.info.run_id,
    #                                 MLFLOW_PROJECT_BACKEND, 'kubernetes')
    # _validate_docker_env(project)
    # _validate_docker_installation()
    # kube_config = _parse_kubernetes_config(backend_config)
    # image = _build_docker_image(
    #     work_dir=work_dir,
    #     repository_uri=kube_config['repository-uri'],
    #     base_image=project.docker_env.get('image'),
    #     run_id=active_run.info.run_id)
    # image_digest = kb.push_image_to_registry(image.tags[0])
    # submitted_run = kb.run_kubernetes_job(
    #     project.name, active_run, image.tags[0], image_digest,
    #     _get_entry_point_command(project, entry_point, parameters,
    #                              storage_dir),
    #     _get_run_env_vars(
    #         run_id=active_run.info.run_uuid,
    #         experiment_id=active_run.info.experiment_id),
    #     kube_config.get('kube-context', None),
    #     kube_config['kube-job-template'])
    # return submitted_run

    supported_backends = ['local', 'kubernetes']
    raise ExecutionException('Got unsupported execution mode %s. Supported '
                             'values: %s' % (backend, supported_backends))


def run(uri,
        entry_point='main',
        version=None,
        parameters=None,
        experiment_name=None,
        experiment_id=None,
        backend=None,
        backend_config=None,
        use_conda=True,
        storage_dir=None,
        synchronous=True,
        run_id=None):
    """Run an MLflow project. The project can be local or stored at a Git URI.

    You can run the project locally or remotely on a Databricks.

    For information on using this method in chained workflows, see `Building
    Multistep Workflows <../projects.html#building-multistep-workflows>`_.

    :raises: :py:class:`segmind_track.exceptions.ExecutionException` If a run
    launched in blocking mode is unsuccessful.

    :Args
    uri: URI of project to run. A local filesystem path
        or a Git repository URI (e.g. https://github.com/mlflow/mlflow-example)
        pointing to a project directory containing an MLproject file.
    entry_point: Entry point to run within the project. If no entry point with
                the specified name is found, runs the project file
                ``entry_point`` as a script, using "python" to run ``.py``
                files and the default shell (specified by environmentvariable
                ``$SHELL``) to run ``.sh`` files.
    version: For Git-based projects, either a commit hash or a branch name.
    experiment_name: Name of experiment under which to launch the run.
    experiment_id: ID of experiment under which to launch the run.
    backend: Execution backend for the run: "local", "databricks", or
                "kubernetes" (experimental). If running against Databricks,
                will run against a Databricks workspace determined as follows:
                if a Databricks tracking URI of the form
                ``databricks://profile`` has been set (e.g. by setting the
                MLFLOW_TRACKING_URI environment variable), will run against
                the workspace specified by <profile>. Otherwise, runs against
                the workspace specified by the default Databricks CLI profile.
    backend_config: A dictionary, or a path to a JSON file (must end in
                    '.json'), which will be passed as config to the backend.
                    The exact content which should be provided is different
                    for each execution backend and is documented at
                    https://www.segmind_track.org/docs/latest/projects.html.
    use_conda: If True (the default), create a new Conda environment for the
                run and install project dependencies within that environment.
                Otherwise, run the project in the current environment without
                installing any project dependencies.
    storage_dir: Used only if ``backend`` is "local". MLflow downloads
                artifacts from distributed URIs passed to parameters of type
                 ``path`` to subdirectories of ``storage_dir``.
    synchronous: Whether to block while waiting for a run to complete.
                Defaults to True. Note that if ``synchronous`` is False and
                ``backend`` is "local", this method will return, but the
                current process will block when exiting until the local run
                completes. If the current process is interrupted, any
                asynchronous runs launched via this method will be terminated.
                If ``synchronous`` is True and the run fails, the current
                process will error out as well.
    run_id: Note: this argument is used internally by the MLflow project APIs
                and should not be specified. If specified, the run ID will be
                used instead of creating a new run.
    """

    cluster_spec_dict = backend_config
    if (backend_config and type(backend_config) != dict
            and os.path.splitext(backend_config)[-1] == '.json'):
        with open(backend_config, 'r') as handle:
            try:
                cluster_spec_dict = json.load(handle)
            except ValueError:
                _logger.error(
                    'Error when attempting to load and parse JSON cluster \
                    spec from file %s', backend_config)
                raise

    experiment_id = _resolve_experiment_id(
        experiment_name=experiment_name, experiment_id=experiment_id)

    submitted_run_obj = _run(
        uri=uri,
        experiment_id=experiment_id,
        entry_point=entry_point,
        version=version,
        parameters=parameters,
        backend=backend,
        backend_config=cluster_spec_dict,
        use_conda=use_conda,
        storage_dir=storage_dir,
        synchronous=synchronous,
        run_id=run_id)
    if synchronous:
        _wait_for(submitted_run_obj)
    return submitted_run_obj


def _wait_for(submitted_run_obj):
    """Wait on the passed-in submitted run, reporting its status to the
    tracking server."""
    run_id = submitted_run_obj.run_id
    active_run = None
    # Note: there's a small chance we fail to report the run's status to the
    # tracking server if
    # we're interrupted before we reach the try block below
    try:
        active_run = MlflowClient().get_run(
            run_id) if run_id is not None else None
        if submitted_run_obj.wait():
            _logger.info("=== Run (ID '%s') succeeded ===", run_id)
            _maybe_set_run_terminated(active_run, 'FINISHED')
        else:
            _maybe_set_run_terminated(active_run, 'FAILED')
            raise ExecutionException("Run (ID '%s') failed" % run_id)
    except KeyboardInterrupt:
        _logger.error("=== Run (ID '%s') interrupted, cancelling run ===",
                      run_id)
        submitted_run_obj.cancel()
        _maybe_set_run_terminated(active_run, 'FAILED')
        raise


def _fetch_project(uri, force_tempdir, version=None):
    """Fetch a project into a local directory, returning the path to the local
    project directory.

    :Args
    force_tempdir: If True, will fetch the project into a temporary directory.
                Otherwise, will fetch ZIP or Git projects into a temporary
                directory but simply return the path of local projects
                (i.e. perform a no-op for local projects).
    """
    parsed_uri, subdirectory = _parse_subdirectory(uri)
    use_temp_dst_dir = force_tempdir or _is_zip_uri(
        parsed_uri) or not _is_local_uri(parsed_uri)
    dst_dir = tempfile.mkdtemp() if use_temp_dst_dir else parsed_uri
    if use_temp_dst_dir:
        _logger.info('=== Fetching project from %s into %s ===', uri, dst_dir)
    if _is_zip_uri(parsed_uri):
        if _is_file_uri(parsed_uri):
            parsed_file_uri = urllib.parse.urlparse(
                urllib.parse.unquote(parsed_uri))
            parsed_uri = os.path.join(parsed_file_uri.netloc,
                                      parsed_file_uri.path)
        _unzip_repo(
            zip_file=(parsed_uri if _is_local_uri(parsed_uri) else
                      _fetch_zip_repo(parsed_uri)),
            dst_dir=dst_dir)
    elif _is_local_uri(uri):
        if version is not None:
            raise ExecutionException(
                'Setting a version is only supported for Git project URIs')
        if use_temp_dst_dir:
            dir_util.copy_tree(src=parsed_uri, dst=dst_dir)
    else:
        assert _GIT_URI_REGEX.match(
            parsed_uri), 'Non-local URI %s should be a Git URI' % parsed_uri
        _fetch_git_repo(parsed_uri, version, dst_dir)
    res = os.path.abspath(os.path.join(dst_dir, subdirectory))
    if not os.path.exists(res):
        raise ExecutionException('Could not find subdirectory %s of %s' %
                                 (subdirectory, dst_dir))
    return res


def _unzip_repo(zip_file, dst_dir):
    import zipfile
    with zipfile.ZipFile(zip_file) as zip_in:
        zip_in.extractall(dst_dir)


def _fetch_zip_repo(uri):
    import requests
    from io import BytesIO

    # TODO (dbczumar): Replace HTTP resolution via ``requests.get`` with an
    # invocation of ```segmind_track.data.download_uri()`` when the API
    # supports the same set of available stores as the artifact repository
    # (Azure, FTP, etc). See the following issue:
    # https://github.com/mlflow/mlflow/issues/763.
    response = requests.get(uri)
    try:
        response.raise_for_status()
    except requests.HTTPError as error:
        raise ExecutionException('Unable to retrieve ZIP file. Reason: %s' %
                                 str(error))
    return BytesIO(response.content)


def _fetch_git_repo(uri, version, dst_dir):
    """Clone the git repo at ``uri`` into ``dst_dir``, checking out commit
    ``version`` (or defaulting to the head commit of the repository's master
    branch if version is unspecified).

    Assumes authentication parameters are specified by the environment, e.g. by
    a Git credential helper.
    """
    # We defer importing git until the last moment, because the import
    # requires that the git executable is availble on the PATH, so we only
    # want to fail if we actually need it.
    import git
    repo = git.Repo.init(dst_dir)
    origin = repo.create_remote('origin', uri)
    origin.fetch()
    if version is not None:
        try:
            repo.git.checkout(version)
        except git.exc.GitCommandError as e:
            raise ExecutionException(
                "Unable to checkout version '%s' of git repo %s"
                '- please ensure that the version exists in the repo. '
                'Error: %s' % (version, uri, e))
    else:
        repo.create_head('master', origin.refs.master)
        repo.heads.master.checkout()
    repo.submodule_update(init=True, recursive=True)


def _maybe_set_run_terminated(active_run, status):
    """If the passed-in active run is defined and still running (i.e. hasn't
    already been terminated within user code), mark it as terminated with the
    passed-in status."""
    if active_run is None:
        return
    run_id = active_run.info.run_id
    cur_status = MlflowClient().get_run(run_id).info.status
    if RunStatus.is_terminated(cur_status):
        return
    MlflowClient().set_terminated(run_id, status)


def _get_entry_point_command(project, entry_point, parameters, storage_dir):
    """Returns the shell command to execute in order to run the specified entry
    point.

    :Args
    project: Project containing the target entry point
    entry_point: Entry point to run
    parameters: Parameters (dictionary) for the entry point command
    storage_dir: Base local directory to use for downloading remote artifacts
                passed to arguments of type 'path'. If None, a temporary base
                directory is used.
    """
    storage_dir_for_run = _get_storage_dir(storage_dir)
    _logger.info(
        '=== Created directory %s for downloading remote URIs passed to '
        "arguments of type 'path' ===", storage_dir_for_run)
    commands = []
    commands.append(
        project.get_entry_point(entry_point).compute_command(
            parameters, storage_dir_for_run))
    return commands


def _run_entry_point(command, work_dir, experiment_id, run_id):
    """Run an entry point command in a subprocess, returning a SubmittedRun
    that can be used to query the run's status.

    :param command: Entry point command to run
    :param work_dir: Working directory in which to run the command
    :param run_id: MLflow run ID associated with the entry point execution.
    """
    env = os.environ.copy()
    env.update(_get_run_env_vars(run_id, experiment_id))
    _logger.info("=== Running command '%s' in run with ID '%s' === ", command,
                 run_id)
    # in case os name is not 'nt', we are not running on windows. It
    # introduces bash command otherwise.
    if os.name != 'nt':
        process = subprocess.Popen(['bash', '-c', command],
                                   close_fds=True,
                                   cwd=work_dir,
                                   env=env)
    else:
        process = subprocess.Popen(
            command, close_fds=True, cwd=work_dir, env=env)
    return LocalSubmittedRun(run_id, process)


def _build_mlflow_run_cmd(uri, entry_point, storage_dir, use_conda, run_id,
                          parameters):
    """Build and return an array containing an ``mlflow run`` command that can
    be invoked to locally run the project at the specified URI."""
    mlflow_run_arr = [
        'mlflow', 'run', uri, '-e', entry_point, '--run-id', run_id
    ]
    if storage_dir is not None:
        mlflow_run_arr.extend(['--storage-dir', storage_dir])
    if not use_conda:
        mlflow_run_arr.append('--no-conda')
    for key, value in parameters.items():
        mlflow_run_arr.extend(['-P', '%s=%s' % (key, value)])
    return mlflow_run_arr


def _run_mlflow_run_cmd(mlflow_run_arr, env_map):
    """Invoke ``mlflow run`` in a subprocess, which in turn runs the entry
    point in a child process.

    Returns a handle to the subprocess. Popen launched to invoke ``mlflow
    run``.
    """
    final_env = os.environ.copy()
    final_env.update(env_map)
    # Launch `mlflow run` command as the leader of its own process group so
    # that we can do a
    # best-effort cleanup of all its descendant processes if needed
    if sys.platform == 'win32':
        return subprocess.Popen(
            mlflow_run_arr,
            env=final_env,
            universal_newlines=True,
            creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)
    else:
        return subprocess.Popen(
            mlflow_run_arr,
            env=final_env,
            universal_newlines=True,
            preexec_fn=os.setsid)


def _create_run(uri, experiment_id, work_dir, entry_point):
    """Create a ``Run`` against the current MLflow tracking server, logging
    metadata (e.g. the URI, entry point, and parameters of the project) about
    the run.

    Return an ``ActiveRun`` that can be used to report additional data about
    the run (metrics/params) to the tracking server.
    """
    if _is_local_uri(uri):
        source_name = _tracking_service.utils._get_git_url_if_present(
            _expand_uri(uri))
    else:
        source_name = _expand_uri(uri)
    source_version = _get_git_commit(work_dir)
    existing_run = active_run()
    if existing_run:
        parent_run_id = existing_run.info.run_id
    else:
        parent_run_id = None

    tags = {
        MLFLOW_USER: _get_user(),
        MLFLOW_SOURCE_NAME: source_name,
        MLFLOW_SOURCE_TYPE: SourceType.to_string(SourceType.PROJECT),
        MLFLOW_PROJECT_ENTRY_POINT: entry_point
    }
    if source_version is not None:
        tags[MLFLOW_GIT_COMMIT] = source_version
    if parent_run_id is not None:
        tags[MLFLOW_PARENT_RUN_ID] = parent_run_id

    active_run = MlflowClient().create_run(
        experiment_id=experiment_id, tags=tags)
    return active_run


def _get_run_env_vars(run_id, experiment_id):
    """Returns a dictionary of environment variable key-value pairs to set in
    subprocess launched to run MLflow projects."""
    return {
        _RUN_ID_ENV_VAR: run_id,
        _TRACKING_URI_ENV_VAR: get_tracking_uri(),
        _EXPERIMENT_ID_ENV_VAR: str(experiment_id),
    }


def _invoke_mlflow_run_subprocess(work_dir, entry_point, parameters,
                                  experiment_id, use_conda, storage_dir,
                                  run_id):
    """Run an MLflow project asynchronously by invoking ``mlflow run`` in a
    subprocess, returning a SubmittedRun that can be used to query run
    status."""
    _logger.info('=== Asynchronously launching MLflow run with ID %s ===',
                 run_id)
    mlflow_run_arr = _build_mlflow_run_cmd(
        uri=work_dir,
        entry_point=entry_point,
        storage_dir=storage_dir,
        use_conda=use_conda,
        run_id=run_id,
        parameters=parameters)
    mlflow_run_subprocess = _run_mlflow_run_cmd(
        mlflow_run_arr, _get_run_env_vars(run_id, experiment_id))
    return LocalSubmittedRun(run_id, mlflow_run_subprocess)


def _get_local_uri_or_none(uri):
    # if uri == 'databricks':
    #     return None, None
    parsed_uri = urllib.parse.urlparse(uri)
    if not parsed_uri.netloc and parsed_uri.scheme in ('', 'file', 'sqlite'):
        path = urllib.request.url2pathname(parsed_uri.path)
        if parsed_uri.scheme == 'sqlite':
            uri = path_to_local_sqlite_uri(_MLFLOW_DOCKER_TRACKING_DIR_PATH)
        else:
            uri = path_to_local_file_uri(_MLFLOW_DOCKER_TRACKING_DIR_PATH)
        return path, uri
    else:
        return None, None


def _parse_kubernetes_config(backend_config):
    """Creates build context tarfile containing Dockerfile and project code,
    returning path to tarfile."""
    if not backend_config:
        raise ExecutionException('Backend_config file not found.')
    kube_config = backend_config.copy()
    if 'kube-job-template-path' not in backend_config.keys():
        raise ExecutionException(
            "'kube-job-template-path' attribute must be specified in "
            'backend_config.')
    kube_job_template = backend_config['kube-job-template-path']
    if os.path.exists(kube_job_template):
        with open(kube_job_template, 'r') as job_template:
            yaml_obj = yaml.safe_load(job_template.read())
        kube_job_template = yaml_obj
        kube_config['kube-job-template'] = kube_job_template
    else:
        raise ExecutionException(
            "Could not find 'kube-job-template-path': {}".format(
                kube_job_template))
    if 'kube-context' not in backend_config.keys():
        _logger.debug('Could not find kube-context in backend_config.'
                      ' Using current context or in-cluster config.')
    if 'repository-uri' not in backend_config.keys():
        raise ExecutionException(
            "Could not find 'repository-uri' in backend_config.")
    return kube_config


def _get_local_artifact_cmd_and_envs(artifact_repo):
    artifact_dir = artifact_repo.artifact_dir
    container_path = artifact_dir
    if not os.path.isabs(container_path):
        container_path = os.path.join(_MLFLOW_DOCKER_WORKDIR_PATH,
                                      container_path)
        container_path = os.path.normpath(container_path)
    abs_artifact_dir = os.path.abspath(artifact_dir)
    return ['-v', '%s:%s' % (abs_artifact_dir, container_path)], {}


__all__ = ['run', 'SubmittedRun']
