Skip to content

Commit 2c927a4

Browse files
feat: alerts REST client (#416)
**Issue number:** ADDON-77073 ### PR Type **What kind of change does this PR introduce?** * [x] Feature * [ ] Bug Fix * [ ] Refactoring (no functional or API changes) * [ ] Documentation Update * [ ] Maintenance (dependency updates, CI, etc.) ## Summary ### Changes REST client used to create, read, update and delete alerts. ### User experience No changes to the existing code. New class added. ## Checklist If an item doesn't apply to your changes, leave it unchecked. * [x] I have performed a self-review of this change according to the [development guidelines](https://splunk.github.io/addonfactory-ucc-generator/contributing/#development-guidelines) * [x] Tests have been added/modified to cover the changes [(testing doc)](https://splunk.github.io/addonfactory-ucc-generator/contributing/#build-and-test) * [x] PR title and description follows the [contributing principles](https://splunk.github.io/addonfactory-ucc-generator/contributing/#pull-requests) --------- Co-authored-by: sgoral <sgoral@splunk.com> Co-authored-by: sgoral-splunk <138458044+sgoral-splunk@users.noreply.github.com>
1 parent a0d0e03 commit 2c927a4

File tree

4 files changed

+477
-6
lines changed

4 files changed

+477
-6
lines changed

poetry.lock

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

solnlib/alerts_rest_client.py

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
#
2+
# Copyright 2024 Splunk Inc.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#
16+
import json
17+
from enum import Enum
18+
from typing import Tuple, Union, Optional
19+
20+
from solnlib import splunk_rest_client as rest_client
21+
22+
23+
class AlertType(Enum):
24+
CUSTOM = "custom"
25+
NUMBER_OF_EVENTS = "number of events"
26+
NUMBER_OF_HOSTS = "number of hosts"
27+
NUMBER_OF_SOURCES = "number of sources"
28+
29+
30+
class AlertSeverity(Enum):
31+
DEBUG = 1
32+
INFO = 2
33+
WARN = 3
34+
ERROR = 4
35+
SEVERE = 5
36+
FATAL = 6
37+
38+
39+
class AlertComparator(Enum):
40+
GREATER_THAN = "greater than"
41+
LESS_THAN = "less than"
42+
EQUAL_TO = "equal to"
43+
RISES_BY = "rises by"
44+
DROPS_BY = "drops by"
45+
RISES_BY_PERC = "rises by perc"
46+
DROPS_BY_PERC = "drops by perc"
47+
48+
49+
class AlertsRestClient:
50+
"""REST client for handling alerts."""
51+
52+
ENDPOINT = "/servicesNS/{owner}/{app}/saved/searches"
53+
headers = [("Content-Type", "application/json")]
54+
55+
def __init__(
56+
self,
57+
session_key: str,
58+
app: str,
59+
owner: str = "nobody",
60+
**context: dict,
61+
):
62+
"""Initializes AlertsRestClient.
63+
64+
Arguments:
65+
session_key: Splunk access token.
66+
app: App name of namespace.
67+
context: Other configurations for Splunk rest client.
68+
"""
69+
self.session_key = session_key
70+
self.app = app
71+
72+
self._rest_client = rest_client.SplunkRestClient(
73+
self.session_key,
74+
app=self.app,
75+
owner=owner,
76+
**context,
77+
)
78+
79+
self.endpoint = self.ENDPOINT.format(owner=owner, app=app)
80+
81+
def create_search_alert(
82+
self,
83+
name: str,
84+
search: str,
85+
*,
86+
disabled: bool = True,
87+
description: str = "",
88+
alert_type: AlertType = AlertType.NUMBER_OF_EVENTS,
89+
alert_condition: str = "",
90+
alert_comparator: AlertComparator = AlertComparator.GREATER_THAN,
91+
alert_threshold: Union[int, float, str] = 0,
92+
time_window: Tuple[str, str] = ("-15m", "now"),
93+
alert_severity: AlertSeverity = AlertSeverity.WARN,
94+
cron_schedule: str = "* * * * *",
95+
expires: Union[int, str] = "24h",
96+
**kwargs,
97+
):
98+
"""Creates a search alert in Splunk.
99+
100+
Arguments:
101+
name: Name of the alert.
102+
search: Search query for the alert.
103+
disabled: Whether the alert is disabled. Default is True.
104+
description: Description of the alert.
105+
alert_type: Type of the alert (see AlertType). If it equals to CUSTOM, Splunk executes a check in
106+
alert_condition. Otherwise, alert_comparator and alert_threshold are used.
107+
alert_condition: Condition for the alert.
108+
alert_comparator: Comparator for the alert. Default is GREATER_THAN.
109+
alert_threshold: Threshold for the alert. Default is 0.
110+
time_window: Time window for the alert. Tuple of earliest and latest time. Default is ("-15m", "now").
111+
alert_severity: Severity level of the alert. Default is WARN.
112+
cron_schedule: Cron schedule for the alert. Default is "* * * * *".
113+
expires: Expiration time for the alert (i.e. how long you can access the result of triggered alert).
114+
Default is "24h".
115+
kwargs: Additional parameters for the alert. See Splunk documentation for more details.
116+
"""
117+
params = {
118+
"output_mode": "json",
119+
"name": name,
120+
"search": search,
121+
"description": description,
122+
"alert_type": alert_type.value,
123+
"alert_condition": alert_condition,
124+
"alert_comparator": alert_comparator.value,
125+
"alert_threshold": alert_threshold,
126+
"alert.severity": str(alert_severity.value),
127+
"is_scheduled": "1",
128+
"cron_schedule": cron_schedule,
129+
"dispatch.earliest_time": time_window[0],
130+
"dispatch.latest_time": time_window[1],
131+
"alert.digest_mode": "1",
132+
"alert.expires": str(expires),
133+
"disabled": "1" if disabled else "0",
134+
"realtime_schedule": "1",
135+
}
136+
137+
params.update(kwargs)
138+
139+
self._rest_client.post(self.endpoint, body=params, headers=self.headers)
140+
141+
def delete_search_alert(self, name: str):
142+
"""Deletes a search alert in Splunk.
143+
144+
Arguments:
145+
name: Name of the alert to delete.
146+
"""
147+
self._rest_client.delete(f"{self.endpoint}/{name}")
148+
149+
def get_search_alert(self, name: str):
150+
"""Retrieves a specific search alert from Splunk.
151+
152+
Arguments:
153+
name: Name of the alert to retrieve.
154+
155+
Returns:
156+
A dictionary containing the alert details.
157+
"""
158+
response = (
159+
self._rest_client.get(f"{self.endpoint}/{name}", output_mode="json")
160+
.body.read()
161+
.decode("utf-8")
162+
)
163+
164+
return json.loads(response)
165+
166+
def get_all_search_alerts(self):
167+
"""Retrieves all search alerts from Splunk.
168+
169+
Returns:
170+
A dictionary containing all search alerts.
171+
"""
172+
response = (
173+
self._rest_client.get(self.endpoint, output_mode="json")
174+
.body.read()
175+
.decode("utf-8")
176+
)
177+
178+
return json.loads(response)
179+
180+
def update_search_alert(
181+
self,
182+
name: str,
183+
*,
184+
search: Optional[str] = None,
185+
disabled: Optional[bool] = None,
186+
description: Optional[str] = None,
187+
alert_type: Optional[AlertType] = None,
188+
alert_condition: Optional[str] = None,
189+
alert_comparator: Optional[AlertComparator] = None,
190+
alert_threshold: Optional[Union[int, float, str]] = None,
191+
time_window: Optional[Tuple[str, str]] = None,
192+
alert_severity: Optional[AlertSeverity] = None,
193+
cron_schedule: Optional[str] = None,
194+
expires: Optional[Union[int, str]] = None,
195+
**kwargs,
196+
):
197+
"""Updates a search alert in Splunk.
198+
199+
Arguments:
200+
name: Name of the alert to update.
201+
search: Search query for the alert.
202+
disabled: Whether the alert is disabled.
203+
description: Description of the alert.
204+
alert_type: Type of the alert (see AlertType). If it equals to CUSTOM, Splunk executes a check in
205+
alert_condition. Otherwise, alert_comparator and alert_threshold are used.
206+
alert_condition: Condition for the alert.
207+
alert_comparator: Comparator for the alert.
208+
alert_threshold: Threshold for the alert.
209+
time_window: Time window for the alert. Tuple of earliest and latest time.
210+
alert_severity: Severity level of the alert.
211+
cron_schedule: Cron schedule for the alert.
212+
expires: Expiration time for the alert.
213+
kwargs: Additional parameters for the alert. See Splunk documentation for more details.
214+
"""
215+
params = {
216+
"output_mode": "json",
217+
}
218+
219+
if search:
220+
params["search"] = search
221+
222+
if disabled is not None:
223+
params["disabled"] = "1" if disabled else "0"
224+
225+
if description:
226+
params["description"] = description
227+
228+
if alert_type:
229+
params["alert_type"] = alert_type.value
230+
231+
if alert_condition:
232+
params["alert_condition"] = alert_condition
233+
234+
if alert_comparator:
235+
params["alert_comparator"] = alert_comparator.value
236+
237+
if alert_threshold:
238+
params["alert_threshold"] = str(alert_threshold)
239+
240+
if time_window:
241+
params["dispatch.earliest_time"] = time_window[0]
242+
params["dispatch.latest_time"] = time_window[1]
243+
244+
if alert_severity:
245+
params["alert.severity"] = str(alert_severity.value)
246+
247+
if cron_schedule:
248+
params["is_scheduled"] = "1"
249+
params["cron_schedule"] = cron_schedule
250+
251+
if expires:
252+
params["alert.expires"] = str(expires)
253+
254+
params.update(kwargs)
255+
256+
self._rest_client.post(
257+
f"{self.endpoint}/{name}", body=params, headers=self.headers
258+
)

tests/integration/conftest.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,23 @@
11
import os
22
import sys
33

4-
# path manipulation get the 'splunk' library for the imports while running on GH Actions
5-
sys.path.append(
6-
os.path.sep.join([os.environ["SPLUNK_HOME"], "lib", "python3.7", "site-packages"])
7-
)
8-
# TODO: 'python3.7' needs to be updated as and when Splunk has new folder for Python.
4+
import pytest
5+
6+
import context
7+
8+
9+
@pytest.fixture(autouse=True, scope="session")
10+
def setup_env():
11+
# path manipulation get the 'splunk' library for the imports while running on GH Actions
12+
if "SPLUNK_HOME" in os.environ:
13+
sys.path.append(
14+
os.path.sep.join(
15+
[os.environ["SPLUNK_HOME"], "lib", "python3.7", "site-packages"]
16+
)
17+
)
18+
# TODO: 'python3.7' needs to be updated as and when Splunk has new folder for Python.
19+
20+
21+
@pytest.fixture(scope="session")
22+
def session_key():
23+
return context.get_session_key()

0 commit comments

Comments
 (0)