Add support for 3 new tool dependency installation recipe action tag types: assert_directory_exists, assert_file_exists and assert_file_executable.

This commit is contained in:
Greg Von Kuster
2014-04-25 10:58:15 -04:00
parent c2d192810c
commit 42e9adb8b2
4 changed files with 144 additions and 9 deletions
@@ -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
@@ -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(),
@@ -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 ):
# <action type="make_directory">$INSTALL_DIR/mira</action>
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 ):
# <action type="assert_executable">$INSTALL_DIR/mira/my_file</action>
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 ):
# <action type="assert_on_path">$INSTALL_DIR/mira/my_file</action>
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 ):
@@ -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 ) )