# Copyright (C) 2005-2024 Splunk Inc. All Rights Reserved. from ITOA.setup_logging import setup_logging from itsi.content_packs.acl import AccessConfigurator from itsi.content_packs.backfiller import ContentObjectsBackfiller from itsi.content_packs.conflicts import ConflictResolver from itsi.content_packs.preview import ContentPackPreviewer from itsi.content_packs.constants import ( CONTENT_PACK_SOURCE_FIELD, CONTENT_PACK_SOURCE_ID_FIELD, CONTENT_PACK_SOURCE_VERSION_FIELD, ContentType, ContentPackInstallOptions ) from itsi.content_packs.itoa import get_itoa_identifier_fields from itsi.content_packs.enabler import ContentObjectsEnabler from itsi.content_packs.entitlements_processor import ContentPackEntitlementsProcessor from itsi.content_packs.journal import TransactionJournal from itsi.content_packs.readers import ContentPackContentReader from itsi.content_packs.prefixer import ContentObjectsPrefixer from itsi.content_packs.writers import ItoaContentWriter from itsi.objects.itsi_content_pack_saved_search_status import ItsiContentPackSavedSearchStatus from itsi.objects.itsi_content_pack_status import ItsiContentPackStatus from ITOA.saved_search_utility import APP_FILTER_PREFIX, SavedSearch LOGGER = setup_logging( logfile_name='itsi_content_packs_install.log', logger_name='itsi.content_packs.install' ) def install(content_pack_id, version, session_key=None, options={}): """ Installs a content pack based for the given content pack id and version. :param content_pack_id: the content pack id :type content_pack_id: str :param version: the content pack version :type version: str :param session_key: the session key :type session_key: str :param options: various parameters to determine how content pack objects are installed :type options: dict :return: a journal of the install results :rtype: TransactionJournal """ if options.get(ContentPackInstallOptions.INSTALL_ALL): previewer = ContentPackPreviewer( logger=LOGGER, session_key=session_key) content_filter_data = previewer.format_preview_objects(content_pack_id, version) else: content_filter_data = options.get(ContentPackInstallOptions.CONTENT, {}) installer = ContentPackInstaller( logger=LOGGER, session_key=session_key, processors=[ ContentObjectsFilter( logger=LOGGER, session_key=session_key, content_filter=content_filter_data ), ContentObjectsPrefixer( logger=LOGGER, session_key=session_key, prefix=options.get(ContentPackInstallOptions.PREFIX, '') ), ConflictResolver( logger=LOGGER, session_key=session_key, resolution_type=options.get(ContentPackInstallOptions.RESOLUTION, ContentPackInstallOptions.SKIP) ), ContentObjectsEnabler( logger=LOGGER, session_key=session_key, enabled=options.get(ContentPackInstallOptions.ENABLED, False) ), ContentPackEntitlementsProcessor( content_pack_id=content_pack_id, logger=LOGGER, session_key=session_key, ), ContentObjectsBackfiller( logger=LOGGER, session_key=session_key, backfill=options.get(ContentPackInstallOptions.BACKFILL, True) ), AccessConfigurator(logger=LOGGER, session_key=session_key), ContentSourceAnnotater( logger=LOGGER, session_key=session_key, content_pack_id=content_pack_id, version=version ) ] ) return installer.install(content_pack_id, version) class ContentPackInstaller(object): """This class is responsible for installing content packs.""" def __init__(self, logger, session_key, processors=None): """ :param logger: a logger instance :type logger: Logger :param session_key: the session key :type session_key: str :param processors: a list of objects to process content pack data :type processors: list """ self.logger = logger self.session_key = session_key self.processors = processors if processors else [] self.reader = ContentPackContentReader( logger=logger, session_key=session_key ) self.writer = ItoaContentWriter( logger=logger, session_key=session_key ) def install(self, content_pack_id, version): """ Installs a content pack based for the given content pack id and version. :param content_pack_id: the content pack version id :type content_pack_id: str :param version: the content pack version :type version: str :return: a journal of the install results :rtype: TransactionJournal """ journal = TransactionJournal() content_objects = self.reader.read(content_pack_id, version, journal=journal) processed_objects = self.process_objects(content_objects, journal=journal) written_objects = self.writer.write(processed_objects, journal=journal) # Only update status collection if objects actually get installed if written_objects: ItsiContentPackStatus(self.session_key, 'nobody').update_content_pack_status(content_pack_id, version) journal.success(written_objects) return journal def process_objects(self, content_objects, journal): """ Processes and filters the given content objects before the objects are saved. :param content_objects: the content objects separated by content type :type content_objects: dict :param journal: a journal instance :type journal: TransactionJournal :return: the filtered and processed content objects :rtype: dict """ processed_objects = content_objects for processor in self.processors: processed_objects = processor.process_objects(processed_objects, journal=journal) return processed_objects class ContentObjectsFilter(object): """Includes content objects based on the provided filter.""" def __init__(self, logger, session_key, content_filter): """ :param logger: a logger instance :type logger: Logger :param session_key: the session key :type session_key: str :param content_filter: the objects filter :type content_filter: dict """ self.logger = logger self.session_key = session_key self.content_filter = content_filter or {} def process_objects(self, content_objects, **kwargs): """ Employs the content filter on the given content objects. :param content_objects: the content objects data :type content_objects: dict :return: the content objects data :rtype: dict """ if not self.content_filter: return content_objects processed_objects = {} for content_type, objects in content_objects.items(): content_type_filter = set(self.content_filter.get(content_type, [])) identifier = get_itoa_identifier_fields(content_type)[0] filtered_objects = filter( # Excluding Glass Table Images and Glass Table Icons from filtering as they are not available for user selection lambda obj: obj[identifier] in content_type_filter or content_type == ContentType.GLASS_TABLE_IMAGE or content_type == ContentType.GLASS_TABLE_ICON, objects ) processed_objects[content_type] = list(filtered_objects) return processed_objects class ContentSourceAnnotater(object): """Annotates the objects with metadata about the content pack.""" def __init__(self, logger, session_key, content_pack_id, version): """ :param logger: a logger instance :type logger: Logger :param session_key: the session key :type session_key: str :param content_pack_id: the content pack id :type content_pack_id: str :param version: the content pack version :type version: str """ self.logger = logger self.session_key = session_key self.content_pack_id = content_pack_id self.version = version def process_objects(self, content_objects, **kwargs): """ Annotates the objects with metadata about the content pack. :param content_objects: the content objects data :type content_objects: dict :return: the content objects data :rtype: dict """ for content_type, objects in content_objects.items(): for obj in objects: obj[CONTENT_PACK_SOURCE_FIELD] = self.content_pack_id obj[CONTENT_PACK_SOURCE_ID_FIELD] = obj['_key'] obj[CONTENT_PACK_SOURCE_VERSION_FIELD] = self.version return content_objects def update_saved_searches_status(session_key, content_pack_id, action): """ Enables or disables all the savedsearches of contentpack based on saved_search_action value in request body. This function also update the count of savedsearches in collection `itsi_content_pack_saved_search_status` after enabling or disabling savedsearches for given content_pack_id. If any other value besides "enable" or "disable" is provided to the saved_search_action, then it is treated as "retain_status". :param session_key: the session key :type session_key: str :param content_pack_id: the content pack id :type content_pack_id: str :param action: action to perform on the saved searches (enable/ disable/ retain_status). In case of no value or incorrect values, retain_status is used :type options: str :return: updated savedsearch status :rtype: dict """ updated_searches = [] failed_searches = [] saved_searches_obj = { 'total': 0, 'enabled': 0, 'disabled': 0 } if not (action == ContentPackInstallOptions.SAVED_SEARCH_ENABLE or action == ContentPackInstallOptions.SAVED_SEARCH_DISABLE): saved_searches = { 'action_performed': ContentPackInstallOptions.SAVED_SEARCH_RETAIN_STATUS, 'success': updated_searches, 'failure': failed_searches } return saved_searches try: cp_saved_searches = SavedSearch.get_all_searches( session_key=session_key, namespace='-', search=APP_FILTER_PREFIX + content_pack_id ) except Exception as reason: raise Exception('Failed to retrieve saved searches of the content pack {content_pack_id}.\ ERROR {reason}'.format(content_pack_id=content_pack_id, reason=reason)) if len(cp_saved_searches) > 0: search_param = {'disabled': '1'} if action \ == ContentPackInstallOptions.SAVED_SEARCH_DISABLE else {'disabled': '0'} for search in cp_saved_searches: if (search.get('disabled').strip() == '1' and action == ContentPackInstallOptions.SAVED_SEARCH_ENABLE) or \ (search.get('disabled').strip() == '0' and action == ContentPackInstallOptions.SAVED_SEARCH_DISABLE): try: SavedSearch.update_search( session_key, search.name, namespace=content_pack_id, **search_param ) if action == ContentPackInstallOptions.SAVED_SEARCH_ENABLE: saved_searches_obj['enabled'] = saved_searches_obj['enabled'] + 1 else: saved_searches_obj['disabled'] = saved_searches_obj['disabled'] + 1 updated_searches.append(search.name) LOGGER.info('Operation "{action}" performed on saved search "{name}" is successful' .format(action=action, name=search.name)) except Exception as reason: failed_searches.append({'name': search.name, 'error_message': str(reason)}) if search.get('disabled').strip() == '0': saved_searches_obj['enabled'] = saved_searches_obj['enabled'] + 1 else: saved_searches_obj['disabled'] = saved_searches_obj['disabled'] + 1 LOGGER.exception( 'Operation "{action}" on saved search "{name}" has failed due to "{reason}"' .format(action=action, name=search.name, reason=reason) ) else: updated_searches.append(search.name) if action == ContentPackInstallOptions.SAVED_SEARCH_ENABLE: saved_searches_obj['enabled'] = saved_searches_obj['enabled'] + 1 else: saved_searches_obj['disabled'] = saved_searches_obj['disabled'] + 1 LOGGER.info('Saved search "{name}" is already on state "{action}"' .format(name=search.name, action=action)) saved_searches_obj['total'] = saved_searches_obj['enabled'] + saved_searches_obj['disabled'] # update saved_searches count in collection itsi_content_pack_saved_search_status for content pack. ItsiContentPackSavedSearchStatus(session_key, 'nobody').update_content_pack_saved_search_status( content_pack_id, saved_searches_obj['total'], saved_searches_obj['enabled'], saved_searches_obj['disabled'] ) saved_searches = { 'action_performed': action, 'success': updated_searches, 'failure': failed_searches } return saved_searches