def add_to_shed_tool_config(self, shed_tool_conf_dict, elem_list): """ "A tool shed repository is being installed so change the shed_tool_conf file. Parse the config file to generate the entire list of config_elems instead of using the in-memory list since it will be a subset of the entire list if one or more repositories have been deactivated. """ if not elem_list: # We may have an empty elem_list in case a data manager is being installed. # In that case we don't want to wait for a toolbox reload that will never happen. return old_toolbox = self.app.toolbox shed_tool_conf = shed_tool_conf_dict['config_filename'] tool_cache_data_dir = shed_tool_conf_dict.get('tool_cache_data_dir') tool_path = shed_tool_conf_dict['tool_path'] config_elems = [] # Ideally shed_tool_conf.xml would be created before the repo is cloned and added to the DB, but this is called # from too many places to make it feasible at this time try: tree, error_message = parse_xml(shed_tool_conf, check_exists=False) except OSError as exc: if (exc.errno == errno.ENOENT and shed_tool_conf_dict.get('create', None) is not None): log.info('Creating shed tool config with default contents: %s', shed_tool_conf) with open(shed_tool_conf, 'w') as fh: fh.write(shed_tool_conf_dict['create']) tree, error_message = parse_xml(shed_tool_conf) else: log.error('Unable to load shed tool config: %s', shed_tool_conf) raise if tree: root = tree.getroot() for elem in root: config_elems.append(elem) # Add the new elements to the in-memory list of config_elems. for elem_entry in elem_list: if elem_entry.tag == 'section': # Loop through section entries in the in-memory tool panel. for existing_elem in config_elems: # Compare the section ID for each one to the section ID for the tool being installed. if existing_elem.tag == 'section' and existing_elem.attrib.get('id', None) == elem_entry.attrib.get('id', None): # We've found a section, add the incoming tools to it. for child in elem_entry: existing_elem.append(child) # Break out of the config_elems loop back to the elem_list loop. break # If we reach this point, no section was found. Create one with contents. else: config_elems.append(elem_entry) # This is not a section, but a tool or label. No need to search for matching sections, just add it. else: config_elems.append(elem_entry) # Persist the altered shed_tool_config file. self.config_elems_to_xml_file(config_elems, shed_tool_conf, tool_path, tool_cache_data_dir) self.app.wait_for_toolbox_reload(old_toolbox) else: log.error(error_message)
def __init__(self, config=None): self.tool_sheds = OrderedDict() self.tool_sheds_auth = OrderedDict() if config: # Parse tool_sheds_conf.xml tree, error_message = parse_xml(config) if tree is None: log.warning("Unable to load references to tool sheds defined in file %s" % str(config)) return root = tree.getroot() else: root = xml.etree.ElementTree.fromstring(DEFAULT_TOOL_SHEDS_CONF_XML) config = "internal default config" log.debug('Loading references to tool sheds from %s' % config) for elem in root.findall('tool_shed'): try: name = elem.get('name', None) url = elem.get('url', None) username = elem.get('user', None) password = elem.get('pass', None) if name and url: self.tool_sheds[name] = url self.tool_sheds_auth[name] = None log.debug('Loaded reference to tool shed: %s' % name) if name and url and username and password: pass_mgr = urlrequest.HTTPPasswordMgrWithDefaultRealm() pass_mgr.add_password(None, url, username, password) self.tool_sheds_auth[name] = pass_mgr except Exception as e: log.warning('Error loading reference to tool shed "%s", problem: %s' % (name, str(e)))
def __init__(self, config=None): self.tool_sheds = {} self.tool_sheds_auth = {} if config: # Parse tool_sheds_conf.xml tree, error_message = parse_xml(config) if tree is None: log.warning( "Unable to load references to tool sheds defined in file %s" % str(config)) return root = tree.getroot() else: root = parse_xml_string(DEFAULT_TOOL_SHEDS_CONF_XML) config = "internal default config" log.debug('Loading references to tool sheds from %s' % config) for elem in root.findall('tool_shed'): try: name = elem.get('name', None) url = elem.get('url', None) username = elem.get('user', None) password = elem.get('pass', None) if name and url: self.tool_sheds[name] = url self.tool_sheds_auth[name] = None log.debug('Loaded reference to tool shed: %s' % name) if name and url and username and password: self.tool_sheds_auth[name] = AUTH_TUPLE(username, password) except Exception as e: log.warning( 'Error loading reference to tool shed "{}", problem: {}'. format(name, str(e)))
def data_managers_path(self): tree, error_message = parse_xml( self.app.config.shed_data_manager_config_file) if tree: root = tree.getroot() return root.get('tool_path', None) return None
def filter_and_persist_proprietary_tool_panel_configs(self, tool_configs_to_filter): """Eliminate all entries in all non-shed-related tool panel configs for all tool config file names in the received tool_configs_to_filter.""" for proprietary_tool_conf in self.proprietary_tool_confs: persist_required = False tree, error_message = parse_xml(proprietary_tool_conf) if tree: root = tree.getroot() for elem in root: if elem.tag == 'tool': # Tools outside of sections. file_path = elem.get('file', None) if file_path: if file_path in tool_configs_to_filter: root.remove(elem) persist_required = True elif elem.tag == 'section': # Tools contained in a section. for section_elem in elem: if section_elem.tag == 'tool': file_path = section_elem.get('file', None) if file_path: if file_path in tool_configs_to_filter: elem.remove(section_elem) persist_required = True if persist_required: fh = tempfile.NamedTemporaryFile('wb', prefix="tmp-toolshed-fapptpc") tmp_filename = fh.name fh.close() fh = open(tmp_filename, 'wb') tree.write(tmp_filename, encoding='utf-8', xml_declaration=True) fh.close() shutil.move(tmp_filename, os.path.abspath(proprietary_tool_conf)) os.chmod(proprietary_tool_conf, RW_R__R__)
def get_proprietary_tool_panel_elems(self, latest_tool_migration_script_number): """ Parse each config in self.proprietary_tool_confs (the default is tool_conf.xml) and generate a list of Elements that are either ToolSection elements or Tool elements. These will be used to generate new entries in the migrated_tools_conf.xml file for the installed tools. """ tools_xml_file_path = os.path.abspath( os.path.join( 'scripts', 'migrate_tools', '%04d_tools.xml' % latest_tool_migration_script_number)) # Parse the XML and load the file attributes for later checking against the integrated elements from self.proprietary_tool_confs. migrated_tool_configs = [] tree, error_message = parse_xml(tools_xml_file_path) if tree is None: return [] root = tree.getroot() for elem in root: if elem.tag == 'repository': for tool_elem in elem: migrated_tool_configs.append(tool_elem.get('file')) # Parse each file in self.proprietary_tool_confs and generate the integrated list of tool panel Elements that contain them. tool_panel_elems = [] for proprietary_tool_conf in self.proprietary_tool_confs: tree, error_message = parse_xml(proprietary_tool_conf) if tree is None: return [] root = tree.getroot() for elem in root: if elem.tag == 'tool': # Tools outside of sections. file_path = elem.get('file', None) if file_path: if file_path in migrated_tool_configs: if elem not in tool_panel_elems: tool_panel_elems.append(elem) elif elem.tag == 'section': # Tools contained in a section. for section_elem in elem: if section_elem.tag == 'tool': file_path = section_elem.get('file', None) if file_path: if file_path in migrated_tool_configs: # Append the section, not the tool. if elem not in tool_panel_elems: tool_panel_elems.append(elem) return tool_panel_elems
def generate_tool_panel_dict_from_shed_tool_conf_entries(self, repository): """ Keep track of the section in the tool panel in which this repository's tools will be contained by parsing the shed_tool_conf in which the repository's tools are defined and storing the tool panel definition of each tool in the repository. This method is called only when the repository is being deactivated or un-installed and allows for activation or re-installation using the original layout. """ tool_panel_dict = {} shed_tool_conf, tool_path, relative_install_dir = \ get_tool_panel_config_tool_path_install_dir(self.app, repository) metadata = repository.metadata # Create a dictionary of tool guid and tool config file name for each tool in the repository. guids_and_configs = {} if 'tools' in metadata: for tool_dict in metadata['tools']: guid = tool_dict['guid'] tool_config = tool_dict['tool_config'] file_name = strip_path(tool_config) guids_and_configs[guid] = file_name # Parse the shed_tool_conf file in which all of this repository's tools are defined and generate the tool_panel_dict. tree, error_message = parse_xml(shed_tool_conf) if tree is None: return tool_panel_dict root = tree.getroot() for elem in root: if elem.tag == 'tool': guid = elem.get('guid') if guid in guids_and_configs: # The tool is displayed in the tool panel outside of any tool sections. tool_section_dict = dict( tool_config=guids_and_configs[guid], id='', name='', version='') if guid in tool_panel_dict: tool_panel_dict[guid].append(tool_section_dict) else: tool_panel_dict[guid] = [tool_section_dict] elif elem.tag == 'section': section_id = elem.get('id') or '' section_name = elem.get('name') or '' section_version = elem.get('version') or '' for section_elem in elem: if section_elem.tag == 'tool': guid = section_elem.get('guid') if guid in guids_and_configs: # The tool is displayed in the tool panel inside the current tool section. tool_section_dict = dict( tool_config=guids_and_configs[guid], id=section_id, name=section_name, version=section_version) if guid in tool_panel_dict: tool_panel_dict[guid].append(tool_section_dict) else: tool_panel_dict[guid] = [tool_section_dict] return tool_panel_dict
def add_to_shed_tool_config(self, shed_tool_conf_dict, elem_list): """ "A tool shed repository is being installed so change the shed_tool_conf file. Parse the config file to generate the entire list of config_elems instead of using the in-memory list since it will be a subset of the entire list if one or more repositories have been deactivated. """ if not elem_list: # We may have an empty elem_list in case a data manager is being installed. # In that case we don't want to wait for a toolbox reload that will never happen. return old_toolbox = self.app.toolbox shed_tool_conf = shed_tool_conf_dict['config_filename'] tool_path = shed_tool_conf_dict['tool_path'] config_elems = [] # Ideally shed_tool_conf.xml would be created before the repo is cloned and added to the DB, but this is called # from too many places to make it feasible at this time try: tree, error_message = parse_xml(shed_tool_conf, check_exists=False) except (OSError, IOError) as exc: if (exc.errno == errno.ENOENT and shed_tool_conf_dict.get('create', None) is not None): log.info('Creating shed tool config with default contents: %s', shed_tool_conf) with open(shed_tool_conf, 'w') as fh: fh.write(shed_tool_conf_dict['create']) tree, error_message = parse_xml(shed_tool_conf) else: log.error('Unable to load shed tool config: %s', shed_tool_conf) raise if tree: root = tree.getroot() for elem in root: config_elems.append(elem) # Add the new elements to the in-memory list of config_elems. for elem_entry in elem_list: config_elems.append(elem_entry) # Persist the altered shed_tool_config file. self.config_elems_to_xml_file(config_elems, shed_tool_conf, tool_path) self.app.wait_for_toolbox_reload(old_toolbox) else: log.error(error_message)
def create_tool_dependency_objects(app, tool_shed_repository, relative_install_dir, set_status=True): """ Create or update a ToolDependency for each entry in tool_dependencies_config. This method is called when installing a new tool_shed_repository. """ tool_dependency_objects = [] shed_config_dict = tool_shed_repository.get_shed_config_dict(app) if shed_config_dict.get('tool_path'): relative_install_dir = os.path.join(shed_config_dict.get('tool_path'), relative_install_dir) # Get the tool_dependencies.xml file from the repository. tool_dependencies_config = get_config_from_disk('tool_dependencies.xml', relative_install_dir) tree, error_message = parse_xml(tool_dependencies_config) if tree is None: return tool_dependency_objects root = tree.getroot() for elem in root: tool_dependency_type = elem.tag if tool_dependency_type == 'package': name = elem.get('name', None) version = elem.get('version', None) if name and version: status = app.install_model.ToolDependency.installation_status.NEVER_INSTALLED tool_dependency = create_or_update_tool_dependency( app, tool_shed_repository, name=name, version=version, type=tool_dependency_type, status=status, set_status=set_status) tool_dependency_objects.append(tool_dependency) elif tool_dependency_type == 'set_environment': for env_elem in elem: # <environment_variable name="R_SCRIPT_PATH" action="set_to">$REPOSITORY_INSTALL_DIR</environment_variable> name = env_elem.get('name', None) action = env_elem.get('action', None) if name and action: status = app.install_model.ToolDependency.installation_status.NEVER_INSTALLED tool_dependency = create_or_update_tool_dependency( app, tool_shed_repository, name=name, version=None, type=tool_dependency_type, status=status, set_status=set_status) tool_dependency_objects.append(tool_dependency) return tool_dependency_objects
def install_tool_data_tables(self, tool_shed_repository, tool_index_sample_files): TOOL_DATA_TABLE_FILE_NAME = 'tool_data_table_conf.xml' TOOL_DATA_TABLE_FILE_SAMPLE_NAME = '%s.sample' % (TOOL_DATA_TABLE_FILE_NAME) SAMPLE_SUFFIX = '.sample' SAMPLE_SUFFIX_OFFSET = -len(SAMPLE_SUFFIX) target_dir, tool_path, relative_target_dir = self.get_target_install_dir(tool_shed_repository) for sample_file in tool_index_sample_files: path, filename = os.path.split(sample_file) target_filename = filename if target_filename.endswith(SAMPLE_SUFFIX): target_filename = target_filename[: SAMPLE_SUFFIX_OFFSET] source_file = os.path.join(tool_path, sample_file) # We're not currently uninstalling index files, do not overwrite existing files. target_path_filename = os.path.join(target_dir, target_filename) if not os.path.exists(target_path_filename) or target_filename == TOOL_DATA_TABLE_FILE_NAME: shutil.copy2(source_file, target_path_filename) else: log.debug("Did not copy sample file '%s' to install directory '%s' because file already exists.", filename, target_dir) # For provenance and to simplify introspection, let's keep the original data table sample file around. if filename == TOOL_DATA_TABLE_FILE_SAMPLE_NAME: shutil.copy2(source_file, os.path.join(target_dir, filename)) tool_data_table_conf_filename = os.path.join(target_dir, TOOL_DATA_TABLE_FILE_NAME) elems = [] if os.path.exists(tool_data_table_conf_filename): tree, error_message = xml_util.parse_xml(tool_data_table_conf_filename) if tree: for elem in tree.getroot(): # Append individual table elems or other elemes, but not tables elems. if elem.tag == 'tables': for table_elem in elems: elems.append(elem) else: elems.append(elem) else: log.debug("The '%s' data table file was not found, but was expected to be copied from '%s' during repository installation.", tool_data_table_conf_filename, TOOL_DATA_TABLE_FILE_SAMPLE_NAME) for elem in elems: if elem.tag == 'table': for file_elem in elem.findall('file'): path = file_elem.get('path', None) if path: file_elem.set('path', os.path.normpath(os.path.join(target_dir, os.path.split(path)[1]))) # Store repository info in the table tag set for trace-ability. self.generate_repository_info_elem_from_repository(tool_shed_repository, parent_elem=elem) if elems: # Remove old data_table os.unlink(tool_data_table_conf_filename) # Persist new data_table content. self.app.tool_data_tables.to_xml_file(tool_data_table_conf_filename, elems) return tool_data_table_conf_filename, elems
def remove_from_data_manager(self, repository): metadata_dict = repository.metadata if metadata_dict and 'data_manager' in metadata_dict: shed_data_manager_conf_filename = self.app.config.shed_data_manager_config_file tree, error_message = parse_xml(shed_data_manager_conf_filename) if tree: root = tree.getroot() assert root.tag == 'data_managers', 'The file provided (%s) for removing data managers from is not a valid data manager xml file.' % ( shed_data_manager_conf_filename) guids = [ data_manager_dict.get('guid') for data_manager_dict in metadata_dict.get( 'data_manager', {}).get('data_managers', {}).values() if 'guid' in data_manager_dict ] load_old_data_managers_by_guid = {} data_manager_config_has_changes = False config_elems = [] for elem in root: # Match Data Manager elements by guid and installed_changeset_revision elem_matches_removed_data_manager = False if elem.tag == 'data_manager': guid = elem.get('guid', None) if guid in guids: tool_elem = elem.find('tool') if tool_elem is not None: installed_changeset_revision_elem = tool_elem.find( 'installed_changeset_revision') if installed_changeset_revision_elem is not None: if installed_changeset_revision_elem.text == repository.installed_changeset_revision: elem_matches_removed_data_manager = True else: # This is a different version, which had been previously overridden load_old_data_managers_by_guid[ guid] = elem if elem_matches_removed_data_manager: data_manager_config_has_changes = True else: config_elems.append(elem) # Remove data managers from in memory self.app.data_managers.remove_manager(guids) # Load other versions of any now uninstalled data managers, if any for elem in load_old_data_managers_by_guid.values(): self.app.data_managers.load_manager_from_elem(elem) # Persist the altered shed_data_manager_config file. if data_manager_config_has_changes: self.data_manager_config_elems_to_xml_file( config_elems, shed_data_manager_conf_filename)
def remove_from_shed_tool_config(self, shed_tool_conf_dict, metadata): """ A tool shed repository is being uninstalled so change the shed_tool_conf file. Parse the config file to generate the entire list of config_elems instead of using the in-memory list since it will be a subset of the entire list if one or more repositories have been deactivated. """ if 'tools' not in metadata: return # We need to use the tool path to uniquely identify the tools to remove, # since multiple installable revisions of a repository can provide the # same version of a tool (i.e. there may be another tool with the same # guid that we should not remove from shed_tool_conf). guid_paths_to_remove = [(_['guid'], _['tool_config']) for _ in metadata['tools']] shed_tool_conf = shed_tool_conf_dict['config_filename'] tool_path = shed_tool_conf_dict['tool_path'] config_elems = [] tree, error_message = parse_xml(shed_tool_conf) if tree: root = tree.getroot() for elem in root: config_elems.append(elem) config_elems_to_remove = [] for config_elem in config_elems: if config_elem.tag == 'section': tool_elems_to_remove = [] for tool_elem in config_elem: if (tool_elem.get('guid'), tool_elem.get('file')) in guid_paths_to_remove: tool_elems_to_remove.append(tool_elem) for tool_elem in tool_elems_to_remove: # Remove all of the appropriate tool sub-elements from the section element. config_elem.remove(tool_elem) if len(config_elem) < 1: # Keep a list of all empty section elements so they can be removed. config_elems_to_remove.append(config_elem) elif config_elem.tag == 'tool': if (config_elem.get('guid'), config_elem.get('file')) in guid_paths_to_remove: config_elems_to_remove.append(config_elem) for config_elem in config_elems_to_remove: config_elems.remove(config_elem) # Persist the altered in-memory version of the tool config. self.config_elems_to_xml_file(config_elems, shed_tool_conf, tool_path)
def update_in_shed_tool_config(self): """ A tool shed repository is being updated so change the shed_tool_conf file. Parse the config file to generate the entire list of config_elems instead of using the in-memory list. """ shed_conf_dict = self.shed_config_dict or self.repository.get_shed_config_dict( self.app) shed_tool_conf = shed_conf_dict['config_filename'] tool_path = shed_conf_dict['tool_path'] self.tpm.generate_tool_panel_dict_from_shed_tool_conf_entries( self.repository) repository_tools_tups = self.get_repository_tools_tups() clone_url = common_util.generate_clone_url_for_installed_repository( self.app, self.repository) tool_shed = self.tool_shed_from_repository_clone_url() owner = self.repository.owner if not owner: cleaned_repository_clone_url = common_util.remove_protocol_and_user_from_clone_url( clone_url) owner = get_repository_owner(cleaned_repository_clone_url) guid_to_tool_elem_dict = {} for tool_config_filename, guid, tool in repository_tools_tups: guid_to_tool_elem_dict[guid] = self.tpm.generate_tool_elem( tool_shed, self.repository.name, self.repository.changeset_revision, self.repository.owner or '', tool_config_filename, tool, None) config_elems = [] tree, error_message = xml_util.parse_xml(shed_tool_conf) if tree: root = tree.getroot() for elem in root: if elem.tag == 'section': for i, tool_elem in enumerate(elem): guid = tool_elem.attrib.get('guid') if guid in guid_to_tool_elem_dict: elem[i] = guid_to_tool_elem_dict[guid] elif elem.tag == 'tool': guid = elem.attrib.get('guid') if guid in guid_to_tool_elem_dict: elem = guid_to_tool_elem_dict[guid] config_elems.append(elem) self.tpm.config_elems_to_xml_file(config_elems, shed_tool_conf, tool_path)
def get_non_shed_tool_panel_configs(app): """Get the non-shed related tool panel configs - there can be more than one, and the default is tool_conf.xml.""" config_filenames = [] for config_filename in app.config.tool_configs: # Any config file that includes a tool_path attribute in the root tag set like the following is shed-related. # <toolbox tool_path="database/shed_tools"> try: tree, error_message = xml_util.parse_xml(config_filename) except OSError as exc: if (config_filename == app.config.shed_tool_conf and not app.config.shed_tool_conf_set and exc.errno == errno.ENOENT): continue raise if tree is None: continue root = tree.getroot() tool_path = root.get('tool_path', None) if tool_path is None: config_filenames.append(config_filename) return config_filenames
def install_data_managers(self, shed_data_manager_conf_filename, metadata_dict, shed_config_dict, relative_install_dir, repository, repository_tools_tups): rval = [] if 'data_manager' in metadata_dict: tpm = tool_panel_manager.ToolPanelManager(self.app) repository_tools_by_guid = {} for tool_tup in repository_tools_tups: repository_tools_by_guid[tool_tup[1]] = dict( tool_config_filename=tool_tup[0], tool=tool_tup[2]) # Load existing data managers. try: tree, error_message = parse_xml( shed_data_manager_conf_filename, check_exists=False) except OSError as exc: if exc.errno == errno.ENOENT: with open(shed_data_manager_conf_filename, 'w') as fh: fh.write(SHED_DATA_MANAGER_CONF_XML) tree, error_message = parse_xml( shed_data_manager_conf_filename) else: raise if tree is None: return rval config_elems = [elem for elem in tree.getroot()] repo_data_manager_conf_filename = metadata_dict[ 'data_manager'].get('config_filename', None) if repo_data_manager_conf_filename is None: log.debug("No data_manager_conf.xml file has been defined.") return rval data_manager_config_has_changes = False relative_repo_data_manager_dir = os.path.join( shed_config_dict.get('tool_path', ''), relative_install_dir) repo_data_manager_conf_filename = os.path.join( relative_repo_data_manager_dir, repo_data_manager_conf_filename) tree, error_message = parse_xml(repo_data_manager_conf_filename) if tree is None: return rval root = tree.getroot() for elem in root: if elem.tag == 'data_manager': data_manager_id = elem.get('id', None) if data_manager_id is None: log.error( "A data manager was defined that does not have an id and will not be installed:\n%s" % xml_to_string(elem)) continue data_manager_dict = metadata_dict['data_manager'].get( 'data_managers', {}).get(data_manager_id, None) if data_manager_dict is None: log.error( "Data manager metadata is not defined properly for '%s'." % (data_manager_id)) continue guid = data_manager_dict.get('guid', None) if guid is None: log.error( "Data manager guid '{}' is not set in metadata for '{}'." .format(guid, data_manager_id)) continue elem.set('guid', guid) tool_guid = data_manager_dict.get('tool_guid', None) if tool_guid is None: log.error( "Data manager tool guid '{}' is not set in metadata for '{}'." .format(tool_guid, data_manager_id)) continue tool_dict = repository_tools_by_guid.get(tool_guid, None) if tool_dict is None: log.error( "Data manager tool guid '%s' could not be found for '%s'. Perhaps the tool is invalid?" % (tool_guid, data_manager_id)) continue tool = tool_dict.get('tool', None) if tool is None: log.error( "Data manager tool with guid '%s' could not be found for '%s'. Perhaps the tool is invalid?" % (tool_guid, data_manager_id)) continue tool_config_filename = tool_dict.get( 'tool_config_filename', None) if tool_config_filename is None: log.error( "Data manager metadata is missing 'tool_config_file' for '%s'." % (data_manager_id)) continue elem.set('shed_conf_file', shed_config_dict['config_filename']) if elem.get('tool_file', None) is not None: del elem.attrib[ 'tool_file'] # remove old tool_file info tool_elem = tpm.generate_tool_elem( repository.tool_shed, repository.name, repository.installed_changeset_revision, repository.owner, tool_config_filename, tool, None) elem.insert(0, tool_elem) data_manager = \ self.app.data_managers.load_manager_from_elem(elem, tool_path=shed_config_dict.get('tool_path', '')) if data_manager: rval.append(data_manager) elif elem.tag is etree.Comment: pass else: log.warning( "Encountered unexpected element '{}':\n{}".format( elem.tag, xml_to_string(elem))) config_elems.append(elem) data_manager_config_has_changes = True # Persist the altered shed_data_manager_config file. if data_manager_config_has_changes: reload_count = self.app.data_managers._reload_count self.data_manager_config_elems_to_xml_file( config_elems, shed_data_manager_conf_filename) while self.app.data_managers._reload_count <= reload_count: time.sleep( 0.1 ) # Wait for shed_data_manager watcher thread to pick up changes return rval
def main(): parser = argparse.ArgumentParser( description='Update galaxy shed_tool_conf.xml with tool labels') parser.add_argument('-g', '--galaxy_url', help='Galaxy server URL', required=True) parser.add_argument('-u', '--remote_user', help='Remote user', default='galaxy') parser.add_argument('-f', '--remote_file_path', help='File name on galaxy', required=True) parser.add_argument( '-k', '--key_path', help='Path to private ssh key file' ) # for local testing - jenkins has the ssh identity already parser.add_argument('--display_new_days', type=int, help='Number of days to display label for new tool', required=True) parser.add_argument( '--display_updated_days', type=int, help='Number of days to display label for updated tool', required=True) parser.add_argument( '--safe', action='store_true', help= 'Do not overwrite the original file, give the updated file a new name') args = parser.parse_args() file = os.path.basename(args.remote_file_path) galaxy_url = args.galaxy_url display_new_days = args.display_new_days display_updated_days = args.display_updated_days copy_args = { 'file': file, 'remote_user': args.remote_user, 'url': galaxy_url.split('//')[1] if galaxy_url.startswith('https://') else galaxy_url, 'remote_file_path': args.remote_file_path, 'key_path': args.key_path, } def filter_new(row): return row['Status'] == 'Installed' and in_time_window( row['Date (AEST)'], display_new_days) and row['New Tool'] == 'True' def filter_updated(row): return row['Status'] == 'Installed' and in_time_window( row['Date (AEST)'], display_updated_days) and row['New Tool'] == 'False' with open(tool_labels_file) as handle: tool_labels = yaml.safe_load(handle) tool_labels.update({ new_label: [], updated_label: [], }) toolshed_tools = get_toolshed_tools(galaxy_url) for row in load_log(filter=filter_new): tool_ids = [ t['id'] for t in toolshed_tools if (t['tool_shed_repository']['name'] == row['Name'] and t['tool_shed_repository']['owner'] == row['Owner'] and t['tool_shed_repository']['changeset_revision'] == row['Installed Revision']) ] tool_labels[new_label].extend(tool_ids) for row in load_log(filter=filter_updated): tool_ids = [ t['id'] for t in toolshed_tools if (t['tool_shed_repository']['name'] == row['Name'] and t['tool_shed_repository']['owner'] == row['Owner'] and t['tool_shed_repository']['changeset_revision'] == row['Installed Revision']) ] tool_labels[updated_label].extend(tool_ids) try: get_remote_file(**copy_args) except Exception as e: print(e) raise Exception('Failed to fetch remote file') tree, error_message = parse_xml(file) root = tree.getroot() # shed_tool_conf.xml has multiple section elements containing tools # loop through all sections and tools for section in root: if section.tag == 'section': for tool in section.getchildren(): if tool.tag == 'tool': tool_id = tool.find('id').text # remove all existing labels tool.attrib.pop('labels', None) # replace labels from dict labels_for_tool = [] for label in tool_labels: for id in tool_labels[label]: if tool_id == id or ( id.endswith('*') and get_deversioned_id(id) == get_deversioned_id(tool_id)): labels_for_tool.append(label) break if labels_for_tool: tool.set('labels', ','.join(labels_for_tool)) with open(file, 'w') as handle: handle.write(xml_to_string(root, pretty=True)) if args.safe: remote_file_path = copy_args['remote_file_path'] copy_args.update({ 'remote_file_path': '%s_jenkins_%s' % (remote_file_path, arrow.now().format('YYYYMMDD')) }) try: copy_file_to_remote_location(**copy_args) except Exception as e: print(e) raise Exception('Failed to copy file to remote instance')
def check_for_missing_tools(app, tool_panel_configs, latest_tool_migration_script_number): # Get the 000x_tools.xml file associated with the current migrate_tools version number. tools_xml_file_path = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, 'galaxy_install', 'migrate', 'scripts', '%04d_tools.xml' % latest_tool_migration_script_number)) # Parse the XML and load the file attributes for later checking against the proprietary tool_panel_config. migrated_tool_configs_dict = {} tree, error_message = xml_util.parse_xml(tools_xml_file_path) if tree is None: return False, {} root = tree.getroot() tool_shed = root.get('name') tool_shed_url = get_tool_shed_url_from_tool_shed_registry(app, tool_shed) # The default behavior is that the tool shed is down. tool_shed_accessible = False missing_tool_configs_dict = {} if tool_shed_url: for elem in root: if elem.tag == 'repository': repository_dependencies = [] all_tool_dependencies = [] repository_name = elem.get('name') changeset_revision = elem.get('changeset_revision') tool_shed_accessible, repository_dependencies_dict = get_repository_dependencies(app, tool_shed_url, repository_name, REPOSITORY_OWNER, changeset_revision) if tool_shed_accessible: # Accumulate all tool dependencies defined for repository dependencies for display to the user. for rd_key, rd_tups in repository_dependencies_dict.items(): if rd_key in ['root_key', 'description']: continue for rd_tup in rd_tups: tool_shed, name, owner, changeset_revision, prior_installation_required, only_if_compiling_contained_td = \ parse_repository_dependency_tuple(rd_tup) tool_shed_accessible, tool_dependencies = get_tool_dependencies(app, tool_shed_url, name, owner, changeset_revision) all_tool_dependencies = accumulate_tool_dependencies(tool_shed_accessible, tool_dependencies, all_tool_dependencies) tool_shed_accessible, tool_dependencies = get_tool_dependencies(app, tool_shed_url, repository_name, REPOSITORY_OWNER, changeset_revision) all_tool_dependencies = accumulate_tool_dependencies(tool_shed_accessible, tool_dependencies, all_tool_dependencies) for tool_elem in elem.findall('tool'): tool_config_file_name = tool_elem.get('file') if tool_config_file_name: # We currently do nothing with repository dependencies except install them (we do not display repositories that will be # installed to the user). However, we'll store them in the following dictionary in case we choose to display them in the # future. dependencies_dict = dict(tool_dependencies=all_tool_dependencies, repository_dependencies=repository_dependencies) migrated_tool_configs_dict[tool_config_file_name] = dependencies_dict else: break if tool_shed_accessible: # Parse the proprietary tool_panel_configs (the default is tool_conf.xml) and generate the list of missing tool config file names. for tool_panel_config in tool_panel_configs: tree, error_message = xml_util.parse_xml(tool_panel_config) if tree: root = tree.getroot() for elem in root: if elem.tag == 'tool': missing_tool_configs_dict = check_tool_tag_set(elem, migrated_tool_configs_dict, missing_tool_configs_dict) elif elem.tag == 'section': for section_elem in elem: if section_elem.tag == 'tool': missing_tool_configs_dict = check_tool_tag_set(section_elem, migrated_tool_configs_dict, missing_tool_configs_dict) else: exception_msg = f'\n\nThe entry for the main Galaxy tool shed at {tool_shed} is missing from the {app.config.tool_sheds_config} file. ' exception_msg += 'The entry for this tool shed must always be available in this file, so re-add it before attempting to start your Galaxy server.\n' raise Exception(exception_msg) return tool_shed_accessible, missing_tool_configs_dict
def __init__(self, app, latest_migration_script_number, tool_shed_install_config, migrated_tools_config, install_dependencies): """ Check tool settings in tool_shed_install_config and install all repositories that are not already installed. The tool panel configuration file is the received migrated_tools_config, which is the reserved file named migrated_tools_conf.xml. """ self.app = app self.toolbox = self.app.toolbox self.migrated_tools_config = migrated_tools_config # Initialize the ToolPanelManager. self.tpm = tool_panel_manager.ToolPanelManager(self.app) # If install_dependencies is True but tool_dependency_dir is not set, do not attempt # to install but print informative error message. if install_dependencies and app.tool_dependency_dir is None: message = 'You are attempting to install tool dependencies but do not have a value ' message += 'for "tool_dependency_dir" set in your galaxy.ini file. Set this ' message += 'location value to the path where you want tool dependencies installed and ' message += 'rerun the migration script.' raise Exception(message) # Get the local non-shed related tool panel configs (there can be more than one, and the # default name is tool_conf.xml). self.proprietary_tool_confs = self.non_shed_tool_panel_configs self.proprietary_tool_panel_elems = self.get_proprietary_tool_panel_elems( latest_migration_script_number) # Set the location where the repositories will be installed by retrieving the tool_path # setting from migrated_tools_config. try: tree, error_message = parse_xml(migrated_tools_config) except OSError as exc: if exc.errno == errno.ENOENT: with open(migrated_tools_config, 'w') as fh: fh.write( MIGRATED_TOOLS_CONF_XML.format( shed_tools_dir=self.app.config.shed_tools_dir)) tree, error_message = parse_xml(migrated_tools_config) else: raise if tree is None: log.error(error_message) else: root = tree.getroot() self.tool_path = root.get('tool_path') log.debug( "Repositories will be installed into configured tool_path location ", str(self.tool_path)) # Parse tool_shed_install_config to check each of the tools. self.tool_shed_install_config = tool_shed_install_config tree, error_message = parse_xml(tool_shed_install_config) if tree is None: log.error(error_message) else: root = tree.getroot() defined_tool_shed_url = root.get('name') self.tool_shed_url = common_util.get_tool_shed_url_from_tool_shed_registry( self.app, defined_tool_shed_url) self.tool_shed = common_util.remove_protocol_and_port_from_tool_shed_url( self.tool_shed_url) self.repository_owner = common_util.REPOSITORY_OWNER self.shed_config_dict = self.tpm.get_shed_tool_conf_dict( self.migrated_tools_config) # Since tool migration scripts can be executed any number of times, we need to # make sure the appropriate tools are defined in tool_conf.xml. If no tools # associated with the migration stage are defined, no repositories will be installed # on disk. The default behavior is that the tool shed is down. tool_shed_accessible = False tool_panel_configs = common_util.get_non_shed_tool_panel_configs( app) if tool_panel_configs: # The missing_tool_configs_dict contents are something like: # {'emboss_antigenic.xml': [('emboss', '5.0.0', 'package', '\nreadme blah blah blah\n')]} tool_shed_accessible, missing_tool_configs_dict = \ common_util.check_for_missing_tools(app, tool_panel_configs, latest_migration_script_number) else: # It doesn't matter if the tool shed is accessible since there are no migrated # tools defined in the local Galaxy instance, but we have to set the value of # tool_shed_accessible to True so that the value of migrate_tools.version can # be correctly set in the database. tool_shed_accessible = True missing_tool_configs_dict = {} if tool_shed_accessible: if len(self.proprietary_tool_confs) == 1: plural = '' file_names = self.proprietary_tool_confs[0] else: plural = 's' file_names = ', '.join(self.proprietary_tool_confs) if missing_tool_configs_dict: for proprietary_tool_conf in self.proprietary_tool_confs: # Create a backup of the tool configuration in the un-migrated state. shutil.copy( proprietary_tool_conf, '%s-pre-stage-%04d' % (proprietary_tool_conf, latest_migration_script_number)) for repository_elem in root: # Make sure we have a valid repository tag. if self.__is_valid_repository_tag(repository_elem): # Get all repository dependencies for the repository defined by the # current repository_elem. Repository dependency definitions contained # in tool shed repositories with migrated tools must never define a # relationship to a repository dependency that contains a tool. The # repository dependency can only contain items that are not loaded into # the Galaxy tool panel (e.g., tool dependency definitions, custom datatypes, # etc). This restriction must be followed down the entire dependency hierarchy. name = repository_elem.get('name') changeset_revision = repository_elem.get( 'changeset_revision') tool_shed_accessible, repository_dependencies_dict = \ common_util.get_repository_dependencies(app, self.tool_shed_url, name, self.repository_owner, changeset_revision) # Make sure all repository dependency records exist (as tool_shed_repository # table rows) in the Galaxy database. created_tool_shed_repositories = \ self.create_or_update_tool_shed_repository_records(name=name, changeset_revision=changeset_revision, repository_dependencies_dict=repository_dependencies_dict) # Order the repositories for proper installation. This process is similar to the # process used when installing tool shed repositories, but does not handle managing # tool panel sections and other components since repository dependency definitions # contained in tool shed repositories with migrated tools must never define a relationship # to a repository dependency that contains a tool. ordered_tool_shed_repositories = \ self.order_repositories_for_installation(created_tool_shed_repositories, repository_dependencies_dict) for tool_shed_repository in ordered_tool_shed_repositories: is_repository_dependency = self.__is_repository_dependency( name, changeset_revision, tool_shed_repository) self.install_repository( repository_elem, tool_shed_repository, install_dependencies, is_repository_dependency= is_repository_dependency) else: message = "\nNo tools associated with migration stage %s are defined in your " % \ str(latest_migration_script_number) message += "file%s named %s,\nso no repositories will be installed on disk.\n" % \ (plural, file_names) log.info(message) else: message = "\nThe main Galaxy tool shed is not currently available, so skipped migration stage %s.\n" % \ str(latest_migration_script_number) message += "Try again later.\n" log.error(message)
def alter_config_and_load_prorietary_datatypes(self, datatypes_config, relative_install_dir, deactivate=False, override=True): """ Parse a custom datatypes config (a datatypes_conf.xml file included in an installed tool shed repository) and add information to appropriate element attributes that will enable custom datatype class modules, datatypes converters and display applications to be discovered and properly imported by the datatypes registry. The value of override will be False when a tool shed repository is being installed. Since installation is occurring after the datatypes registry has been initialized, the registry's contents cannot be overridden by conflicting data types. """ tree, error_message = parse_xml(datatypes_config) if tree is None: return None, None datatypes_config_root = tree.getroot() registration = datatypes_config_root.find('registration') if registration is None: # We have valid XML, but not a valid custom datatypes definition. return None, None converter_path, display_path = self.get_converter_and_display_paths( registration, relative_install_dir) if converter_path: # Path to datatype converters registration.attrib['proprietary_converter_path'] = converter_path if display_path: # Path to datatype display applications registration.attrib['proprietary_display_path'] = display_path relative_path_to_datatype_file_name = None datatype_files = datatypes_config_root.find('datatype_files') datatype_class_modules = [] if datatype_files is not None: # The <datatype_files> tag set contains any number of <datatype_file> tags. # <datatype_files> # <datatype_file name="gmap.py"/> # <datatype_file name="metagenomics.py"/> # </datatype_files> # We'll add attributes to the datatype tag sets so that the modules can be properly imported # by the datatypes registry. for elem in datatype_files.findall('datatype_file'): datatype_file_name = elem.get('name', None) if datatype_file_name: # Find the file in the installed repository. for root, dirs, files in os.walk(relative_install_dir): if root.find('.hg') < 0: for name in files: if name == datatype_file_name: datatype_class_modules.append( os.path.join(root, name)) break break if datatype_class_modules: for relative_path_to_datatype_file_name in datatype_class_modules: datatype_file_name_path, datatype_file_name = os.path.split( relative_path_to_datatype_file_name) for elem in registration.findall('datatype'): # Handle 'type' attribute which should be something like one of the following: # type="gmap:GmapDB" # type="galaxy.datatypes.gmap:GmapDB" dtype = elem.get('type', None) if dtype: fields = dtype.split(':') proprietary_datatype_module = fields[0] if proprietary_datatype_module.find('.') >= 0: # Handle the case where datatype_module is "galaxy.datatypes.gmap". proprietary_datatype_module = proprietary_datatype_module.split( '.')[-1] # The value of proprietary_path must be an absolute path due to job_working_directory. elem.attrib['proprietary_path'] = os.path.abspath( datatype_file_name_path) elem.attrib[ 'proprietary_datatype_module'] = proprietary_datatype_module # Load custom datatypes self.app.datatypes_registry.load_datatypes( root_dir=self.app.config.root, config=datatypes_config_root, deactivate=deactivate, override=override) return converter_path, display_path
def create_tool_dependency_with_initialized_env_sh_file( self, dependent_install_dir, tool_shed_repository, required_repository, package_name, package_version, tool_dependencies_config): """ Create or get a tool_dependency record that is defined by the received package_name and package_version. An env.sh file will be created for the tool_dependency in the received dependent_install_dir. """ # The received required_repository refers to a tool_shed_repository record that is defined as a complex # repository dependency for this tool_dependency. The required_repository may or may not be currently # installed (it doesn't matter). If it is installed, it is associated with a tool_dependency that has # an env.sh file that this new tool_dependency must be able to locate and "source". If it is not installed, # we can still determine where that env.sh file will be, so we'll initialize this new tool_dependency's env.sh # file in either case. If the required repository ends up with an installation error, this new tool # dependency will still be fine because its containing repository will be defined as missing dependencies. tool_dependencies = [] if not os.path.exists(dependent_install_dir): os.makedirs(dependent_install_dir) required_tool_dependency_env_file_path = None if tool_dependencies_config: required_td_tree, error_message = parse_xml( tool_dependencies_config) if required_td_tree: required_td_root = required_td_tree.getroot() for required_td_elem in required_td_root: # Find the appropriate package name and version. if required_td_elem.tag == 'package': # <package name="bwa" version="0.5.9"> required_td_package_name = required_td_elem.get( 'name', None) required_td_package_version = required_td_elem.get( 'version', None) # Check the database to see if we have a record for the required tool dependency (we may not which is ok). If we # find a record, we need to see if it is in an error state and if so handle it appropriately. required_tool_dependency = \ tool_dependency_util.get_tool_dependency_by_name_version_type_repository(self.app, required_repository, required_td_package_name, required_td_package_version, 'package') if required_td_package_name == package_name and required_td_package_version == package_version: # Get or create a database tool_dependency record with which the installed package on disk will be associated. tool_dependency = \ tool_dependency_util.create_or_update_tool_dependency(app=self.app, tool_shed_repository=tool_shed_repository, name=package_name, version=package_version, type='package', status=self.app.install_model.ToolDependency.installation_status.NEVER_INSTALLED, set_status=True) # Create an env.sh file for the tool_dependency whose first line will source the env.sh file located in # the path defined by required_tool_dependency_env_file_path. It doesn't matter if the required env.sh # file currently exists.. required_tool_dependency_env_file_path = \ self.get_required_repository_package_env_sh_path(package_name, package_version, required_repository) env_file_builder = EnvFileBuilder( tool_dependency.installation_directory( self.app)) env_file_builder.append_line( action="source", value=required_tool_dependency_env_file_path) return_code = env_file_builder.return_code if return_code: error_message = 'Error defining env.sh file for package %s, return_code: %s' % \ (str(package_name), str(return_code)) tool_dependency = \ tool_dependency_util.set_tool_dependency_attributes(self.app, tool_dependency=tool_dependency, status=self.app.install_model.ToolDependency.installation_status.ERROR, error_message=error_message) elif required_tool_dependency is not None and required_tool_dependency.in_error_state: error_message = "This tool dependency's required tool dependency %s version %s has status %s." % \ (str(required_tool_dependency.name), str(required_tool_dependency.version), str(required_tool_dependency.status)) tool_dependency = \ tool_dependency_util.set_tool_dependency_attributes(self.app, tool_dependency=tool_dependency, status=self.app.install_model.ToolDependency.installation_status.ERROR, error_message=error_message) else: tool_dependency = \ tool_dependency_util.set_tool_dependency_attributes(self.app, tool_dependency=tool_dependency, status=self.app.install_model.ToolDependency.installation_status.INSTALLED) tool_dependencies.append(tool_dependency) return tool_dependencies
def main(): for required_file in [ 'automated_tool_installation_log.tsv', 'tool_labels.yml', 'shed_tool_conf.xml' ]: if not os.path.exists(required_file): raise Exception(f'Required file {required_file} is missing') def filter_new(row): return row['Status'] == 'Installed' and in_time_window( row['Date (AEST)'], display_new_days) and row['New Tool'] == 'True' def filter_updated(row): return row['Status'] == 'Installed' and in_time_window( row['Date (AEST)'], display_updated_days) and row['New Tool'] == 'False' def load_log(filter=None): """ Load the installation log tsv file and return it as a list row objects, i.e. [{'Build Num.': '156', 'Name': 'abricate', ...}, {'Build Num.': '156', 'Name': 'bedtools', ...},...] The filter argument is a function that takes a row as input and returns True or False """ table = [] with open(log_file) as tsvfile: reader = csv.DictReader(tsvfile, dialect='excel-tab') for row in reader: if not filter or filter(row): table.append(row) return table with open(tool_labels_file) as handle: tool_labels_constant = yaml.safe_load(handle) if os.path.exists(hidden_tools_file): with open(hidden_tools_file) as handle: hidden_tool_ids = yaml.safe_load(handle).get('hidden_tool_ids', []) else: hidden_tool_ids = [] tool_labels_dynamic = { new_label: load_log(filter=filter_new), updated_label: load_log(filter=filter_updated), } tree, error_message = parse_xml(shed_tool_conf_file) root = tree.getroot() # shed_tool_conf.xml has multiple section elements containing tools # loop through all sections and tools for section in root: if section.tag == 'section': for tool in list(section): if tool.tag == 'tool': tool_id = tool.find('id').text name = tool.find('repository_name').text owner = tool.find('repository_owner').text revision = tool.find('installed_changeset_revision').text # remove all existing labels tool.attrib.pop('labels', None) # replace labels from dict labels_for_tool = [] for label in tool_labels_constant: for id in tool_labels_constant[label]: if tool_id == id or ( id.endswith('*') and get_deversioned_id(id) == get_deversioned_id(tool_id)): labels_for_tool.append(label) break for label in tool_labels_dynamic: for row in tool_labels_dynamic[label]: if row['Name'] == name and row[ 'Owner'] == owner and row[ 'Installed Revision'] == revision: labels_for_tool.append(label) break tool.attrib.pop('hidden', None) for id in hidden_tool_ids: if tool_id == id or (id.endswith('*') and get_deversioned_id(id) == get_deversioned_id(tool_id)): tool.set('hidden', 'True') break if labels_for_tool: tool.set('labels', ','.join(labels_for_tool)) with open(shed_tool_conf_file, 'w') as handle: handle.write(xml_to_string(root, pretty=True))