Skip to content

Commit bf3ffe6

Browse files
authored
Merge branch 'master' into defaultcontainerWarning
2 parents cf3c2f7 + b4aa8d5 commit bf3ffe6

File tree

147 files changed

+7272
-1076
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

147 files changed

+7272
-1076
lines changed

cwltool/builder.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,6 @@
1818
from .stdfsaccess import StdFsAccess
1919
from .utils import aslist, get_feature, docker_windows_path_adjust, onWindows
2020

21-
# if six.PY3:
22-
# AvroSchemaFromJSONData = avro.schema.SchemaFromJSONData
23-
# else:
2421
AvroSchemaFromJSONData = avro.schema.make_avsc_object
2522

2623
CONTENT_LIMIT = 64 * 1024

cwltool/docker_uid.py renamed to cwltool/docker_id.py

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,25 @@
22
from __future__ import absolute_import
33

44
import subprocess
5-
from typing import List, Text
5+
from typing import List, Text, Tuple
66

77

8-
def docker_vm_uid(): # type: () -> int
8+
def docker_vm_id(): # type: () -> Tuple[int, int]
99
"""
10-
Returns the UID of the default docker user inside the VM
10+
Returns the User ID and Group ID of the default docker user inside the VM
1111
1212
When a host is using boot2docker or docker-machine to run docker with
1313
boot2docker.iso (As on Mac OS X), the UID that mounts the shared filesystem
1414
inside the VirtualBox VM is likely different than the user's UID on the host.
15-
:return: The numeric UID (as a string) of the docker account inside
15+
:return: A tuple containing numeric User ID and Group ID of the docker account inside
1616
the boot2docker VM
1717
"""
1818
if boot2docker_running():
19-
return boot2docker_uid()
19+
return boot2docker_id()
2020
elif docker_machine_running():
21-
return docker_machine_uid()
21+
return docker_machine_id()
2222
else:
23-
return None
23+
return (None, None)
2424

2525

2626
def check_output_and_strip(cmd): # type: (List[Text]) -> Text
@@ -95,23 +95,26 @@ def cmd_output_to_int(cmd): # type: (List[Text]) -> int
9595
return None
9696

9797

98-
def boot2docker_uid(): # type: () -> int
98+
def boot2docker_id(): # type: () -> Tuple[int, int]
9999
"""
100-
Gets the UID of the docker user inside a running boot2docker vm
101-
:return: the UID, or None if error (e.g. boot2docker not present or stopped)
100+
Gets the UID and GID of the docker user inside a running boot2docker vm
101+
:return: Tuple (UID, GID), or (None, None) if error (e.g. boot2docker not present or stopped)
102102
"""
103-
return cmd_output_to_int(['boot2docker', 'ssh', 'id', '-u'])
104-
103+
uid = cmd_output_to_int(['boot2docker', 'ssh', 'id', '-u'])
104+
gid = cmd_output_to_int(['boot2docker', 'ssh', 'id', '-g'])
105+
return (uid, gid)
105106

106-
def docker_machine_uid(): # type: () -> int
107+
def docker_machine_id(): # type: () -> Tuple[int, int]
107108
"""
108109
Asks docker-machine for active machine and gets the UID of the docker user
109110
inside the vm
110-
:return: the UID, or None if error (e.g. docker-machine not present or stopped)
111+
:return: tuple (UID, GID), or (None, None) if error (e.g. docker-machine not present or stopped)
111112
"""
112113
machine_name = docker_machine_name()
113-
return cmd_output_to_int(['docker-machine', 'ssh', machine_name, "id -u"])
114+
uid = cmd_output_to_int(['docker-machine', 'ssh', machine_name, "id -u"])
115+
gid = cmd_output_to_int(['docker-machine', 'ssh', machine_name, "id -g"])
116+
return (uid, gid)
114117

115118

116119
if __name__ == '__main__':
117-
print(docker_vm_uid())
120+
print(docker_vm_id())

cwltool/draft2tool.py

Lines changed: 54 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,9 @@ def revmap_file(builder, outdir, f):
120120
if f["location"].startswith("file://"):
121121
path = convert_pathsep_to_unix(uri_file_path(f["location"]))
122122
revmap_f = builder.pathmapper.reversemap(path)
123-
if revmap_f:
124-
f["location"] = revmap_f[1]
123+
if revmap_f and not builder.pathmapper.mapper(revmap_f[0]).type.startswith("Writable"):
124+
f["basename"] = os.path.basename(path)
125+
f["location"] = revmap_f[0]
125126
elif path == builder.outdir:
126127
f["location"] = outdir
127128
elif path.startswith(builder.outdir):
@@ -202,7 +203,7 @@ def makeJobRunner(self, use_container=True): # type: (Optional[bool]) -> JobBas
202203
dockerReq = self.requirements[0]
203204
if default_container == windows_default_container_id and use_container and onWindows():
204205
_logger.warning(DEFAULT_CONTAINER_MSG%(windows_default_container_id, windows_default_container_id))
205-
206+
206207
if dockerReq and use_container:
207208
return DockerCommandLineJob()
208209
else:
@@ -217,6 +218,17 @@ def makePathMapper(self, reffiles, stagedir, **kwargs):
217218
# type: (List[Any], Text, **Any) -> PathMapper
218219
return PathMapper(reffiles, kwargs["basedir"], stagedir)
219220

221+
def updatePathmap(self, outdir, pathmap, fn):
222+
# type: (Text, PathMapper, Dict) -> None
223+
if "location" in fn:
224+
pathmap.update(fn["location"], pathmap.mapper(fn["location"]).resolved,
225+
os.path.join(outdir, fn["basename"]),
226+
("Writable" if fn.get("writable") else "") + fn["class"], False)
227+
for sf in fn.get("secondaryFiles", []):
228+
self.updatePathmap(outdir, pathmap, sf)
229+
for ls in fn.get("listing", []):
230+
self.updatePathmap(os.path.join(outdir, fn["basename"]), pathmap, ls)
231+
220232
def job(self,
221233
job_order, # type: Dict[Text, Text]
222234
output_callbacks, # type: Callable[[Any, Any], Any]
@@ -341,46 +353,10 @@ def rm_pending_output_callback(output_callbacks, jobcachepending,
341353
builder.pathmapper = self.makePathMapper(reffiles, builder.stagedir, **make_path_mapper_kwargs)
342354
builder.requirements = j.requirements
343355

344-
if _logger.isEnabledFor(logging.DEBUG):
345-
_logger.debug(u"[job %s] path mappings is %s", j.name,
346-
json.dumps({p: builder.pathmapper.mapper(p) for p in builder.pathmapper.files()}, indent=4))
347-
348356
_check_adjust = partial(check_adjust, builder)
349357

350358
visit_class([builder.files, builder.bindings], ("File", "Directory"), _check_adjust)
351359

352-
if self.tool.get("stdin"):
353-
with SourceLine(self.tool, "stdin", validate.ValidationException):
354-
j.stdin = builder.do_eval(self.tool["stdin"])
355-
reffiles.append({"class": "File", "path": j.stdin})
356-
357-
if self.tool.get("stderr"):
358-
with SourceLine(self.tool, "stderr", validate.ValidationException):
359-
j.stderr = builder.do_eval(self.tool["stderr"])
360-
if os.path.isabs(j.stderr) or ".." in j.stderr:
361-
raise validate.ValidationException("stderr must be a relative path, got '%s'" % j.stderr)
362-
363-
if self.tool.get("stdout"):
364-
with SourceLine(self.tool, "stdout", validate.ValidationException):
365-
j.stdout = builder.do_eval(self.tool["stdout"])
366-
if os.path.isabs(j.stdout) or ".." in j.stdout or not j.stdout:
367-
raise validate.ValidationException("stdout must be a relative path, got '%s'" % j.stdout)
368-
369-
if _logger.isEnabledFor(logging.DEBUG):
370-
_logger.debug(u"[job %s] command line bindings is %s", j.name, json.dumps(builder.bindings, indent=4))
371-
372-
dockerReq = self.get_requirement("DockerRequirement")[0]
373-
if dockerReq and kwargs.get("use_container"):
374-
out_prefix = kwargs.get("tmp_outdir_prefix")
375-
j.outdir = kwargs.get("outdir") or tempfile.mkdtemp(prefix=out_prefix)
376-
tmpdir_prefix = kwargs.get('tmpdir_prefix')
377-
j.tmpdir = kwargs.get("tmpdir") or tempfile.mkdtemp(prefix=tmpdir_prefix)
378-
j.stagedir = tempfile.mkdtemp(prefix=tmpdir_prefix)
379-
else:
380-
j.outdir = builder.outdir
381-
j.tmpdir = builder.tmpdir
382-
j.stagedir = builder.stagedir
383-
384360
initialWorkdir = self.get_requirement("InitialWorkDirRequirement")[0]
385361
j.generatefiles = {"class": "Directory", "listing": [], "basename": ""}
386362
if initialWorkdir:
@@ -416,6 +392,45 @@ def rm_pending_output_callback(output_callbacks, jobcachepending,
416392
t["entry"]["writable"] = t.get("writable")
417393
ls[i] = t["entry"]
418394
j.generatefiles[u"listing"] = ls
395+
for l in ls:
396+
self.updatePathmap(builder.outdir, builder.pathmapper, l)
397+
visit_class([builder.files, builder.bindings], ("File", "Directory"), _check_adjust)
398+
399+
if _logger.isEnabledFor(logging.DEBUG):
400+
_logger.debug(u"[job %s] path mappings is %s", j.name,
401+
json.dumps({p: builder.pathmapper.mapper(p) for p in builder.pathmapper.files()}, indent=4))
402+
403+
if self.tool.get("stdin"):
404+
with SourceLine(self.tool, "stdin", validate.ValidationException):
405+
j.stdin = builder.do_eval(self.tool["stdin"])
406+
reffiles.append({"class": "File", "path": j.stdin})
407+
408+
if self.tool.get("stderr"):
409+
with SourceLine(self.tool, "stderr", validate.ValidationException):
410+
j.stderr = builder.do_eval(self.tool["stderr"])
411+
if os.path.isabs(j.stderr) or ".." in j.stderr:
412+
raise validate.ValidationException("stderr must be a relative path, got '%s'" % j.stderr)
413+
414+
if self.tool.get("stdout"):
415+
with SourceLine(self.tool, "stdout", validate.ValidationException):
416+
j.stdout = builder.do_eval(self.tool["stdout"])
417+
if os.path.isabs(j.stdout) or ".." in j.stdout or not j.stdout:
418+
raise validate.ValidationException("stdout must be a relative path, got '%s'" % j.stdout)
419+
420+
if _logger.isEnabledFor(logging.DEBUG):
421+
_logger.debug(u"[job %s] command line bindings is %s", j.name, json.dumps(builder.bindings, indent=4))
422+
423+
dockerReq = self.get_requirement("DockerRequirement")[0]
424+
if dockerReq and kwargs.get("use_container"):
425+
out_prefix = kwargs.get("tmp_outdir_prefix")
426+
j.outdir = kwargs.get("outdir") or tempfile.mkdtemp(prefix=out_prefix)
427+
tmpdir_prefix = kwargs.get('tmpdir_prefix')
428+
j.tmpdir = kwargs.get("tmpdir") or tempfile.mkdtemp(prefix=tmpdir_prefix)
429+
j.stagedir = tempfile.mkdtemp(prefix=tmpdir_prefix)
430+
else:
431+
j.outdir = builder.outdir
432+
j.tmpdir = builder.tmpdir
433+
j.stagedir = builder.stagedir
419434

420435
inplaceUpdateReq = self.get_requirement("http://commonwl.org/cwltool#InplaceUpdateRequirement")[0]
421436

cwltool/job.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from .utils import copytree_with_merge, docker_windows_path_adjust, onWindows
2020
from . import docker
2121
from .builder import Builder
22-
from .docker_uid import docker_vm_uid
22+
from .docker_id import docker_vm_id
2323
from .errors import WorkflowException
2424
from .pathmapper import PathMapper
2525
from .process import (UnsupportedRequirement, empty_subtree, get_feature,
@@ -89,6 +89,7 @@ def deref_links(outputs): # type: (Any) -> None
8989
if outputs.get("class") == "File":
9090
st = os.lstat(outputs["path"])
9191
if stat.S_ISLNK(st.st_mode):
92+
outputs["basename"] = os.path.basename(outputs["path"])
9293
outputs["path"] = os.readlink(outputs["path"])
9394
else:
9495
for v in outputs.values():
@@ -147,7 +148,7 @@ def _setup(self): # type: () -> None
147148

148149
for knownfile in self.pathmapper.files():
149150
p = self.pathmapper.mapper(knownfile)
150-
if p.type == "File" and not os.path.isfile(p[0]):
151+
if p.type == "File" and not os.path.isfile(p[0]) and p.staged:
151152
raise WorkflowException(
152153
u"Input file %s (at %s) not found or is not a regular "
153154
"file." % (knownfile, self.pathmapper.mapper(knownfile)[0]))
@@ -390,13 +391,12 @@ def run(self, pull_image=True, rm_container=True,
390391
if self.stdout:
391392
runtime.append("--log-driver=none")
392393

393-
if onWindows(): # windows os dont have getuid or geteuid functions
394-
euid = docker_vm_uid()
395-
else:
396-
euid = docker_vm_uid() or os.geteuid()
394+
euid, egid = docker_vm_id()
395+
if not onWindows(): # MS Windows does not have getuid() or geteuid() functions
396+
euid, egid = euid or os.geteuid(), egid or os.getgid()
397397

398-
if kwargs.get("no_match_user", None) is False and euid is not None:
399-
runtime.append(u"--user=%s" % (euid))
398+
if kwargs.get("no_match_user", None) is False and (euid, egid) != (None, None):
399+
runtime.append(u"--user=%d:%d" % (euid, egid))
400400

401401
if rm_container:
402402
runtime.append(u"--rm")

cwltool/load_tool.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from . import process, update
2323
from .errors import WorkflowException
2424
from .process import Process, shortname
25+
from .update import ALLUPDATES
2526

2627
_logger = logging.getLogger("cwltool")
2728

@@ -161,12 +162,17 @@ def validate_document(document_loader, # type: Loader
161162
if "cwlVersion" in workflowobj:
162163
if not isinstance(workflowobj["cwlVersion"], (str, Text)):
163164
raise Exception("'cwlVersion' must be a string, got %s" % type(workflowobj["cwlVersion"]))
165+
if workflowobj["cwlVersion"] not in list(ALLUPDATES):
166+
# print out all the Supported Versions of cwlVersion
167+
versions = list(ALLUPDATES) # ALLUPDATES is a dict
168+
versions.sort()
169+
raise ValidationException("'cwlVersion' not valid. Supported CWL versions are: \n{}".format("\n".join(versions)))
164170
workflowobj["cwlVersion"] = re.sub(
165171
r"^(?:cwl:|https://w3id.org/cwl/cwl#)", "",
166172
workflowobj["cwlVersion"])
167173
else:
168-
_logger.warning("No cwlVersion found, treating this file as draft-2.")
169-
workflowobj["cwlVersion"] = "draft-2"
174+
raise ValidationException("No cwlVersion found."
175+
"Use the following syntax in your CWL workflow to declare version: cwlVersion: <version>")
170176

171177
if workflowobj["cwlVersion"] == "draft-2":
172178
workflowobj = cast(CommentedMap, cmap(update._draft2toDraft3dev1(

cwltool/pathmapper.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,3 +249,6 @@ def reversemap(self, target): # type: (Text) -> Tuple[Text, Text]
249249
if v[1] == target:
250250
return (k, v[0])
251251
return None
252+
253+
def update(self, key, resolved, target, type, stage): # type: (Text, Text, Text, Text, bool) -> None
254+
self._pathmap[key] = MapperEnt(resolved, target, type, stage)

cwltool/process.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
from .builder import Builder
3535
from .errors import UnsupportedRequirement, WorkflowException
3636
from .pathmapper import (PathMapper, adjustDirObjs, get_listing,
37-
normalizeFilesDirs, visit_class)
37+
normalizeFilesDirs, visit_class, trim_listing)
3838
from .stdfsaccess import StdFsAccess
3939
from .utils import aslist, get_feature, copytree_with_merge, onWindows
4040

@@ -283,7 +283,7 @@ def moveIt(src, dst):
283283
outfiles = [] # type: List[Dict[Text, Any]]
284284
collectFilesAndDirs(outputObj, outfiles)
285285
pm = PathMapper(outfiles, "", outdir, separateDirs=False)
286-
stageFiles(pm, stageFunc=moveIt,symLink=False)
286+
stageFiles(pm, stageFunc=moveIt, symLink=False)
287287

288288
def _check_adjust(f):
289289
f["location"] = file_uri(pm.mapper(f["location"])[1])
@@ -386,12 +386,13 @@ def fillInDefaults(inputs, job):
386386
# type: (List[Dict[Text, Text]], Dict[Text, Union[Dict[Text, Any], List, Text]]) -> None
387387
for e, inp in enumerate(inputs):
388388
with SourceLine(inputs, e, WorkflowException):
389-
if shortname(inp[u"id"]) in job:
390-
pass
391-
elif shortname(inp[u"id"]) not in job and u"default" in inp:
392-
job[shortname(inp[u"id"])] = copy.copy(inp[u"default"])
393-
elif shortname(inp[u"id"]) not in job and aslist(inp[u"type"])[0] == u"null":
389+
fieldname = shortname(inp[u"id"])
390+
if job.get(fieldname) is not None:
394391
pass
392+
elif job.get(fieldname) is None and u"default" in inp:
393+
job[fieldname] = copy.copy(inp[u"default"])
394+
elif job.get(fieldname) is None and u"null" in aslist(inp[u"type"]):
395+
job[fieldname] = None
395396
else:
396397
raise WorkflowException("Missing required input parameter `%s`" % shortname(inp["id"]))
397398

cwltool/sandboxjs.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def check_js_threshold_version(working_alias):
4242
"""
4343
# parse nodejs version into int Tuple: 'v4.2.6\n' -> [4, 2, 6]
4444
current_version_str = subprocess.check_output(
45-
[working_alias, "-v"]).decode('ascii')
45+
[working_alias, "-v"]).decode('utf-8')
4646

4747
current_version = [int(v) for v in current_version_str.strip().strip('v').split('.')]
4848
minimum_node_version = [int(v) for v in minimum_node_version_str.split('.')]
@@ -64,7 +64,7 @@ def new_js_proc():
6464
trynodes = ("nodejs", "node")
6565
for n in trynodes:
6666
try:
67-
if subprocess.check_output([n, "--eval", "process.stdout.write('t')"]) != "t":
67+
if subprocess.check_output([n, "--eval", "process.stdout.write('t')"]).decode('utf-8') != "t":
6868
continue
6969
else:
7070
nodejs = subprocess.Popen([n, "--eval", nodecode],
@@ -87,9 +87,11 @@ def new_js_proc():
8787
nodeimg = "node:slim"
8888
global have_node_slim
8989
if not have_node_slim:
90-
dockerimgs = subprocess.check_output(["docker", "images", nodeimg]).decode('utf-8')
90+
dockerimgs = subprocess.check_output(["docker", "images", "-q", nodeimg]).decode('utf-8')
91+
# if output is an empty string
9192
if len(dockerimgs.split("\n")) <= 1:
92-
nodejsimg = subprocess.check_output(["docker", "pull", nodeimg])
93+
# pull node:slim docker container
94+
nodejsimg = subprocess.check_output(["docker", "pull", nodeimg]).decode('utf-8')
9395
_logger.info("Pulled Docker image %s %s", nodeimg, nodejsimg)
9496
have_node_slim = True
9597
nodejs = subprocess.Popen(["docker", "run",
@@ -134,7 +136,8 @@ def execjs(js, jslib, timeout=None, debug=False): # type: (Union[Mapping, Text]
134136

135137
killed = []
136138

137-
def term():
139+
""" Kill the node process if it exceeds timeout limit"""
140+
def terminate():
138141
try:
139142
killed.append(True)
140143
nodejs.kill()
@@ -144,7 +147,7 @@ def term():
144147
if timeout is None:
145148
timeout = 20
146149

147-
tm = threading.Timer(timeout, term)
150+
tm = threading.Timer(timeout, terminate)
148151
tm.start()
149152

150153
stdin_buf = BytesIO((json.dumps(fn) + "\n").encode('utf-8'))
@@ -154,7 +157,8 @@ def term():
154157
rselect = [nodejs.stdout, nodejs.stderr] # type: List[BytesIO]
155158
wselect = [nodejs.stdin] # type: List[BytesIO]
156159

157-
# On windows system standard input/output are not handled properly by select module(modules like pywin32, msvcrt, gevent don't work either)
160+
# On windows system standard input/output are not handled properly by select module
161+
# (modules like pywin32, msvcrt, gevent don't work either)
158162
if sys.platform=='win32':
159163
READ_BYTES_SIZE = 512
160164

cwltool/schemas/.gitattributes

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
*.txt text eol=lf
2+
*.fastq text eol=lf
3+
*.fai text eol=lf
4+
*.fasta text eol=lf

0 commit comments

Comments
 (0)