You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

377 lines
14 KiB

# 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