From 2cd22e4f1aaa0a3e89c9db99daffe7f723fbc00c Mon Sep 17 00:00:00 2001 From: Greg Von Kuster Date: Thu, 25 Oct 2012 11:54:07 -0400 Subject: [PATCH] Add an altered version of the code contributed by Bjorn Gruning to support installation of tool dependencies that are zip archives when installing repositories from the tool shed. Bjorn's initial code required Python 2.6, so change ser made to support Python 2.5+. --- .../tool_dependencies/common_util.py | 40 ++++++++++++++++++- .../tool_dependencies/fabric_util.py | 8 ++-- .../browse_tool_dependency.mako | 2 +- 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/lib/galaxy/tool_shed/tool_dependencies/common_util.py b/lib/galaxy/tool_shed/tool_dependencies/common_util.py index 726d9e012e9..bd49f9b18b0 100644 --- a/lib/galaxy/tool_shed/tool_dependencies/common_util.py +++ b/lib/galaxy/tool_shed/tool_dependencies/common_util.py @@ -1,12 +1,24 @@ -import os, shutil, tarfile, urllib2 +import os, shutil, tarfile, urllib2, zipfile from galaxy.datatypes.checkers import * +def zipfile_ok( path_to_archive ): + """ + This function is a bit pedantic and not functionally necessary. It checks whether there is no file pointing outside of the extraction, + because ZipFile.extractall() has some potential security holes. See python zipfile documentation for more details. + """ + basename = os.path.realpath( os.path.dirname( path_to_archive ) ) + zip_archive = zipfile.ZipFile( path_to_archive ) + for member in zip_archive.namelist(): + member_path = os.path.realpath( os.path.join( basename, member ) ) + if not member_path.startswith( basename ): + return False + return True def create_env_var_dict( elem, tool_dependency_install_dir=None, tool_shed_repository_install_dir=None ): env_var_name = elem.get( 'name', 'PATH' ) env_var_action = elem.get( 'action', 'prepend_to' ) env_var_text = None if elem.text and elem.text.find( 'REPOSITORY_INSTALL_DIR' ) >= 0: - if tool_shed_repository_install_dir: + if tool_shed_repository_install_dir and elem.text.find( '$REPOSITORY_INSTALL_DIR' ) != -1: env_var_text = elem.text.replace( '$REPOSITORY_INSTALL_DIR', tool_shed_repository_install_dir ) return dict( name=env_var_name, action=env_var_action, value=env_var_text ) else: @@ -50,6 +62,20 @@ def extract_tar( file_name, file_path ): tar = tarfile.open( file_name ) tar.extractall( path=file_path ) tar.close() +def extract_zip( archive_path, extraction_path ): + # TODO: change this method to use zipfile.Zipfile.extractall() when we stop supporting Python 2.5. + if not zipfile_ok( archive_path ): + return False + zip_archive = zipfile.ZipFile( archive_path, 'r' ) + for name in zip_archive.namelist(): + uncompressed_path = os.path.join( extraction_path, name ) + if uncompressed_path.endswith( '/' ): + if not os.path.isdir( uncompressed_path ): + os.makedirs( uncompressed_path ) + else: + file( uncompressed_path, 'wb' ).write( zip_archive.read( name ) ) + zip_archive.close() + return True def isbz2( file_path ): return is_bz2( file_path ) def isgzip( file_path ): @@ -102,3 +128,13 @@ def url_download( install_dir, downloaded_file_name, download_url ): if dst: dst.close() return os.path.abspath( file_path ) +def zip_extraction_directory( file_path, file_name ): + """Try to return the correct extraction directory.""" + files = [ filename for filename in os.listdir( file_path ) if not filename.endswith( '.zip' ) ] + if len( files ) > 1: + return os.path.abspath( file_path ) + elif len( files ) == 1: + # If there is only on file it should be a directory. + if os.path.isdir( os.path.join( file_path, files[ 0 ] ) ): + return os.path.abspath( os.path.join( file_path, files[ 0 ] ) ) + raise ValueError( 'Could not find directory for the extracted file %s' % os.path.abspath( os.path.join( file_path, file_name ) ) ) diff --git a/lib/galaxy/tool_shed/tool_dependencies/fabric_util.py b/lib/galaxy/tool_shed/tool_dependencies/fabric_util.py index 8a09c96a180..5103af2a2bb 100644 --- a/lib/galaxy/tool_shed/tool_dependencies/fabric_util.py +++ b/lib/galaxy/tool_shed/tool_dependencies/fabric_util.py @@ -49,8 +49,6 @@ def install_and_build_package( app, tool_dependency, actions_dict ): sa_session = app.model.context.current install_dir = actions_dict[ 'install_dir' ] package_name = actions_dict[ 'package_name' ] - #download_url = actions_dict.get( 'download_url', None ) - #clone_cmd = actions_dict.get( 'clone_cmd', None ) actions = actions_dict.get( 'actions', None ) if actions: with make_tmp_dir() as work_dir: @@ -59,13 +57,17 @@ def install_and_build_package( app, tool_dependency, actions_dict ): # are currently only two supported processes; download_by_url and clone via a "shell_command" action type. action_type, action_dict = actions[ 0 ] if action_type == 'download_by_url': - # http://sourceforge.net/projects/samtools/files/samtools/0.1.18/samtools-0.1.18.tar.bz2 url = action_dict[ 'url' ] downloaded_filename = os.path.split( url )[ -1 ] downloaded_file_path = common_util.url_download( work_dir, downloaded_filename, url ) if common_util.istar( downloaded_file_path ): + # http://sourceforge.net/projects/samtools/files/samtools/0.1.18/samtools-0.1.18.tar.bz2 common_util.extract_tar( downloaded_file_path, work_dir ) dir = common_util.tar_extraction_directory( work_dir, downloaded_filename ) + elif common_util.iszip( downloaded_file_path ): + # http://downloads.sourceforge.net/project/picard/picard-tools/1.56/picard-tools-1.56.zip + zip_archive_extracted = common_util.extract_zip( downloaded_file_path, work_dir ) + dir = common_util.zip_extraction_directory( work_dir, downloaded_filename ) else: dir = work_dir elif action_type == 'shell_command': diff --git a/templates/admin/tool_shed_repository/browse_tool_dependency.mako b/templates/admin/tool_shed_repository/browse_tool_dependency.mako index f3a170bcdc7..601a768b8c1 100644 --- a/templates/admin/tool_shed_repository/browse_tool_dependency.mako +++ b/templates/admin/tool_shed_repository/browse_tool_dependency.mako @@ -58,7 +58,7 @@ ${tool_dependency.status}
- %if repository.in_error_state: + %if tool_dependency.in_error_state:
${tool_dependency.error_message}