diff --git a/lib/tool_shed/galaxy_install/tool_dependencies/fabric_util.py b/lib/tool_shed/galaxy_install/tool_dependencies/fabric_util.py
index 06621797679..52f87aabe3c 100755
--- a/lib/tool_shed/galaxy_install/tool_dependencies/fabric_util.py
+++ b/lib/tool_shed/galaxy_install/tool_dependencies/fabric_util.py
@@ -4,6 +4,8 @@ import os
from galaxy import eggs
eggs.require( 'Fabric' )
+eggs.require( 'ssh' )
+eggs.require( 'paramiko' )
from fabric.api import env
from fabric.api import lcd
diff --git a/lib/tool_shed/galaxy_install/tool_dependencies/recipe/recipe_manager.py b/lib/tool_shed/galaxy_install/tool_dependencies/recipe/recipe_manager.py
index e0d036e1f6c..75898596861 100644
--- a/lib/tool_shed/galaxy_install/tool_dependencies/recipe/recipe_manager.py
+++ b/lib/tool_shed/galaxy_install/tool_dependencies/recipe/recipe_manager.py
@@ -13,6 +13,8 @@ from contextlib import contextmanager
# TODO: eliminate the use of fabric here.
from galaxy import eggs
eggs.require( 'Fabric' )
+eggs.require( 'ssh' )
+eggs.require( 'paramiko' )
from fabric.operations import _AttributeString
from fabric import state
@@ -412,7 +414,10 @@ class RecipeManager( object ):
return tool_dependency, filtered_actions, dir
def load_step_handlers( self ):
- step_handlers_by_type = dict( autoconf=step_handler.Autoconf(),
+ step_handlers_by_type = dict( assert_directory_exists=step_handler.AssertDirectoryExists(),
+ assert_file_executable=step_handler.AssertFileExecutable(),
+ assert_file_exists=step_handler.AssertFileExists(),
+ autoconf=step_handler.Autoconf(),
change_directory=step_handler.ChangeDirectory(),
chmod=step_handler.Chmod(),
download_binary=step_handler.DownloadBinary(),
diff --git a/lib/tool_shed/galaxy_install/tool_dependencies/recipe/step_handler.py b/lib/tool_shed/galaxy_install/tool_dependencies/recipe/step_handler.py
index 49a76c4731c..e9f5f89c27a 100644
--- a/lib/tool_shed/galaxy_install/tool_dependencies/recipe/step_handler.py
+++ b/lib/tool_shed/galaxy_install/tool_dependencies/recipe/step_handler.py
@@ -14,6 +14,9 @@ from tool_shed.galaxy_install.tool_dependencies import td_common_util
# TODO: eliminate the use of fabric here.
from galaxy import eggs
eggs.require( 'Fabric' )
+eggs.require( 'ssh' )
+eggs.require( 'paramiko' )
+
from fabric.api import settings
from fabric.api import lcd
@@ -33,6 +36,105 @@ class RecipeStep( object ):
raise "Unimplemented Method"
+class AssertDirectoryExists( RecipeStep ):
+
+ def __init__( self ):
+ self.type = 'assert_directory_exists'
+
+ def execute_step( self, app, tool_dependency, package_name, actions, action_dict, filtered_actions, env_file_builder,
+ install_environment, work_dir, install_dir, current_dir=None, initial_download=False ):
+ """
+ Make sure a directory on disk exists. Since this class is not used in the initial download stage,
+ no recipe step filtering is performed here, and None values are always returned for filtered_actions
+ and dir.
+ """
+ if os.path.isabs( action_dict[ 'full_path' ] ):
+ full_path = action_dict[ 'full_path' ]
+ else:
+ full_path = os.path.join( current_dir, action_dict[ 'full_path' ] )
+ if not td_common_util.assert_directory_exists( full_path=full_path ):
+ status = app.install_model.ToolDependency.installation_status.ERROR
+ error_message = 'The required directory %s does not exist.' % str( full_path )
+ tool_dependency = tool_dependency_util.set_tool_dependency_attributes( app,
+ tool_dependency,
+ status=status,
+ error_message=error_message,
+ remove_from_disk=False )
+ return tool_dependency, None, None
+
+ def prepare_step( self, app, tool_dependency, action_elem, action_dict, install_dir, is_binary_download ):
+ # $INSTALL_DIR/mira
+ if action_elem.text:
+ action_dict[ 'full_path' ] = td_common_util.evaluate_template( action_elem.text, install_dir )
+ return action_dict
+
+
+class AssertFileExecutable( RecipeStep ):
+
+ def __init__( self ):
+ self.type = 'assert_file_executable'
+
+ def execute_step( self, app, tool_dependency, package_name, actions, action_dict, filtered_actions, env_file_builder,
+ install_environment, work_dir, install_dir, current_dir=None, initial_download=False ):
+ """
+ Make sure a file on disk exists and is executable. Since this class is not used in the initial
+ download stage, no recipe step filtering is performed here, and None values are always returned
+ for filtered_actions and dir.
+ """
+ if os.path.isabs( action_dict[ 'full_path' ] ):
+ full_path = action_dict[ 'full_path' ]
+ else:
+ full_path = os.path.join( current_dir, action_dict[ 'full_path' ] )
+ if not td_common_util.assert_file_executable( full_path=full_path ):
+ status = app.install_model.ToolDependency.installation_status.ERROR
+ error_message = 'The file %s is not executable by the owner.' % str( full_path )
+ tool_dependency = tool_dependency_util.set_tool_dependency_attributes( app,
+ tool_dependency,
+ status=status,
+ error_message=error_message,
+ remove_from_disk=False )
+ return tool_dependency, None, None
+
+ def prepare_step( self, app, tool_dependency, action_elem, action_dict, install_dir, is_binary_download ):
+ # $INSTALL_DIR/mira/my_file
+ if action_elem.text:
+ action_dict[ 'full_path' ] = td_common_util.evaluate_template( action_elem.text, install_dir )
+ return action_dict
+
+
+class AssertFileExists( RecipeStep ):
+
+ def __init__( self ):
+ self.type = 'assert_file_exists'
+
+ def execute_step( self, app, tool_dependency, package_name, actions, action_dict, filtered_actions, env_file_builder,
+ install_environment, work_dir, install_dir, current_dir=None, initial_download=False ):
+ """
+ Make sure a file on disk exists. Since this class is not used in the initial download stage,
+ no recipe step filtering is performed here, and None values are always returned for
+ filtered_actions and dir.
+ """
+ if os.path.isabs( action_dict[ 'full_path' ] ):
+ full_path = action_dict[ 'full_path' ]
+ else:
+ full_path = os.path.join( current_dir, action_dict[ 'full_path' ] )
+ if not td_common_util.assert_file_exists( full_path=full_path ):
+ status = app.install_model.ToolDependency.installation_status.ERROR
+ error_message = 'The required file %s does not exist.' % str( full_path )
+ tool_dependency = tool_dependency_util.set_tool_dependency_attributes( app,
+ tool_dependency,
+ status=status,
+ error_message=error_message,
+ remove_from_disk=False )
+ return tool_dependency, None, None
+
+ def prepare_step( self, app, tool_dependency, action_elem, action_dict, install_dir, is_binary_download ):
+ # $INSTALL_DIR/mira/my_file
+ if action_elem.text:
+ action_dict[ 'full_path' ] = td_common_util.evaluate_template( action_elem.text, install_dir )
+ return action_dict
+
+
class Autoconf( RecipeStep ):
def __init__( self ):
diff --git a/lib/tool_shed/galaxy_install/tool_dependencies/td_common_util.py b/lib/tool_shed/galaxy_install/tool_dependencies/td_common_util.py
index 500cf84bbfd..6eb48b7259f 100755
--- a/lib/tool_shed/galaxy_install/tool_dependencies/td_common_util.py
+++ b/lib/tool_shed/galaxy_install/tool_dependencies/td_common_util.py
@@ -2,6 +2,7 @@ import logging
import os
import re
import shutil
+import stat
import sys
import tarfile
import time
@@ -110,6 +111,31 @@ class CompressedFile( object ):
def open_zip( self, filepath, mode ):
return zipfile.ZipFile( filepath, mode )
+def assert_directory_exists( full_path ):
+ """Return True if a directory exists and is not a symbolic link."""
+ if os.path.islink( full_path ):
+ return False
+ if os.path.is_dir( full_path ):
+ return True
+ return False
+
+def assert_file_exists( full_path ):
+ """Return True if a file exists. This will work for both symbolic linke and files."""
+ if os.path.exists( full_path ):
+ return True
+ return False
+
+def assert_file_executable( full_path ):
+ """Return True if a file exists and is executable."""
+ if os.path.islink( full_path ):
+ return False
+ if os.path.is_file( full_path ):
+ # Make sure the owner has execute permission on the file.
+ # See http://docs.python.org/2/library/stat.html
+ if stat.S_IXUSR & os.stat( full_path )[ stat.ST_MODE ] == 64:
+ return True
+ return False
+
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' )
@@ -155,6 +181,10 @@ def egrep_escape( text ):
regex = regex.replace( r"\'", "'" )
return regex
+def evaluate_template( text, install_dir ):
+ """ Substitute variables defined in XML blocks from dependencies file."""
+ return Template( text ).safe_substitute( get_env_var_values( install_dir ) )
+
def format_traceback():
ex_type, ex, tb = sys.exc_info()
return ''.join( traceback.format_tb( tb ) )
@@ -444,6 +474,10 @@ def parse_package_elem( package_elem, platform_info_dict=None, include_after_ins
continue
return actions_elem_tuples
+def __shellquote( s ):
+ """Quote and escape the supplied string for use in shell expressions."""
+ return "'" + s.replace( "'", "'\\''" ) + "'"
+
def url_download( install_dir, downloaded_file_name, download_url, extract=True ):
file_path = os.path.join( install_dir, downloaded_file_name )
src = None
@@ -494,11 +528,3 @@ def zipfile_ok( path_to_archive ):
if not member_path.startswith( basename ):
return False
return True
-
-def __shellquote(s):
- """Quote and escape the supplied string for use in shell expressions."""
- return "'" + s.replace( "'", "'\\''" ) + "'"
-
-def evaluate_template( text, install_dir ):
- """ Substitute variables defined in XML blocks from dependencies file."""
- return Template( text ).safe_substitute( get_env_var_values( install_dir ) )