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
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
|