Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions cmk/gui/wato/_notification_parameter/_flowtriq.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#!/usr/bin/env python3
# Copyright (C) 2024 Checkmk GmbH - License: GNU General Public License v2
# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
# conditions defined in the file COPYING, which is part of this source code package.


from cmk.gui.watolib.password_store import passwordstore_choices_without_user
from cmk.rulesets.internal.form_specs import SingleChoiceElementExtended, SingleChoiceExtended
from cmk.rulesets.v1 import Help, Label, Message, Title
from cmk.rulesets.v1.form_specs import (
CascadingSingleChoice,
CascadingSingleChoiceElement,
DefaultValue,
DictElement,
Dictionary,
FixedValue,
migrate_to_password,
migrate_to_proxy,
Password,
Proxy,
String,
)
from cmk.rulesets.v1.form_specs.validators import LengthInRange, MatchRegex

from ._helpers import _get_url_prefix_setting


def form_spec() -> Dictionary:
return Dictionary(
title=Title("Flowtriq parameters"),
elements={
"webhook_url": DictElement(
required=True,
parameter_form=CascadingSingleChoice(
title=Title("Flowtriq webhook URL"),
prefill=DefaultValue("webhook_url"),
help_text=Help(
"The URL of your Flowtriq webhook endpoint. "
"Alerts will be sent as JSON POST requests to this URL."
"<br />This URL can also be collected from the password store of Checkmk."
),
elements=[
CascadingSingleChoiceElement(
name="webhook_url",
title=Title("Explicit"),
parameter_form=String(
custom_validate=[
LengthInRange(
min_value=1,
error_msg=Message(
"Please enter a valid webhook URL"
),
),
MatchRegex(
regex=r"^https?://.+",
error_msg=Message(
"The webhook URL must begin with "
"<tt>https://</tt> or <tt>http://</tt>"
),
),
],
),
),
CascadingSingleChoiceElement(
name="store",
title=Title("From password store"),
parameter_form=SingleChoiceExtended(
no_elements_text=Message(
"There are no passwords defined for this selection yet."
),
elements=[
SingleChoiceElementExtended(
title=Title("%s") % title, name=ident
)
for ident, title in passwordstore_choices_without_user()
if ident is not None
],
),
),
],
),
),
"api_key": DictElement(
parameter_form=Password(
title=Title("API Key"),
help_text=Help(
"An optional API key for authenticating with the Flowtriq webhook. "
"If set, it will be sent as an <tt>X-API-Key</tt> HTTP header."
),
migrate=migrate_to_password,
),
),
"ignore_ssl": DictElement(
parameter_form=FixedValue(
value=True,
title=Title("Disable SSL certificate verification"),
label=Label("Disable SSL certificate verification"),
help_text=Help(
"Ignore unverified HTTPS request warnings. Use with caution."
),
),
),
"proxy_url": DictElement(parameter_form=Proxy(migrate=migrate_to_proxy)),
"url_prefix": _get_url_prefix_setting(),
},
)
8 changes: 8 additions & 0 deletions cmk/gui/wato/_notification_parameter/registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
)

from . import _cisco_webex_teams as cisco_webex_teams
from . import _flowtriq as flowtriq
from . import _ilert as ilert
from . import _mail as mail
from . import _ms_teams as ms_teams
Expand Down Expand Up @@ -42,6 +43,13 @@ def register(
form_spec=cisco_webex_teams.form_spec,
)
)
notification_parameter_registry.register(
NotificationParameter(
ident="flowtriq",
spec=lambda: convert_dictionary_formspec_to_valuespec(flowtriq.form_spec),
form_spec=flowtriq.form_spec,
)
)
notification_parameter_registry.register(
NotificationParameter(
ident="victorops",
Expand Down
14 changes: 14 additions & 0 deletions cmk/utils/notify_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -714,9 +714,20 @@ class SplunkPluginModel(TypedDict, total=False):
url_prefix: URLPrefix


class FlowtriqPluginModel(TypedDict, total=False):
webhook_url: Required[WebhookURL]
api_key: CheckmkPassword
ignore_ssl: Literal[True]
proxy_url: ProxyUrl
url_prefix: URLPrefix


CiscoPluginName = Literal["cisco_webex_teams"]
CiscoNotify = tuple[CiscoPluginName, CiscoPluginModel | None]

FlowtriqPluginName = Literal["flowtriq"]
FlowtriqNotify = tuple[FlowtriqPluginName, FlowtriqPluginModel | None]

MkeventdPluginName = Literal["mkeventd"]
MkeventdNotify = tuple[MkeventdPluginName, MKEventdPluginModel | None]

Expand Down Expand Up @@ -775,6 +786,7 @@ class SplunkPluginModel(TypedDict, total=False):
MailNotify
| AsciiMailNotify
| CiscoNotify
| FlowtriqNotify
| MkeventdNotify
| IlertNotify
| JiraNotify
Expand All @@ -794,6 +806,7 @@ class SplunkPluginModel(TypedDict, total=False):

BuiltInPluginNames = (
CiscoPluginName
| FlowtriqPluginName
| MkeventdPluginName
| AsciiMailPluginName
| MailPluginName
Expand All @@ -817,6 +830,7 @@ class SplunkPluginModel(TypedDict, total=False):
MailPluginModel
| AsciiMailPluginModel
| CiscoPluginModel
| FlowtriqPluginModel
| MKEventdPluginModel
| IlertPluginModel
| JiraIssuePluginModel
Expand Down
1 change: 1 addition & 0 deletions packages/cmk-notification-plugins/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ pkg_files(
renames = {
"notifications/asciimail.py": "asciimail",
"notifications/cisco_webex_teams.py": "cisco_webex_teams",
"notifications/flowtriq.py": "flowtriq",
"notifications/ilert.py": "ilert",
"notifications/mail.py": "mail",
"notifications/msteams.py": "msteams",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#!/usr/bin/env python3
# Copyright (C) 2024 Checkmk GmbH - License: GNU General Public License v2
# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
# conditions defined in the file COPYING, which is part of this source code package.
r"""
Send notification messages to Flowtriq
=======================================

Post alerts to a Flowtriq webhook endpoint for DDoS detection
and traffic analytics.
"""

from cmk.notification_plugins.utils import (
get_password_from_env_or_context,
host_url_from_context,
post_request,
process_by_status_code,
service_url_from_context,
)


def _flowtriq_msg(context: dict[str, str]) -> dict[str, object]:
"""Build the message payload for Flowtriq"""

if context.get("WHAT") == "SERVICE":
state = context["SERVICESTATE"]
service = context["SERVICEDESC"]
output = context["SERVICEOUTPUT"]
url = service_url_from_context(context)
else:
state = context["HOSTSTATE"]
service = ""
output = context["HOSTOUTPUT"]
url = host_url_from_context(context)

msg: dict[str, object] = {
"source": "checkmk",
"host": context["HOSTNAME"],
"host_address": context.get("HOSTADDRESS", ""),
"service": service,
"state": state,
"output": output,
"notification_type": context["NOTIFICATIONTYPE"],
}

if url:
msg["url"] = url

return msg


def _get_headers() -> dict[str, str]:
"""Build request headers, including optional API key"""
headers: dict[str, str] = {
"Content-type": "application/json",
}

try:
api_key = get_password_from_env_or_context(key="NOTIFY_PARAMETER_API_KEY")
headers["X-API-Key"] = api_key
except (KeyError, IndexError):
pass

return headers


def main() -> int:
return process_by_status_code(
post_request(_flowtriq_msg, headers=_get_headers()),
success_code=(200, 201, 202),
)
13 changes: 13 additions & 0 deletions packages/cmk-notification-plugins/notifications/flowtriq.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/usr/bin/env python3
# Flowtriq

# Copyright (C) 2024 Checkmk GmbH - License: GNU General Public License v2
# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
# conditions defined in the file COPYING, which is part of this source code package.

import sys

from cmk.notification_plugins.flowtriq import main

if __name__ == "__main__":
sys.exit(main())
87 changes: 87 additions & 0 deletions packages/cmk-notification-plugins/tests/test_flowtriq.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#!/usr/bin/env python3
# Copyright (C) 2024 Checkmk GmbH - License: GNU General Public License v2
# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
# conditions defined in the file COPYING, which is part of this source code package.

import pytest

from cmk.notification_plugins.flowtriq import _flowtriq_msg


@pytest.mark.parametrize(
"context, result",
[
(
{
"PARAMETER_URL_PREFIX_1": "automatic_http",
"MONITORING_HOST": "localhost",
"OMD_SITE": "testsite",
"HOSTURL": "/view?key=val",
"SERVICEURL": "/view?key=val2",
"HOSTNAME": "site1",
"HOSTADDRESS": "127.0.0.1",
"SERVICEDESC": "CPU load",
"SERVICESTATE": "CRITICAL",
"SERVICEOUTPUT": "CPU load is critical",
"NOTIFICATIONTYPE": "PROBLEM",
"WHAT": "SERVICE",
},
{
"source": "checkmk",
"host": "site1",
"host_address": "127.0.0.1",
"service": "CPU load",
"state": "CRITICAL",
"output": "CPU load is critical",
"notification_type": "PROBLEM",
"url": "http://localhost/testsite/view?key=val2",
},
),
(
{
"PARAMETER_URL_PREFIX_1": "automatic_https",
"MONITORING_HOST": "localhost",
"OMD_SITE": "testsite",
"HOSTURL": "/view?key=val",
"HOSTNAME": "webserver01",
"HOSTADDRESS": "10.0.0.5",
"HOSTSTATE": "DOWN",
"HOSTOUTPUT": "PING CRITICAL - Packet loss = 100%",
"NOTIFICATIONTYPE": "PROBLEM",
"WHAT": "HOST",
},
{
"source": "checkmk",
"host": "webserver01",
"host_address": "10.0.0.5",
"service": "",
"state": "DOWN",
"output": "PING CRITICAL - Packet loss = 100%",
"notification_type": "PROBLEM",
"url": "https://localhost/testsite/view?key=val",
},
),
(
{
"HOSTNAME": "router01",
"HOSTADDRESS": "192.168.1.1",
"HOSTSTATE": "UP",
"HOSTOUTPUT": "PING OK - Packet loss = 0%",
"NOTIFICATIONTYPE": "RECOVERY",
"WHAT": "HOST",
},
{
"source": "checkmk",
"host": "router01",
"host_address": "192.168.1.1",
"service": "",
"state": "UP",
"output": "PING OK - Packet loss = 0%",
"notification_type": "RECOVERY",
},
),
],
)
def test_flowtriq_message(context: dict[str, str], result: dict[str, str]) -> None:
msg = _flowtriq_msg(context)
assert msg == result
1 change: 1 addition & 0 deletions tests/integration/test_binaries.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class RepoScript:
NOTIFICATION_PLUGINS: Sequence[BinarySmoke] = [
BinarySmoke("asciimail", expected_stderr=r"KeyError.*NOTIF", path=_NOTIF_PATH),
BinarySmoke("cisco_webex_teams", expected_stderr=r"KeyError.*NOTIF", path=_NOTIF_PATH),
BinarySmoke("flowtriq", expected_stderr=r"KeyError.*NOTIF", path=_NOTIF_PATH),
BinarySmoke("ilert", expected_stderr=r"IndexError.*list index out of range", path=_NOTIF_PATH),
BinarySmoke("mail", expected_stderr=r"KeyError.*NOTIF", path=_NOTIF_PATH),
BinarySmoke("msteams", expected_stderr=r"KeyError.*NOTIF", path=_NOTIF_PATH),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ def test_registered_notification_parameters() -> None:
expected_plugins = [
"asciimail",
"cisco_webex_teams",
"flowtriq",
"ilert",
"mail",
"mkeventd",
Expand Down
1 change: 1 addition & 0 deletions tests/unit/cmk/gui/watolib/test_configuration_entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
[
("asciimail", "Legacy email (ASCII) parameter"),
("cisco_webex_teams", "Cisco Webex Teams parameter"),
("flowtriq", "Flowtriq parameter"),
("ilert", "iLert parameter"),
("mail", "Email (HTML) parameter"),
("mkeventd", "Forward Notification to Event Console parameter"),
Expand Down
Loading