Skip to content

Commit a150dfd

Browse files
authored
Move var/spool/cwl detector to process.py and make it a warning. (#733)
Move var/spool/cwl detector to process.py and make it a warning. Do the check alongside other static checks.
1 parent b7b0ed0 commit a150dfd

File tree

5 files changed

+135
-32
lines changed

5 files changed

+135
-32
lines changed

cwltool/load_tool.py

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -224,33 +224,6 @@ def validate_document(document_loader, # type: Loader
224224

225225
workflowobj = fetch_document(uri, fetcher_constructor=fetcher_constructor)[1]
226226

227-
def var_spool_cwl_detector(obj, # type: Union[Mapping, Iterable, Text]
228-
item=None, # type: Optional[Any]
229-
obj_key=None, # type: Optional[Any]
230-
): # type: (...)->None
231-
""" Detects any textual reference to /var/spool/cwl. """
232-
if isinstance(obj, string_types):
233-
if "var/spool/cwl" in obj:
234-
message = SourceLine(
235-
item=item, key=obj_key, raise_type=Text,
236-
include_traceback=_logger.isEnabledFor(logging.DEBUG)).makeError(
237-
"Non-portable reference to /var/spool/cwl found: "
238-
"'{}'.\n Replace with /var/spool/cwl/ with "
239-
"$(runtime.outdir).".format(obj))
240-
if not strict:
241-
_logger.warning(message)
242-
else:
243-
raise ValidationException(message)
244-
else:
245-
return
246-
elif isinstance(obj, Mapping):
247-
for key, value in iteritems(obj):
248-
var_spool_cwl_detector(value, obj, key)
249-
elif isinstance(obj, Iterable):
250-
for element in obj:
251-
var_spool_cwl_detector(element, obj, None)
252-
var_spool_cwl_detector(workflowobj)
253-
254227
fileuri = urllib.parse.urldefrag(uri)[0]
255228
if "cwlVersion" not in workflowobj:
256229
if metadata and 'cwlVersion' in metadata:

cwltool/process.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from io import open
1717
from functools import cmp_to_key
1818
from typing import (Any, Callable, Dict, Generator, List, Set, Text,
19-
Tuple, Union, cast)
19+
Tuple, Union, cast, Optional)
2020

2121
import schema_salad.schema as schema
2222
import schema_salad.validate as validate
@@ -28,6 +28,7 @@
2828
from schema_salad.ref_resolver import Loader, file_uri
2929
from schema_salad.sourceline import SourceLine
3030
from six.moves import urllib
31+
from six import iteritems, itervalues, string_types
3132

3233
from .validate_js import validate_js_expressions
3334
from .utils import cmp_like_py2
@@ -388,6 +389,32 @@ def get_overrides(overrides, toolid): # type: (List[Dict[Text, Any]], Text) ->
388389
req.update(ov)
389390
return req
390391

392+
393+
def var_spool_cwl_detector(obj, # type: Union[Dict, List, Text]
394+
item=None, # type: Optional[Any]
395+
obj_key=None, # type: Optional[Any]
396+
): # type: (...)->bool
397+
""" Detects any textual reference to /var/spool/cwl. """
398+
r = False
399+
if isinstance(obj, string_types):
400+
if "var/spool/cwl" in obj and obj_key != "dockerOutputDirectory":
401+
_logger.warn(SourceLine(
402+
item=item, key=obj_key, raise_type=Text).makeError(
403+
"""Non-portable reference to /var/spool/cwl detected:
404+
'{}'
405+
To fix, replace /var/spool/cwl with $(runtime.outdir) or
406+
add DockerRequirement to the 'requirements' section and
407+
declare 'dockerOutputDirectory: /var/spool/cwl'.""".format(obj)))
408+
r = True
409+
elif isinstance(obj, dict):
410+
for key, value in iteritems(obj):
411+
r = var_spool_cwl_detector(value, obj, key) or r
412+
elif isinstance(obj, list):
413+
for key, value in enumerate(obj):
414+
r = var_spool_cwl_detector(value, obj, key) or r
415+
return r
416+
417+
391418
class Process(six.with_metaclass(abc.ABCMeta, object)):
392419
def __init__(self, toolpath_object, **kwargs):
393420
# type: (Dict[Text, Any], **Any) -> None
@@ -499,6 +526,24 @@ def __init__(self, toolpath_object, **kwargs):
499526

500527
validate_js_expressions(cast(CommentedMap, toolpath_object), self.doc_schema.names[toolpath_object["class"]], validate_js_options)
501528

529+
dockerReq, is_req = self.get_requirement("DockerRequirement")
530+
531+
if dockerReq and dockerReq.get("dockerOutputDirectory") and not is_req:
532+
_logger.warn(SourceLine(
533+
item=dockerReq, raise_type=Text).makeError(
534+
"""When 'dockerOutputDirectory' is declared, DockerRequirement
535+
should go in the 'requirements' section, not 'hints'."""))
536+
537+
if dockerReq and dockerReq.get("dockerOutputDirectory") == "/var/spool/cwl":
538+
if is_req:
539+
# In this specific case, it is legal to have /var/spool/cwl, so skip the check.
540+
pass
541+
else:
542+
# Must be a requirement
543+
var_spool_cwl_detector(self.tool)
544+
else:
545+
var_spool_cwl_detector(self.tool)
546+
502547
def _init_job(self, joborder, **kwargs):
503548
# type: (Dict[Text, Text], **Any) -> Builder
504549
"""

tests/non_portable2.cwl

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#!/usr/bin/env cwl-runner
2+
cwlVersion: v1.0
3+
class: CommandLineTool
4+
5+
hints:
6+
DockerRequirement:
7+
dockerPull: debian
8+
dockerOutputDirectory: /var/spool/cwl
9+
InitialWorkDirRequirement:
10+
listing:
11+
- class: File
12+
basename: hi.txt
13+
contents: Hello, World!
14+
15+
inputs: []
16+
17+
baseCommand:
18+
- cat
19+
- /var/spool/cwl/hi.txt
20+
21+
stdout: result.txt
22+
23+
outputs:
24+
result: stdout

tests/portable.cwl

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#!/usr/bin/env cwl-runner
2+
cwlVersion: v1.0
3+
class: CommandLineTool
4+
5+
requirements:
6+
DockerRequirement:
7+
dockerPull: debian
8+
dockerOutputDirectory: /var/spool/cwl
9+
InitialWorkDirRequirement:
10+
listing:
11+
- class: File
12+
basename: hi.txt
13+
contents: Hello, World!
14+
15+
inputs: []
16+
17+
baseCommand:
18+
- cat
19+
- /var/spool/cwl/hi.txt
20+
21+
stdout: result.txt
22+
23+
outputs:
24+
result: stdout

tests/test_examples.py

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import subprocess
55
from os import path
66
import sys
7+
import logging
78

89
from io import StringIO
910

@@ -556,13 +557,49 @@ def test_checker(self):
556557
f = cwltool.factory.Factory()
557558
f.make(get_data("tests/checker_wf/broken-wf2.cwl"))
558559

559-
def test_var_spool_cwl_checker(self):
560+
def test_var_spool_cwl_checker1(self):
560561
""" Confirm that references to /var/spool/cwl are caught."""
561-
with self.assertRaises(schema_salad.validate.ValidationException):
562+
563+
stream = StringIO()
564+
streamhandler = logging.StreamHandler(stream)
565+
_logger = logging.getLogger("cwltool")
566+
_logger.addHandler(streamhandler)
567+
568+
try:
562569
f = cwltool.factory.Factory()
563570
f.make(get_data("tests/non_portable.cwl"))
564-
f = cwltool.factory.Factory(strict=False)
565-
f.make(get_data("tests/non_portable.cwl"))
571+
self.assertIn("non_portable.cwl:18:4: Non-portable reference to /var/spool/cwl detected", stream.getvalue())
572+
finally:
573+
_logger.removeHandler(streamhandler)
574+
575+
def test_var_spool_cwl_checker2(self):
576+
""" Confirm that references to /var/spool/cwl are caught."""
577+
578+
stream = StringIO()
579+
streamhandler = logging.StreamHandler(stream)
580+
_logger = logging.getLogger("cwltool")
581+
_logger.addHandler(streamhandler)
582+
583+
try:
584+
f = cwltool.factory.Factory()
585+
f.make(get_data("tests/non_portable2.cwl"))
586+
self.assertIn("non_portable2.cwl:19:4: Non-portable reference to /var/spool/cwl detected", stream.getvalue())
587+
finally:
588+
_logger.removeHandler(streamhandler)
589+
590+
def test_var_spool_cwl_checker3(self):
591+
""" Confirm that references to /var/spool/cwl are caught."""
592+
593+
stream = StringIO()
594+
streamhandler = logging.StreamHandler(stream)
595+
_logger = logging.getLogger("cwltool")
596+
_logger.addHandler(streamhandler)
597+
try:
598+
f = cwltool.factory.Factory()
599+
f.make(get_data("tests/portable.cwl"))
600+
self.assertNotIn("Non-portable reference to /var/spool/cwl detected", stream.getvalue())
601+
finally:
602+
_logger.removeHandler(streamhandler)
566603

567604
class TestPrintDot(unittest.TestCase):
568605
def test_print_dot(self):

0 commit comments

Comments
 (0)