Skip to content

Commit 51fde70

Browse files
author
Peter Amstutz
committed
Merge branch 'toil-fixes' of github.com:common-workflow-language/cwltool into toil-fixes
2 parents d033349 + 3d0c647 commit 51fde70

File tree

10 files changed

+91
-23
lines changed

10 files changed

+91
-23
lines changed

cwltool/builder.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ def __init__(self): # type: () -> None
5252
self.make_fs_access = None # type: Type[StdFsAccess]
5353
self.debug = False # type: bool
5454
self.mutation_manager = None # type: MutationManager
55+
self.force_docker_pull = False # type: bool
5556

5657
# One of "no_listing", "shallow_listing", "deep_listing"
5758
# Will be default "no_listing" for CWL v1.1
@@ -255,4 +256,5 @@ def do_eval(self, ex, context=None, pull_image=True, recursive=False):
255256
self.resources,
256257
context=context, pull_image=pull_image,
257258
timeout=self.timeout,
259+
force_docker_pull=self.force_docker_pull,
258260
debug=self.debug)

cwltool/expression.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,8 @@ def next_seg(remain, obj): # type: (Text, Any) -> Any
153153
return obj
154154

155155

156-
def evaluator(ex, jslib, obj, fullJS=False, timeout=None, debug=False):
157-
# type: (Text, Text, Dict[Text, Any], bool, int, bool) -> JSON
156+
def evaluator(ex, jslib, obj, fullJS=False, timeout=None, force_docker_pull=False, debug=False):
157+
# type: (Text, Text, Dict[Text, Any], bool, int, bool, bool) -> JSON
158158
m = param_re.match(ex)
159159
if m:
160160
if m.end(1)+1 == len(ex) and m.group(1) == "null":
@@ -164,16 +164,17 @@ def evaluator(ex, jslib, obj, fullJS=False, timeout=None, debug=False):
164164
except Exception as w:
165165
raise WorkflowException("%s%s" % (m.group(1), w))
166166
elif fullJS:
167-
return sandboxjs.execjs(ex, jslib, timeout=timeout, debug=debug)
167+
return sandboxjs.execjs(ex, jslib, timeout=timeout, force_docker_pull=force_docker_pull, debug=debug)
168168
else:
169169
raise sandboxjs.JavascriptException(
170170
"Syntax error in parameter reference '%s' or used Javascript code without specifying InlineJavascriptRequirement.",
171171
ex)
172172

173173

174174
def interpolate(scan, rootvars,
175-
timeout=None, fullJS=None, jslib="", debug=False):
176-
# type: (Text, Dict[Text, Any], int, bool, Union[str, Text], bool) -> JSON
175+
timeout=None, fullJS=None, jslib="",force_docker_pull=False,
176+
debug=False):
177+
# type: (Text, Dict[Text, Any], int, bool, Union[str, Text], bool, bool) -> JSON
177178
scan = scan.strip()
178179
parts = []
179180
w = scanner(scan)
@@ -182,7 +183,8 @@ def interpolate(scan, rootvars,
182183

183184
if scan[w[0]] == '$':
184185
e = evaluator(scan[w[0] + 1:w[1]], jslib, rootvars, fullJS=fullJS,
185-
timeout=timeout, debug=debug)
186+
timeout=timeout, force_docker_pull=force_docker_pull,
187+
debug=debug)
186188
if w[0] == 0 and w[1] == len(scan):
187189
return e
188190
leaf = json.dumps(e, sort_keys=True)
@@ -200,8 +202,8 @@ def interpolate(scan, rootvars,
200202

201203

202204
def do_eval(ex, jobinput, requirements, outdir, tmpdir, resources,
203-
context=None, pull_image=True, timeout=None, debug=False):
204-
# type: (Union[dict, AnyStr], Dict[Text, Union[Dict, List, Text]], List[Dict[Text, Any]], Text, Text, Dict[Text, Union[int, Text]], Any, bool, int, bool) -> Any
205+
context=None, pull_image=True, timeout=None, force_docker_pull=False, debug=False):
206+
# type: (Union[dict, AnyStr], Dict[Text, Union[Dict, List, Text]], List[Dict[Text, Any]], Text, Text, Dict[Text, Union[int, Text]], Any, bool, int, bool, bool) -> Any
205207

206208
runtime = copy.copy(resources)
207209
runtime["tmpdir"] = docker_windows_path_adjust(tmpdir)
@@ -227,6 +229,7 @@ def do_eval(ex, jobinput, requirements, outdir, tmpdir, resources,
227229
timeout=timeout,
228230
fullJS=fullJS,
229231
jslib=jslib,
232+
force_docker_pull=force_docker_pull,
230233
debug=debug)
231234
except Exception as e:
232235
raise WorkflowException("Expression evaluation error:\n%s" % e)

cwltool/main.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,9 @@ def arg_parser(): # type: () -> argparse.ArgumentParser
226226
exgroup.add_argument("--make-template", action="store_true",
227227
help="Generate a template input object")
228228

229-
229+
parser.add_argument("--force-docker-pull", action="store_true",
230+
default=False, help="Pull latest docker image even if"
231+
" it is locally present", dest="force_docker_pull")
230232
parser.add_argument("workflow", type=Text, nargs="?", default=None)
231233
parser.add_argument("job_order", nargs=argparse.REMAINDER)
232234

cwltool/pathmapper.py

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@
55
import stat
66
import uuid
77
from functools import partial
8+
from tempfile import NamedTemporaryFile
9+
10+
import requests
11+
from cachecontrol import CacheControl
12+
from cachecontrol.caches import FileCache
813
from typing import Any, Callable, Dict, Iterable, List, Set, Text, Tuple, Union
914

1015
import schema_salad.validate as validate
@@ -139,6 +144,29 @@ def trim_listing(obj):
139144
if obj.get("location", "").startswith("file://") and "listing" in obj:
140145
del obj["listing"]
141146

147+
# Download http Files
148+
def downloadHttpFile(httpurl):
149+
# type: (Text) -> Text
150+
cache_session = None
151+
if "XDG_CACHE_HOME" in os.environ:
152+
directory = os.environ["XDG_CACHE_HOME"]
153+
elif "HOME" in os.environ:
154+
directory = os.environ["HOME"]
155+
else:
156+
directory = os.path.expanduser('~')
157+
158+
cache_session = CacheControl(
159+
requests.Session(),
160+
cache=FileCache(
161+
os.path.join(directory, ".cache", "cwltool")))
162+
163+
r = cache_session.get(httpurl, stream=True)
164+
with NamedTemporaryFile(mode='wb', delete=False) as f:
165+
for chunk in r.iter_content(chunk_size=16384):
166+
if chunk: # filter out keep-alive new chunks
167+
f.write(chunk)
168+
r.close()
169+
return f.name
142170

143171
class PathMapper(object):
144172
"""Mapping of files from relative path provided in the file to a tuple of
@@ -208,14 +236,18 @@ def visit(self, obj, stagedir, basedir, copy=False, staged=False):
208236
self._pathmap[obj["location"]] = MapperEnt(obj["contents"], tgt, "CreateFile", staged)
209237
else:
210238
with SourceLine(obj, "location", validate.ValidationException):
211-
# Dereference symbolic links
212239
deref = ab
213-
st = os.lstat(deref)
214-
while stat.S_ISLNK(st.st_mode):
215-
rl = os.readlink(deref)
216-
deref = rl if os.path.isabs(rl) else os.path.join(
217-
os.path.dirname(deref), rl)
240+
if urllib.parse.urlsplit(deref).scheme in ['http','https']:
241+
deref = downloadHttpFile(path)
242+
else:
243+
# Dereference symbolic links
218244
st = os.lstat(deref)
245+
while stat.S_ISLNK(st.st_mode):
246+
rl = os.readlink(deref)
247+
deref = rl if os.path.isabs(rl) else os.path.join(
248+
os.path.dirname(deref), rl)
249+
st = os.lstat(deref)
250+
219251
self._pathmap[path] = MapperEnt(deref, tgt, "WritableFile" if copy else "File", staged)
220252
self.visitlisting(obj.get("secondaryFiles", []), stagedir, basedir, copy=copy, staged=staged)
221253

cwltool/process.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,7 @@ def _init_job(self, joborder, **kwargs):
560560

561561
builder.make_fs_access = kwargs.get("make_fs_access") or StdFsAccess
562562
builder.fs_access = builder.make_fs_access(kwargs["basedir"])
563+
builder.force_docker_pull = kwargs.get("force_docker_pull")
563564

564565
loadListingReq, _ = self.get_requirement("http://commonwl.org/cwltool#LoadListingRequirement")
565566
if loadListingReq:

cwltool/sandboxjs.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,11 @@ def check_js_threshold_version(working_alias):
5353
return False
5454

5555

56-
def new_js_proc():
57-
# type: () -> subprocess.Popen
56+
def new_js_proc(force_docker_pull=False):
57+
# type: (bool) -> subprocess.Popen
5858

5959
res = resource_stream(__name__, 'cwlNodeEngine.js')
6060
nodecode = res.read()
61-
6261
required_node_version, docker = (False,)*2
6362
nodejs = None
6463
trynodes = ("nodejs", "node")
@@ -86,10 +85,11 @@ def new_js_proc():
8685
try:
8786
nodeimg = "node:slim"
8887
global have_node_slim
88+
8989
if not have_node_slim:
9090
dockerimgs = subprocess.check_output(["docker", "images", "-q", nodeimg]).decode('utf-8')
9191
# if output is an empty string
92-
if len(dockerimgs.split("\n")) <= 1:
92+
if (len(dockerimgs.split("\n")) <= 1) or force_docker_pull:
9393
# pull node:slim docker container
9494
nodejsimg = subprocess.check_output(["docker", "pull", nodeimg]).decode('utf-8')
9595
_logger.info("Pulled Docker image %s %s", nodeimg, nodejsimg)
@@ -124,10 +124,10 @@ def new_js_proc():
124124
return nodejs
125125

126126

127-
def execjs(js, jslib, timeout=None, debug=False): # type: (Union[Mapping, Text], Any, int, bool) -> JSON
127+
def execjs(js, jslib, timeout=None, force_docker_pull=False, debug=False): # type: (Union[Mapping, Text], Any, int, bool, bool) -> JSON
128128

129129
if not hasattr(localdata, "proc") or localdata.proc.poll() is not None or onWindows():
130-
localdata.proc = new_js_proc()
130+
localdata.proc = new_js_proc(force_docker_pull=force_docker_pull)
131131

132132
nodejs = localdata.proc
133133

cwltool/stdfsaccess.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
def abspath(src, basedir): # type: (Text, Text) -> Text
1414
if src.startswith(u"file://"):
1515
ab = six.text_type(uri_file_path(str(src)))
16+
elif urllib.parse.urlsplit(src).scheme in ['http','https']:
17+
return src
1618
else:
1719
if basedir.startswith(u"file://"):
1820
ab = src if os.path.isabs(src) else basedir+ '/'+ src

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
requests>=1.0
1+
requests>=2.12.4
22
ruamel.yaml>=0.12.4,<0.15
33
rdflib==4.2.2
44
rdflib-jsonld==0.4.0

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
include_package_data=True,
4949
install_requires=[
5050
'setuptools',
51-
'requests >= 1.0',
51+
'requests >= 2.12.4',
5252
'ruamel.yaml >= 0.12.4, < 0.15',
5353
'rdflib >= 4.2.2, < 4.3.0',
5454
'shellescape >= 3.4.1, < 3.5',

tests/test_http_input.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from __future__ import absolute_import
2+
import unittest
3+
import os
4+
import tempfile
5+
from cwltool.pathmapper import PathMapper
6+
7+
8+
class TestHttpInput(unittest.TestCase):
9+
def test_http_path_mapping(self):
10+
class SubPathMapper(PathMapper):
11+
def __init__(self, referenced_files, basedir, stagedir):
12+
super(SubPathMapper, self).__init__(referenced_files, basedir, stagedir)
13+
input_file_path = "https://raw.githubusercontent.com/common-workflow-language/cwltool/master/tests/2.fasta"
14+
tempdir = tempfile.mkdtemp()
15+
base_file = [{
16+
"class": "File",
17+
"location": "https://raw.githubusercontent.com/common-workflow-language/cwltool/master/tests/2.fasta",
18+
"basename": "chr20.fa"
19+
}]
20+
path_map_obj = SubPathMapper(base_file, os.getcwd(), tempdir)
21+
22+
self.assertIn(input_file_path,path_map_obj._pathmap)
23+
assert os.path.exists(path_map_obj._pathmap[input_file_path].resolved) == 1
24+
with open(path_map_obj._pathmap[input_file_path].resolved) as f:
25+
self.assertIn(">Sequence 561 BP; 135 A; 106 C; 98 G; 222 T; 0 other;",f.read())
26+
f.close()

0 commit comments

Comments
 (0)