Mercurial > repos > mvdbeek > docker_scriptrunner
comparison scriptrunner.py @ 2:495946ffc2d6 draft default tip
planemo upload for repository https://github.com/mvdbeek/docker_scriptrunner/ commit dded837d19aeb3f06b84e5076282cedeeaf713fa
author | mvdbeek |
---|---|
date | Sun, 22 Jul 2018 13:38:01 -0400 |
parents | 21d312776891 |
children |
comparison
equal
deleted
inserted
replaced
1:315a7e9ed6eb | 2:495946ffc2d6 |
---|---|
1 # DockerToolFactory.py | 1 # DockerToolFactory.py |
2 # see https://github.com/mvdbeek/scriptrunner | 2 # see https://github.com/mvdbeek/scriptrunner |
3 | 3 |
4 import sys | 4 from __future__ import print_function |
5 import shutil | 5 import sys |
6 import subprocess | 6 import shutil |
7 import os | 7 import subprocess |
8 import time | 8 import os |
9 import tempfile | 9 import time |
10 import tempfile | |
10 import argparse | 11 import argparse |
11 import getpass | |
12 import tarfile | |
13 import re | |
14 import shutil | |
15 import math | 12 import math |
16 import fileinput | 13 from os.path import abspath |
17 from os.path import abspath | 14 |
18 | 15 progname = os.path.split(sys.argv[0])[1] |
19 | 16 verbose = False |
20 progname = os.path.split(sys.argv[0])[1] | |
21 verbose = False | |
22 debug = False | 17 debug = False |
23 | 18 |
19 html_escape_table = { | |
20 "&": "&", | |
21 ">": ">", | |
22 "<": "<", | |
23 "$": "\$" | |
24 } | |
25 | |
26 | |
24 def timenow(): | 27 def timenow(): |
25 """return current time as a string | 28 """Return current time as a string.""" |
26 """ | |
27 return time.strftime('%d/%m/%Y %H:%M:%S', time.localtime(time.time())) | 29 return time.strftime('%d/%m/%Y %H:%M:%S', time.localtime(time.time())) |
28 | 30 |
29 html_escape_table = { | |
30 "&": "&", | |
31 ">": ">", | |
32 "<": "<", | |
33 "$": "\$" | |
34 } | |
35 | 31 |
36 def html_escape(text): | 32 def html_escape(text): |
37 """Produce entities within text.""" | 33 """Produce entities within text.""" |
38 return "".join(html_escape_table.get(c,c) for c in text) | 34 return "".join(html_escape_table.get(c, c) for c in text) |
35 | |
39 | 36 |
40 def cmd_exists(cmd): | 37 def cmd_exists(cmd): |
41 return subprocess.call("type " + cmd, shell=True, | 38 return subprocess.call("type " + cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) == 0 |
42 stdout=subprocess.PIPE, stderr=subprocess.PIPE) == 0 | 39 |
43 | 40 |
44 def construct_bind(host_path, container_path=False, binds=None, ro=True): | 41 def construct_bind(host_path, container_path=False, binds=None, ro=True): |
45 #TODO remove container_path if it's alwyas going to be the same as host_path | 42 # TODO remove container_path if it's alwyas going to be the same as host_path |
46 '''build or extend binds dictionary with container path. binds is used | 43 """Build or extend binds dictionary with container path. binds is used |
47 to mount all files using the docker-py client.''' | 44 to mount all files using the docker-py client.""" |
48 if not binds: | 45 if not binds: |
49 binds={} | 46 binds = {} |
50 if isinstance(host_path, list): | 47 if isinstance(host_path, list): |
51 for k,v in enumerate(host_path): | 48 for k, v in enumerate(host_path): |
52 if not container_path: | 49 if not container_path: |
53 container_path=host_path[k] | 50 container_path = host_path[k] |
54 binds[host_path[k]]={'bind':container_path, 'ro':ro} | 51 binds[host_path[k]] = {'bind': container_path, 'ro': ro} |
55 container_path=False #could be more elegant | 52 container_path = False # could be more elegant |
56 return binds | 53 return binds |
57 else: | 54 else: |
58 if not container_path: | 55 if not container_path: |
59 container_path=host_path | 56 container_path = host_path |
60 binds[host_path]={'bind':container_path, 'ro':ro} | 57 binds[host_path] = {'bind': container_path, 'ro': ro} |
61 return binds | 58 return binds |
62 | 59 |
60 | |
63 def switch_to_docker(opts): | 61 def switch_to_docker(opts): |
64 import docker #need local import, as container does not have docker-py | 62 import docker # need local import, as container does not have docker-py |
65 user_id = os.getuid() | 63 user_id = os.getuid() |
66 group_id = os.getgid() | 64 group_id = os.getgid() |
67 docker_client=docker.Client() | 65 docker_client = docker.APIClient() |
68 toolfactory_path=abspath(sys.argv[0]) | 66 toolfactory_path = abspath(sys.argv[0]) |
69 binds=construct_bind(host_path=opts.script_path, ro=False) | 67 binds = construct_bind(host_path=opts.script_path, ro=False) |
70 binds=construct_bind(binds=binds, host_path=abspath(opts.output_dir), ro=False) | 68 binds = construct_bind(binds=binds, host_path=abspath(opts.output_dir), ro=False) |
71 if len(opts.input_tab)>0: | 69 if len(opts.input_file) > 0: |
72 binds=construct_bind(binds=binds, host_path=opts.input_tab, ro=True) | 70 binds = construct_bind(binds=binds, host_path=opts.input_file, ro=True) |
73 if not opts.output_tab == 'None': | 71 if not opts.output_file == 'None': |
74 binds=construct_bind(binds=binds, host_path=opts.output_tab, ro=False) | 72 binds = construct_bind(binds=binds, host_path=opts.output_file, ro=False) |
75 if opts.make_HTML: | 73 if opts.make_HTML: |
76 binds=construct_bind(binds=binds, host_path=opts.output_html, ro=False) | 74 binds = construct_bind(binds=binds, host_path=opts.output_html, ro=False) |
77 binds=construct_bind(binds=binds, host_path=toolfactory_path) | 75 binds = construct_bind(binds=binds, host_path=toolfactory_path) |
78 volumes=binds.keys() | 76 volumes = list(binds.keys()) |
79 sys.argv=[abspath(opts.output_dir) if sys.argv[i-1]=='--output_dir' else arg for i,arg in enumerate(sys.argv)] ##inject absolute path of working_dir | 77 sys.argv = [abspath(opts.output_dir) if sys.argv[i - 1] == '--output_dir' else arg for i, arg in enumerate(sys.argv)] # inject absolute path of working_dir |
80 cmd=['python', '-u']+sys.argv+['--dockerized', '1', "--user_id", str(user_id), "--group_id", str(group_id)] | 78 cmd = ['python', '-u'] + sys.argv + ['--dockerized', '1', "--user_id", str(user_id), "--group_id", str(group_id)] |
81 image_exists = [ True for image in docker_client.images() if opts.docker_image in image['RepoTags'] ] | 79 image_exists = [True for image in docker_client.images() if opts.docker_image in image['RepoTags']] |
82 if not image_exists: | 80 if not image_exists: |
83 docker_client.pull(opts.docker_image) | 81 docker_client.pull(opts.docker_image) |
84 container=docker_client.create_container( | 82 container = docker_client.create_container( |
85 image=opts.docker_image, | 83 image=opts.docker_image, |
86 volumes=volumes, | 84 volumes=volumes, |
87 command=cmd | 85 command=cmd, |
88 ) | 86 host_config=docker_client.create_host_config(binds=binds)) |
89 docker_client.start(container=container[u'Id'], binds=binds) | 87 docker_client.start(container=container[u'Id']) |
90 docker_client.wait(container=container[u'Id']) | 88 exit_code = docker_client.wait(container=container[u'Id'])['StatusCode'] |
91 logs=docker_client.logs(container=container[u'Id']) | 89 logs = docker_client.logs(container=container[u'Id']) |
92 print "".join([log for log in logs]) | 90 print(logs, end="", file=sys.stderr) |
93 docker_client.remove_container(container[u'Id']) | 91 docker_client.remove_container(container[u'Id']) |
92 return exit_code | |
93 | |
94 | 94 |
95 class ScriptRunner: | 95 class ScriptRunner: |
96 """class is a wrapper for an arbitrary script | 96 """class is a wrapper for an arbitrary script |
97 """ | 97 """ |
98 | 98 |
99 def __init__(self,opts=None,treatbashSpecial=True, image_tag='base'): | 99 def __init__(self, opts=None, treatbashSpecial=True, image_tag='base'): |
100 """ | 100 """ |
101 cleanup inputs, setup some outputs | 101 cleanup inputs, setup some outputs |
102 | |
103 """ | 102 """ |
104 self.opts = opts | 103 self.opts = opts |
105 self.scriptname = 'script' | 104 self.scriptname = 'script' |
106 self.useIM = cmd_exists('convert') | |
107 self.useGS = cmd_exists('gs') | |
108 self.temp_warned = False # we want only one warning if $TMP not set | |
109 self.treatbashSpecial = treatbashSpecial | 105 self.treatbashSpecial = treatbashSpecial |
110 self.image_tag = image_tag | 106 self.image_tag = image_tag |
111 os.chdir(abspath(opts.output_dir)) | 107 os.chdir(abspath(opts.output_dir)) |
112 self.thumbformat = 'png' | 108 self.thumbformat = 'png' |
113 s = open(self.opts.script_path,'r').readlines() | 109 s = open(self.opts.script_path, 'r').readlines() |
114 s = [x.rstrip() for x in s] # remove pesky dos line endings if needed | 110 s = [x.rstrip() for x in s] # remove pesky dos line endings if needed |
115 self.script = '\n'.join(s) | 111 self.script = '\n'.join(s) |
116 fhandle,self.sfile = tempfile.mkstemp(prefix='script',suffix=".%s" % (opts.interpreter)) | 112 fhandle, self.sfile = tempfile.mkstemp(prefix='script', suffix=".%s" % (opts.interpreter)) |
117 tscript = open(self.sfile,'w') # use self.sfile as script source for Popen | 113 tscript = open(self.sfile, 'w') # use self.sfile as script source for Popen |
118 tscript.write(self.script) | 114 tscript.write(self.script) |
119 tscript.close() | 115 tscript.close() |
120 self.indentedScript = '\n'.join([' %s' % html_escape(x) for x in s]) # for restructured text in help | 116 self.indentedScript = '\n'.join([' %s' % html_escape(x) for x in s]) # for restructured text in help |
121 self.escapedScript = '\n'.join([html_escape(x) for x in s]) | 117 self.escapedScript = '\n'.join([html_escape(x) for x in s]) |
122 self.elog = os.path.join(self.opts.output_dir,"%s_error.log" % self.scriptname) | 118 self.elog = os.path.join(self.opts.output_dir, "%s_error.log" % self.scriptname) |
123 if opts.output_dir: # may not want these complexities | 119 if opts.output_dir: # may not want these complexities |
124 self.tlog = os.path.join(self.opts.output_dir,"%s_runner.log" % self.scriptname) | 120 self.tlog = os.path.join(self.opts.output_dir, "%s_runner.log" % self.scriptname) |
125 art = '%s.%s' % (self.scriptname,opts.interpreter) | 121 art = '%s.%s' % (self.scriptname, opts.interpreter) |
126 artpath = os.path.join(self.opts.output_dir,art) # need full path | 122 artpath = os.path.join(self.opts.output_dir, art) # need full path |
127 artifact = open(artpath,'w') # use self.sfile as script source for Popen | 123 artifact = open(artpath, 'w') # use self.sfile as script source for Popen |
128 artifact.write(self.script) | 124 artifact.write(self.script) |
129 artifact.close() | 125 artifact.close() |
130 self.cl = [] | 126 self.cl = [] |
131 self.html = [] | 127 self.html = [] |
132 a = self.cl.append | 128 self.cl.append(opts.interpreter) |
133 a(opts.interpreter) | 129 if self.treatbashSpecial and opts.interpreter in ['bash', 'sh']: |
134 if self.treatbashSpecial and opts.interpreter in ['bash','sh']: | 130 self.cl.append(self.sfile) |
135 a(self.sfile) | |
136 else: | 131 else: |
137 a('-') # stdin | 132 self.cl.append('-') # stdin |
138 for input in opts.input_tab: | 133 for input in opts.input_file: |
139 a(input) | 134 self.cl.append(input) |
140 if opts.output_tab == 'None': #If tool generates only HTML, set output name to toolname | 135 if opts.output_file == 'None': # If tool generates only HTML, set output name to toolname |
141 a(str(self.scriptname)+'.out') | 136 self.cl.append(str(self.scriptname) + '.out') |
142 a(opts.output_tab) | 137 self.cl.append(opts.output_file) |
143 for param in opts.additional_parameters: | 138 for param in opts.additional_parameters: |
144 param, value=param.split(',') | 139 param, value = param.split(',') |
145 a('--'+param) | 140 self.cl.append('--' + param) |
146 a(value) | 141 self.cl.append(value) |
147 self.outFormats = opts.output_format | 142 self.outFormats = opts.output_format |
148 self.inputFormats = [formats for formats in opts.input_formats] | 143 self.inputFormats = [formats for formats in opts.input_formats] |
149 self.test1Input = '%s_test1_input.xls' % self.scriptname | 144 self.test1Input = '%s_test1_input.xls' % self.scriptname |
150 self.test1Output = '%s_test1_output.xls' % self.scriptname | 145 self.test1Output = '%s_test1_output.xls' % self.scriptname |
151 self.test1HTML = '%s_test1_output.html' % self.scriptname | 146 self.test1HTML = '%s_test1_output.html' % self.scriptname |
152 | 147 |
153 | 148 def compressPDF(self, inpdf=None, thumbformat='png'): |
154 def compressPDF(self,inpdf=None,thumbformat='png'): | 149 """ |
155 """need absolute path to pdf | 150 inpdf is absolute path to PDF |
156 note that GS gets confoozled if no $TMP or $TEMP | 151 """ |
157 so we set it | 152 assert os.path.isfile(inpdf), "## Input %s supplied to %s compressPDF not found" % (inpdf, self.myName) |
158 """ | 153 hlog = os.path.join(self.opts.output_dir, "compress_%s.txt" % os.path.basename(inpdf)) |
159 assert os.path.isfile(inpdf), "## Input %s supplied to %s compressPDF not found" % (inpdf,self.myName) | 154 sto = open(hlog, 'a') |
160 hlog = os.path.join(self.opts.output_dir,"compress_%s.txt" % os.path.basename(inpdf)) | |
161 sto = open(hlog,'a') | |
162 our_env = os.environ.copy() | 155 our_env = os.environ.copy() |
163 our_tmp = our_env.get('TMP',None) | 156 our_tmp = our_env.get('TMP', None) |
164 if not our_tmp: | 157 if not our_tmp: |
165 our_tmp = our_env.get('TEMP',None) | 158 our_env['TMP'] = tempfile.gettempdir() |
166 if not (our_tmp and os.path.exists(our_tmp)): | |
167 newtmp = os.path.join(self.opts.output_dir,'tmp') | |
168 try: | |
169 os.mkdir(newtmp) | |
170 except: | |
171 sto.write('## WARNING - cannot make %s - it may exist or permissions need fixing\n' % newtmp) | |
172 our_env['TEMP'] = newtmp | |
173 if not self.temp_warned: | |
174 sto.write('## WARNING - no $TMP or $TEMP!!! Please fix - using %s temporarily\n' % newtmp) | |
175 self.temp_warned = True | |
176 outpdf = '%s_compressed' % inpdf | 159 outpdf = '%s_compressed' % inpdf |
177 cl = ["gs", "-sDEVICE=pdfwrite", "-dNOPAUSE", "-dUseCIEColor", "-dBATCH","-dPDFSETTINGS=/printer", "-sOutputFile=%s" % outpdf,inpdf] | 160 cl = ["gs", "-sDEVICE=pdfwrite", "-dNOPAUSE", "-dUseCIEColor", "-dBATCH", "-dPDFSETTINGS=/printer", "-sOutputFile=%s" % outpdf, inpdf] |
178 x = subprocess.Popen(cl,stdout=sto,stderr=sto,cwd=self.opts.output_dir,env=our_env) | 161 x = subprocess.Popen(cl, stdout=sto, stderr=sto, cwd=self.opts.output_dir, env=our_env) |
179 retval1 = x.wait() | 162 retval1 = x.wait() |
180 sto.close() | 163 sto.close() |
181 if retval1 == 0: | 164 if retval1 == 0: |
182 os.unlink(inpdf) | 165 os.unlink(inpdf) |
183 shutil.move(outpdf,inpdf) | 166 shutil.move(outpdf, inpdf) |
184 os.unlink(hlog) | 167 os.unlink(hlog) |
185 hlog = os.path.join(self.opts.output_dir,"thumbnail_%s.txt" % os.path.basename(inpdf)) | 168 hlog = os.path.join(self.opts.output_dir, "thumbnail_%s.txt" % os.path.basename(inpdf)) |
186 sto = open(hlog,'w') | 169 sto = open(hlog, 'w') |
187 outpng = '%s.%s' % (os.path.splitext(inpdf)[0],thumbformat) | 170 outpng = '%s.%s' % (os.path.splitext(inpdf)[0], thumbformat) |
188 cl2 = ['convert', inpdf, outpng] | 171 cl2 = ['convert', inpdf, outpng] |
189 x = subprocess.Popen(cl2,stdout=sto,stderr=sto,cwd=self.opts.output_dir,env=our_env) | 172 x = subprocess.Popen(cl2, stdout=sto, stderr=sto, cwd=self.opts.output_dir, env=our_env) |
190 retval2 = x.wait() | 173 retval2 = x.wait() |
191 sto.close() | 174 sto.close() |
192 if retval2 == 0: | 175 if retval2 == 0: |
193 os.unlink(hlog) | 176 os.unlink(hlog) |
194 retval = retval1 or retval2 | 177 retval = retval1 or retval2 |
195 return retval | 178 return retval |
196 | 179 |
197 | 180 def getfSize(self, fpath, outpath): |
198 def getfSize(self,fpath,outpath): | |
199 """ | 181 """ |
200 format a nice file size string | 182 format a nice file size string |
201 """ | 183 """ |
202 size = '' | 184 size = '' |
203 fp = os.path.join(outpath,fpath) | 185 fp = os.path.join(outpath, fpath) |
204 if os.path.isfile(fp): | 186 if os.path.isfile(fp): |
205 size = '0 B' | 187 size = '0 B' |
206 n = float(os.path.getsize(fp)) | 188 n = float(os.path.getsize(fp)) |
207 if n > 2**20: | 189 if n > 2**20: |
208 size = '%1.1f MB' % (n/2**20) | 190 size = '%1.1f MB' % (n / 2**20) |
209 elif n > 2**10: | 191 elif n > 2**10: |
210 size = '%1.1f KB' % (n/2**10) | 192 size = '%1.1f KB' % (n / 2**10) |
211 elif n > 0: | 193 elif n > 0: |
212 size = '%d B' % (int(n)) | 194 size = '%d B' % (int(n)) |
213 return size | 195 return size |
214 | 196 |
215 def makeHtml(self): | 197 def makeHtml(self): |
216 """ Create an HTML file content to list all the artifacts found in the output_dir | 198 """ Create an HTML file content to list all the artifacts found in the output_dir |
217 """ | 199 """ |
218 | 200 |
219 galhtmlprefix = """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> | 201 galhtmlprefix = """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> |
220 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> | 202 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> |
221 <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> | 203 <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> |
222 <meta name="generator" content="Galaxy %s tool output - see http://g2.trac.bx.psu.edu/" /> | 204 <meta name="generator" content="Galaxy %s tool output - see http://g2.trac.bx.psu.edu/" /> |
223 <title></title> | 205 <title></title> |
224 <link rel="stylesheet" href="/static/style/base.css" type="text/css" /> | 206 <link rel="stylesheet" href="/static/style/base.css" type="text/css" /> |
225 </head> | 207 </head> |
226 <body> | 208 <body> |
227 <div class="toolFormBody"> | 209 <div class="toolFormBody"> |
228 """ | 210 """ |
229 galhtmlattr = """<hr/><div class="infomessage">This tool (%s) was generated by the <a href="https://bitbucket.org/fubar/galaxytoolfactory/overview">Galaxy Tool Factory</a></div><br/>""" | |
230 galhtmlpostfix = """</div></body></html>\n""" | 211 galhtmlpostfix = """</div></body></html>\n""" |
231 | 212 |
232 flist = os.listdir(self.opts.output_dir) | 213 flist = os.listdir(self.opts.output_dir) |
233 flist = [x for x in flist if x <> 'Rplots.pdf'] | 214 flist = [x for x in flist if x != 'Rplots.pdf'] |
234 flist.sort() | 215 flist.sort() |
235 html = [] | 216 html = [] |
236 html.append(galhtmlprefix % progname) | 217 html.append(galhtmlprefix % progname) |
237 html.append('<div class="infomessage">Galaxy Tool "%s" run at %s</div><br/>' % (self.scriptname,timenow())) | 218 html.append('<div class="infomessage">Galaxy Tool "%s" run at %s</div><br/>' % (self.scriptname, timenow())) |
238 fhtml = [] | 219 fhtml = [] |
239 if len(flist) > 0: | 220 if len(flist) > 0: |
240 logfiles = [x for x in flist if x.lower().endswith('.log')] # log file names determine sections | 221 logfiles = [x for x in flist if x.lower().endswith('.log')] # log file names determine sections |
241 logfiles.sort() | 222 logfiles.sort() |
242 logfiles = [x for x in logfiles if abspath(x) <> abspath(self.tlog)] | 223 logfiles = [x for x in logfiles if abspath(x) != abspath(self.tlog)] |
243 logfiles.append(abspath(self.tlog)) # make it the last one | 224 logfiles.append(abspath(self.tlog)) # make it the last one |
244 pdflist = [] | 225 pdflist = [] |
245 npdf = len([x for x in flist if os.path.splitext(x)[-1].lower() == '.pdf']) | 226 npdf = len([x for x in flist if os.path.splitext(x)[-1].lower() == '.pdf']) |
246 for rownum,fname in enumerate(flist): | 227 for rownum, fname in enumerate(flist): |
247 dname,e = os.path.splitext(fname) | 228 dname, e = os.path.splitext(fname) |
248 sfsize = self.getfSize(fname,self.opts.output_dir) | 229 sfsize = self.getfSize(fname, self.opts.output_dir) |
249 if e.lower() == '.pdf' : # compress and make a thumbnail | 230 if e.lower() == '.pdf': # compress and make a thumbnail |
250 thumb = '%s.%s' % (dname,self.thumbformat) | 231 thumb = '%s.%s' % (dname, self.thumbformat) |
251 pdff = os.path.join(self.opts.output_dir,fname) | 232 pdff = os.path.join(self.opts.output_dir, fname) |
252 retval = self.compressPDF(inpdf=pdff,thumbformat=self.thumbformat) | 233 retval = self.compressPDF(inpdf=pdff, thumbformat=self.thumbformat) |
253 if retval == 0: | 234 if retval == 0: |
254 pdflist.append((fname,thumb)) | 235 pdflist.append((fname, thumb)) |
255 else: | 236 else: |
256 pdflist.append((fname,fname)) | 237 pdflist.append((fname, fname)) |
257 if (rownum+1) % 2 == 0: | 238 if (rownum + 1) % 2 == 0: |
258 fhtml.append('<tr class="odd_row"><td><a href="%s">%s</a></td><td>%s</td></tr>' % (fname,fname,sfsize)) | 239 fhtml.append('<tr class="odd_row"><td><a href="%s">%s</a></td><td>%s</td></tr>' % (fname, fname, sfsize)) |
259 else: | 240 else: |
260 fhtml.append('<tr><td><a href="%s">%s</a></td><td>%s</td></tr>' % (fname,fname,sfsize)) | 241 fhtml.append('<tr><td><a href="%s">%s</a></td><td>%s</td></tr>' % (fname, fname, sfsize)) |
261 for logfname in logfiles: # expect at least tlog - if more | 242 for logfname in logfiles: # expect at least tlog - if more |
262 if abspath(logfname) == abspath(self.tlog): # handled later | 243 if abspath(logfname) == abspath(self.tlog): # handled later |
263 sectionname = 'All tool run' | 244 sectionname = 'All tool run' |
264 if (len(logfiles) > 1): | 245 if (len(logfiles) > 1): |
265 sectionname = 'Other' | 246 sectionname = 'Other' |
266 ourpdfs = pdflist | 247 ourpdfs = pdflist |
267 else: | 248 else: |
268 realname = os.path.basename(logfname) | 249 realname = os.path.basename(logfname) |
269 sectionname = os.path.splitext(realname)[0].split('_')[0] # break in case _ added to log | 250 sectionname = os.path.splitext(realname)[0].split('_')[0] # break in case _ added to log |
270 ourpdfs = [x for x in pdflist if os.path.basename(x[0]).split('_')[0] == sectionname] | 251 ourpdfs = [x for x in pdflist if os.path.basename(x[0]).split('_')[0] == sectionname] |
271 pdflist = [x for x in pdflist if os.path.basename(x[0]).split('_')[0] <> sectionname] # remove | 252 pdflist = [x for x in pdflist if os.path.basename(x[0]).split('_')[0] != sectionname] # remove |
272 nacross = 1 | 253 nacross = 1 |
273 npdf = len(ourpdfs) | 254 npdf = len(ourpdfs) |
274 | 255 |
275 if npdf > 0: | 256 if npdf > 0: |
276 nacross = math.sqrt(npdf) ## int(round(math.log(npdf,2))) | 257 nacross = math.sqrt(npdf) |
277 if int(nacross)**2 != npdf: | 258 if int(nacross)**2 != npdf: |
278 nacross += 1 | 259 nacross += 1 |
279 nacross = int(nacross) | 260 nacross = int(nacross) |
280 width = min(400,int(1200/nacross)) | 261 width = min(400, int(1200 / nacross)) |
281 html.append('<div class="toolFormTitle">%s images and outputs</div>' % sectionname) | 262 html.append('<div class="toolFormTitle">%s images and outputs</div>' % sectionname) |
282 html.append('(Click on a thumbnail image to download the corresponding original PDF image)<br/>') | 263 html.append('(Click on a thumbnail image to download the corresponding original PDF image)<br/>') |
283 ntogo = nacross # counter for table row padding with empty cells | 264 ntogo = nacross # counter for table row padding with empty cells |
284 html.append('<div><table class="simple" cellpadding="2" cellspacing="2">\n<tr>') | 265 html.append('<div><table class="simple" cellpadding="2" cellspacing="2">\n<tr>') |
285 for i,paths in enumerate(ourpdfs): | 266 for i, paths in enumerate(ourpdfs): |
286 fname,thumb = paths | 267 fname, thumb = paths |
287 s= """<td><a href="%s"><img src="%s" title="Click to download a PDF of %s" hspace="5" width="%d" | 268 s = """<td><a href="%s"><img src="%s" title="Click to download a PDF of %s" hspace="5" width="%d" |
288 alt="Image called %s"/></a></td>\n""" % (fname,thumb,fname,width,fname) | 269 alt="Image called %s"/></a></td>\n""" % (fname, thumb, fname, width, fname) |
289 if ((i+1) % nacross == 0): | 270 if ((i + 1) % nacross == 0): |
290 s += '</tr>\n' | 271 s += '</tr>\n' |
291 ntogo = 0 | 272 ntogo = 0 |
292 if i < (npdf - 1): # more to come | 273 if i < (npdf - 1): # more to come |
293 s += '<tr>' | 274 s += '<tr>' |
294 ntogo = nacross | 275 ntogo = nacross |
295 else: | 276 else: |
296 ntogo -= 1 | 277 ntogo -= 1 |
297 html.append(s) | 278 html.append(s) |
298 if html[-1].strip().endswith('</tr>'): | 279 if html[-1].strip().endswith('</tr>'): |
299 html.append('</table></div>\n') | 280 html.append('</table></div>\n') |
300 else: | 281 else: |
301 if ntogo > 0: # pad | 282 if ntogo > 0: # pad |
302 html.append('<td> </td>'*ntogo) | 283 html.append('<td> </td>' * ntogo) |
303 html.append('</tr></table></div>\n') | 284 html.append('</tr></table></div>\n') |
304 logt = open(logfname,'r').readlines() | 285 logt = open(logfname, 'r').readlines() |
305 logtext = [x for x in logt if x.strip() > ''] | 286 logtext = [x for x in logt if x.strip() > ''] |
306 html.append('<div class="toolFormTitle">%s log output</div>' % sectionname) | 287 html.append('<div class="toolFormTitle">%s log output</div>' % sectionname) |
307 if len(logtext) > 1: | 288 if len(logtext) > 1: |
308 html.append('\n<pre>\n') | 289 html.append('\n<pre>\n') |
309 html += logtext | 290 html += logtext |
310 html.append('\n</pre>\n') | 291 html.append('\n</pre>\n') |
311 else: | 292 else: |
312 html.append('%s is empty<br/>' % logfname) | 293 html.append('%s is empty<br/>' % logfname) |
313 if len(fhtml) > 0: | 294 if len(fhtml) > 0: |
314 fhtml.insert(0,'<div><table class="colored" cellpadding="3" cellspacing="3"><tr><th>Output File Name (click to view)</th><th>Size</th></tr>\n') | 295 fhtml.insert(0, '<div><table class="colored" cellpadding="3" cellspacing="3"><tr><th>Output File Name (click to view)</th><th>Size</th></tr>\n') |
315 fhtml.append('</table></div><br/>') | 296 fhtml.append('</table></div><br/>') |
316 html.append('<div class="toolFormTitle">All output files available for downloading</div>\n') | 297 html.append('<div class="toolFormTitle">All output files available for downloading</div>\n') |
317 html += fhtml # add all non-pdf files to the end of the display | 298 html += fhtml # add all non-pdf files to the end of the display |
318 else: | 299 else: |
319 html.append('<div class="warningmessagelarge">### Error - %s returned no files - please confirm that parameters are sane</div>' % self.opts.interpreter) | 300 html.append('<div class="warningmessagelarge">### Error - %s returned no files - please confirm that parameters are sane</div>' % self.opts.interpreter) |
320 html.append(galhtmlpostfix) | 301 html.append(galhtmlpostfix) |
321 htmlf = file(self.opts.output_html,'w') | 302 with open(self.opts.output_html, 'w') as htmlf: |
322 htmlf.write('\n'.join(html)) | 303 htmlf.write('\n'.join(html)) |
323 htmlf.write('\n') | 304 htmlf.write('\n') |
324 htmlf.close() | |
325 self.html = html | 305 self.html = html |
326 | 306 |
327 | |
328 def run(self): | 307 def run(self): |
329 """ | 308 """ |
330 scripts must be small enough not to fill the pipe! | 309 scripts must be small enough not to fill the pipe! |
331 """ | 310 """ |
332 if self.treatbashSpecial and self.opts.interpreter in ['bash','sh']: | 311 if self.treatbashSpecial and self.opts.interpreter in ['bash', 'sh']: |
333 retval = self.runBash() | 312 retval = self.runBash() |
334 else: | 313 else: |
335 if self.opts.output_dir: | 314 if self.opts.output_dir: |
336 ste = open(self.elog,'w') | 315 ste = open(self.elog, 'w') |
337 sto = open(self.tlog,'w') | 316 sto = open(self.tlog, 'w') |
338 sto.write('## Toolfactory generated command line = %s\n' % ' '.join(self.cl)) | 317 sto.write('## Toolfactory generated command line = %s\n' % ' '.join(self.cl)) |
339 sto.flush() | 318 sto.flush() |
340 p = subprocess.Popen(self.cl,shell=False,stdout=sto,stderr=ste,stdin=subprocess.PIPE,cwd=self.opts.output_dir) | 319 p = subprocess.Popen(self.cl, shell=False, stdout=sto, stderr=ste, stdin=subprocess.PIPE, cwd=self.opts.output_dir) |
341 else: | 320 else: |
342 p = subprocess.Popen(self.cl,shell=False,stdin=subprocess.PIPE) | 321 p = subprocess.Popen(self.cl, shell=False, stdin=subprocess.PIPE) |
343 p.stdin.write(self.script) | 322 p.stdin.write(self.script) |
344 p.stdin.close() | 323 p.stdin.close() |
345 retval = p.wait() | 324 retval = p.wait() |
346 if self.opts.output_dir: | 325 if self.opts.output_dir: |
347 sto.close() | 326 sto.close() |
348 ste.close() | 327 ste.close() |
349 err = open(self.elog,'r').readlines() | 328 err = open(self.elog, 'r').readlines() |
350 if retval <> 0 and err: # problem | 329 if retval != 0 and err: # problem |
351 print >> sys.stderr,err #same problem, need to capture docker stdin/stdout | 330 print(err, end="", file=sys.stderr) # same problem, need to capture docker stdin/stdout |
352 if self.opts.make_HTML: | 331 if self.opts.make_HTML: |
353 self.makeHtml() | 332 self.makeHtml() |
354 return retval | 333 return retval |
355 | 334 |
356 def runBash(self): | 335 def runBash(self): |
357 """ | 336 """ |
358 cannot use - for bash so use self.sfile | 337 cannot use - for bash so use self.sfile |
359 """ | 338 """ |
360 if self.opts.output_dir: | 339 if self.opts.output_dir: |
361 s = '## Toolfactory generated command line = %s\n' % ' '.join(self.cl) | 340 s = '## Toolfactory generated command line = %s\n' % ' '.join(self.cl) |
362 sto = open(self.tlog,'w') | 341 sto = open(self.tlog, 'w') |
363 sto.write(s) | 342 sto.write(s) |
364 sto.flush() | 343 sto.flush() |
365 p = subprocess.Popen(self.cl,shell=False,stdout=sto,stderr=sto,cwd=self.opts.output_dir) | 344 p = subprocess.Popen(self.cl, shell=False, stdout=sto, stderr=sto, cwd=self.opts.output_dir) |
366 else: | 345 else: |
367 p = subprocess.Popen(self.cl,shell=False) | 346 p = subprocess.Popen(self.cl, shell=False) |
368 retval = p.wait() | 347 retval = p.wait() |
369 if self.opts.output_dir: | 348 if self.opts.output_dir: |
370 sto.close() | 349 sto.close() |
371 if self.opts.make_HTML: | 350 if self.opts.make_HTML: |
372 self.makeHtml() | 351 self.makeHtml() |
373 return retval | 352 return retval |
374 | 353 |
375 | 354 |
376 def change_user_id(new_uid, new_gid): | 355 def change_user_id(new_uid, new_gid): |
377 """ | 356 """ |
378 To avoid issues with wrong user ids, we change the user id of the 'galaxy' user in the container | 357 To avoid issues with wrong user ids, we change the user id of the 'galaxy' user in the container |
379 to the user id with which the script has been called initially. | 358 to the user id with which the script has been called initially. |
384 cmd4 = ["/usr/sbin/usermod", "-d", "/home/galaxy", "galaxy"] | 363 cmd4 = ["/usr/sbin/usermod", "-d", "/home/galaxy", "galaxy"] |
385 [subprocess.call(cmd) for cmd in [cmd1, cmd2, cmd3, cmd4]] | 364 [subprocess.call(cmd) for cmd in [cmd1, cmd2, cmd3, cmd4]] |
386 | 365 |
387 | 366 |
388 def main(): | 367 def main(): |
389 u = """ | |
390 This is a Galaxy wrapper. It expects to be called by a special purpose tool.xml as: | |
391 <command interpreter="python">rgBaseScriptWrapper.py --script_path "$scriptPath" --tool_name "foo" --interpreter "Rscript" | |
392 </command> | |
393 """ | |
394 op = argparse.ArgumentParser() | 368 op = argparse.ArgumentParser() |
395 a = op.add_argument | 369 a = op.add_argument |
396 a('--docker_image',default=None) | 370 a('--docker_image', default=None) |
397 a('--script_path',default=None) | 371 a('--script_path', default=None) |
398 a('--tool_name',default=None) | 372 a('--tool_name', default=None) |
399 a('--interpreter',default=None) | 373 a('--interpreter', default=None) |
400 a('--output_dir',default='./') | 374 a('--output_dir', default='./') |
401 a('--output_html',default=None) | 375 a('--output_html', default=None) |
402 a('--input_tab',default='None', nargs='*') | 376 a('--input_file', default='None', nargs='*') |
403 a('--output_tab',default='None') | 377 a('--output_file', default='None') |
404 a('--user_email',default='Unknown') | 378 a('--user_email', default='Unknown') |
405 a('--bad_user',default=None) | 379 a('--bad_user', default=None) |
406 a('--make_HTML',default=None) | 380 a('--make_HTML', default=None) |
407 a('--new_tool',default=None) | 381 a('--new_tool', default=None) |
408 a('--dockerized',default=0) | 382 a('--dockerized', default=0) |
409 a('--group_id',default=None) | 383 a('--group_id', default=None) |
410 a('--user_id',default=None) | 384 a('--user_id', default=None) |
411 a('--output_format', default='tabular') | 385 a('--output_format', default='tabular') |
412 a('--input_format', dest='input_formats', action='append', default=[]) | 386 a('--input_format', dest='input_formats', action='append', default=[]) |
413 a('--additional_parameters', dest='additional_parameters', action='append', default=[]) | 387 a('--additional_parameters', dest='additional_parameters', action='append', default=[]) |
414 opts = op.parse_args() | 388 opts = op.parse_args() |
415 assert not opts.bad_user,'UNAUTHORISED: %s is NOT authorized to use this tool until Galaxy admin adds %s to admin_users in universe_wsgi.ini' % (opts.bad_user,opts.bad_user) | 389 assert not opts.bad_user, 'UNAUTHORISED: %s is NOT authorized to use this tool until Galaxy admin adds %s to admin_users in universe_wsgi.ini' % (opts.bad_user, opts.bad_user) |
416 assert os.path.isfile(opts.script_path),'## Tool Factory wrapper expects a script path - eg --script_path=foo.R' | 390 assert os.path.isfile(opts.script_path), '## Tool Factory wrapper expects a script path - eg --script_path=foo.R' |
417 if opts.output_dir: | 391 if opts.output_dir: |
418 try: | 392 try: |
419 os.makedirs(opts.output_dir) | 393 os.makedirs(opts.output_dir) |
420 except: | 394 except Exception: |
421 pass | 395 pass |
422 if opts.dockerized==0: | 396 if opts.dockerized == 0: |
423 switch_to_docker(opts) | 397 retcode = switch_to_docker(opts) |
424 return | 398 sys.exit(retcode) |
425 change_user_id(opts.user_id, opts.group_id) | 399 change_user_id(opts.user_id, opts.group_id) |
426 os.setgid(int(opts.group_id)) | 400 os.setgid(int(opts.group_id)) |
427 os.setuid(int(opts.user_id)) | 401 os.setuid(int(opts.user_id)) |
428 r = ScriptRunner(opts) | 402 r = ScriptRunner(opts) |
429 retcode = r.run() | 403 retcode = r.run() |
430 os.unlink(r.sfile) | 404 os.unlink(r.sfile) |
431 if retcode: | 405 if retcode: |
432 sys.exit(retcode) # indicate failure to job runner | 406 sys.exit(retcode) # indicate failure to job runner |
433 | 407 |
434 | 408 |
435 if __name__ == "__main__": | 409 if __name__ == "__main__": |
436 main() | 410 main() |