# HG changeset patch # User mvdbeek # Date 1532281081 14400 # Node ID 495946ffc2d62c0de51c6dc3a48b59c9561c0819 # Parent 315a7e9ed6eb7bccedde36ed5a96ea13f803140d planemo upload for repository https://github.com/mvdbeek/docker_scriptrunner/ commit dded837d19aeb3f06b84e5076282cedeeaf713fa diff -r 315a7e9ed6eb -r 495946ffc2d6 README.md --- a/README.md Sat Jul 09 17:00:06 2016 -0400 +++ b/README.md Sun Jul 22 13:38:01 2018 -0400 @@ -1,6 +1,35 @@ +[![Build Status](https://travis-ci.org/ARTbio/docker-scriptrunner.svg?branch=master)](https://travis-ci.org/ARTbio/docker-scriptrunner) + +You need to have docker Docker scriptrunner for galaxy ----------------------------- This is a [galaxy](https://github.com/galaxyproject/galaxy) tool that allows users to submit random scripts. -You can install it on your galaxy server from the [galaxy toolshed]() +You can install it on your galaxy server from the [galaxy toolshed] (https://toolshed.g2.bx.psu.edu/view/mvdbeek/docker_scriptrunner/). + + +This tool is heavily inspired by Ross Lazarus' tool factory +(https://www.ncbi.nlm.nih.gov/pubmed/23024011), but removes the ability to +create galaxy tools. (If you are looking at creating galaxy tools, you may want +to use [planemo](https://planemo.readthedocs.io/en/latest/)). + + +You need to have docker installed on any machine that can run +galaxy jobs, or route this tool to a dedicated docker host +in galaxy's job_conf.xml. Note that the tool itself talks +to the docker daemon and bypasses galaxy's docker configuration. +Making use of galaxy's docker capabilities is on the roadmap. + + +The tool comes with two docker images that can be easily extended. +"artbio/scriptrunner" is a base image, that has very few dependencies installed. +You can extend the image by following the example in the dockerfiles/r-bioperl-python +folder. + + +All security relies on docker; the container only mounts those files that the user +has selected as input files, and the script itself drops privileged inside +the container. This should be secure, but caution should be taken on public servers. +By default the container has networking enabled, so make sure your firewall rules +forbid traffic to the local network. diff -r 315a7e9ed6eb -r 495946ffc2d6 dockerfiles/build.sh --- a/dockerfiles/build.sh Sat Jul 09 17:00:06 2016 -0400 +++ b/dockerfiles/build.sh Sun Jul 22 13:38:01 2018 -0400 @@ -1,3 +1,4 @@ #!/usr/bin/env bash +cd "${0%/*}" docker build -t artbio/scriptrunner scriptrunner && \ docker build -t artbio/scriptrunner-r-bioperl-python r-bioperl-python diff -r 315a7e9ed6eb -r 495946ffc2d6 dockerfiles/scriptrunner/Dockerfile --- a/dockerfiles/scriptrunner/Dockerfile Sat Jul 09 17:00:06 2016 -0400 +++ b/dockerfiles/scriptrunner/Dockerfile Sun Jul 22 13:38:01 2018 -0400 @@ -26,8 +26,8 @@ RUN curl https://repo.continuum.io/miniconda/Miniconda-latest-Linux-x86_64.sh -L -o miniconda.sh RUN bash miniconda.sh -b -p "$CONDA_PREFIX" && rm miniconda.sh -RUN bash -c "conda create -y -q -c bioconda --name default samtools==0.1.19 bcftools && \ - conda config --add channels r && \ +RUN bash -c "conda create -y -q -c bioconda --name default samtools bcftools && \ + conda config --add channels conda-forge && \ conda config --add channels bioconda && \ conda config --add channels iuc && \ . activate default && \ diff -r 315a7e9ed6eb -r 495946ffc2d6 planemo_serve_script.sh --- a/planemo_serve_script.sh Sat Jul 09 17:00:06 2016 -0400 +++ b/planemo_serve_script.sh Sun Jul 22 13:38:01 2018 -0400 @@ -1,1 +1,1 @@ -planemo serve --galaxy_branch dev --conda_prefix=/conda --conda_dependency_resolution --profile tf --port 80 --host 0.0.0.0 --job_config_file ~/mydisk/job_conf.xml --tool_data_table tool_data_table_conf.xml.sample.test +planemo serve --tool_data_table tool_data_table_conf.xml.sample.test "$@" diff -r 315a7e9ed6eb -r 495946ffc2d6 scriptrunner.py --- a/scriptrunner.py Sat Jul 09 17:00:06 2016 -0400 +++ b/scriptrunner.py Sun Jul 22 13:38:01 2018 -0400 @@ -1,213 +1,195 @@ # DockerToolFactory.py # see https://github.com/mvdbeek/scriptrunner -import sys -import shutil -import subprocess -import os -import time -import tempfile +from __future__ import print_function +import sys +import shutil +import subprocess +import os +import time +import tempfile import argparse -import getpass -import tarfile -import re -import shutil import math -import fileinput -from os.path import abspath - +from os.path import abspath -progname = os.path.split(sys.argv[0])[1] -verbose = False +progname = os.path.split(sys.argv[0])[1] +verbose = False debug = False -def timenow(): - """return current time as a string - """ - return time.strftime('%d/%m/%Y %H:%M:%S', time.localtime(time.time())) - html_escape_table = { - "&": "&", - ">": ">", - "<": "<", - "$": "\$" - } + "&": "&", + ">": ">", + "<": "<", + "$": "\$" +} + + +def timenow(): + """Return current time as a string.""" + return time.strftime('%d/%m/%Y %H:%M:%S', time.localtime(time.time())) + def html_escape(text): - """Produce entities within text.""" - return "".join(html_escape_table.get(c,c) for c in text) + """Produce entities within text.""" + return "".join(html_escape_table.get(c, c) for c in text) + def cmd_exists(cmd): - return subprocess.call("type " + cmd, shell=True, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) == 0 + return subprocess.call("type " + cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) == 0 + def construct_bind(host_path, container_path=False, binds=None, ro=True): - #TODO remove container_path if it's alwyas going to be the same as host_path - '''build or extend binds dictionary with container path. binds is used - to mount all files using the docker-py client.''' + # TODO remove container_path if it's alwyas going to be the same as host_path + """Build or extend binds dictionary with container path. binds is used + to mount all files using the docker-py client.""" if not binds: - binds={} + binds = {} if isinstance(host_path, list): - for k,v in enumerate(host_path): + for k, v in enumerate(host_path): if not container_path: - container_path=host_path[k] - binds[host_path[k]]={'bind':container_path, 'ro':ro} - container_path=False #could be more elegant + container_path = host_path[k] + binds[host_path[k]] = {'bind': container_path, 'ro': ro} + container_path = False # could be more elegant return binds else: if not container_path: - container_path=host_path - binds[host_path]={'bind':container_path, 'ro':ro} + container_path = host_path + binds[host_path] = {'bind': container_path, 'ro': ro} return binds + def switch_to_docker(opts): - import docker #need local import, as container does not have docker-py + import docker # need local import, as container does not have docker-py user_id = os.getuid() group_id = os.getgid() - docker_client=docker.Client() - toolfactory_path=abspath(sys.argv[0]) - binds=construct_bind(host_path=opts.script_path, ro=False) - binds=construct_bind(binds=binds, host_path=abspath(opts.output_dir), ro=False) - if len(opts.input_tab)>0: - binds=construct_bind(binds=binds, host_path=opts.input_tab, ro=True) - if not opts.output_tab == 'None': - binds=construct_bind(binds=binds, host_path=opts.output_tab, ro=False) + docker_client = docker.APIClient() + toolfactory_path = abspath(sys.argv[0]) + binds = construct_bind(host_path=opts.script_path, ro=False) + binds = construct_bind(binds=binds, host_path=abspath(opts.output_dir), ro=False) + if len(opts.input_file) > 0: + binds = construct_bind(binds=binds, host_path=opts.input_file, ro=True) + if not opts.output_file == 'None': + binds = construct_bind(binds=binds, host_path=opts.output_file, ro=False) if opts.make_HTML: - binds=construct_bind(binds=binds, host_path=opts.output_html, ro=False) - binds=construct_bind(binds=binds, host_path=toolfactory_path) - volumes=binds.keys() - 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 - cmd=['python', '-u']+sys.argv+['--dockerized', '1', "--user_id", str(user_id), "--group_id", str(group_id)] - image_exists = [ True for image in docker_client.images() if opts.docker_image in image['RepoTags'] ] + binds = construct_bind(binds=binds, host_path=opts.output_html, ro=False) + binds = construct_bind(binds=binds, host_path=toolfactory_path) + volumes = list(binds.keys()) + 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 + cmd = ['python', '-u'] + sys.argv + ['--dockerized', '1', "--user_id", str(user_id), "--group_id", str(group_id)] + image_exists = [True for image in docker_client.images() if opts.docker_image in image['RepoTags']] if not image_exists: docker_client.pull(opts.docker_image) - container=docker_client.create_container( + container = docker_client.create_container( image=opts.docker_image, volumes=volumes, - command=cmd - ) - docker_client.start(container=container[u'Id'], binds=binds) - docker_client.wait(container=container[u'Id']) - logs=docker_client.logs(container=container[u'Id']) - print "".join([log for log in logs]) + command=cmd, + host_config=docker_client.create_host_config(binds=binds)) + docker_client.start(container=container[u'Id']) + exit_code = docker_client.wait(container=container[u'Id'])['StatusCode'] + logs = docker_client.logs(container=container[u'Id']) + print(logs, end="", file=sys.stderr) docker_client.remove_container(container[u'Id']) + return exit_code + class ScriptRunner: """class is a wrapper for an arbitrary script """ - def __init__(self,opts=None,treatbashSpecial=True, image_tag='base'): + def __init__(self, opts=None, treatbashSpecial=True, image_tag='base'): """ cleanup inputs, setup some outputs - """ self.opts = opts self.scriptname = 'script' - self.useIM = cmd_exists('convert') - self.useGS = cmd_exists('gs') - self.temp_warned = False # we want only one warning if $TMP not set self.treatbashSpecial = treatbashSpecial self.image_tag = image_tag os.chdir(abspath(opts.output_dir)) self.thumbformat = 'png' - s = open(self.opts.script_path,'r').readlines() - s = [x.rstrip() for x in s] # remove pesky dos line endings if needed + s = open(self.opts.script_path, 'r').readlines() + s = [x.rstrip() for x in s] # remove pesky dos line endings if needed self.script = '\n'.join(s) - fhandle,self.sfile = tempfile.mkstemp(prefix='script',suffix=".%s" % (opts.interpreter)) - tscript = open(self.sfile,'w') # use self.sfile as script source for Popen + fhandle, self.sfile = tempfile.mkstemp(prefix='script', suffix=".%s" % (opts.interpreter)) + tscript = open(self.sfile, 'w') # use self.sfile as script source for Popen tscript.write(self.script) tscript.close() - self.indentedScript = '\n'.join([' %s' % html_escape(x) for x in s]) # for restructured text in help + self.indentedScript = '\n'.join([' %s' % html_escape(x) for x in s]) # for restructured text in help self.escapedScript = '\n'.join([html_escape(x) for x in s]) - self.elog = os.path.join(self.opts.output_dir,"%s_error.log" % self.scriptname) - if opts.output_dir: # may not want these complexities - self.tlog = os.path.join(self.opts.output_dir,"%s_runner.log" % self.scriptname) - art = '%s.%s' % (self.scriptname,opts.interpreter) - artpath = os.path.join(self.opts.output_dir,art) # need full path - artifact = open(artpath,'w') # use self.sfile as script source for Popen + self.elog = os.path.join(self.opts.output_dir, "%s_error.log" % self.scriptname) + if opts.output_dir: # may not want these complexities + self.tlog = os.path.join(self.opts.output_dir, "%s_runner.log" % self.scriptname) + art = '%s.%s' % (self.scriptname, opts.interpreter) + artpath = os.path.join(self.opts.output_dir, art) # need full path + artifact = open(artpath, 'w') # use self.sfile as script source for Popen artifact.write(self.script) artifact.close() self.cl = [] self.html = [] - a = self.cl.append - a(opts.interpreter) - if self.treatbashSpecial and opts.interpreter in ['bash','sh']: - a(self.sfile) + self.cl.append(opts.interpreter) + if self.treatbashSpecial and opts.interpreter in ['bash', 'sh']: + self.cl.append(self.sfile) else: - a('-') # stdin - for input in opts.input_tab: - a(input) - if opts.output_tab == 'None': #If tool generates only HTML, set output name to toolname - a(str(self.scriptname)+'.out') - a(opts.output_tab) - for param in opts.additional_parameters: - param, value=param.split(',') - a('--'+param) - a(value) + self.cl.append('-') # stdin + for input in opts.input_file: + self.cl.append(input) + if opts.output_file == 'None': # If tool generates only HTML, set output name to toolname + self.cl.append(str(self.scriptname) + '.out') + self.cl.append(opts.output_file) + for param in opts.additional_parameters: + param, value = param.split(',') + self.cl.append('--' + param) + self.cl.append(value) self.outFormats = opts.output_format self.inputFormats = [formats for formats in opts.input_formats] self.test1Input = '%s_test1_input.xls' % self.scriptname self.test1Output = '%s_test1_output.xls' % self.scriptname self.test1HTML = '%s_test1_output.html' % self.scriptname - - def compressPDF(self,inpdf=None,thumbformat='png'): - """need absolute path to pdf - note that GS gets confoozled if no $TMP or $TEMP - so we set it + def compressPDF(self, inpdf=None, thumbformat='png'): + """ + inpdf is absolute path to PDF """ - assert os.path.isfile(inpdf), "## Input %s supplied to %s compressPDF not found" % (inpdf,self.myName) - hlog = os.path.join(self.opts.output_dir,"compress_%s.txt" % os.path.basename(inpdf)) - sto = open(hlog,'a') + assert os.path.isfile(inpdf), "## Input %s supplied to %s compressPDF not found" % (inpdf, self.myName) + hlog = os.path.join(self.opts.output_dir, "compress_%s.txt" % os.path.basename(inpdf)) + sto = open(hlog, 'a') our_env = os.environ.copy() - our_tmp = our_env.get('TMP',None) + our_tmp = our_env.get('TMP', None) if not our_tmp: - our_tmp = our_env.get('TEMP',None) - if not (our_tmp and os.path.exists(our_tmp)): - newtmp = os.path.join(self.opts.output_dir,'tmp') - try: - os.mkdir(newtmp) - except: - sto.write('## WARNING - cannot make %s - it may exist or permissions need fixing\n' % newtmp) - our_env['TEMP'] = newtmp - if not self.temp_warned: - sto.write('## WARNING - no $TMP or $TEMP!!! Please fix - using %s temporarily\n' % newtmp) - self.temp_warned = True + our_env['TMP'] = tempfile.gettempdir() outpdf = '%s_compressed' % inpdf - cl = ["gs", "-sDEVICE=pdfwrite", "-dNOPAUSE", "-dUseCIEColor", "-dBATCH","-dPDFSETTINGS=/printer", "-sOutputFile=%s" % outpdf,inpdf] - x = subprocess.Popen(cl,stdout=sto,stderr=sto,cwd=self.opts.output_dir,env=our_env) + cl = ["gs", "-sDEVICE=pdfwrite", "-dNOPAUSE", "-dUseCIEColor", "-dBATCH", "-dPDFSETTINGS=/printer", "-sOutputFile=%s" % outpdf, inpdf] + x = subprocess.Popen(cl, stdout=sto, stderr=sto, cwd=self.opts.output_dir, env=our_env) retval1 = x.wait() sto.close() if retval1 == 0: os.unlink(inpdf) - shutil.move(outpdf,inpdf) + shutil.move(outpdf, inpdf) os.unlink(hlog) - hlog = os.path.join(self.opts.output_dir,"thumbnail_%s.txt" % os.path.basename(inpdf)) - sto = open(hlog,'w') - outpng = '%s.%s' % (os.path.splitext(inpdf)[0],thumbformat) + hlog = os.path.join(self.opts.output_dir, "thumbnail_%s.txt" % os.path.basename(inpdf)) + sto = open(hlog, 'w') + outpng = '%s.%s' % (os.path.splitext(inpdf)[0], thumbformat) cl2 = ['convert', inpdf, outpng] - x = subprocess.Popen(cl2,stdout=sto,stderr=sto,cwd=self.opts.output_dir,env=our_env) + x = subprocess.Popen(cl2, stdout=sto, stderr=sto, cwd=self.opts.output_dir, env=our_env) retval2 = x.wait() sto.close() if retval2 == 0: - os.unlink(hlog) + os.unlink(hlog) retval = retval1 or retval2 return retval - - def getfSize(self,fpath,outpath): + def getfSize(self, fpath, outpath): """ format a nice file size string """ size = '' - fp = os.path.join(outpath,fpath) + fp = os.path.join(outpath, fpath) if os.path.isfile(fp): size = '0 B' n = float(os.path.getsize(fp)) if n > 2**20: - size = '%1.1f MB' % (n/2**20) + size = '%1.1f MB' % (n / 2**20) elif n > 2**10: - size = '%1.1f KB' % (n/2**10) + size = '%1.1f KB' % (n / 2**10) elif n > 0: size = '%d B' % (int(n)) return size @@ -216,92 +198,91 @@ """ Create an HTML file content to list all the artifacts found in the output_dir """ - galhtmlprefix = """ - - - - - - - -
- """ - galhtmlattr = """
This tool (%s) was generated by the Galaxy Tool Factory

""" + galhtmlprefix = """ + + + + + + + +
+""" galhtmlpostfix = """
\n""" flist = os.listdir(self.opts.output_dir) - flist = [x for x in flist if x <> 'Rplots.pdf'] + flist = [x for x in flist if x != 'Rplots.pdf'] flist.sort() html = [] html.append(galhtmlprefix % progname) - html.append('
Galaxy Tool "%s" run at %s

' % (self.scriptname,timenow())) + html.append('
Galaxy Tool "%s" run at %s

' % (self.scriptname, timenow())) fhtml = [] if len(flist) > 0: - logfiles = [x for x in flist if x.lower().endswith('.log')] # log file names determine sections + logfiles = [x for x in flist if x.lower().endswith('.log')] # log file names determine sections logfiles.sort() - logfiles = [x for x in logfiles if abspath(x) <> abspath(self.tlog)] - logfiles.append(abspath(self.tlog)) # make it the last one + logfiles = [x for x in logfiles if abspath(x) != abspath(self.tlog)] + logfiles.append(abspath(self.tlog)) # make it the last one pdflist = [] npdf = len([x for x in flist if os.path.splitext(x)[-1].lower() == '.pdf']) - for rownum,fname in enumerate(flist): - dname,e = os.path.splitext(fname) - sfsize = self.getfSize(fname,self.opts.output_dir) - if e.lower() == '.pdf' : # compress and make a thumbnail - thumb = '%s.%s' % (dname,self.thumbformat) - pdff = os.path.join(self.opts.output_dir,fname) - retval = self.compressPDF(inpdf=pdff,thumbformat=self.thumbformat) + for rownum, fname in enumerate(flist): + dname, e = os.path.splitext(fname) + sfsize = self.getfSize(fname, self.opts.output_dir) + if e.lower() == '.pdf': # compress and make a thumbnail + thumb = '%s.%s' % (dname, self.thumbformat) + pdff = os.path.join(self.opts.output_dir, fname) + retval = self.compressPDF(inpdf=pdff, thumbformat=self.thumbformat) if retval == 0: - pdflist.append((fname,thumb)) + pdflist.append((fname, thumb)) else: - pdflist.append((fname,fname)) - if (rownum+1) % 2 == 0: - fhtml.append('%s%s' % (fname,fname,sfsize)) + pdflist.append((fname, fname)) + if (rownum + 1) % 2 == 0: + fhtml.append('%s%s' % (fname, fname, sfsize)) else: - fhtml.append('%s%s' % (fname,fname,sfsize)) - for logfname in logfiles: # expect at least tlog - if more - if abspath(logfname) == abspath(self.tlog): # handled later + fhtml.append('%s%s' % (fname, fname, sfsize)) + for logfname in logfiles: # expect at least tlog - if more + if abspath(logfname) == abspath(self.tlog): # handled later sectionname = 'All tool run' if (len(logfiles) > 1): sectionname = 'Other' ourpdfs = pdflist else: realname = os.path.basename(logfname) - sectionname = os.path.splitext(realname)[0].split('_')[0] # break in case _ added to log + sectionname = os.path.splitext(realname)[0].split('_')[0] # break in case _ added to log ourpdfs = [x for x in pdflist if os.path.basename(x[0]).split('_')[0] == sectionname] - pdflist = [x for x in pdflist if os.path.basename(x[0]).split('_')[0] <> sectionname] # remove + pdflist = [x for x in pdflist if os.path.basename(x[0]).split('_')[0] != sectionname] # remove nacross = 1 npdf = len(ourpdfs) if npdf > 0: - nacross = math.sqrt(npdf) ## int(round(math.log(npdf,2))) + nacross = math.sqrt(npdf) if int(nacross)**2 != npdf: nacross += 1 nacross = int(nacross) - width = min(400,int(1200/nacross)) + width = min(400, int(1200 / nacross)) html.append('
%s images and outputs
' % sectionname) html.append('(Click on a thumbnail image to download the corresponding original PDF image)
') - ntogo = nacross # counter for table row padding with empty cells + ntogo = nacross # counter for table row padding with empty cells html.append('
\n') - for i,paths in enumerate(ourpdfs): - fname,thumb = paths - s= """\n""" % (fname,thumb,fname,width,fname) - if ((i+1) % nacross == 0): + for i, paths in enumerate(ourpdfs): + fname, thumb = paths + s = """\n""" % (fname, thumb, fname, width, fname) + if ((i + 1) % nacross == 0): s += '\n' ntogo = 0 - if i < (npdf - 1): # more to come - s += '' - ntogo = nacross + if i < (npdf - 1): # more to come + s += '' + ntogo = nacross else: ntogo -= 1 html.append(s) if html[-1].strip().endswith(''): html.append('
Image called %sImage called %s
\n') else: - if ntogo > 0: # pad - html.append(' '*ntogo) + if ntogo > 0: # pad + html.append(' ' * ntogo) html.append('
\n') - logt = open(logfname,'r').readlines() + logt = open(logfname, 'r').readlines() logtext = [x for x in logt if x.strip() > ''] html.append('
%s log output
' % sectionname) if len(logtext) > 1: @@ -311,44 +292,42 @@ else: html.append('%s is empty
' % logfname) if len(fhtml) > 0: - fhtml.insert(0,'
\n') - fhtml.append('
Output File Name (click to view)Size

') - html.append('
All output files available for downloading
\n') - html += fhtml # add all non-pdf files to the end of the display + fhtml.insert(0, '
\n') + fhtml.append('
Output File Name (click to view)Size

') + html.append('
All output files available for downloading
\n') + html += fhtml # add all non-pdf files to the end of the display else: html.append('
### Error - %s returned no files - please confirm that parameters are sane
' % self.opts.interpreter) html.append(galhtmlpostfix) - htmlf = file(self.opts.output_html,'w') - htmlf.write('\n'.join(html)) - htmlf.write('\n') - htmlf.close() + with open(self.opts.output_html, 'w') as htmlf: + htmlf.write('\n'.join(html)) + htmlf.write('\n') self.html = html - def run(self): """ scripts must be small enough not to fill the pipe! """ - if self.treatbashSpecial and self.opts.interpreter in ['bash','sh']: - retval = self.runBash() + if self.treatbashSpecial and self.opts.interpreter in ['bash', 'sh']: + retval = self.runBash() else: if self.opts.output_dir: - ste = open(self.elog,'w') - sto = open(self.tlog,'w') + ste = open(self.elog, 'w') + sto = open(self.tlog, 'w') sto.write('## Toolfactory generated command line = %s\n' % ' '.join(self.cl)) sto.flush() - p = subprocess.Popen(self.cl,shell=False,stdout=sto,stderr=ste,stdin=subprocess.PIPE,cwd=self.opts.output_dir) + p = subprocess.Popen(self.cl, shell=False, stdout=sto, stderr=ste, stdin=subprocess.PIPE, cwd=self.opts.output_dir) else: - p = subprocess.Popen(self.cl,shell=False,stdin=subprocess.PIPE) + p = subprocess.Popen(self.cl, shell=False, stdin=subprocess.PIPE) p.stdin.write(self.script) p.stdin.close() retval = p.wait() if self.opts.output_dir: sto.close() ste.close() - err = open(self.elog,'r').readlines() - if retval <> 0 and err: # problem - print >> sys.stderr,err #same problem, need to capture docker stdin/stdout + err = open(self.elog, 'r').readlines() + if retval != 0 and err: # problem + print(err, end="", file=sys.stderr) # same problem, need to capture docker stdin/stdout if self.opts.make_HTML: self.makeHtml() return retval @@ -359,19 +338,19 @@ """ if self.opts.output_dir: s = '## Toolfactory generated command line = %s\n' % ' '.join(self.cl) - sto = open(self.tlog,'w') + sto = open(self.tlog, 'w') sto.write(s) sto.flush() - p = subprocess.Popen(self.cl,shell=False,stdout=sto,stderr=sto,cwd=self.opts.output_dir) + p = subprocess.Popen(self.cl, shell=False, stdout=sto, stderr=sto, cwd=self.opts.output_dir) else: - p = subprocess.Popen(self.cl,shell=False) + p = subprocess.Popen(self.cl, shell=False) retval = p.wait() if self.opts.output_dir: sto.close() if self.opts.make_HTML: self.makeHtml() return retval - + def change_user_id(new_uid, new_gid): """ @@ -386,42 +365,37 @@ def main(): - u = """ - This is a Galaxy wrapper. It expects to be called by a special purpose tool.xml as: - rgBaseScriptWrapper.py --script_path "$scriptPath" --tool_name "foo" --interpreter "Rscript" - - """ op = argparse.ArgumentParser() a = op.add_argument - a('--docker_image',default=None) - a('--script_path',default=None) - a('--tool_name',default=None) - a('--interpreter',default=None) - a('--output_dir',default='./') - a('--output_html',default=None) - a('--input_tab',default='None', nargs='*') - a('--output_tab',default='None') - a('--user_email',default='Unknown') - a('--bad_user',default=None) - a('--make_HTML',default=None) - a('--new_tool',default=None) - a('--dockerized',default=0) - a('--group_id',default=None) - a('--user_id',default=None) + a('--docker_image', default=None) + a('--script_path', default=None) + a('--tool_name', default=None) + a('--interpreter', default=None) + a('--output_dir', default='./') + a('--output_html', default=None) + a('--input_file', default='None', nargs='*') + a('--output_file', default='None') + a('--user_email', default='Unknown') + a('--bad_user', default=None) + a('--make_HTML', default=None) + a('--new_tool', default=None) + a('--dockerized', default=0) + a('--group_id', default=None) + a('--user_id', default=None) a('--output_format', default='tabular') a('--input_format', dest='input_formats', action='append', default=[]) a('--additional_parameters', dest='additional_parameters', action='append', default=[]) opts = op.parse_args() - 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) - assert os.path.isfile(opts.script_path),'## Tool Factory wrapper expects a script path - eg --script_path=foo.R' + 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) + assert os.path.isfile(opts.script_path), '## Tool Factory wrapper expects a script path - eg --script_path=foo.R' if opts.output_dir: try: os.makedirs(opts.output_dir) - except: + except Exception: pass - if opts.dockerized==0: - switch_to_docker(opts) - return + if opts.dockerized == 0: + retcode = switch_to_docker(opts) + sys.exit(retcode) change_user_id(opts.user_id, opts.group_id) os.setgid(int(opts.group_id)) os.setuid(int(opts.user_id)) @@ -429,7 +403,7 @@ retcode = r.run() os.unlink(r.sfile) if retcode: - sys.exit(retcode) # indicate failure to job runner + sys.exit(retcode) # indicate failure to job runner if __name__ == "__main__": diff -r 315a7e9ed6eb -r 495946ffc2d6 scriptrunner.xml --- a/scriptrunner.xml Sat Jul 09 17:00:06 2016 -0400 +++ b/scriptrunner.xml Sun Jul 22 13:38:01 2018 -0400 @@ -1,40 +1,40 @@ - + Runs scripts using docker macros.xml - docker-py + docker-py - - python "$__tool_directory__/scriptrunner.py" - --script_path "$runme" - --interpreter "$interpreter" - --user_email "$__user_email__" - #if $generate_simple_output.make_TAB=="yes": - --output_tab "$tab_file" - #end if - #if $make_HTML.value=="yes": - --output_dir "$html_file.files_path" --output_html "$html_file" --make_HTML "yes" - #else: - --output_dir "." - #end if - #if $additional_parameters != 'None': - #for i in $additional_parameters: - --additional_parameters - "$i.param_name, $i.param_value" - #end for - #end if - #if $input_files != 'None': - --input_tab - #for i in $input_files: - $i.input - #end for - #for i in $input_files: - --input_format "Any" - #end for - #end if - --docker_image "$docker_image" + +python '$__tool_directory__/scriptrunner.py' +--script_path '$runme' +--interpreter '$interpreter' +--user_email '$__user_email__' +#if $generate_simple_output.make_file=="yes": + --output_file "$output_file" +#end if +#if $make_HTML.value=="yes": + --output_dir "$html_file.files_path" --output_html "$html_file" --make_HTML "yes" +#else: + --output_dir "." +#end if +#if $additional_parameters != 'None': + #for i in $additional_parameters: + --additional_parameters + "$i.param_name, $i.param_value" + #end for +#end if +#if $input_files != 'None': + --input_file + #for i in $input_files: + $i.input + #end for + #for i in $input_files: + --input_format "Any" + #end for +#end if +--docker_image "$docker_image" $dynScript @@ -64,8 +64,8 @@ - + @@ -89,7 +89,7 @@ + help="Script must deal with two command line parameters: Path to input file path (or 'None' if none selected) and path to output tabular history file (or 'None')."> @@ -98,8 +98,8 @@ - - generate_simple_output['make_TAB'] == "yes" + + generate_simple_output['make_file'] == "yes" - - - - - + + + + + - - + + diff -r 315a7e9ed6eb -r 495946ffc2d6 test-data/tf2_test.html --- a/test-data/tf2_test.html Sat Jul 09 17:00:06 2016 -0400 +++ b/test-data/tf2_test.html Sun Jul 22 13:38:01 2018 -0400 @@ -1,25 +1,25 @@ - - - - - - - - -
- -
Galaxy Tool "script" run at 09/07/2016 11:37:54

+ + + + + + + + +
+ +
Galaxy Tool "script" run at 22/07/2018 17:09:47

script log output
script_error.log is empty
Other log output
-/tmp/tmpG7m9zp/job_working_directory/000/1/dataset_2_files/script_runner.log is empty
+/private/var/folders/df/6xqpqpcd7h73b6jpx9t6cwhw0000gn/T/tmptx2hJF/job_working_directory/000/1/dataset_2_files/script_runner.log is empty
All output files available for downloading
- +
Output File Name (click to view)Size
script.python0 B
script_error.log0 B
script_runner.log100 B
script_runner.log140 B

diff -r 315a7e9ed6eb -r 495946ffc2d6 test.sh --- a/test.sh Sat Jul 09 17:00:06 2016 -0400 +++ b/test.sh Sun Jul 22 13:38:01 2018 -0400 @@ -1,7 +1,2 @@ #!/usr/bin/env bash -planemo test --galaxy_branch dev \ - --conda_auto_init \ - --conda_dependency_resolution \ - --conda_auto_install \ - --conda_ensure_channels scrapinghub \ - --tool_data_table tool_data_table_conf.xml.sample.test +planemo test --tool_data_table tool_data_table_conf.xml.sample.test "$@" diff -r 315a7e9ed6eb -r 495946ffc2d6 tool_dependencies.xml --- a/tool_dependencies.xml Sat Jul 09 17:00:06 2016 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,14 +0,0 @@ - - - - - - docker-py==1.8.1 - - - - - - Only Admins can use this tool generator but please do NOT install o - -