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.

912 lines
38 KiB

# Copyright (C) 2005-2025 Splunk Inc. All Rights Reserved.
"""
Use this module to update information on an ITSI Notable vis-a-vis its external
tickets.
"""
import itsi_py3
import re
import sys
import json
import time
from uuid import uuid1
import splunk.rest as splunk_rest
from splunk.util import safeURLQuote, normalizeBoolean
from splunk.clilib.bundle_paths import make_splunkhome_path
from splunk import ResourceNotFound, RESTException
from ITOA.storage import itoa_storage
from .notable_event_utils import Audit
from ITOA.itoa_common import JsonPathElement
from ITOA import setup_logging
class ExternalTicket(object):
"""
An instance of this class corresponds to an external ticket
and is persisted in KV Store in the collection
'itsi_notable_event_ticketing'
An example of records is given further below. In the example below we see
two records. Each record represents tickets in a specific external ticket
system.
ticket_system is also included in tickets because lookup cannot combine a multivalue
fields with a single value
~~~~~~~~~~~~~~~~
Example:
~~~~~~~~~~~~~~~~
{
event_id: 123,
ticket_system: snow,
tickets: [{
ticket_id: <id>
ticket_url: <some url>
ticket_system: <system>
<other params for this ticket>
},{
ticket_id: <id2>
ticket_url: <some url>
ticket_system: <system>
<other params for this ticket>
}]
},
{
event_id: 124,
ticket_system: remedy,
tickets: [{
ticket_id: <id>
ticket_url: <some url>
ticket_system: <system>
<other params for this ticket>
},{
ticket_id: <id2>
ticket_url: <some url>
ticket_system: <system>
<other params for this ticket>
}]
}
"""
default_kv_collection = 'itsi_notable_event_ticketing'
default_kv_ns = 'nobody'
default_kv_app = 'SA-ITOA'
default_token_name = 'Auto Generated ITSI Notable Index Audit Token'
# keys in a record in our kv store collection
KEY_TICKET_SYSTEM = 'ticket_system' # jira, snow, siebel etc...
KEY_TICKETS = 'tickets' # all external tickets for this ticket_system
KEY_TICKETS_TICKET_ID = 'ticket_id' # external ticket id
KEY_TICKETS_TICKET_URL = 'ticket_url' # external ticket url
KEY_OBJECT_TYPE = 'object_type' # object type
KEY_POLICY_ID = 'itsi_policy_id' # policy ID
VAL_OBJECT_TYPE = 'external_ticket' # value corresponding to the key above
KEY_EVENT_ID = 'event_id' # notable event id
KEY_UID = '_key' # unique id for this object in kv store
KEY_TIME = 'mod_time' # time at which event was modified
KEY_CREATE_TIME = 'create_time' # time at which event was created
@staticmethod
def get_auditor(session_key, token):
"""
Returns an audit object
@param session_key: splunk session key
@param audit_token_name: token name for this auditor
"""
return Audit(session_key, audit_token_name=token)
def __init__(self, event_id, session_key, logger,
collection=default_kv_collection, ns=default_kv_ns,
app=default_kv_app, audit_token_name=default_token_name,
action_dispatch_config=None, current_user_name=None, **kwargs):
"""
For a given notable id, this object corresponds to an external ticket
@type event_id: basestring
@param event_id: ITSI notable event id
@type session_key: basestring
@param session_key: splunkd session key
@type logger: logger
@param logger: caller's logger
@type collection: basestring
@param collection: kv store collection
@type action_dispatch_config: ActionDispatchConfiguration
@param action_dispatch_config: the configuration of hybrid action dispatch
@type kwargs: dict
@param kwargs: other
"""
if not isinstance(event_id, itsi_py3.string_type):
raise TypeError('Invalid type "event_id".')
if not event_id.strip():
raise ValueError('Invalid value "event_id".')
if not isinstance(session_key, itsi_py3.string_type):
raise TypeError('Invalid type "session_key".')
if not session_key.strip():
raise ValueError('Invalid value "session_key".')
if not isinstance(collection, itsi_py3.string_type):
raise TypeError('Invalid type "collection".')
if not collection.strip():
raise ValueError('Invalid value "collection".')
if not isinstance(ns, itsi_py3.string_type):
raise TypeError('Invalid type "ns".')
if not ns.strip():
raise ValueError('Invalid value "ns".')
if not isinstance(app, itsi_py3.string_type):
raise TypeError('Invalid type "app".')
if not app.strip():
raise ValueError('Invalid value "app".')
self.event_id = event_id
self.session_key = session_key
self.ns = ns
self.app = app
self.collection = collection
self.object_type = ExternalTicket.VAL_OBJECT_TYPE
self.storage_interface = itoa_storage.ITOAStorage(collection=collection)
self.logger = logger
self.auditor = ExternalTicket.get_auditor(session_key, audit_token_name)
self.master_session_key = self.session_key
self.action_dispatch_config = action_dispatch_config
self.host_base_uri = ''
if self.action_dispatch_config:
self.host_base_uri = self.action_dispatch_config.remote_ea_mgmt_uri
self.master_session_key = self.action_dispatch_config.get_master_host_session_key()
self.current_user_name = current_user_name
@staticmethod
def curate_search_prepend_command(prepend_command, ids, is_group, action_name, action_config, logger):
"""
Given an input prepend command, curate it for ticketing.
For actions on a notable event associated with an external ticket,
we append the event id as a correlation id.
When working on a notable event group, we ought to pass in the group_id as the
correlation id.
All actions, read event data from the `itsi_tracked_alerts` index
regardless of whether it belongs to a group or not.
Events in this index though, have no field to indicate a group id if
applicable.
This method will append an eval field to the existing prepend_command and
make it more pertinent for a notable event group, so we assign group_id
as correlation_id.
Currently this method only does something if we are working on a group
i.e. `is_group` is True.
@type prepend_command: basestring
@param prepend_command: existing prepend command.
@type ids: list of event ids
@param ids: event ids that were passed to us as input.
@type is_group: boolean
@param is_group: Indicates if we are working on a group.
@type action_name: basestring
@param action_name: name of the action that is being executed.
@type action_config: dict
@param action_config: configuration for this action. everything under
the action's stanza in notable_event_actions.conf
@type logger: logger
@param logger: caller's logger
"""
if not logger:
logger = setup_logging.logger
if not isinstance(prepend_command, itsi_py3.string_type):
message = 'Invalid type="%s" for "%s". Expecting string.' % (type(prepend_command).__name__, prepend_command)
logger.exception(message)
raise TypeError(message)
if not isinstance(ids, list) or not ids:
message = 'Invalid ids list. Received="%s". Type="%s".' % (ids, type(ids).__name__)
logger.exception(message)
raise TypeError(message)
if not is_group:
logger.info('No special curation of prepend_command="%s" is required for regular notable events', prepend_command)
return prepend_command
# when operating on more than one group,
# we will use first id. Refresh should take care
# updating others...
group_captain_id = ids[0]
# obtain value to append to search string as an eval. we want to append this
# because sendalert should get a group_id to use as a correlation id
correlation_value = action_config.get('correlation_value_for_group', '') # `$result.itsi_group_id$`
pattern = re.compile(
r'^' # begin
r'(?:\$result.)' # ignore group. our string begins with "$result."
r'([\w-]*)' # capture group
r'(?:\$)' # ignore group. our string ends with `$`
r'$') # end
match = re.match(pattern, correlation_value)
if not match:
logger.error('Empty/missing correlation_value_for_group in your notable_event_actions.conf.')
raise KeyError('Empty/missing correlation_value_for_group in your notable_event_actions.conf.')
eval_field = match.group(1) # we are guaranteed this group.
prepend_command += ' | eval %s="%s"' % (eval_field, group_captain_id)
return prepend_command
@staticmethod
def curate_params(params, action_name, action_config, logger, **kwargs):
"""
Given params to a sendalert command, curate them. We will ensure that a
correlation id parameter is always passed on with the command to splunk
search.
@type params: basestring
@param params: already constructed params
@type action_name: basestring
@param action_name: the action that is being executed, i.e.
snow_incident etc...
@type action_config: dict
@param action_config: the configuration of the action name, essentially
everything under the action's stanza in notable_event_actions.conf
@type logger: logger
@param logger: caller's logger
@rtype params: basestring
@return newly curated params consisting of the mandatory correlation id
/ value kv pair if it doesnt already exist.
"""
if not logger:
logger = setup_logging.logger
if not isinstance(params, itsi_py3.string_type):
message = 'Expecting string type for params. Received="%s".' % type(params).__name__
logger.exception(message)
raise TypeError(message)
is_group = kwargs.get('is_group', False)
logger.debug('pre-curating params=`%s`\nis_group=`%s` action_name=`%s` action_config=`%s`', params,
is_group, action_name, json.dumps(action_config))
msg = 'Correlation id is mandated for operations pertaining to external ticket.'
correlation_key = action_config.get('correlation_key') # `correlation_id` etc..
if is_group:
correlation_value = action_config.get('correlation_value_for_group') # `$result.itsi_group_id$`
else:
correlation_value = action_config.get('correlation_value') # `$result.event_id$` etc...
if correlation_key is None or correlation_value is None:
logger.error('%s. Missing `correlation_value`/`correlation_value_for_group`/`correlation_key`. Config="%s"',
msg, action_config)
raise KeyError(msg)
# we want to capture two potential groups and check if group(2) is
# alright, i.e. not empty.
# group 1 = param.correlation_id
# group 2 = "$result.event_id$" (or $result.itsi_group_id$ for a group)
# the rest of them need not be captured
# if group 2 is empty i.e. "" then we will append event_id as correlation id,
# else we will respect what is passed in to us.
pattern = re.compile(
'^' # begin
'(?:.*)' # ignore group
'(param.' + correlation_key + '=)(\"[a-zA-Z._$-]*\")' # capture two groups
'(?:.*)$') # ignore the rest
match = re.match(pattern, params)
if match:
logger.debug('params match found. groups=`%s`', match.groups())
to_add = ' param.%s="%s"' % (correlation_key, correlation_value)
logger.debug('params to_add=%s', to_add)
# add correlation id k-v if there is no match
if not match:
logger.info('%s. None found or incorrect correlation_id kv. Will append `%s`. Config="%s"',
msg, to_add, action_config)
params += to_add
else:
# Replace incorrect correlation id kv with correct kv. Blindly appending
# with param.correlation_id=blah (say) causes unpredictable results,
# including refresh failure. TA_snow seems to randomly pick from
# `param.correlation_id=blah` vs `param.correlation_id=""` when creating
# an external ticket.
if len(match.groups()) == 2:
if match.group(2) == '""':
logger.info('%s. Empty correlation_id kv found. Will replace'
' with `%s` as correlation_id. Config=`%s`', msg, to_add, action_config)
empty_correlation_kv = 'param.%s=""' % correlation_key
params = params.replace(empty_correlation_kv, to_add)
else:
logger.info('User-given correlation_id kv found. Will replace'
' with `%s` as correlation_id. Config=`%s`', to_add, action_config)
user_given_correlation_kv = '%s%s' % (match.group(1), match.group(2))
logger.debug("user_given_correlation_kv=%s", user_given_correlation_kv)
params = params.replace(user_given_correlation_kv, to_add)
# NOTE: When operating on more than one Group, we cannot restrict the user
# from overwriting correlation_value to something else. We advise an
# end-user to not overwrite correlation_id value in the HTML modal.
logger.debug('post-curating params=`%s`', params)
return params
@staticmethod
def get_kvstore_filter(filter_string, logger):
"""
Given a filter_string, validate against allowed kvstore regex characters
and return a filter for kvstore
@type filter_string: string
@param filter_string: string sent by user
@type logger: logger
@param logger: caller's logger
@rtype kvstore_filter: dict
@return kvstore filter
"""
pattern = re.compile(r'^([\w-]+)$')
match = re.match(pattern, filter_string)
kvstore_filter = None
if not match or len(match.groups()) != 1:
logger.error('User-given filter_string has special characters other than `-` and `_`.'
'It is not clean for kvstore, filter_string=`%s`', filter_string)
else:
logger.info('User-given filter_string is correct. groups=`%s`', match.groups())
kvstore_filter = { 'tickets.ticket_id': { '$regex': '^(.*' + filter_string + '.*)$', '$options': 'i' } }
return kvstore_filter
@staticmethod
def do_refresh(session_key, logger, action_name, event_action_executed_on_ids, action_config, action_data=None,
itsi_policy_id=None, action_dispatch_config=None, current_user_name=None):
"""
Assumes that an event action has already been executed on a list of
events.
Refreshes notable events with external ticket information.
@type session_key: basestring
@param session_key: the session key for the operation
@type logger: logger
@param logger: caller's logger
@type action_name: basestring
@param action_name: the action name
@type event_action_executed_on_ids: list
@param event_action_executed_on_ids: ids on which action has been executed
@type action_data: dict
@param action_data: action data in the request
@type action_config: dict
@param action_config: configuration for our action, key'ed by action name
@type itsi_policy_id: basestring
@param itsi_policy_id: the policy ID that generated this action
@type action_dispatch_config: ActionDispatchConfiguration
@param action_dispatch_config: configuration for hybrid action dispatch
@type current_user_name: basestring
@param current_user_name: the name of the user who generated this action
@returns nothing.
@raises TypeError on invalid input parameters
"""
if action_name == "itsi_pagerduty_event":
logger.info("Skipping do_refresh for PagerDuty")
return
if not logger:
logger = setup_logging.logger
if any([not isinstance(action_name, itsi_py3.string_type),
isinstance(action_name, itsi_py3.string_type) and not action_name.strip()]):
logger.error('Invalid action_name')
raise TypeError('Invalid action_name')
if not action_config:
logger.error('No refresh config found for %s', action_name)
raise KeyError('No refresh config found for %s.' % action_name)
uri = action_config.get('relative_refresh_uri')
if not isinstance(uri, itsi_py3.string_type):
logger.error('Expecting str for uri. received="%s"', type(uri).__name__)
raise TypeError('Expecting str for uri. received="%s".' % type(uri).__name__)
# extract correlation id. w/o this our refresh is a no-go.
query_param = action_config.get('relative_refresh_correlation_key') or action_config.get('correlation_key')
getargs = {'output_mode': 'json'}
event_id = event_action_executed_on_ids[0]
if not query_param:
# means our correlation id will not go as getargs but as part of the URL
uri += '/' + event_id
else:
getargs[query_param] = event_id
if action_name == 'remedy_incident_rest':
account_name = action_data.get('action.remedy_incident_rest.param.account', None)
if account_name:
logger.info('account for remedy_incident_rest command=%s', account_name)
getargs['account'] = account_name
else:
logger.error('Unable to get account. Account is required when fetching link for '
'remedy_incident_rest action')
res, content = splunk_rest.simpleRequest(
safeURLQuote(uri),
sessionKey=session_key,
getargs=getargs)
if res.status != 200:
logger.error(
'Failed to refresh notable event=%s\n'
'uri=`%s`, getargs=`%s` response=`%s` content=`%s`',
event_id, uri, getargs, res, content
)
raise Exception('Failed to refresh notable event=%s.' % event_id)
content = json.loads(content)
logger.info('uri=`%s` getargs=`%s` \n content=`%s`', uri, getargs, json.dumps(content))
# Extract refresh data from response. For this we will first walk the
# response content which is json.
json_path = action_config.get('refresh_response_json_path')
json_path = json_path.split('.')
pertinent = content # content containing the refresh values that we care about.
for e in json_path:
path_elem = JsonPathElement(e)
if not path_elem.is_array():
pertinent = pertinent.get(str(path_elem))
else:
idx = path_elem.get_array_index()
pertinent = pertinent[idx]
logger.debug('refresh response config=`%s`', json.dumps(pertinent))
# we have `pertinent` which should be the blob we care about...
ticket_id_key = action_config.get('refresh_response_ticket_id_key')
ticket_url_key = action_config.get('refresh_response_ticket_url_key')
ticket_system = action_config.get('ticket_system_name')
ticket_id = pertinent.get(ticket_id_key)
ticket_url = pertinent.get(ticket_url_key)
# check whether the smartIT URL is configured
if len(ticket_url) > 1:
ticket_url = ticket_url[1]
# we will always work with the assumption that there can be more than
# one ticket ID/URL pair for a given Notable Event. BMC's Remedy
# supports such a config. So, bottom line is, always work with a list.
if isinstance(ticket_id, itsi_py3.string_type):
ticket_id = [ticket_id]
if isinstance(ticket_url, itsi_py3.string_type):
ticket_url = [ticket_url]
if not isinstance(ticket_id, list) or not isinstance(ticket_url, list):
raise TypeError(
'Unable to fetch the ticket ID and URL to link the ticket. '
'Check the configuration of the action, the corresponding '
'add-on, and the status of your ticketing system.')
logger.debug('Refreshing ids=%s\nticket id=%s ticket url=%s.',
event_action_executed_on_ids, ticket_id, ticket_url)
for id_, url in zip(ticket_id, ticket_url):
ExternalTicket.bulk_upsert(
event_action_executed_on_ids,
ticket_system,
id_,
url,
session_key,
logger,
itsi_policy_id=itsi_policy_id,
action_dispatch_config=action_dispatch_config,
current_user_name=current_user_name
)
def get(self, ticket_system=None):
"""
Fetch ticket details for given `ticket_system`.
Set `ticket_system` to None to get all tickets for this event.
@type ticket_system: basestring
@param ticket_system: concerned ticket system.
Could be 'remedy', 'servicenow' or 'siebel' or 'jira' or 'bugzilla'
@rtype: basestring
@return: requested ticket info
"""
query_op = "$and"
query_val = []
query_val.append({ExternalTicket.KEY_EVENT_ID: self.event_id})
if ticket_system:
self.logger.debug('Requested tickets for ticket_system=%s', ticket_system)
query_val.append({ExternalTicket.KEY_TICKET_SYSTEM: ticket_system})
query = {query_op: query_val}
self.logger.debug('query=%s', json.dumps(query))
result = self.storage_interface.get_all(
self.master_session_key,
self.ns,
self.object_type,
filter_data=query,
current_user_name=self.current_user_name,
host_base_uri=self.host_base_uri
)
self.logger.debug('Storage result get: %s', result)
return result
@staticmethod
def bulk_upsert(event_ids, ticket_system, ticket_id, ticket_url, session_key, logger, itsi_policy_id=None,
action_dispatch_config=None, current_user_name=None, **kwargs):
"""
Do bulk upsert.
@param ticket_system: external ticket system's identifier Ex: remedy
@param ticket_id: external ticket's identifier
@param ticket_url: external ticket's URL
@param itsi_policy_id: policy ID associated with this ticket.
@param action_dispatch_config: the hybrid action dispatch configuration
@param kwargs: extra key-value args to add as part of our update.
@rtype: list
@returns: result in the order of execution.
"""
if not isinstance(event_ids, list):
raise TypeError('Invalid type event_ids. received="%s".' % type(event_ids).__name__)
result = []
activities = []
activity = 'Successfully linked with external ticket system="%s", ticket ID="%s", ticket URL="%s".' % (
ticket_system, ticket_id, ticket_url)
bulk_data = []
for e in event_ids:
obj = ExternalTicket(
e,
session_key,
logger,
action_dispatch_config=action_dispatch_config,
current_user_name=current_user_name
)
r = obj.upsert(
ticket_system,
ticket_id,
ticket_url,
itsi_policy_id=itsi_policy_id,
do_audit=False,
**kwargs
)
result.extend(r)
activities.append(activity)
bulk_data.append({ExternalTicket.KEY_EVENT_ID: e,
ExternalTicket.KEY_TICKET_SYSTEM: ticket_system,
ExternalTicket.KEY_TICKETS_TICKET_ID: ticket_id,
ExternalTicket.KEY_TICKETS_TICKET_URL: ticket_url,
ExternalTicket.KEY_POLICY_ID: itsi_policy_id
})
auditor = ExternalTicket.get_auditor(
session_key,
kwargs.get('audit_token_name', ExternalTicket.default_token_name)
)
auditor.send_activity_to_audit_bulk(bulk_data, activities, 'Linked external ticket')
return result
def upsert(self, ticket_system, ticket_id, ticket_url, itsi_policy_id=None, do_audit=True, **kwargs):
"""
Update event_id with external ticket information.
@param ticket_system: external ticket system's identifier Ex: remedy
@param ticket_id: external ticket's identifier
@param ticket_url: external ticket's URL
@param itsi_policy_id: policy ID associated with this ticket.
@param do_audit: activity will be passed to audit or not. defaults to True.
@param kwargs: extra key-value args to add as part of our update.
"""
# Validations
if not isinstance(ticket_system, itsi_py3.string_type):
raise TypeError('Expecting ticket_system to be str type.')
if not ticket_system.strip():
raise ValueError('Expecting ticket_system to be non-zero length str.')
if not isinstance(ticket_id, itsi_py3.string_type):
raise TypeError('Expecting ticket_id to be str type.'
' Received={}.'.format(type(ticket_id).__name__))
if not ticket_id.strip():
raise ValueError('Expecting ticket_id to be non-zero length str.'
' Received={}.'.format(ticket_id))
# first fetch existing ticket entry for given ticket_system
existing = self.get(ticket_system)
record = json.loads(existing) if isinstance(existing, itsi_py3.string_type) else existing
if not record:
# no existing ticket(s) for ticket_system
record = {
ExternalTicket.KEY_EVENT_ID: self.event_id,
ExternalTicket.KEY_UID: str(uuid1()),
ExternalTicket.KEY_TICKET_SYSTEM: ticket_system,
ExternalTicket.KEY_TICKETS: [{
ExternalTicket.KEY_TICKET_SYSTEM: ticket_system,
ExternalTicket.KEY_TICKETS_TICKET_ID: ticket_id,
ExternalTicket.KEY_TICKETS_TICKET_URL: ticket_url
}],
ExternalTicket.KEY_POLICY_ID: itsi_policy_id,
ExternalTicket.KEY_OBJECT_TYPE: ExternalTicket.VAL_OBJECT_TYPE,
ExternalTicket.KEY_TIME: time.time(),
ExternalTicket.KEY_CREATE_TIME: time.time()
}
record[ExternalTicket.KEY_TICKETS][0].update(kwargs)
else:
if len(record) > 1:
self.logger.warning('Expecting only 1 record for an "event_id" + '
'`ticket_system` combination. Received more. Will only work '
'with the first event')
record = record[0]
record[ExternalTicket.KEY_TIME] = time.time()
record[ExternalTicket.KEY_POLICY_ID] = itsi_policy_id
# event_id has tickets for given 'ticket_system'
tickets = record.get(ExternalTicket.KEY_TICKETS, [])
ticket_exists = False
for ticket in tickets:
if ticket['ticket_id'] == ticket_id.strip():
ticket_exists = True
ticket[ExternalTicket.KEY_TICKETS_TICKET_URL] = ticket_url
ticket.update(kwargs)
if not ticket_exists:
# no such ticket exists
ticket_val = {
ExternalTicket.KEY_TICKET_SYSTEM: ticket_system,
ExternalTicket.KEY_TICKETS_TICKET_ID: ticket_id,
ExternalTicket.KEY_TICKETS_TICKET_URL: ticket_url
}
ticket_val.update(kwargs)
tickets.append(ticket_val)
results = self.storage_interface.batch_save(
self.master_session_key,
self.ns,
[record],
objecttype=self.object_type,
current_user_name=self.current_user_name,
host_base_uri=self.host_base_uri
)
if do_audit:
activity = 'Successfully linked with external ticket system="%s", ticket ID="%s", ticket URL="%s".' % (ticket_system, ticket_id, ticket_url)
data = {
ExternalTicket.KEY_EVENT_ID: self.event_id,
ExternalTicket.KEY_TICKET_SYSTEM: ticket_system,
ExternalTicket.KEY_TICKETS_TICKET_ID: ticket_id,
ExternalTicket.KEY_TICKETS_TICKET_URL: ticket_url,
ExternalTicket.KEY_POLICY_ID: itsi_policy_id
}
self.auditor.send_activity_to_audit(data, activity, 'Linked external ticket')
return results
@staticmethod
def bulk_delete(event_ids, ticket_system, ticket_id, session_key, logger, action_dispatch_config=None, current_user_name=None):
"""
Do bulk delete.
@param ticket_system: external ticket system's identifier Ex: remedy
@param ticket_id: external ticket's identifier
@param action_dispatch_config: the hybrid action dispatch configuration
@param kwargs: extra key-value args to add as part of our update.
@rtype: list
@returns: result in the order of execution.
"""
if not isinstance(event_ids, list):
raise TypeError('Invalid type event_ids. received="%s".' % type(event_ids).__name__)
for e in event_ids:
try:
obj = ExternalTicket(
e, session_key, logger,
action_dispatch_config=action_dispatch_config,
current_user_name=current_user_name)
obj.delete(ticket_system, ticket_id)
except ValueError as e:
logger.error('Bulk delete Exception: %s', e)
pass # don't break the loop if some event didn't match
def delete(self, ticket_system=None, ticket_id=None):
"""
Delete ticket corresponding to ticket system and ticket id
/snow/123 delete ticket with id 123 of ticket system snow
/snow delete all tickets of snow
/ delete all tickets for this event
@param ticket_system: external ticket system's identifier Ex: remedy
@param ticket_id: external ticket's identifier
"""
query_op = "$and"
query_val = []
query_val.append({ExternalTicket.KEY_EVENT_ID: self.event_id})
# if no ticket_system is passed delete all the tickets
if not ticket_system:
query = {query_op: query_val}
result = self.storage_interface.delete_all(
self.master_session_key,
self.ns,
self.object_type,
query,
current_user_name=self.current_user_name,
host_base_uri=self.host_base_uri
)
self.logger.debug('Successfully deleted all tickets for event_id=%s', self.event_id)
return
# retrieve list for a given ticket system
query_val.append({ExternalTicket.KEY_TICKET_SYSTEM: ticket_system})
query = {query_op: query_val}
# if ticket id is not passed delete all the tickets for the ticket system
if not ticket_id:
result = self.storage_interface.delete_all(
self.master_session_key,
self.ns,
self.object_type,
query,
current_user_name=self.current_user_name,
host_base_uri=self.host_base_uri
)
self.logger.debug('Successfully deleted all tickets for ticket_system=%s', ticket_system)
return
# if ticket_id is passed retrieve the list of tickets for a ticket system
# iterate through list for a ticket id and delete
result = self.storage_interface.get_all(
self.master_session_key,
self.ns,
self.object_type,
filter_data=query,
current_user_name=self.current_user_name,
host_base_uri=self.host_base_uri
)
if isinstance(result, itsi_py3.string_type):
result = json.loads(result)
if len(result) == 0:
self.logger.error('Could not find ticket system. ticket_system=%s', ticket_system)
raise ValueError('Failed to delete ticket. Ticketing system does not exist or has been deleted already.')
record = result[0]
tickets = record.get(ExternalTicket.KEY_TICKETS, [])
number_of_tickets = len(tickets)
if number_of_tickets == 0:
self.logger.error('Could not find ticket. ticket_id=%s ', ticket_id)
raise ValueError('Failed to delete ticket. Ticket does not exist or has been deleted already.')
# searching for the ticket in the tickets list by passed in ticket id
ticket_exists = False
for ticket in tickets:
if ticket['ticket_id'] == ticket_id.strip():
# found ticket. Need to remove ticket from tickets list
ticket_exists = True
tickets.remove(ticket)
self.logger.debug('Deleting. ticket_id=%s details=%s ',
ticket_id, json.dumps(ticket))
if not ticket_exists:
self.logger.error('Could not find ticket. ticket_id=%s ', ticket_id)
raise ValueError('Failed to delete ticket. Unable to find ticket with ID {}.'.format(ticket_id))
# if there is only one ticket for a ticket system delete the entire ticket system
if number_of_tickets == 1:
self.logger.debug(('Successfully deleted single ticket with ticket_id=%s') % ticket_id)
result = self.storage_interface.delete(
self.master_session_key,
self.ns,
self.object_type,
record.get(ExternalTicket.KEY_UID),
current_user_name=self.current_user_name,
host_base_uri=self.host_base_uri
)
return
# if there are more than one tickets
# update the record with updated ticket array
record[ExternalTicket.KEY_TICKETS] = tickets
self.storage_interface.batch_save(
self.master_session_key,
self.ns,
result,
objecttype=self.object_type,
current_user_name=self.current_user_name,
host_base_uri=self.host_base_uri
)
self.logger.debug('Successfully deleted ticket_id=%s record=%s' , ticket_id, record)
def bulk_get_info(self, **kwargs):
"""
Get relevant ticket info
@param kwargs: extra key-value args to add as part of our fetch.
@rtype: list
@returns: results unique by ticket_id
"""
def _format_results(results):
tickets = []
for result in results:
for ticket in result[ExternalTicket.KEY_TICKETS]:
tickets.append({
'text': ticket[ExternalTicket.KEY_TICKETS_TICKET_ID],
'id': ticket[ExternalTicket.KEY_TICKETS_TICKET_ID],
'ticket_system': ticket[ExternalTicket.KEY_TICKET_SYSTEM],
'group_id': result[ExternalTicket.KEY_EVENT_ID]
})
return tickets
query_op = '$and'
query_val = []
offset = kwargs.get('offset', 0)
sort_key = kwargs.get('sort_key', 'mod_time')
sort_dir = kwargs.get('sort_dir', 'desc')
filter_string = kwargs.get('filter_string', None)
is_get_summary = kwargs.get('is_get_summary', False)
count = kwargs.get('count', 20)
if filter_string and len(filter_string) != 0:
query = ExternalTicket.get_kvstore_filter(filter_string, self.logger)
else:
query_val.append({ExternalTicket.KEY_OBJECT_TYPE: ExternalTicket.VAL_OBJECT_TYPE})
query = {query_op: query_val}
if not query:
raise ValueError('Invalid query={}, Received filter_string={}.'
'Special characters other than `-` and `_` are not allowed.'.format(query, filter_string))
try:
if is_get_summary:
results = self.storage_interface.get_count(
session_key=self.master_session_key,
owner=self.ns,
object_type=self.object_type,
filter_data=query,
host_base_uri=self.host_base_uri,
)
else:
results = self.storage_interface.get_all(
session_key=self.master_session_key,
owner=self.ns,
objecttype=self.object_type,
current_user_name=self.current_user_name,
host_base_uri=self.host_base_uri,
sort_key=sort_key,
sort_dir=sort_dir,
skip=offset,
limit=count,
filter_data=query
)
results = _format_results(results)
return results
except Exception as e:
self.logger.error('Bulk get info Exception: %s', e)