Skip to content

Commit 9e1112f

Browse files
authored
Fix docker volume staging for writable files / file literals. (#533)
* Fix docker volume staging for writable files / file literals. * Ensure that files copied for update are writable.
1 parent b7f87da commit 9e1112f

File tree

3 files changed

+61
-33
lines changed

3 files changed

+61
-33
lines changed

cwltool/job.py

+37-31
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from .builder import Builder
2222
from .docker_id import docker_vm_id
2323
from .errors import WorkflowException
24-
from .pathmapper import PathMapper
24+
from .pathmapper import PathMapper, ensure_writable
2525
from .process import (UnsupportedRequirement, empty_subtree, get_feature,
2626
stageFiles)
2727
from .utils import bytes2str_in_dicts
@@ -98,24 +98,26 @@ def deref_links(outputs): # type: (Any) -> None
9898
for v in outputs:
9999
deref_links(v)
100100

101-
def relink_initialworkdir(pathmapper, inplace_update=False):
102-
# type: (PathMapper, bool) -> None
101+
def relink_initialworkdir(pathmapper, host_outdir, container_outdir, inplace_update=False):
102+
# type: (PathMapper, Text, Text, bool) -> None
103103
for src, vol in pathmapper.items():
104104
if not vol.staged:
105105
continue
106+
106107
if vol.type in ("File", "Directory") or (inplace_update and
107108
vol.type in ("WritableFile", "WritableDirectory")):
108-
if os.path.islink(vol.target) or os.path.isfile(vol.target):
109-
os.remove(vol.target)
110-
elif os.path.isdir(vol.target):
111-
shutil.rmtree(vol.target)
109+
host_outdir_tgt = os.path.join(host_outdir, vol.target[len(container_outdir)+1:])
110+
if os.path.islink(host_outdir_tgt) or os.path.isfile(host_outdir_tgt):
111+
os.remove(host_outdir_tgt)
112+
elif os.path.isdir(host_outdir_tgt):
113+
shutil.rmtree(host_outdir_tgt)
112114
if onWindows():
113115
if vol.type in ("File", "WritableFile"):
114-
shutil.copy(vol.resolved,vol.target)
116+
shutil.copy(vol.resolved, host_outdir_tgt)
115117
elif vol.type in ("Directory", "WritableDirectory"):
116-
copytree_with_merge(vol.resolved, vol.target)
118+
copytree_with_merge(vol.resolved, host_outdir_tgt)
117119
else:
118-
os.symlink(vol.resolved, vol.target)
120+
os.symlink(vol.resolved, host_outdir_tgt)
119121

120122
class JobBase(object):
121123
def __init__(self): # type: () -> None
@@ -160,7 +162,7 @@ def _setup(self, kwargs): # type: (Dict) -> None
160162
make_path_mapper_kwargs = make_path_mapper_kwargs.copy()
161163
del make_path_mapper_kwargs["basedir"]
162164
self.generatemapper = self.make_pathmapper(cast(List[Any], self.generatefiles["listing"]),
163-
self.outdir, basedir=self.outdir, separateDirs=False, **make_path_mapper_kwargs)
165+
self.builder.outdir, basedir=self.outdir, separateDirs=False, **make_path_mapper_kwargs)
164166
_logger.debug(u"[job %s] initial work dir %s", self.name,
165167
json.dumps({p: self.generatemapper.mapper(p) for p in self.generatemapper.files()}, indent=4))
166168

@@ -234,7 +236,7 @@ def _execute(self, runtime, env, rm_tmpdir=True, move_outputs="move"):
234236
processStatus = "permanentFail"
235237

236238
if self.generatefiles["listing"]:
237-
relink_initialworkdir(self.generatemapper, inplace_update=self.inplace_update)
239+
relink_initialworkdir(self.generatemapper, self.outdir, self.builder.outdir, inplace_update=self.inplace_update)
238240

239241
outputs = self.collect_outputs(self.outdir)
240242
outputs = bytes2str_in_dicts(outputs) # type: ignore
@@ -303,48 +305,52 @@ def run(self, pull_image=True, rm_container=True,
303305
stageFiles(self.pathmapper, ignoreWritable=True, symLink=True)
304306
if self.generatemapper:
305307
stageFiles(self.generatemapper, ignoreWritable=self.inplace_update, symLink=True)
306-
relink_initialworkdir(self.generatemapper, inplace_update=self.inplace_update)
308+
relink_initialworkdir(self.generatemapper, self.outdir, self.builder.outdir, inplace_update=self.inplace_update)
307309

308310
self._execute([], env, rm_tmpdir=rm_tmpdir, move_outputs=move_outputs)
309311

310312

311313
class DockerCommandLineJob(JobBase):
312314

313-
def add_volumes(self, pathmapper, runtime, stage_output):
314-
# type: (PathMapper, List[Text], bool) -> None
315+
def add_volumes(self, pathmapper, runtime):
316+
# type: (PathMapper, List[Text]) -> None
315317

316318
host_outdir = self.outdir
317319
container_outdir = self.builder.outdir
318320
for src, vol in pathmapper.items():
319321
if not vol.staged:
320322
continue
321-
if stage_output:
322-
containertgt = container_outdir + vol.target[len(host_outdir):]
323+
if vol.target.startswith(container_outdir+"/"):
324+
host_outdir_tgt = os.path.join(host_outdir, vol.target[len(container_outdir)+1:])
323325
else:
324-
containertgt = vol.target
326+
host_outdir_tgt = None
325327
if vol.type in ("File", "Directory"):
326328
if not vol.resolved.startswith("_:"):
327-
runtime.append(u"--volume=%s:%s:ro" % (docker_windows_path_adjust(vol.resolved), docker_windows_path_adjust(containertgt)))
329+
runtime.append(u"--volume=%s:%s:ro" % (docker_windows_path_adjust(vol.resolved), docker_windows_path_adjust(vol.target)))
328330
elif vol.type == "WritableFile":
329331
if self.inplace_update:
330-
runtime.append(u"--volume=%s:%s:rw" % (docker_windows_path_adjust(vol.resolved), docker_windows_path_adjust(containertgt)))
332+
runtime.append(u"--volume=%s:%s:rw" % (docker_windows_path_adjust(vol.resolved), docker_windows_path_adjust(vol.target)))
331333
else:
332-
shutil.copy(vol.resolved, vol.target)
334+
shutil.copy(vol.resolved, host_outdir_tgt)
335+
ensure_writable(host_outdir_tgt)
333336
elif vol.type == "WritableDirectory":
334337
if vol.resolved.startswith("_:"):
335338
os.makedirs(vol.target, 0o0755)
336339
else:
337340
if self.inplace_update:
338-
runtime.append(u"--volume=%s:%s:rw" % (docker_windows_path_adjust(vol.resolved), docker_windows_path_adjust(containertgt)))
341+
runtime.append(u"--volume=%s:%s:rw" % (docker_windows_path_adjust(vol.resolved), docker_windows_path_adjust(vol.target)))
339342
else:
340-
shutil.copytree(vol.resolved, vol.target)
343+
shutil.copytree(vol.resolved, host_outdir_tgt)
344+
ensure_writable(host_outdir_tgt)
341345
elif vol.type == "CreateFile":
342-
createtmp = os.path.join(host_outdir, os.path.basename(vol.target))
343-
with open(createtmp, "wb") as f:
344-
f.write(vol.resolved.encode("utf-8"))
345-
if not vol.target.startswith(container_outdir):
346-
runtime.append(u"--volume=%s:%s:ro" % (docker_windows_path_adjust(createtmp), docker_windows_path_adjust(vol.target)))
347-
346+
if host_outdir_tgt:
347+
with open(host_outdir_tgt, "wb") as f:
348+
f.write(vol.resolved.encode("utf-8"))
349+
else:
350+
fd, createtmp = tempfile.mkstemp(dir=self.tmpdir)
351+
with os.fdopen(fd, "wb") as f:
352+
f.write(vol.resolved.encode("utf-8"))
353+
runtime.append(u"--volume=%s:%s:rw" % (docker_windows_path_adjust(createtmp), docker_windows_path_adjust(vol.target)))
348354

349355
def run(self, pull_image=True, rm_container=True,
350356
rm_tmpdir=True, move_outputs="move", **kwargs):
@@ -384,9 +390,9 @@ def run(self, pull_image=True, rm_container=True,
384390
runtime.append(u"--volume=%s:%s:rw" % (docker_windows_path_adjust(os.path.realpath(self.outdir)), self.builder.outdir))
385391
runtime.append(u"--volume=%s:%s:rw" % (docker_windows_path_adjust(os.path.realpath(self.tmpdir)), "/tmp"))
386392

387-
self.add_volumes(self.pathmapper, runtime, False)
393+
self.add_volumes(self.pathmapper, runtime)
388394
if self.generatemapper:
389-
self.add_volumes(self.generatemapper, runtime, True)
395+
self.add_volumes(self.generatemapper, runtime)
390396

391397
runtime.append(u"--workdir=%s" % (docker_windows_path_adjust(self.builder.outdir)))
392398
runtime.append(u"--read-only=true")

cwltool/pathmapper.py

+19
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,25 @@ def downloadHttpFile(httpurl):
168168
r.close()
169169
return f.name
170170

171+
def ensure_writable(path):
172+
# type: (Text) -> None
173+
if os.path.isdir(path):
174+
for root, dirs, files in os.walk(path):
175+
for name in files:
176+
j = os.path.join(root, name)
177+
st = os.stat(j)
178+
mode = stat.S_IMODE(st.st_mode)
179+
os.chmod(j, mode|stat.S_IWUSR)
180+
for name in dirs:
181+
j = os.path.join(root, name)
182+
st = os.stat(j)
183+
mode = stat.S_IMODE(st.st_mode)
184+
os.chmod(j, mode|stat.S_IWUSR)
185+
else:
186+
st = os.stat(path)
187+
mode = stat.S_IMODE(st.st_mode)
188+
os.chmod(path, mode|stat.S_IWUSR)
189+
171190
class PathMapper(object):
172191
"""Mapping of files from relative path provided in the file to a tuple of
173192
(absolute local path, absolute container path)

cwltool/process.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@
3434
from .builder import Builder
3535
from .errors import UnsupportedRequirement, WorkflowException
3636
from .pathmapper import (PathMapper, adjustDirObjs, get_listing,
37-
normalizeFilesDirs, visit_class, trim_listing)
37+
normalizeFilesDirs, visit_class, trim_listing,
38+
ensure_writable)
3839
from .stdfsaccess import StdFsAccess
3940
from .utils import aslist, get_feature, copytree_with_merge, onWindows
4041

@@ -230,15 +231,17 @@ def stageFiles(pm, stageFunc=None, ignoreWritable=False, symLink=True):
230231
os.makedirs(p.target, 0o0755)
231232
elif p.type == "WritableFile" and not ignoreWritable:
232233
shutil.copy(p.resolved, p.target)
234+
ensure_writable(p.target)
233235
elif p.type == "WritableDirectory" and not ignoreWritable:
234236
if p.resolved.startswith("_:"):
235237
os.makedirs(p.target, 0o0755)
236238
else:
237239
shutil.copytree(p.resolved, p.target)
240+
ensure_writable(p.target)
238241
elif p.type == "CreateFile":
239242
with open(p.target, "wb") as n:
240243
n.write(p.resolved.encode("utf-8"))
241-
244+
ensure_writable(p.target)
242245

243246
def collectFilesAndDirs(obj, out):
244247
# type: (Union[Dict[Text, Any], List[Dict[Text, Any]]], List[Dict[Text, Any]]) -> None

0 commit comments

Comments
 (0)