Skip to content

Commit eb75632

Browse files
authored
Merge branch 'master' into no-container-fix
2 parents 5dc1ecb + e441522 commit eb75632

File tree

7 files changed

+165
-31
lines changed

7 files changed

+165
-31
lines changed

.github/ISSUE_TEMPLATE.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
This issue tracker is for reporting bugs in cwltool (the CWL reference implementation) itself. Please use the [Gitter channel](https://gitter.im/common-workflow-language/common-workflow-language) or [Biostars forum](https://www.biostars.org/) for general questions about using cwltool to create/run workflows or issues not related to cwltool. Don't forget to use cwl tag when posting on Biostars Forum.
2+
3+
If you'd like to report a bug, fill out the template below and provide any extra information that may be useful / related to your problem. Ideally, you create an [a Minimal, Complete, and Verifiable example](http://stackoverflow.com/help/mcve) reproducing the problem before opening an issue to ensure it's not caused by something in your code.
4+
5+
Before you submit, please delete this help text above the ---
6+
Thanks!
7+
---
8+
9+
## Expected Behavior
10+
Tell us what should happen
11+
12+
## Actual Behavior
13+
Tell us what happens instead
14+
15+
## Workflow Code
16+
```
17+
Paste the template code (ideally a minimal example) that causes the issue
18+
19+
```
20+
21+
## Full Traceback
22+
```pytb
23+
Paste the full traceback in case there is an exception
24+
Run the workflow with ``--debug`` flag for more verbose logging
25+
```
26+
27+
## Your Environment
28+
* cwltool version:
29+
Check using ``cwltool --version``

cwltool/docker.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,17 @@ def get_image(dockerRequirement, pull_image, dry_run=False):
2727
sp = dockerRequirement["dockerImageId"].split(":")
2828
if len(sp) == 1:
2929
sp.append("latest")
30+
elif len(sp) == 2:
31+
# if sp[1] doesn't match valid tag names, it is a part of repository
32+
if not re.match(r'[\w][\w.-]{0,127}', sp[1]):
33+
sp[0] = sp[0] + ":" + sp[1]
34+
sp[1] = "latest"
35+
elif len(sp) == 3:
36+
if re.match(r'[\w][\w.-]{0,127}', sp[2]):
37+
sp[0] = sp[0] + ":" + sp[1]
38+
sp[1] = sp[2]
39+
del sp[2]
40+
3041
# check for repository:tag match or image id match
3142
if ((sp[0] == m.group(1) and sp[1] == m.group(2)) or dockerRequirement["dockerImageId"] == m.group(3)):
3243
found = True

cwltool/main.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -431,8 +431,8 @@ def load_job_order(args, t, stdin, print_input_deps=False, relative_deps=False,
431431
if len(args.job_order) == 1 and args.job_order[0][0] != "-":
432432
job_order_file = args.job_order[0]
433433
elif len(args.job_order) == 1 and args.job_order[0] == "-":
434-
job_order_object = yaml.load(stdin)
435-
job_order_object, _ = loader.resolve_all(job_order_object, "")
434+
job_order_object = yaml.round_trip_load(stdin) # type: ignore
435+
job_order_object, _ = loader.resolve_all(job_order_object, file_uri(os.getcwd()) + "/")
436436
else:
437437
job_order_file = None
438438

cwltool/sandboxjs.py

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
from io import BytesIO
99

1010
from pkg_resources import resource_stream
11-
from typing import Any, Dict, List, Mapping, Text, Union
11+
from typing import Any, Dict, List, Mapping, Text, Union, Tuple
12+
1213

1314
class JavascriptException(Exception):
1415
pass
@@ -21,6 +22,27 @@ class JavascriptException(Exception):
2122
localdata = threading.local()
2223

2324
have_node_slim = False
25+
# minimum acceptable version of nodejs engine
26+
minimum_node_version_str = '0.10.26'
27+
28+
def check_js_threshold_version(working_alias):
29+
# type: (str) -> bool
30+
31+
"""Checks if the nodeJS engine version on the system
32+
with the allowed minimum version.
33+
https://github.com/nodejs/node/blob/master/CHANGELOG.md#nodejs-changelog
34+
"""
35+
# parse nodejs version into int Tuple: 'v4.2.6\n' -> [4, 2, 6]
36+
current_version_str = subprocess.check_output(
37+
[working_alias, "-v"]).decode('ascii')
38+
39+
current_version = [int(v) for v in current_version_str.strip().strip('v').split('.')]
40+
minimum_node_version = [int(v) for v in minimum_node_version_str.split('.')]
41+
42+
if current_version >= minimum_node_version:
43+
return True
44+
else:
45+
return False
2446

2547

2648
def new_js_proc():
@@ -29,17 +51,21 @@ def new_js_proc():
2951
res = resource_stream(__name__, 'cwlNodeEngine.js')
3052
nodecode = res.read()
3153

54+
required_node_version, docker = (False,)*2
3255
nodejs = None
3356
trynodes = ("nodejs", "node")
3457
for n in trynodes:
3558
try:
3659
if subprocess.check_output([n, "--eval", "process.stdout.write('t')"]) != "t":
3760
continue
38-
nodejs = subprocess.Popen([n, "--eval", nodecode],
39-
stdin=subprocess.PIPE,
40-
stdout=subprocess.PIPE,
41-
stderr=subprocess.PIPE)
42-
break
61+
else:
62+
nodejs = subprocess.Popen([n, "--eval", nodecode],
63+
stdin=subprocess.PIPE,
64+
stdout=subprocess.PIPE,
65+
stderr=subprocess.PIPE)
66+
67+
required_node_version = check_js_threshold_version(n)
68+
break
4369
except subprocess.CalledProcessError:
4470
pass
4571
except OSError as e:
@@ -48,7 +74,7 @@ def new_js_proc():
4874
else:
4975
raise
5076

51-
if nodejs is None:
77+
if nodejs is None or nodejs is not None and required_node_version is False:
5278
try:
5379
nodeimg = "node:slim"
5480
global have_node_slim
@@ -63,6 +89,7 @@ def new_js_proc():
6389
"--sig-proxy=true", "--interactive",
6490
"--rm", nodeimg, "node", "--eval", nodecode],
6591
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
92+
docker = True
6693
except OSError as e:
6794
if e.errno == errno.ENOENT:
6895
pass
@@ -71,12 +98,19 @@ def new_js_proc():
7198
except subprocess.CalledProcessError:
7299
pass
73100

101+
# docker failed and nodejs not on system
74102
if nodejs is None:
75103
raise JavascriptException(
76104
u"cwltool requires Node.js engine to evaluate Javascript "
77105
"expressions, but couldn't find it. Tried %s, docker run "
78106
"node:slim" % u", ".join(trynodes))
79107

108+
# docker failed, but nodejs is installed on system but the version is below the required version
109+
if docker is False and required_node_version is False:
110+
raise JavascriptException(
111+
u'cwltool requires minimum v{} version of Node.js engine.'.format(minimum_node_version_str),
112+
u'Try updating: https://docs.npmjs.com/getting-started/installing-node')
113+
80114
return nodejs
81115

82116

cwltool/workflow.py

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,9 @@ def job(self, joborder, output_callback, **kwargs):
245245
# type: (Dict[Text, Text], functools.partial[None], **Any) -> Generator
246246
kwargs["part_of"] = self.name
247247
kwargs["name"] = shortname(self.id)
248+
249+
_logger.info(u"[%s] start", self.name)
250+
248251
for j in self.step.job(joborder, output_callback, **kwargs):
249252
yield j
250253

@@ -257,6 +260,8 @@ def __init__(self, workflow, **kwargs):
257260
self.steps = [WorkflowJobStep(s) for s in workflow.steps]
258261
self.state = None # type: Dict[Text, WorkflowStateItem]
259262
self.processStatus = None # type: Text
263+
self.did_callback = False
264+
260265
if "outdir" in kwargs:
261266
self.outdir = kwargs["outdir"]
262267
elif "tmp_outdir_prefix" in kwargs:
@@ -270,8 +275,27 @@ def __init__(self, workflow, **kwargs):
270275
_logger.debug(u"[%s] initialized from %s", self.name,
271276
self.tool.get("id", "workflow embedded in %s" % kwargs.get("part_of")))
272277

273-
def receive_output(self, step, outputparms, jobout, processStatus):
274-
# type: (WorkflowJobStep, List[Dict[Text,Text]], Dict[Text,Text], Text) -> None
278+
def do_output_callback(self, final_output_callback):
279+
# type: (Callable[[Any, Any], Any]) -> None
280+
281+
supportsMultipleInput = bool(self.workflow.get_requirement("MultipleInputFeatureRequirement")[0])
282+
283+
try:
284+
wo = object_from_state(self.state, self.tool["outputs"], True, supportsMultipleInput, "outputSource",
285+
incomplete=True)
286+
except WorkflowException as e:
287+
_logger.error(u"[%s] Cannot collect workflow output: %s", self.name, e)
288+
wo = {}
289+
self.processStatus = "permanentFail"
290+
291+
_logger.info(u"[%s] completed %s", self.name, self.processStatus)
292+
293+
self.did_callback = True
294+
295+
final_output_callback(wo, self.processStatus)
296+
297+
def receive_output(self, step, outputparms, final_output_callback, jobout, processStatus):
298+
# type: (WorkflowJobStep, List[Dict[Text,Text]], Callable[[Any, Any], Any], Dict[Text,Text], Text) -> None
275299

276300
for i in outputparms:
277301
if "id" in i:
@@ -295,8 +319,12 @@ def receive_output(self, step, outputparms, jobout, processStatus):
295319
step.completed = True
296320
self.made_progress = True
297321

298-
def try_make_job(self, step, **kwargs):
299-
# type: (WorkflowJobStep, **Any) -> Generator
322+
completed = sum(1 for s in self.steps if s.completed)
323+
if completed == len(self.steps):
324+
self.do_output_callback(final_output_callback)
325+
326+
def try_make_job(self, step, final_output_callback, **kwargs):
327+
# type: (WorkflowJobStep, Callable[[Any, Any], Any], **Any) -> Generator
300328
inputparms = step.tool["inputs"]
301329
outputparms = step.tool["outputs"]
302330

@@ -315,7 +343,7 @@ def try_make_job(self, step, **kwargs):
315343

316344
_logger.debug(u"[%s] starting %s", self.name, step.name)
317345

318-
callback = functools.partial(self.receive_output, step, outputparms)
346+
callback = functools.partial(self.receive_output, step, outputparms, final_output_callback)
319347

320348
valueFrom = {
321349
i["id"]: i["valueFrom"] for i in step.tool["inputs"]
@@ -394,7 +422,7 @@ def valueFromFunc(k, v): # type: (Any, Any) -> Any
394422
step.completed = True
395423

396424
def run(self, **kwargs):
397-
_logger.debug(u"[%s] workflow starting", self.name)
425+
_logger.info(u"[%s] start", self.name)
398426

399427
def job(self, joborder, output_callback, **kwargs):
400428
# type: (Dict[Text, Any], Callable[[Any, Any], Any], **Any) -> Generator
@@ -429,7 +457,7 @@ def job(self, joborder, output_callback, **kwargs):
429457

430458
if not step.submitted:
431459
try:
432-
step.iterable = self.try_make_job(step, **kwargs)
460+
step.iterable = self.try_make_job(step, output_callback, **kwargs)
433461
except WorkflowException as e:
434462
_logger.error(u"[%s] Cannot make job: %s", step.name, e)
435463
_logger.debug("", exc_info=True)
@@ -458,19 +486,8 @@ def job(self, joborder, output_callback, **kwargs):
458486
else:
459487
yield None
460488

461-
supportsMultipleInput = bool(self.workflow.get_requirement("MultipleInputFeatureRequirement")[0])
462-
463-
try:
464-
wo = object_from_state(self.state, self.tool["outputs"], True, supportsMultipleInput, "outputSource",
465-
incomplete=True)
466-
except WorkflowException as e:
467-
_logger.error(u"[%s] Cannot collect workflow output: %s", self.name, e)
468-
wo = {}
469-
self.processStatus = "permanentFail"
470-
471-
_logger.info(u"[%s] outdir is %s", self.name, self.outdir)
472-
473-
output_callback(wo, self.processStatus)
489+
if not self.did_callback:
490+
self.do_output_callback(output_callback)
474491

475492

476493
class Workflow(Process):

setup.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@
88
SETUP_DIR = os.path.dirname(__file__)
99
README = os.path.join(SETUP_DIR, 'README.rst')
1010

11+
# if python3 runtime and `setup.py install` is called
12+
if sys.version_info.major == 3 and sys.argv[1] == 'install':
13+
print("Aborting installation. CWL Tool doesn't support Python 3 currently.")
14+
print("Install using Python 2 pip.")
15+
exit(1)
16+
1117
try:
1218
import gittaggers
1319

@@ -54,14 +60,19 @@
5460
'schema-salad >= 2.4.20170308171942, < 3',
5561
'typing >= 3.5.2, < 3.6',
5662
'six >= 1.8.0',
57-
5863
],
5964
setup_requires=[] + pytest_runner,
6065
test_suite='tests',
61-
tests_require=['pytest'],
66+
tests_require=['pytest', 'mock >= 2.0.0',],
6267
entry_points={
6368
'console_scripts': ["cwltool=cwltool.main:main"]
6469
},
6570
zip_safe=True,
6671
cmdclass={'egg_info': tagger},
72+
classifiers=[
73+
'Development Status :: 5 - Production/Stable',
74+
'Operating System :: POSIX',
75+
'Operating System :: OS Independent',
76+
'Programming Language :: Python :: 2 :: Only',
77+
]
6778
)

tests/test_js_sandbox.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import unittest
2+
from mock import Mock, patch
3+
4+
# we should modify the subprocess imported from cwltool.sandboxjs
5+
from cwltool.sandboxjs import check_js_threshold_version, subprocess, minimum_node_version_str
6+
7+
class Javascript_Sanity_Checks(unittest.TestCase):
8+
9+
def setUp(self):
10+
self.check_output = subprocess.check_output
11+
12+
def tearDown(self):
13+
subprocess.check_output = self.check_output
14+
15+
def test_node_version(self):
16+
subprocess.check_output = Mock(return_value=b'v0.8.26\n')
17+
self.assertEquals(check_js_threshold_version('node'), False)
18+
19+
subprocess.check_output = Mock(return_value=b'v0.10.25\n')
20+
self.assertEquals(check_js_threshold_version('node'), False)
21+
22+
subprocess.check_output = Mock(return_value=b'v0.10.26\n')
23+
self.assertEquals(check_js_threshold_version('node'), True)
24+
25+
subprocess.check_output = Mock(return_value=b'v4.4.2\n')
26+
self.assertEquals(check_js_threshold_version('node'), True)
27+
28+
subprocess.check_output = Mock(return_value=b'v7.7.3\n')
29+
self.assertEquals(check_js_threshold_version('node'), True)
30+
31+
def test_is_javascript_installed(self):
32+
pass

0 commit comments

Comments
 (0)