Skip to content

Commit 3c91449

Browse files
committed
support colons in paths on Singularity 3.9+/Apptainer 1.0+
1 parent 75df2cf commit 3c91449

File tree

5 files changed

+43
-16
lines changed

5 files changed

+43
-16
lines changed

cwltool/docker.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,13 @@ def get_from_requirements(
207207
raise WorkflowException("Docker image %s not found" % r["dockerImageId"])
208208

209209
@staticmethod
210-
def append_volume(runtime: List[str], source: str, target: str, writable: bool = False) -> None:
210+
def append_volume(
211+
runtime: List[str],
212+
source: str,
213+
target: str,
214+
writable: bool = False,
215+
skip_mkdirs: bool = False,
216+
) -> None:
211217
"""Add binding arguments to the runtime list."""
212218
options = [
213219
"type=bind",
@@ -221,7 +227,7 @@ def append_volume(runtime: List[str], source: str, target: str, writable: bool =
221227
mount_arg = output.getvalue().strip()
222228
runtime.append(f"--mount={mount_arg}")
223229
# Unlike "--volume", "--mount" will fail if the volume doesn't already exist.
224-
if not os.path.exists(source):
230+
if (not skip_mkdirs) and (not os.path.exists(source)):
225231
os.makedirs(source)
226232

227233
def add_file_or_directory_volume(

cwltool/singularity.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
from .builder import Builder
1515
from .context import RuntimeContext
16+
from .docker import DockerCommandLineJob
1617
from .errors import WorkflowException
1718
from .job import ContainerCommandLineJob
1819
from .loghandler import _logger
@@ -106,6 +107,14 @@ def is_version_3_4_or_newer() -> bool:
106107
return v[0][0] >= 4 or (v[0][0] == 3 and v[0][1] >= 4)
107108

108109

110+
def is_version_3_9_or_newer() -> bool:
111+
"""Detect if Singularity v3.9+ is available."""
112+
if is_apptainer_1_or_newer():
113+
return True # this is equivalent to singularity-ce > 3.9.5
114+
v = get_version()
115+
return v[0][0] >= 4 or (v[0][0] == 3 and v[0][1] >= 9)
116+
117+
109118
def _normalize_image_id(string: str) -> str:
110119
return string.replace("/", "_") + ".img"
111120

@@ -297,13 +306,16 @@ def get_from_requirements(
297306
@staticmethod
298307
def append_volume(runtime: List[str], source: str, target: str, writable: bool = False) -> None:
299308
"""Add binding arguments to the runtime list."""
300-
runtime.append("--bind")
301-
# Mounts are writable by default, so 'rw' is optional and not
302-
# supported (due to a bug) in some 3.6 series releases.
303-
vol = f"{source}:{target}"
304-
if not writable:
305-
vol += ":ro"
306-
runtime.append(vol)
309+
if is_version_3_9_or_newer():
310+
DockerCommandLineJob.append_volume(runtime, source, target, writable, skip_mkdirs=True)
311+
else:
312+
runtime.append("--bind")
313+
# Mounts are writable by default, so 'rw' is optional and not
314+
# supported (due to a bug) in some 3.6 series releases.
315+
vol = f"{source}:{target}"
316+
if not writable:
317+
vol += ":ro"
318+
runtime.append(vol)
307319

308320
def add_file_or_directory_volume(
309321
self, runtime: List[str], volume: MapperEnt, host_outdir_tgt: Optional[str]

cwltool/udocker.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ class UDockerCommandLineJob(DockerCommandLineJob):
99
"""Runs a CommandLineJob in a software container using the udocker engine."""
1010

1111
@staticmethod
12-
def append_volume(runtime: List[str], source: str, target: str, writable: bool = False) -> None:
12+
def append_volume(
13+
runtime: List[str],
14+
source: str,
15+
target: str,
16+
writable: bool = False,
17+
skip_mkdirs: bool = False,
18+
) -> None:
1319
"""Add binding arguments to the runtime list."""
1420
runtime.append("--volume={}:{}:{}".format(source, target, "rw" if writable else "ro"))

tests/test_environment.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def assert_envvar_matches(check: CheckerTypes, k: str, env: Mapping[str, str]) -
2828
if isinstance(check, str):
2929
assert v == check, f"Environment variable {k} == {v!r} != {check!r}"
3030
else:
31-
assert check(v, env), f"Environment variable {k}={v!r} fails check"
31+
assert check(v, env), f"Environment variable {k}={v!r} fails check."
3232

3333

3434
def assert_env_matches(
@@ -149,11 +149,14 @@ def PWD(v: str, env: Env) -> bool:
149149
elif vminor > 5:
150150
sing_vars["SINGULARITY_COMMAND"] = "exec"
151151
if vminor >= 7:
152+
if vminor > 9:
153+
sing_vars["SINGULARITY_BIND"] = ""
154+
else:
152155

153-
def BIND(v: str, env: Env) -> bool:
154-
return v.startswith(tmp_prefix) and v.endswith(":/tmp")
156+
def BIND(v: str, env: Env) -> bool:
157+
return v.startswith(tmp_prefix) and v.endswith(":/tmp")
155158

156-
sing_vars["SINGULARITY_BIND"] = BIND
159+
sing_vars["SINGULARITY_BIND"] = BIND
157160

158161
result.update(sing_vars)
159162

tests/util.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,13 +152,13 @@ def get_tool_env(
152152
args.append(inputs_file)
153153

154154
with working_directory(tmp_path):
155-
rc, stdout, _ = get_main_output(
155+
rc, stdout, stderr = get_main_output(
156156
args,
157157
replacement_env=replacement_env,
158158
extra_env=extra_env,
159159
monkeypatch=monkeypatch,
160160
)
161-
assert rc == 0
161+
assert rc == 0, stdout + "\n" + stderr
162162

163163
output = json.loads(stdout)
164164
with open(output["env"]["path"]) as _:

0 commit comments

Comments
 (0)