Repository: common-workflow-language/cwltool

Issue:
Error if colon in input/output paths when running with DockerRequirement
## Expected Behavior
When run with `DockerRequirement`, input paths with colons should work.

## Actual Behavior
It fails: 

```
INFO [job workflow.cwl] /tmp/4cf898fg$ docker \
   run \
   -i \
   --volume=/tmp/4cf898fg:/HZfoyx:rw \
   --volume=/tmp/e8pu8i7i:/tmp:rw \
   --volume=/home/airflow/repositories/ingest-pipeline/src/ingest-pipeline/airflow/data/temp/trig_
_2020-02-08T22:34:44.290910+00:00/cwl_out/cluster-marker-genes:/var/lib/cwl/stg706d994b-d3e7-49fd-9
9a2-d236a8783326/cluster-marker-genes:ro \
   --workdir=/HZfoyx \
   --read-only=true \
   --user=1008:1008 \
...
docker: Error response from daemon: invalid volume specification: '/home/airflow/repositories/inges
t-pipeline/src/ingest-pipeline/airflow/data/temp/trig__2020-02-08T22:34:44.290910+00:00/cwl_out/clu
ster-marker-genes:/var/lib/cwl/stg706d994b-d3e7-49fd-99a2-d236a8783326/cluster-marker-genes:ro'.
```

(from https://github.com/hubmapconsortium/airflow-dev/issues/33)

## Root Cause
in `cwltool/docker.py`:
```
    def append_volume(runtime, source, target, writable=False):
        # type: (List[str], str, str, bool) -> None
        """Add binding arguments to the runtime list."""
        runtime.append(
            "--volume={}:{}:{}".format(
                docker_windows_path_adjust(source), target, "rw" if writable else "ro"
            )
        )
```
With the `--volume`, colons have a special meaning. The **[solution](https://github.com/moby/moby/issues/8604#issuecomment-332673783)** is to use `--mount` instead.

## Next Steps
I'll make a PR to address this.

## Workflow Code
(skip for now)
## Full Traceback
(skip for now)
## Your Environment
(skip for now)



Discussion:

Resolving patch:
diff --git a/cwltool/docker.py b/cwltool/docker.py
--- a/cwltool/docker.py
+++ b/cwltool/docker.py
@@ -1,6 +1,7 @@
 """Enables Docker software containers via the {dx-,u,}docker runtimes."""
 
 import datetime
+import csv
 import os
 import re
 import shutil
@@ -8,7 +9,7 @@
 import tempfile
 import threading
 from distutils import spawn
-from io import open  # pylint: disable=redefined-builtin
+from io import open, StringIO  # pylint: disable=redefined-builtin
 from typing import Dict, List, MutableMapping, Optional, Set, Tuple
 
 import requests
@@ -222,11 +223,22 @@ def get_from_requirements(
     def append_volume(runtime, source, target, writable=False):
         # type: (List[str], str, str, bool) -> None
         """Add binding arguments to the runtime list."""
+        options = [
+            'type=bind',
+            'source=' + source,
+            'target=' + target,
+        ]
+        if not writable:
+            options.append('readonly')
+        output = StringIO()
+        csv.writer(output).writerow(options)
+        mount_arg = output.getvalue().strip()
         runtime.append(
-            "--volume={}:{}:{}".format(
-                docker_windows_path_adjust(source), target, "rw" if writable else "ro"
-            )
+            "--mount={}".format(mount_arg)
         )
+        # Unlike "--volume", "--mount" will fail if the volume doesn't already exist.
+        if not os.path.exists(source):
+            os.mkdir(source)
 
     def add_file_or_directory_volume(
         self, runtime: List[str], volume: MapperEnt, host_outdir_tgt: Optional[str]


Test patch:
diff --git a/tests/test_docker.py b/tests/test_docker.py
--- a/tests/test_docker.py
+++ b/tests/test_docker.py
@@ -22,6 +22,7 @@ def test_docker_workflow(tmpdir):
         ]
     )
     assert "completed success" in stderr
+    assert (tmpdir / 'response.txt').read_text('utf-8') == 'hello'
     assert result_code == 0
 
 
diff --git a/tests/test_docker_paths_with_colons.py b/tests/test_docker_paths_with_colons.py
new file mode 100644
--- /dev/null
+++ b/tests/test_docker_paths_with_colons.py
@@ -0,0 +1,41 @@
+import pytest
+
+from cwltool.docker import DockerCommandLineJob
+from cwltool.main import main
+
+from .util import needs_docker
+
+
+def test_docker_append_volume_read_only(mocker):
+    mocker.patch('os.mkdir')
+    runtime = ['runtime']
+    characters = ':,"\''
+    DockerCommandLineJob.append_volume(
+        runtime,
+        '/source' + characters,
+        '/target' + characters
+    )
+    assert runtime == [
+        'runtime',
+        '--mount=type=bind,'
+        '"source=/source:,""\'",'
+        '"target=/target:,""\'",'
+        'readonly'
+    ]
+
+def test_docker_append_volume_read_write(mocker):
+    mocker.patch('os.mkdir')
+    runtime = ['runtime']
+    characters = ':,"\''
+    DockerCommandLineJob.append_volume(
+        runtime,
+        '/source' + characters,
+        '/target' + characters,
+        True
+    )
+    assert runtime == [
+        'runtime',
+        '--mount=type=bind,'
+        '"source=/source:,""\'",'
+        '"target=/target:,""\'"'
+    ]