1 new commit in galaxy-central: https://bitbucket.org/galaxy/galaxy-central/commits/c92ebce92ef7/ Changeset: c92ebce92ef7 User: greg Date: 2013-11-08 22:51:25 Summary: Enhance the Tool Shed API to enable importing a repository capsule. Affected #: 4 files diff -r caf63d089c0034385757e6c20337a1d244434eb5 -r c92ebce92ef72ff999d1e8fd97601fb2153a09bc lib/galaxy/webapps/tool_shed/api/repositories.py --- a/lib/galaxy/webapps/tool_shed/api/repositories.py +++ b/lib/galaxy/webapps/tool_shed/api/repositories.py @@ -1,5 +1,7 @@ import logging import os +import tarfile +import tempfile from time import strftime from galaxy import eggs from galaxy import util @@ -10,7 +12,10 @@ import tool_shed.repository_types.util as rt_util import tool_shed.util.shed_util_common as suc from tool_shed.galaxy_install import repository_util +from tool_shed.util import encoding_util +from tool_shed.util import import_util from tool_shed.util import metadata_util +from tool_shed.util import repository_maintenance_util from tool_shed.util import tool_util eggs.require( 'mercurial' ) @@ -145,6 +150,86 @@ trans.response.status = 500 return message + @web.expose_api + def import_capsule( self, trans, payload, **kwd ): + """ + POST /api/repositories/new/import_capsule + Import a repository capsule into the Tool Shed. + + :param key: the user's API key + + The following parameters are included in the payload. + :param tool_shed_url (required): the base URL of the Tool Shed into which the capsule should be imported. + :param capsule_file_name (required): the name of the capsule file. + """ + # Get the information about the capsule to be imported from the payload. + tool_shed_url = payload.get( 'tool_shed_url', '' ) + if not tool_shed_url: + raise HTTPBadRequest( detail="Missing required parameter 'tool_shed_url'." ) + capsule_file_name = payload.get( 'capsule_file_name', '' ) + if not capsule_file_name: + raise HTTPBadRequest( detail="Missing required parameter 'capsule_file_name'." ) + capsule_file_path = os.path.abspath( capsule_file_name ) + capsule_dict = dict( error_message='', + encoded_file_path=None, + status='ok', + tar_archive=None, + uploaded_file=None, + capsule_file_name=None ) + if os.path.getsize( os.path.abspath( capsule_file_name ) ) == 0: + message = 'Your capsule file is empty.' + log.error( message, exc_info=True ) + trans.response.status = 500 + return message + try: + # Open for reading with transparent compression. + tar_archive = tarfile.open( capsule_file_path, 'r:*' ) + except tarfile.ReadError, e: + message = 'Error opening file %s: %s' % ( str( capsule_file_name ), str( e ) ) + log.error( message, exc_info=True ) + trans.response.status = 500 + return message + capsule_dict[ 'tar_archive' ] = tar_archive + capsule_dict[ 'capsule_file_name' ] = capsule_file_name + capsule_dict = import_util.extract_capsule_files( trans, **capsule_dict ) + capsule_dict = import_util.validate_capsule( trans, **capsule_dict ) + status = capsule_dict.get( 'status', 'error' ) + if status == 'error': + message = 'The capsule contents are invalid and cannpt be imported:<br/>%s' % str( capsule_dict.get( 'error_message', '' ) ) + log.error( message, exc_info=True ) + trans.response.status = 500 + return message + encoded_file_path = capsule_dict.get( 'encoded_file_path', None ) + file_path = encoding_util.tool_shed_decode( encoded_file_path ) + export_info_file_path = os.path.join( file_path, 'export_info.xml' ) + export_info_dict = import_util.get_export_info_dict( export_info_file_path ) + manifest_file_path = os.path.join( file_path, 'manifest.xml' ) + # The manifest.xml file has already been validated, so no error_message should be returned here. + repository_info_dicts, error_message = import_util.get_repository_info_from_manifest( manifest_file_path ) + # Determine the status for each exported repository archive contained within the capsule. + repository_status_info_dicts = import_util.get_repository_status_from_tool_shed( trans, repository_info_dicts ) + # Generate a list of repository name / import results message tuples for display after the capsule is imported. + import_results_tups = [] + # Only create repositories that do not yet exist and that the current user is authorized to create. The + # status will be None for repositories that fall into the intersection of these 2 categories. + for repository_status_info_dict in repository_status_info_dicts: + # Add the capsule_file_name and encoded_file_path to the repository_status_info_dict. + repository_status_info_dict[ 'capsule_file_name' ] = capsule_file_name + repository_status_info_dict[ 'encoded_file_path' ] = encoded_file_path + import_results_tups = repository_maintenance_util.create_repository_and_import_archive( trans, + repository_status_info_dict, + import_results_tups ) + suc.remove_dir( file_path ) + # NOTE: the order of installation is defined in import_results_tups, but order will be lost when transferred to return_dict. + return_dict = {} + for import_results_tup in import_results_tups: + name_owner, message = import_results_tup + name, owner = name_owner + key = 'Archive of repository "%s" owned by "%s"' % ( str( name ), str( owner ) ) + val = message.replace( '<b>', '"' ).replace( '</b>', '"' ) + return_dict[ key ] = val + return return_dict + @web.expose_api_anonymous def index( self, trans, deleted=False, **kwd ): """ diff -r caf63d089c0034385757e6c20337a1d244434eb5 -r c92ebce92ef72ff999d1e8fd97601fb2153a09bc lib/galaxy/webapps/tool_shed/buildapp.py --- a/lib/galaxy/webapps/tool_shed/buildapp.py +++ b/lib/galaxy/webapps/tool_shed/buildapp.py @@ -90,6 +90,7 @@ 'reset_metadata_on_repository' : 'POST' }, name_prefix='repository_', path_prefix='/api', + new={ 'import_capsule' : 'POST' }, parent_resources=dict( member_name='repository', collection_name='repositories' ) ) webapp.mapper.resource( 'repository_revision', 'repository_revisions', diff -r caf63d089c0034385757e6c20337a1d244434eb5 -r c92ebce92ef72ff999d1e8fd97601fb2153a09bc lib/tool_shed/scripts/api/import_capsule.py --- /dev/null +++ b/lib/tool_shed/scripts/api/import_capsule.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python +""" +Import the contents of a repository capsule exported from a Tool Shed into another Tool Shed. For each exported repository +archive contained in the capsule, inspect the Tool Shed to see if that repository already exists or if the current user is +authorized to create the repository. If repository dependencies are included in the capsule, repositories may have various +owners. We will keep repositories associated with owners, so we need to restrict created repositories to those the current +user can create. If the current user is an admin or a member of the IUC, all repositories will be created no matter the owner. +Otherwise, only repositories whose associated owner is the current user will be created. + +Repositories are also associated with 1 or more categories in the Tool Shed from which the capsule was exported. If any of +these categories are not contained in the Tool Shed to which the capsule is being imported, they will NOT be created by this +method (they'll have to be created manually, which can be done after the import). + +Here is a working example of how to use this script to install a repository from the test tool shed. +./import_capsule.py -a <api key> -u http://localhost:9009 -c capsule_localhost_colon_9009_filter_test1_8923f52d5c6d.tar.gz +""" + +import os +import sys +import argparse +sys.path.insert( 0, os.path.dirname( __file__ ) ) +from common import submit + +def main( options ): + api_key = options.api + base_tool_shed_url = options.tool_shed_url.rstrip( '/' ) + capsule_file_name = options.capsule_file_name + data = {} + data[ 'tool_shed_url' ] = options.tool_shed_url + data[ 'capsule_file_name' ] = options.capsule_file_name + url = '%s/api/repositories/new/import_capsule' % base_tool_shed_url + try: + submit( url, data, api_key ) + except Exception, e: + log.exception( str( e ) ) + sys.exit( 1 ) + +if __name__ == '__main__': + parser = argparse.ArgumentParser( description='Import the contents of a repository capsule via the Tool Shed API.' ) + parser.add_argument( "-u", "--url", dest="tool_shed_url", required=True, help="Tool Shed URL" ) + parser.add_argument( "-a", "--api", dest="api", required=True, help="API Key" ) + parser.add_argument( "-c", "--capsule_file_name", required=True, help="Capsule file name." ) + options = parser.parse_args() + main( options ) diff -r caf63d089c0034385757e6c20337a1d244434eb5 -r c92ebce92ef72ff999d1e8fd97601fb2153a09bc lib/tool_shed/util/import_util.py --- a/lib/tool_shed/util/import_util.py +++ b/lib/tool_shed/util/import_util.py @@ -25,9 +25,8 @@ """Extract the uploaded capsule archive into a temporary location for inspection, validation and potential import.""" return_dict = {} tar_archive = kwd.get( 'tar_archive', None ) - uploaded_file = kwd.get( 'uploaded_file', None ) capsule_file_name = kwd.get( 'capsule_file_name', None ) - if tar_archive is not None and uploaded_file is not None and capsule_file_name is not None: + if tar_archive is not None and capsule_file_name is not None: return_dict.update( kwd ) extract_directory_path = tempfile.mkdtemp( prefix="tmp-capsule-ecf" ) if capsule_file_name.endswith( '.tar.gz' ): @@ -39,10 +38,11 @@ file_path = os.path.join( extract_directory_path, extract_directory_name ) return_dict[ 'encoded_file_path' ] = encoding_util.tool_shed_encode( file_path ) tar_archive.extractall( path=file_path ) - tar_archive.close() - uploaded_file.close() + try: + tar_archive.close() + except Exception, e: + log.exception( "Cannot close tar_archive: %s" % str( e ) ) del return_dict[ 'tar_archive' ] - del return_dict[ 'uploaded_file' ] return return_dict def get_archives_from_manifest( manifest_file_path ): @@ -297,7 +297,7 @@ if uploaded_file is not None: if isempty: uploaded_file.close() - return_dict[ 'error_message' ] = 'Your uploaded file is empty.' + return_dict[ 'error_message' ] = 'Your uploaded capsule file is empty.' return_dict[ 'status' ] = 'error' return return_dict try: @@ -312,8 +312,8 @@ tar_archive.close() return return_dict return_dict[ 'tar_archive' ] = tar_archive - return_dict[ 'uploaded_file' ] = uploaded_file return_dict[ 'capsule_file_name' ] = uploaded_file_filename + uploaded_file.close() else: return_dict[ 'error_message' ] = 'No files were entered on the import form.' return_dict[ 'status' ] = 'error' Repository URL: https://bitbucket.org/galaxy/galaxy-central/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email.