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.
530 lines
20 KiB
530 lines
20 KiB
# Copyright (C) 2005-2025 Splunk Inc. All Rights Reserved.
|
|
|
|
import json
|
|
import re
|
|
import sys
|
|
|
|
from splunk.clilib.bundle_paths import make_splunkhome_path
|
|
from splunk.util import normalizeBoolean
|
|
import splunk.search as search
|
|
|
|
sys.path.append(make_splunkhome_path(['etc', 'apps', 'SA-ITOA', 'lib']))
|
|
sys.path.append(make_splunkhome_path(['etc', 'apps', 'SA-ITOA', 'lib', 'SA_ITOA_app_common']))
|
|
from ITOA.setup_logging import logger
|
|
from ITOA.event_management.event_management_object_manifest import object_manifest
|
|
from ITOA.event_management.notable_event_utils import ActionDispatchConfiguration
|
|
from ITOA.itoa_common import get_conf
|
|
|
|
from ITOA.event_management.notable_event_utils import NotableEventConfiguration, token_replacement
|
|
|
|
# FIXME - this class can't be used along because it has reference of some instance variable like
|
|
# self._session etc, which is initialized by another imported class.
|
|
# this is really very bad pattern. We should address this
|
|
|
|
|
|
class EventManagementService(object):
|
|
|
|
def __init__(self, session_key=None):
|
|
self._session_key = session_key
|
|
|
|
def _get_instance(self, object_type, owner, current_user_name=None):
|
|
"""
|
|
Get instance for given object_ type
|
|
|
|
@type: string
|
|
@param object_type: type of object to instantiate
|
|
|
|
@type: string
|
|
@param owner: owner of the object
|
|
|
|
@rtype: ItsiNotableEvent
|
|
@return: object_ instance of given type
|
|
"""
|
|
object_class = object_manifest.get(object_type)
|
|
# We want to perform all operation at nobody context
|
|
return object_class(self._session_key, current_user_name=current_user_name)
|
|
|
|
def get_all_notable_event_configuration(self, session_key):
|
|
"""
|
|
Get all notable event configuration info like severities, statuses, owners and email formats too
|
|
@param session_key: basestring
|
|
@param session_key: session key
|
|
|
|
@rtype: dict
|
|
@return: dict for all notable event information
|
|
Return a dictionary which hold information about severities, status and owners
|
|
{
|
|
severities: [
|
|
{
|
|
name: <name>,
|
|
value: <name>,
|
|
default: 0|1,
|
|
color: <name>
|
|
}..],
|
|
statuses: [
|
|
{
|
|
name: <name>,
|
|
value: <name>,
|
|
default: 0|1,
|
|
end: 0|1 (optional, only included if it exists)
|
|
} ...],
|
|
owners: [{
|
|
name: <name>,
|
|
value: <name>,
|
|
default: 0|1
|
|
}..],
|
|
email_formats: [{
|
|
name: <name>,
|
|
value: <name>,
|
|
default: 0|1
|
|
}..]
|
|
|
|
"""
|
|
ns_config = NotableEventConfiguration(session_key, logger)
|
|
data = {}
|
|
# Get Severity
|
|
data['severities'] = []
|
|
for severity_value, content in ns_config.severity_contents.items():
|
|
data['severities'].append({
|
|
'label': content.get('label'),
|
|
'value': severity_value,
|
|
'default': ns_config.get_default_severity() == severity_value,
|
|
'color': content.get('color')
|
|
})
|
|
# Get Status
|
|
data['statuses'] = []
|
|
for status_value, content in ns_config.status_contents.items():
|
|
data['statuses'].append({
|
|
'label': content.get('label'),
|
|
'value': status_value,
|
|
'default': ns_config.get_default_status() == status_value
|
|
})
|
|
if int(content.get('end', 0)) == 1:
|
|
data['statuses'][-1]['end'] = 1
|
|
# Get Owner
|
|
data['owners'] = []
|
|
for key, owner in ns_config.owner_contents.items():
|
|
data['owners'].append({
|
|
'label': owner.get('realname') if owner.get('realname') else owner.get('_key'),
|
|
'value': owner.get('_key'),
|
|
'default': ns_config.get_default_owner() == owner.get('_key')
|
|
})
|
|
data['email_formats'] = []
|
|
for eformat in ['table', 'raw', 'csv']:
|
|
data['email_formats'].append({
|
|
'label': eformat,
|
|
'value': eformat,
|
|
'default': eformat == 'table'
|
|
})
|
|
logger.debug('Return notable event configuration "%s"', data)
|
|
return data
|
|
|
|
def _get_actions_conf(self, session_key, search='', host_base_uri=''):
|
|
"""
|
|
Fetches the action conf.
|
|
|
|
:param session_key: session_key for host
|
|
:param host_base_uri: host uri to fetch actions conf
|
|
:return: dict of actions configuration
|
|
"""
|
|
rval = get_conf(
|
|
session_key,
|
|
'notable_event_actions',
|
|
search='',
|
|
count=-1,
|
|
app='SA-ITOA',
|
|
host_base_uri=host_base_uri
|
|
)
|
|
response = rval.get('response')
|
|
|
|
configuration = {}
|
|
if int(response.status) != 200:
|
|
logger.error("Unable to fetch actions conf")
|
|
else:
|
|
actions_conf = rval.get('content')
|
|
content = json.loads(actions_conf)
|
|
|
|
for entry in content.get('entry', []):
|
|
configuration[entry.get('name')] = entry.get('content', {})
|
|
|
|
return configuration
|
|
|
|
def check_remote_host_notable_event_actions(self, session_key):
|
|
"""
|
|
Checks if remote host's actions are available on local host.
|
|
|
|
:param session_key: local session_key
|
|
:return: dictionary of missing_remote_actions and disabled_local_actions
|
|
"""
|
|
action_dispatch_config = ActionDispatchConfiguration(session_key, logger)
|
|
missing_remote_actions = []
|
|
disabled_local_actions = []
|
|
if action_dispatch_config.ea_role == 'executor':
|
|
remote_actions_conf = self._get_actions_conf(
|
|
action_dispatch_config.get_master_host_session_key(),
|
|
host_base_uri=action_dispatch_config.remote_ea_mgmt_uri
|
|
)
|
|
local_actions_conf = self._get_actions_conf(session_key)
|
|
for remote_action in remote_actions_conf.keys():
|
|
if remote_action not in local_actions_conf.keys() and not remote_actions_conf[remote_action].get(
|
|
'disabled'):
|
|
missing_remote_actions.append(remote_action)
|
|
if remote_action in local_actions_conf.keys() and local_actions_conf[remote_action].get('disabled') and \
|
|
not remote_actions_conf[remote_action].get('disabled'):
|
|
disabled_local_actions.append(remote_action)
|
|
return {
|
|
'missing_remote_actions': missing_remote_actions,
|
|
'disabled_local_actions': disabled_local_actions
|
|
}
|
|
|
|
def _update_comment_with_token_replacement(self, data, owner):
|
|
prepend_command = f'search `itsi_event_management_group_index` {data["event_id"]} | dedup itsi_group_id | fields *'
|
|
job = search.dispatch(prepend_command, sessionKey=self._session_key, owner=owner, rf='*')
|
|
search.waitForJob(job)
|
|
if job.isFailed:
|
|
job.cancel()
|
|
raise search.SearchException(('Search failed to clean up event(s), messages="%s".'), job.messages)
|
|
results = job.results
|
|
if not results:
|
|
raise ValueError('No results found for the search job.')
|
|
|
|
# Create a dictionary from the first result's fields
|
|
result_fields = results[0].fields.data
|
|
res = {key: str(result_fields[key]) for key in result_fields}
|
|
job.cancel()
|
|
# Update the comment token from the result's fields
|
|
return token_replacement(data['comment'], res)
|
|
|
|
# CRUD and BULK Notable event CRUD Operation
|
|
|
|
def upsert(self, owner, object_type, identifier, kwargs, current_user_name=None, object_instance=None):
|
|
"""
|
|
Perform create or update
|
|
|
|
@type owner: basestring
|
|
@param owner: owner who is performing this operation
|
|
|
|
@type object_type: basestring
|
|
@param object_type: Target object_type type
|
|
|
|
@type identifier: basestring
|
|
@param identifier: id. Id can be null. When id is not specified then create operation is being
|
|
called otherwise update
|
|
|
|
@type kwargs:dict
|
|
@param kwargs: which contain addition parameters of controller
|
|
|
|
@type current_user_name: basestring
|
|
@param current_user_name: current user who initiated the request
|
|
|
|
@type object_instance: object instance
|
|
@param object_instance: based upon type instance was created
|
|
|
|
@rtype: json or Exception
|
|
@return: json object
|
|
"""
|
|
if not object_instance:
|
|
object_instance = self._get_instance(object_type, owner, current_user_name=current_user_name)
|
|
# add user who is performing this operation
|
|
data = kwargs.get('data') or kwargs
|
|
self._add_user(data, owner)
|
|
self._delete_data(kwargs)
|
|
|
|
# get earliest or latest time from data itself
|
|
if 'earliest_time' not in kwargs:
|
|
kwargs['earliest_time'] = data.get('earliest_time')
|
|
if 'latest_time' not in kwargs:
|
|
kwargs['latest_time'] = data.get('latest_time')
|
|
|
|
# check if the key already exists
|
|
create = True
|
|
if identifier:
|
|
try:
|
|
object_instance.get(identifier)
|
|
create = False
|
|
except Exception:
|
|
logger.debug('Object %s does not exist.' % identifier)
|
|
|
|
if identifier and not create:
|
|
logger.debug('Performing update for id=%s', identifier)
|
|
is_partial_update = normalizeBoolean(kwargs.pop('is_partial_update', True))
|
|
|
|
# Enforcing RBAC checks on update require the current user for correlation search.
|
|
# Other objects are not enforcing rbac check for now
|
|
result = object_instance.update(identifier, data, is_partial_update=is_partial_update, **kwargs)
|
|
else:
|
|
logger.debug('Creating new %s', object_type)
|
|
# Add user who create this object_type in _owner field
|
|
self._add_user(data, owner, '_owner')
|
|
|
|
if normalizeBoolean(data.get('_is_group', False)) is True:
|
|
result = object_instance.create_for_group(data, **kwargs)
|
|
else:
|
|
if object_type == 'notable_event_comment' and 'comment' in data:
|
|
regex = re.compile(r'\%([\w.\s]+)\%')
|
|
dynamic_fields = regex.findall(data['comment'])
|
|
if dynamic_fields:
|
|
data['comment'] = self._update_comment_with_token_replacement(data, owner)
|
|
result = object_instance.create(data, **kwargs)
|
|
|
|
logger.debug('Returning values=%s', result)
|
|
return result
|
|
|
|
def get(self, owner, object_type, identifier, kwargs, current_user_name=None):
|
|
"""
|
|
Perform get operations
|
|
|
|
@type owner: basestring
|
|
@param owner: owner who is performing this operation
|
|
|
|
@type object_type: basestring
|
|
@param object_type: Target object_type type
|
|
|
|
@type identifier: basestring
|
|
@param identifier: id. Id can be null. When id is not specified then create operation is being
|
|
called otherwise update
|
|
|
|
@type kwargs:dict
|
|
@param kwargs: which contain addition parameters of controller
|
|
|
|
@type current_user_name: basestring
|
|
@param current_user_name: the user making the request.
|
|
|
|
@rtype: json or Exception
|
|
@return: json object
|
|
"""
|
|
logger.debug('Getting _key=%s of object=%s ,and user=%s', identifier, object_type, owner)
|
|
object_instance = self._get_instance(object_type, owner, current_user_name=current_user_name)
|
|
self._delete_data(kwargs)
|
|
result = object_instance.get(identifier, **kwargs)
|
|
logger.debug('Returning values=%s', result)
|
|
return result
|
|
|
|
def delete(self, owner, object_type, identifier, kwargs, current_user_name=None):
|
|
"""
|
|
Perform delete operation
|
|
|
|
@type owner: basestring
|
|
@param owner: owner who is performing this operation
|
|
|
|
@type object_type: basestring
|
|
@param object_type: Target object_type type
|
|
|
|
@type identifier: basestring
|
|
@param identifier: id. Id can be null. When id is not specified then create operation is being
|
|
called otherwise update
|
|
|
|
@type kwargs:dict
|
|
@param kwargs: which contain addition parameters of controller
|
|
|
|
@type current_user_name: basestring
|
|
@param current_user_name: the user making the request.
|
|
|
|
@rtype: None or Exception
|
|
@return: None if delete operation is successful otherwise exception
|
|
|
|
"""
|
|
logger.debug('Deleting _key=%s of object=%s', identifier, object_type)
|
|
object_instance = self._get_instance(object_type, owner, current_user_name=current_user_name)
|
|
self._delete_data(kwargs)
|
|
object_instance.delete(identifier, **kwargs)
|
|
|
|
def get_bulk(self, owner, object_type, kwargs, current_user_name=None):
|
|
"""
|
|
Perform get bulk operations
|
|
if data is not specify in the kwargs then all objects are return based
|
|
upon count and offset values
|
|
|
|
@type owner: basestring
|
|
@param owner: owner who is performing this operation
|
|
|
|
@type object_type: basestring
|
|
@param object_type: Target object_type type
|
|
|
|
@type kwargs:dict
|
|
@param kwargs: which contain addition parameters of controller
|
|
|
|
@type current_user_name: basestring
|
|
@param current_user_name: the user making the request.
|
|
|
|
@rtype: json or Exception
|
|
@return: json object
|
|
"""
|
|
logger.debug('Getting %s in bulk for user=%s', object_type, owner)
|
|
object_instance = self._get_instance(object_type, owner, current_user_name=current_user_name)
|
|
ids = kwargs.get('ids')
|
|
if ids:
|
|
object_ids_list = json.loads(ids)
|
|
else:
|
|
object_ids_list = []
|
|
logger.info('Getting %s with ids=%s', object_type, ids)
|
|
object_ids = object_ids_list if object_ids_list else []
|
|
|
|
self._delete_data(kwargs)
|
|
results = object_instance.get_bulk(object_ids, **kwargs)
|
|
logger.debug('Returned objects=%s for object=%s', len(results), object_type)
|
|
return results
|
|
|
|
def delete_bulk(self, owner, object_type, kwargs, current_user_name=None):
|
|
"""
|
|
Perform delete operations in bulk. If data is not specify then all objects is delete
|
|
for provided type
|
|
|
|
@type owner: basestring
|
|
@param owner: owner who is performing this operation
|
|
|
|
@type object_type: basestring
|
|
@param object_type: Target object type
|
|
|
|
@type kwargs:dict
|
|
@param kwargs: which contain addition parameters of controller
|
|
|
|
@type current_user_name: basestring
|
|
@param current_user_name: the user making the request.
|
|
|
|
@rtype: None or Exception
|
|
@return: None if operation is successful otherwise exception
|
|
"""
|
|
logger.debug('Deleting objects for object=%s user=%s', object_type, owner)
|
|
object_instance = self._get_instance(object_type, owner, current_user_name=current_user_name)
|
|
ids = kwargs.get('ids')
|
|
if ids:
|
|
object_ids = json.loads(ids)
|
|
else:
|
|
object_ids = None
|
|
logger.info('Deleting %s with ids=%s', object_type, object_ids)
|
|
self._delete_data(kwargs)
|
|
object_instance.delete_bulk(object_ids, **kwargs)
|
|
logger.debug('Successfully deleted object(s) of object=%s', object_type)
|
|
|
|
def upsert_bulk(self, owner, object_type, kwargs, current_user_name=None):
|
|
"""
|
|
Perform update/create operations in bulk, note that this method is not safe for upserts of mixed creates and
|
|
updates, it must all be create or all update.
|
|
|
|
@type owner: basestring
|
|
@param owner: owner who is performing this operation
|
|
|
|
@type object_type: basestring
|
|
@param object_type: Target object type
|
|
|
|
@type kwargs:dict
|
|
@param kwargs: which contain addition parameters of controller
|
|
|
|
@type current_user_name: basestring
|
|
@param current_user_name: the user making the request.
|
|
|
|
@rtype: json or Exception
|
|
@return: json object
|
|
"""
|
|
object_instance = self._get_instance(object_type, owner, current_user_name=current_user_name)
|
|
data_list = kwargs.get('data') or kwargs
|
|
self._delete_data(kwargs)
|
|
|
|
if len(data_list) > 0:
|
|
|
|
# create a list of object ids that already exist
|
|
get_kwargs = dict(kwargs)
|
|
ids_list = list([data.get(object_instance.id_key) for data in data_list])
|
|
get_kwargs['ids'] = json.dumps(ids_list)
|
|
try:
|
|
get_result = self.get_bulk(owner, object_type, get_kwargs)
|
|
except Exception:
|
|
get_result = []
|
|
existing_ids_list = list([data.get(object_instance.id_key) for data in get_result])
|
|
|
|
# separate items that need to be created or updated
|
|
update_list = []
|
|
create_list = []
|
|
for data in data_list:
|
|
if data.get(object_instance.id_key) in existing_ids_list:
|
|
update_list.append(data)
|
|
else:
|
|
create_list.append(data)
|
|
|
|
update_results = []
|
|
if update_list:
|
|
logger.debug('Updating %s objects object=%s', len(update_list), object_type)
|
|
object_ids_list = [data.get(object_instance.id_key) for data in update_list]
|
|
object_ids = object_ids_list if object_ids_list else None
|
|
is_partial_update = normalizeBoolean(kwargs.pop('is_partial_update', True))
|
|
# Update one
|
|
update_results = object_instance.update_bulk(object_ids, update_list,
|
|
is_partial_update=is_partial_update, **kwargs)
|
|
|
|
create_results = []
|
|
if create_list:
|
|
# Create one
|
|
logger.debug('Creating bulk %s objects %s', len(create_list), object_type)
|
|
create_results = object_instance.create_bulk(create_list, **kwargs)
|
|
|
|
return update_results + create_results
|
|
|
|
# Helper functions
|
|
|
|
def _delete_data(self, kwargs):
|
|
"""
|
|
Deleting extra data from kwargs otherwise data would be passed twice
|
|
|
|
@rtype: dict
|
|
@return: updated kwargs
|
|
"""
|
|
kwargs.pop('data', None)
|
|
|
|
def _add_user(self, data, owner, field_name='_user'):
|
|
"""
|
|
Add user or owner information in data to know who is performing the operation
|
|
|
|
@type data: dict
|
|
@param data: data (inplace update to the dict)
|
|
|
|
@type field_name: basestring
|
|
@param field_name: field name for user
|
|
|
|
@rtype: dict
|
|
@return: Updated dict
|
|
"""
|
|
if isinstance(data, dict):
|
|
data[field_name] = owner
|
|
else:
|
|
raise ValueError('Data should be valid dict')
|
|
|
|
def _check_and_call_operation(self, owner, object, kwargs, single_operation, bulk_operation,
|
|
current_user_name=None):
|
|
"""
|
|
Support function to call
|
|
|
|
@type owner: basestring
|
|
@param owner: owner who is performing this operation
|
|
|
|
@type object: basestring
|
|
@param object: Target object type
|
|
|
|
@type kwargs:dict
|
|
@param kwargs: which contain addition parameters of controller
|
|
|
|
@type single_operation: function
|
|
@param single_operation: function which perform single operation
|
|
|
|
@type bulk_operation: function
|
|
@param bulk_operation: function which perform bulk operation
|
|
|
|
@return: return of either function or exception
|
|
"""
|
|
data = kwargs.get('data') or kwargs
|
|
logger.debug('data="%s" kwargs="%s"', data, kwargs)
|
|
if isinstance(data, list):
|
|
return bulk_operation(owner, object, kwargs,
|
|
current_user_name=current_user_name)
|
|
elif isinstance(data, dict):
|
|
object_instance = self._get_instance(
|
|
object, owner,
|
|
current_user_name=current_user_name)
|
|
object_id = data.get(object_instance.id_key)
|
|
return single_operation(
|
|
owner, object, object_id, kwargs,
|
|
current_user_name=current_user_name,
|
|
object_instance=object_instance)
|
|
else:
|
|
raise TypeError('Invalid data format, data can be either dict or list. {}'.format(type(data)))
|