Repository: mlflow/mlflow

Issue:
[FR] Run Docker with -t argument
**Please fill in this feature request template to ensure a timely and thorough response.**

## Willingness to contribute

- [ ] Yes. I can contribute this feature independently.
- [X] Yes. I would be willing to contribute this feature with guidance from the MLflow community.
- [ ] No. I cannot contribute this feature at this time.

## Proposal Summary

Please enable the -t flag for docker run.

## Motivation
To enable colored output inside a run Docker container I need the `-t` flag. This requires a tty.

Colored output is very helpful to discern status and error messages when training models.

### What component(s), interfaces, languages, and integrations does this feature affect?
Components 
- [ ] `area/artifacts`: Artifact stores and artifact logging
- [ ] `area/build`: Build and test infrastructure for MLflow
- [ ] `area/docs`: MLflow documentation pages
- [ ] `area/examples`: Example code
- [ ] `area/model-registry`: Model Registry service, APIs, and the fluent client calls for
Model Registry
- [ ] `area/models`: MLmodel format, model serialization/deserialization, flavors
- [ ] `area/projects`: MLproject format, project running backends
- [ ] `area/scoring`: Local serving, model deployment tools, spark UDFs
- [ ] `area/tracking`: Tracking Service, tracking client APIs, autologging

Interfaces
- [ ] `area/uiux`: Front-end, user experience, JavaScript, plotting
- [X] `area/docker`: Docker use across MLflow's components, such as MLflow Projects and MLflow Models
- [ ] `area/sqlalchemy`: Use of SQLAlchemy in the Tracking Service or Model Registry
- [ ] `area/windows`: Windows support

Languages 
- [ ] `language/r`: R APIs and clients
- [ ] `language/java`: Java APIs and clients

Integrations
- [ ] `integrations/azure`: Azure and Azure ML integrations
- [ ] `integrations/sagemaker`: SageMaker integrations

## Details

The corresponding code is here: https://github.com/mlflow/mlflow/blob/6ef63a7f33ebfe3481ea57cacd94a8a32fc7efd8/mlflow/projects/__init__.py#L528

The cmd needs to (optionally) expand from `docker run --rm` to `docker run --rm -t`



Discussion:
I am willing to contribute, but would like some response from the developers first.

Do you guys give me green light for this feature?
Hey @Zethson! Thanks for bringing this up - @dbczumar for context
Hi @Zethson . I think adding the `-t` flag makes sense. Can you file a PR for this? More than happy to review it!

Resolving patch:
diff --git a/mlflow/cli.py b/mlflow/cli.py
--- a/mlflow/cli.py
+++ b/mlflow/cli.py
@@ -52,8 +52,9 @@ def cli():
                    "are not in the list of parameters for an entry point will be passed to the "
                    "corresponding entry point as command-line arguments in the form `--name value`")
 @click.option("--docker-args", "-A", metavar="NAME=VALUE", multiple=True,
-              help="A `docker run` flag or argument, of the form -A name=value. Where `name` "
-              "will then be propagated as `docker run --name value`.")
+              help="A `docker run` argument or flag, of the form -A name=value (e.g. -A gpus=all) "
+                   "or -A name (e.g. -A t). The argument will then be passed as "
+                   "`docker run --name value` or `docker run --name` respectively. ")
 @click.option("--experiment-name", envvar=tracking._EXPERIMENT_NAME_ENV_VAR,
               help="Name of the experiment under which to launch the run. If not "
                    "specified, 'experiment-id' option will be used to launch run.")
@@ -103,7 +104,7 @@ def run(uri, entry_point, version, param_list, docker_args, experiment_name, exp
         sys.exit(1)
 
     param_dict = _user_args_to_dict(param_list)
-    args_dict = _user_args_to_dict(docker_args, flag_name='A')
+    args_dict = _user_args_to_dict(docker_args, argument_type='A')
 
     if backend_config is not None and os.path.splitext(backend_config)[-1] != ".json":
         try:
@@ -136,16 +137,21 @@ def run(uri, entry_point, version, param_list, docker_args, experiment_name, exp
         sys.exit(1)
 
 
-def _user_args_to_dict(user_list, flag_name='P'):
+def _user_args_to_dict(arguments, argument_type='P'):
     user_dict = {}
-    for s in user_list:
-        index = s.find("=")
-        if index == -1:
+    for arg in arguments:
+        split = arg.split('=')
+        # Docker arguments such as `t` don't require a value -> set to True if specified
+        if len(split) == 1 and argument_type == 'A':
+            name = split[0]
+            value = True
+        elif len(split) == 2:
+            name = split[0]
+            value = split[1]
+        else:
             eprint("Invalid format for -%s parameter: '%s'. "
-                   "Use -%s name=value." % (flag_name, s, flag_name))
+                   "Use -%s name=value." % (argument_type, arg, argument_type))
             sys.exit(1)
-        name = s[:index]
-        value = s[index + 1:]
         if name in user_dict:
             eprint("Repeated parameter: '%s'" % name)
             sys.exit(1)
diff --git a/mlflow/projects/__init__.py b/mlflow/projects/__init__.py
--- a/mlflow/projects/__init__.py
+++ b/mlflow/projects/__init__.py
@@ -534,8 +534,19 @@ def _get_docker_command(image, active_run, docker_args=None, volumes=None, user_
     cmd = [docker_path, "run", "--rm"]
 
     if docker_args:
-        for key, value in docker_args.items():
-            cmd += ['--' + key, value]
+        for name, value in docker_args.items():
+            # Passed just the name as boolean flag
+            if isinstance(value, bool) and value:
+                if len(name) == 1:
+                    cmd += ['-' + name]
+                else:
+                    cmd += ['--' + name]
+            else:
+                # Passed name=value
+                if len(name) == 1:
+                    cmd += ['-' + name, value]
+                else:
+                    cmd += ['--' + name, value]
 
     env_vars = _get_run_env_vars(run_id=active_run.info.run_id,
                                  experiment_id=active_run.info.experiment_id)


Test patch: