22import subprocess
33import logging
44from bd2k .util .exceptions import panic
5+ from toil_scripts .lib import require
56
67_log = logging .getLogger (__name__ )
78
@@ -15,34 +16,55 @@ def mock_mode():
1516 return True if int (os .environ .get ('TOIL_SCRIPTS_MOCK_MODE' , '0' )) else False
1617
1718
18- def docker_call (tool ,
19+ def docker_call (tool = None ,
20+ tools = None ,
1921 parameters = None ,
2022 work_dir = '.' ,
2123 rm = True ,
2224 env = None ,
2325 outfile = None ,
26+ errfile = None ,
2427 inputs = None ,
2528 outputs = None ,
2629 docker_parameters = None ,
2730 check_output = False ,
31+ return_stderr = False ,
2832 mock = None ):
2933 """
3034 Calls Docker, passing along parameters and tool.
3135
32- :param str tool: Name of the Docker image to be used (e.g. quay.io/ucsc_cgl/samtools)
36+ :param (str tool | str tools): str tool name of the Docker image to be used (e.g. tool='quay.io/ucsc_cgl/samtools')
37+ OR str tools of the Docker images and order to be used when piping commands to
38+ Docker. (e.g. 'quay.io/ucsc_cgl/samtools'). Both tool and tools are mutually
39+ exclusive parameters to docker_call.
3340 :param list[str] parameters: Command line arguments to be passed to the tool
3441 :param str work_dir: Directory to mount into the container via `-v`. Destination convention is /data
3542 :param bool rm: Set to True to pass `--rm` flag.
3643 :param dict[str,str] env: Environment variables to be added (e.g. dict(JAVA_OPTS='-Xmx15G'))
3744 :param bool sudo: If True, prepends `sudo` to the docker call
38- :param file outfile: Pipe output of Docker call to file handle
45+ :param file outfile: Pipe stdout of Docker call to file handle
46+ :param file errfile: Pipe stderr of Docker call to file handle
3947 :param list[str] inputs: A list of the input files.
4048 :param dict[str,str] outputs: A dictionary containing the outputs files as keys with either None
4149 or a url. The value is only used if mock=True
4250 :param dict[str,str] docker_parameters: Parameters to pass to docker
4351 :param bool check_output: When True, this function returns docker's output
52+ :param bool return_stderr: When True, this function includes stderr in docker's output
4453 :param bool mock: Whether to run in mock mode. If this variable is unset, its value will be determined by
4554 the environment variable.
55+
56+ Pipes in docker commands:
57+ Running a pipe in docker in 'pipe-in-single-container' mode produces command structure
58+ docker '... | ... | ...' where each '...' command corresponds to each element in the 'parameters'
59+ argument that uses a docker container. This is the most efficient method if you want to run a pipe of
60+ commands where each command uses the same docker container.
61+
62+ Example for running command 'head -c 1M /dev/urandom | gzip | gunzip | md5sum 1>&2':
63+ Running 'pipe-in-single-container' mode:
64+ command= ['head -c 1M /dev/urandom', 'gzip', 'gunzip', 'md5sum 1>&2']
65+ docker_work_dir=curr_work_dir
66+ docker_tools='ubuntu'
67+ stdout = docker_call(work_dir=docker_work_dir, parameters=command, tools=docker_tools, check_output=True)
4668 """
4769 from toil_scripts .lib .urls import download_url
4870
@@ -83,37 +105,56 @@ def docker_call(tool,
83105 if env :
84106 for e , v in env .iteritems ():
85107 base_docker_call .extend (['-e' , '{}={}' .format (e , v )])
108+
86109 if docker_parameters :
87110 base_docker_call += docker_parameters
111+
112+ docker_call = []
113+
114+ require (bool (tools ) != bool (tool ), 'Either "tool" or "tools" must contain a value, but not both' )
115+
116+ # Pipe functionality
117+ # each element in the parameters list must represent a sub-pipe command
118+ if bool (tools ):
119+ # If tools is set then format the docker call in the 'pipe-in-single-container' mode
120+ docker_call = " " .join (base_docker_call + ['--entrypoint /bin/bash' , tools , '-c \' {}\' ' .format (" | " .join (parameters ))])
121+ _log .debug ("Calling docker with %s." % docker_call )
122+
123+ else :
124+ docker_call = " " .join (base_docker_call + [tool ] + parameters )
125+ _log .debug ("Calling docker with %s." % docker_call )
88126
89- _log .debug ("Calling docker with %s." % " " .join (base_docker_call + [tool ] + parameters ))
90-
91- docker_call = base_docker_call + [tool ] + parameters
92-
127+
93128 try :
94129 if outfile :
95- subprocess .check_call (docker_call , stdout = outfile )
130+ if errfile :
131+ subprocess .check_call (docker_call , stdout = outfile , stderr = errfile , shell = True )
132+ else :
133+ subprocess .check_call (docker_call , stdout = outfile , shell = True )
96134 else :
97135 if check_output :
98- return subprocess .check_output (docker_call )
136+ if return_stderr :
137+ return subprocess .check_output (docker_call , shell = True , stderr = subprocess .STDOUT )
138+ else :
139+ return subprocess .check_output (docker_call , shell = True )
99140 else :
100- subprocess .check_call (docker_call )
141+ subprocess .check_call (docker_call , shell = True )
101142 # Fix root ownership of output files
102143 except :
103144 # Panic avoids hiding the exception raised in the try block
104145 with panic ():
105- _fix_permissions (base_docker_call , tool , work_dir )
146+ _fix_permissions (base_docker_call , tool , tools , work_dir )
106147 else :
107- _fix_permissions (base_docker_call , tool , work_dir )
148+ _fix_permissions (base_docker_call , tool , tools , work_dir )
108149
109150 for filename in outputs .keys ():
110151 if not os .path .isabs (filename ):
111152 filename = os .path .join (work_dir , filename )
112153 assert (os .path .isfile (filename ))
113154
114155
115- def _fix_permissions (base_docker_call , tool , work_dir ):
116- """
156+ def _fix_permissions (base_docker_call , tool , tools , work_dir ):
157+ """
117158 Fix permission of a mounted Docker directory by reusing the tool
118159
119160 :param list base_docker_call: Docker run parameters
@@ -122,5 +163,13 @@ def _fix_permissions(base_docker_call, tool, work_dir):
122163 """
123164 base_docker_call .append ('--entrypoint=chown' )
124165 stat = os .stat (work_dir )
125- command = base_docker_call + [tool ] + ['-R' , '{}:{}' .format (stat .st_uid , stat .st_gid ), '/data' ]
126- subprocess .check_call (command )
166+ if tools :
167+ command = base_docker_call + [tools ] + ['-R' , '{}:{}' .format (stat .st_uid , stat .st_gid ), '/data' ]
168+ subprocess .check_call (command )
169+ else :
170+ command = base_docker_call + [tool ] + ['-R' , '{}:{}' .format (stat .st_uid , stat .st_gid ), '/data' ]
171+ subprocess .check_call (command )
172+
173+
174+
175+
0 commit comments