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.
230 lines
9.2 KiB
230 lines
9.2 KiB
# Copyright (C) 2005-2025 Splunk Inc. All Rights Reserved.
|
|
|
|
import sys
|
|
import json
|
|
import requests
|
|
requests.packages.urllib3.disable_warnings()
|
|
|
|
from datetime import datetime
|
|
from splunk.clilib.bundle_paths import make_splunkhome_path
|
|
from urllib.parse import quote_plus
|
|
|
|
sys.path.append(make_splunkhome_path(['etc', 'apps', 'SA-ITOA', 'lib']))
|
|
|
|
from ITOA.setup_logging import getLogger
|
|
from ITOA.itoa_common import get_conf_stanza, get_clear_password
|
|
from itsi.event_management.sdk.custom_group_action_base import CustomGroupActionBase
|
|
from ITOA.event_management.notable_event_ticketing import ExternalTicket
|
|
from ITOA.event_management.notable_event_utils import ActionDispatchConfiguration
|
|
import splunk.rest as splunk_rest
|
|
from splunk.util import safeURLQuote
|
|
|
|
|
|
class PagerDutyEvent(CustomGroupActionBase):
|
|
"""
|
|
Class that performs action to trigger a PagerDuty event and link PagerDuty
|
|
incident to the episode.
|
|
"""
|
|
|
|
PAGER_DUTY_EVENTS_V2_API = 'https://events.pagerduty.com/v2/enqueue'
|
|
TICKET_SYSTEM = 'PagerDuty'
|
|
PAGERDUTY_REALM = 'itsi_pagerduty_account'
|
|
CONF_FILE = 'itsi_pagerduty_accounts'
|
|
APP = 'SA-ITOA'
|
|
|
|
def __init__(self, settings):
|
|
"""
|
|
Initialize the object
|
|
@type settings: dict/basestring
|
|
@param settings: incoming settings for this alert action that splunkd
|
|
passes via stdin.
|
|
@returns Nothing
|
|
"""
|
|
self.logger = getLogger(logger_name="itsi.pagerduty.event")
|
|
|
|
super(PagerDutyEvent, self).__init__(settings, self.logger)
|
|
|
|
self.action_dispatch_config = ActionDispatchConfiguration(self.get_session_key(), self.logger)
|
|
config = self.get_config()
|
|
self.session_key = self.get_session_key()
|
|
self.pd_account = config.get('pd_account', None)
|
|
self.pd_dedup_key = config.get('pd_dedup_key', None)
|
|
self.pd_event_action = config.get('pd_event_action', 'trigger')
|
|
self.pd_source = config.get('pd_source', None)
|
|
self.pd_summary = config.get('pd_summary', None)
|
|
self.pd_severity = config.get('pd_severity', None)
|
|
self.pd_link_text = config.get('pd_link_text', None)
|
|
self.pd_link_href = config.get('pd_link_href', None)
|
|
self.pd_class = config.get('pd_class', None)
|
|
self.pd_component = config.get('pd_component', None)
|
|
self.pd_group = config.get('pd_group', None)
|
|
self.pd_timestamp = config.get('pd_timestamp', None)
|
|
self.itsi_policy_id = self.settings.get('result', {}).get('itsi_policy_id', None)
|
|
self.itsi_group_id = self.settings.get('result', {}).get('itsi_group_id', None)
|
|
|
|
clear_password = get_clear_password(self.logger, self.session_key, self.PAGERDUTY_REALM, self.pd_account, self.APP)
|
|
self.pd_api_token = clear_password['token']
|
|
self.pd_routing_key = clear_password['routing_key']
|
|
self.pd_url = 'https://api.pagerduty.com'
|
|
|
|
def validate_required_fields(self):
|
|
"""
|
|
Validates that all required fields are populated
|
|
and not left blank or None
|
|
Returns:
|
|
bool: Returns true if all validation passes else returns false
|
|
"""
|
|
|
|
required_fields = (self.pd_routing_key, self.pd_api_token, self.pd_account, self.pd_source, self.pd_summary, self.pd_severity, self.pd_event_action)
|
|
if '' not in required_fields and None not in required_fields:
|
|
if self.pd_severity not in ("critical", "warning", "error", "info"):
|
|
self.logger.warn(f"PagerDuty Event severity value {self.pd_severity} is invalid. Setting severity to info")
|
|
self.pd_severity = "info"
|
|
|
|
if self.pd_event_action not in ("trigger", "acknowledge", "resolve"):
|
|
self.logger.warn(f"PagerDuty Event Type value {self.pd_event_action} is invalid. Setting severity to trigger")
|
|
self.pd_event_action = "trigger"
|
|
|
|
return True
|
|
self.logger.error("Invalid configuration found. Make sure that required fields containing Routing key, API token, account, source, summary, severity, action type are configured correctly.")
|
|
return False
|
|
|
|
def prepare_payload(self):
|
|
"""
|
|
Prepares the payload that needs to be sent to PagerDuty
|
|
|
|
Raises:
|
|
Exception: Raises exception if validation has failed
|
|
|
|
Returns:
|
|
dict: Payload that needs to be sent to PagerDuty
|
|
"""
|
|
if self.validate_required_fields():
|
|
try:
|
|
self.pd_timestamp = datetime.fromtimestamp(int(float(self.pd_timestamp))).isoformat() if self.pd_timestamp else datetime.now().isoformat()
|
|
except Exception:
|
|
self.logger.warn(f"Timestamp {self.pd_timestamp} is not in ISO Format")
|
|
|
|
groups = list(self.get_group())
|
|
links = [{"href": self.pd_link_href, "text": self.pd_link_text}]
|
|
body = {
|
|
"event_action": self.pd_event_action,
|
|
"routing_key": self.pd_routing_key,
|
|
"dedup_key": self.pd_dedup_key,
|
|
"payload": {
|
|
"source": self.pd_source,
|
|
"severity": self.pd_severity,
|
|
"summary": self.pd_summary,
|
|
"class": self.pd_class,
|
|
"component": self.pd_component,
|
|
"group": self.pd_group,
|
|
"custom_details": groups,
|
|
"timestamp": self.pd_timestamp
|
|
}
|
|
}
|
|
|
|
if links[0]["href"] and links[0]["text"]:
|
|
body['links'] = links
|
|
|
|
return body
|
|
self.logger.error("Validation for required fields failed")
|
|
raise Exception("Invalid configuration")
|
|
|
|
def send_payload_to_pagerduty(self, payload):
|
|
"""
|
|
Sends event data to PagerDuty
|
|
|
|
Args:
|
|
payload (dict): Payload with event data
|
|
that needs to be sent to PagerDuty
|
|
|
|
Raises:
|
|
Exception: Raises exception if event is not received
|
|
successfully by PagerDuty
|
|
"""
|
|
response = requests.post(
|
|
self.PAGER_DUTY_EVENTS_V2_API,
|
|
data=json.dumps(payload),
|
|
headers={"Content-Type": "application/json"}
|
|
)
|
|
if response.status_code != 202:
|
|
message = f"Error in sending event to PagerDuty: {response.status_code}, {response.text}"
|
|
self.logger.error(message)
|
|
raise Exception(message)
|
|
|
|
self.logger.info("Event sent successfully to PagerDuty")
|
|
|
|
def fetch_incident_from_pagerduty(self):
|
|
|
|
"""
|
|
Fetches incident from PagerDuty based on group id
|
|
|
|
Returns:
|
|
tuple: Returns incident link and incident number
|
|
"""
|
|
try:
|
|
headers = {"Authorization": f"Token token={self.pd_api_token}"}
|
|
|
|
response = requests.get(
|
|
f"{self.pd_url}/incidents",
|
|
params={"incident_key": self.pd_dedup_key},
|
|
headers=headers
|
|
)
|
|
|
|
# Check if any error has occurred. Will raise an HTTP Error for response code outside of 200-229
|
|
response.raise_for_status()
|
|
|
|
if response.status_code == 200:
|
|
incidents = response.json()
|
|
incident_details = incidents['incidents'][0]
|
|
return incident_details['html_url'], str(incident_details['incident_number'])
|
|
|
|
except Exception:
|
|
message = f"Error fetching incidents from PagerDuty: {response.status_code}"
|
|
self.logger.error(message)
|
|
|
|
def upsert_ticket(self, itsi_group_id, incident_link, incident_number):
|
|
"""
|
|
Links episode with PagerDuty incident
|
|
|
|
Args:
|
|
itsi_group_id (str): Episode id to be linked with incident
|
|
incident_link (str): Link to the PagerDuty Incident
|
|
incident_number (str): PagerDuty Incident Number
|
|
"""
|
|
session_key = self.get_session_key()
|
|
|
|
external_ticket = ExternalTicket(
|
|
itsi_group_id, session_key, self.logger,
|
|
action_dispatch_config=self.action_dispatch_config,
|
|
current_user_name=self.settings.get('owner', None)
|
|
)
|
|
external_ticket.upsert(
|
|
self.TICKET_SYSTEM,
|
|
incident_number,
|
|
incident_link,
|
|
itsi_policy_id=self.itsi_policy_id
|
|
)
|
|
self.logger.info(f"Succesfully linked episode {itsi_group_id} with incident #{incident_number}")
|
|
|
|
def execute(self):
|
|
"""
|
|
Executes alert action
|
|
"""
|
|
payload = self.prepare_payload()
|
|
self.logger.info("Sending event to PagerDuty")
|
|
self.send_payload_to_pagerduty(payload)
|
|
|
|
if self.pd_event_action == 'trigger':
|
|
self.logger.info(f"Fetching incident from PagerDuty for incident_key: {self.pd_dedup_key}")
|
|
incident_link, incident_number = self.fetch_incident_from_pagerduty()
|
|
self.logger.info(f"Linking incident #{incident_number}: {incident_link} to episode {self.itsi_group_id}")
|
|
self.upsert_ticket(self.itsi_group_id, incident_link, incident_number)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
if len(sys.argv) > 1 and sys.argv[1] == '--execute':
|
|
input_params = sys.stdin.read()
|
|
pager_duty = PagerDutyEvent(input_params)
|
|
pager_duty.execute()
|