From f754cd2493100864b4ec440c6c0a0eebef2d2c79 Mon Sep 17 00:00:00 2001 From: Marsell Kukuljevic Date: Tue, 24 Mar 2026 15:27:13 +0100 Subject: [PATCH] Updated eCall plugin for CheckMK v2.3 to work with v2.4. --- .../cmk_addons/plugins/ecall/lib/api.py | 112 +++++ .../ecall/rulesets/notify_ecall_parameters.py | 390 ++++++++++++++++ .../local/share/check_mk/notifications/eCall | 423 ++++++++++++++++++ ecall/umb_ecall_notify-2.2.0.mkp | Bin 0 -> 9915 bytes ecall/umb_ecall_notify-2.3.0.mkp | Bin 0 -> 9963 bytes ecall/umb_ecall_notify-2.4.0.mkp | Bin 0 -> 8765 bytes 6 files changed, 925 insertions(+) create mode 100644 ecall/2.4/local/lib/python3/cmk_addons/plugins/ecall/lib/api.py create mode 100644 ecall/2.4/local/lib/python3/cmk_addons/plugins/ecall/rulesets/notify_ecall_parameters.py create mode 100755 ecall/2.4/local/share/check_mk/notifications/eCall create mode 100755 ecall/umb_ecall_notify-2.2.0.mkp create mode 100755 ecall/umb_ecall_notify-2.3.0.mkp create mode 100755 ecall/umb_ecall_notify-2.4.0.mkp diff --git a/ecall/2.4/local/lib/python3/cmk_addons/plugins/ecall/lib/api.py b/ecall/2.4/local/lib/python3/cmk_addons/plugins/ecall/lib/api.py new file mode 100644 index 0000000..19d06ed --- /dev/null +++ b/ecall/2.4/local/lib/python3/cmk_addons/plugins/ecall/lib/api.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 +import logging + +import requests +from urllib3.exceptions import InsecureRequestWarning + + +class DeliveryException(Exception): + pass + + +class API(object): + """ + Provide basic access to eCall's HTTP/HTTPS API + """ + + def __init__( + self, + baseurl, + username, + password, + address, + message, + requests_proxy_hosts=None, + requests_verify_cert=True, + **kwargs, + ): + self._logger = logging.getLogger("ecall_plugin") + + self._logger.info("Using %s" % self.__class__.__name__) + + # eCall arguments + self.baseurl = baseurl + self.username = username + self.password = password + self.address = address + self.message = message + self.params = kwargs + + # python requests arguments + self.proxy_hosts = requests_proxy_hosts + self.verify_cert = requests_verify_cert if not requests_proxy_hosts else False + + if not self.verify_cert: + # disable InsecureRequestWarning on unverified SSL requests + requests.packages.urllib3.disable_warnings( + category=InsecureRequestWarning + ) # pylint: disable=no-member + + def _get_query_params(self): + base = { + "Address": self.address, + "UserName": self.username, + "Password": self.password, + "Message": self.message, + } + + base.update(self.params) + + if self._logger.isEnabledFor( + logging.DEBUG + ): # check for log level to not run map on every request + # log params but hide password entry + self._logger.debug( + "Query parameters are: %s", + str(dict(map(lambda i: (i[0], "***") if i[0] == "Password" else i, base.items()))), + ) + + return base + + def send(self): + """ + Send message to eCall API via HTTPS. + """ + self._logger.info("Sending message") + self._logger.debug("Using base url %s", self.baseurl) + self._logger.debug("Verifying certificates: %s", self.verify_cert) + self._logger.debug("Using proxies: %s", self.proxy_hosts) + + if not self.address: + self._logger.warning("Got no or empty address information, skipping this notification") + else: + ret = requests.get( + self.baseurl, + params=self._get_query_params(), + verify=self.verify_cert, + proxies=self.proxy_hosts, + ) + + if ret.status_code == 200: + self._logger.info("Successfully delivered message contents to eCall") + else: + msg = "Unable to deliver message to eCall: %i, Error: %s" % ( + ret.status_code, + ret.text, + ) + self._logger.critical(msg) + raise DeliveryException(msg) + + +class SMS(API): + def __init__(self, username, password, address, message, **kwargs): + super(SMS, self).__init__( + "https://url.ecall.ch/api/sms", username, password, address, message, **kwargs + ) + + +class Voice(API): + def __init__(self, username, password, address, message, **kwargs): + super(Voice, self).__init__( + "https://url.ecall.ch/api/voice", username, password, address, message, **kwargs + ) diff --git a/ecall/2.4/local/lib/python3/cmk_addons/plugins/ecall/rulesets/notify_ecall_parameters.py b/ecall/2.4/local/lib/python3/cmk_addons/plugins/ecall/rulesets/notify_ecall_parameters.py new file mode 100644 index 0000000..909708a --- /dev/null +++ b/ecall/2.4/local/lib/python3/cmk_addons/plugins/ecall/rulesets/notify_ecall_parameters.py @@ -0,0 +1,390 @@ +#!/usr/bin/env python3 + +from cmk.rulesets.v1.form_specs import ( + Dictionary, + DictElement, + String, + Integer, + Password, + BooleanChoice, + MultilineText, + CascadingSingleChoice, + CascadingSingleChoiceElement, + SingleChoice, + SingleChoiceElement, + DefaultValue, +) +from cmk.rulesets.v1.rule_specs import ( + Title, + Help, + NotificationParameters, + Topic, +) +from cmk.rulesets.v1.form_specs.validators import ( + LengthInRange, + NumberInRange, + NetworkPort, + ValidationError, +) + + +class CallbackLength: + def __call__(self, value) -> None: + try: + # only numeric -> maxlen 16 + int(value) + maxlen = 16 + errtype = "numeric" + except ValueError: + # alphanumeric -> maxlen 11 + maxlen = 11 + errtype = "alphanumeric" + + if len(value) > maxlen: + raise ValidationError( + "For callbacks using %s characters the maximum length is %i" % (errtype, maxlen), + ) + + +def _params_form(): + return Dictionary( + title=Title("Call with the following parameters"), + elements={ + "action": DictElement( + required=True, + parameter_form=CascadingSingleChoice( + title=Title("eCall action"), + help_text=Help("Configure the action to take by eCall"), + prefill=DefaultValue("sms"), + elements=[ + CascadingSingleChoiceElement( + name="sms", + title=Title("SMS"), + parameter_form=Dictionary( + title=Title("Custom parameters"), + help_text=Help( + "Specify any custom parameters to be sent to the eCall API" + ), + elements={ + "JobID": DictElement( + parameter_form=String( + title=Title("JobID"), + custom_validate=[LengthInRange(min_value=1, max_value=50)], + ), + ), + "NotificationAddress": DictElement( + parameter_form=String( + title=Title("NotificationAddress"), + custom_validate=[LengthInRange(min_value=1, max_value=100)], + ), + ), + "NotificationLevel": DictElement( + parameter_form=SingleChoice( + title=Title("NotificationLevel"), + elements=[ + SingleChoiceElement( + name="level_0", + title=Title("Confirmation of receipt only when a receipt status is present"), + ), + SingleChoiceElement( + name="level_1", + title=Title("Confirmation of receipt once the last possible monitoring point has been reached"), + ), + SingleChoiceElement( + name="level_2", + title=Title("Confirmation of receipt once the last possible monitoring point has been reached, and when the job is still not sent after a number of seconds"), + ), + SingleChoiceElement( + name="level_3", + title=Title("Send confirmation of receipt when the job could not be sent, i.e. in the case of sending errors or timeout when contacting the end device"), + ), + ], + prefill=DefaultValue("level_0"), + ), + ), + "CallBack": DictElement( + parameter_form=String( + title=Title("CallBack"), + custom_validate=[LengthInRange(min_value=1, max_value=16), CallbackLength()], + ), + ), + "Answer": DictElement( + parameter_form=String( + title=Title("Answer"), + custom_validate=[LengthInRange(min_value=1, max_value=100)], + ), + ), + "MsgType": DictElement( + parameter_form=SingleChoice( + title=Title("MsgType"), + elements=[ + SingleChoiceElement(name="Normal", title=Title("Normal SMS")), + SingleChoiceElement(name="Flash", title=Title("Flash SMS")), + SingleChoiceElement(name="PrioSMS", title=Title("Flash and a 'Normal' SMS")) + ], + prefill=DefaultValue("Normal"), + ), + ), + "NoLog": DictElement( + required=True, + parameter_form=BooleanChoice( + title=Title("Prevent message from showing up in the log book"), + prefill=DefaultValue(True), + ), + ), + }, + ), + ), + CascadingSingleChoiceElement( + name="voice", + title=Title("Voice"), + parameter_form=Dictionary( + title=Title("Custom parameters"), + help_text=Help( + "Set custom paramters to be sent additionally to the eCall API" + ), + elements={ + "JobID": DictElement( + parameter_form=String( + title=Title("JobID"), + custom_validate=[LengthInRange(min_value=1, max_value=50)], + ) + ), + "Language": DictElement( + parameter_form=SingleChoice( + title=Title("Language"), + elements=[ + SingleChoiceElement(name="DE", title=Title("German")), + SingleChoiceElement(name="FR", title=Title("French")), + SingleChoiceElement(name="IT", title=Title("Italian")), + SingleChoiceElement(name="EN", title=Title("English")), + ], + prefill=DefaultValue("EN"), + ), + ), + "FromText": DictElement( + parameter_form=String( + title=Title("FromText"), + custom_validate=[LengthInRange(min_value=1)], + ), + ), + }, + ), + ), + ], + ), + ), + "username": DictElement( + required=True, + parameter_form=Password( + title=Title("eCall account name"), + help_text=Help("The user account used to authenticate against the eCall API."), + custom_validate=[LengthInRange(min_value=1)], + ), + ), + "password": DictElement( + required=True, + parameter_form=Password( + title=Title("eCall account password"), + help_text=Help("The password to authenticate against eCall"), + custom_validate=[LengthInRange(min_value=1)], + ), + ), + "content": DictElement( + required=True, + parameter_form=Dictionary( + title=Title("Message content"), + help_text=Help( + "Configure the content to be sent to eCall. Not setting any content will result in an empty message." + ), + elements={ + "shared": DictElement( + required=True, + parameter_form=MultilineText( + title=Title("Content head for host and service notifications"), + help_text=Help( + """You may use any checkmk macro here, as well as the following special macros: +$PARSED_OUTPUT$: Output of either host or service check depending on notification type +$PARSED_STATE$: State of either host or service depending on notification type +$PARSED_ITEM$: Item name of the affected monitored object, either hostname/service or service +""" + ), + monospaced=True, + prefill=DefaultValue("$PARSED_ITEM$ $PARSED_OUTPUT$"), + ), + ), + "host": DictElement( + parameter_form=MultilineText( + title=Title("Add information in case of host notifications"), + monospaced=True, + prefill=DefaultValue("""Event: $EVENT_TXT$ +Output: $HOSTOUTPUT$ +Perfdata: $HOSTPERFDATA$ +$LONGHOSTOUTPUT$ +"""), + ), + ), + "service": DictElement( + parameter_form=MultilineText( + title=Title("Add information in case of service notifications"), + monospaced=True, + prefill=DefaultValue("""Service: $SERVICEDESC$ +Event: $EVENT_TXT$ +Output: $SERVICEOUTPUT$ +Perfdata: $SERVICEPERFDATA$ +$LONGSERVICEOUTPUT$ +"""), + ), + ), + "bulk_prefix": DictElement( + parameter_form=SingleChoice( + title=Title("Prefix for bulk notification messages"), + help_text=Help( + "This setting allows to define a prefix that will be prepended to bulk notification messages." + ), + elements=[ + SingleChoiceElement(name="HOSTGROUPNAMES", title=Title("Use host groups as prefix")), + SingleChoiceElement(name="SERVICEGROUPNAMES", title=Title("Use service groups as prefix")), + ], + prefill=DefaultValue("HOSTGROUPNAMES"), + ), + ), + "show_host_if_service": DictElement( + required=True, + parameter_form=BooleanChoice( + title=Title( + "Show hostname in PARSED_ITEM for service notifications" + ), + help_text=Help( + "When this option is set, the hostname will be shown in the PARSED_ITEM macro. Otherwise it won't." + ), + prefill=DefaultValue(True), + ), + ), + }, + ), + ), + "content_length_limit": DictElement( + required=True, + parameter_form=Integer( + title=Title("Content length limit"), + help_text=Help("Cut the message content after this count of characters"), + custom_validate=[NumberInRange(min_value=1, max_value=1530)], # Max value for GSM + prefill = DefaultValue(160), + ), + ), + "proxy": DictElement( + parameter_form=Dictionary( + title=Title("Proxy servers"), + help_text=Help( + "Proxy servers that should be used when connecting to eCall via HTTPs" + ), + elements={ + "host": DictElement( + required=True, + parameter_form=String( + title=Title("Hostname / IP address"), + help_text=Help("The hostname or IP address of the proxy system"), + custom_validate=[LengthInRange(min_value=1)], + ), + ), + "protocol": DictElement( + required=True, + parameter_form=SingleChoice( + title=Title("Proxy protocol"), + elements=[ + SingleChoiceElement(name="http", title=Title("http")), + SingleChoiceElement(name="https", title=Title("https")), + ], + prefill=DefaultValue("http"), + ), + ), + "port": DictElement( + required=True, + parameter_form=Integer( + title=Title("Port"), + help_text=Help("The port the proxy is serving requests on"), + custom_validate=[NetworkPort()] + ), + ), + "authentication": DictElement( + required=True, + parameter_form=Dictionary( + title=Title("Authentication"), + elements={ + "user": DictElement( + parameter_form=String( + title=Title("Username"), + help_text=Help( + "The username to authenticate on the proxy" + ), + custom_validate=[LengthInRange(min_value=1)], + ), + ), + "password": DictElement( + parameter_form=Password( + title=Title("Password"), + help_text=Help( + "The password to authenticate on the proxy" + ), + custom_validate=[LengthInRange(min_value=1)], + ), + ), + }, + ), + ), + }, + ), + ), + "ssl_skip_verify": DictElement( + required=True, + parameter_form=BooleanChoice( + title=Title("Disable SSL certificate verification"), + help_text=Help( + "Disables verification of SSL certificates. This is useful when behind transparent proxies. When a proxy is configured, this is automatically enabled." + ), + prefill=DefaultValue(True), + ), + ), + "log_file": DictElement( + required=True, + parameter_form=String( + title=Title("Log file"), + help_text=Help("The log file to log to"), + custom_validate=[LengthInRange(min_value=1)], + prefill=DefaultValue("~/var/log/ecall.log"), + ), + ), + "log_level": DictElement( + required=True, + parameter_form=SingleChoice( + title=Title("Log level"), + help_text=Help("Set the minimum log level to show logs for."), + elements=[ + SingleChoiceElement(name="d", title=Title("Debug")), + SingleChoiceElement(name="i", title=Title("Info")), + SingleChoiceElement(name="w", title=Title("Warning")), + SingleChoiceElement(name="c", title=Title("Critical")), + ], + prefill=DefaultValue("i"), + ), + ), + "stdout": DictElement( + required=True, + parameter_form=BooleanChoice( + title=Title("Log to stdout"), + help_text=Help( + "Enable log output to stdout. This will be seen as lines prefixed by Output in var/log/notify.log" + ), + prefill=DefaultValue(True), + ), + ), + }, + ) + +rule_spec_tbs_ticket = NotificationParameters( + name="eCall", + title=Title("eCall – Parameters"), + topic=Topic.NOTIFICATIONS, + parameter_form=_params_form, +) diff --git a/ecall/2.4/local/share/check_mk/notifications/eCall b/ecall/2.4/local/share/check_mk/notifications/eCall new file mode 100755 index 0000000..306fc8b --- /dev/null +++ b/ecall/2.4/local/share/check_mk/notifications/eCall @@ -0,0 +1,423 @@ +#!/usr/bin/env python3 +# eCall +# Bulk: yes +# -*- coding: utf-8 -*- +"eCall SMS notification plugin for Check_MK monitoring systems using rulebased notifications" + +import argparse +import logging +import os +from urllib.parse import quote +from logging.handlers import TimedRotatingFileHandler + +from cmk.notification_plugins.utils import ( + collect_context, + read_bulk_contexts, + get_password_from_env_or_context, + substitute_context, +) + +from cmk_addons.plugins.ecall.lib.api import SMS, Voice + + +class MessageFormat(object): + def __init__(self, shared, service=None, host=None): + self._shared = shared + self._service = service + self._host = host + + @property + def service(self): + if self._shared and self._service: + return "{}\n{}".format(self._shared, self._service) + elif self._service: # shared is empty + return self._service + return self._shared + + @property + def host(self): + if self._shared and self._host: + return "{}\n{}".format(self._shared, self._host) + elif self._host: # shared is empty + return self._host + return self._shared + + +class SettingsException(Exception): + pass + + +class Settings(object): + def __init__(self, notification_parameters): + p = notification_parameters + # keys that may just be copied: + # username, password, log_file, log_level, stdout + + # handle simple mandatory parameters + self.username = get_password_from_env_or_context("USERNAME", p) + self.password = get_password_from_env_or_context("PASSWORD", p) + + # in case of using the store, there will be None values when changes have not been activated + if self.username is None: + raise SettingsException( + "Got empty username. Did you activate changes after adding the user to the store?" + ) + if self.password is None: + raise SettingsException( + "Got empty password. Did you activate changes after adding the password to the store?" + ) + + self.message_length_limit = int(p["CONTENT_LENGTH_LIMIT"]) + self.log_file = os.path.expanduser(p["LOG_FILE"]) + self.log_level = { + "d": logging.DEBUG, + "i": logging.INFO, + "w": logging.WARNING, + "c": logging.CRITICAL, + }.get(p["LOG_LEVEL"]) + + # handle simple optional parameters + if "PROXY_HOST" in p: + proto = p["PROXY_PROTOCOL"] + host = p["PROXY_HOST"] + port = p["PROXY_PORT"] + + user = p.get("PROXY_AUTHENTICATION_USER") + if user: + user = quote(user) + + if p.get("PROXY_AUTHENTICATION_PASSWORD_1"): + passwd = get_password_from_env_or_context("PROXY_AUTHENTICATION_PASSWORD", p) + passwd = quote(passwd) + + if user and passwd: + url = f"{proto}://{user}:{passwd}@{host}:{port}" + elif user: + url = f"{proto}://{user}@{host}:{port}" + else: + url = f"{proto}://{host}:{port}" + + self.proxy = { + "https": url + #proto: url + } + else: + self.proxy = None + + if "SSL_SKIP_VERIFY" in p and p["SSL_SKIP_VERIFY"] == "True": + self.ssl_skip_verify = True + else: + self.ssl_skip_verify = False + + if "STDOUT" in p and p["STDOUT"] == "True": + self.log_stdout = True + else: + self.log_stdout = False + + # handle complex mandatory parameters + ## parse message format + self.bulk_message_prefix = "" + if "CONTENT_BULK_PREFIX" in p: + self.bulk_message_prefix = p["CONTENT_BULK_PREFIX"] + + message_format_shared = p["CONTENT_SHARED"] + message_format_specifc = {} + + if "CONTENT_HOST" in p: + message_format_specifc["host"] = p["CONTENT_HOST"] + + if "CONTENT_SERVICE" in p: + message_format_specifc["service"] = p["CONTENT_SERVICE"] + + self.message_format = MessageFormat(message_format_shared, **message_format_specifc) + + ## iterate over every action parameter key in params + self.api_class = SMS if p["ACTION_1"] == "sms" else Voice + self.api_params = {} + for param_key in filter(lambda k: k.startswith("ACTION_2_"), p.keys()): + key = param_key.split("_")[2].lower() + val = p[param_key] + self.api_params[key] = val + + if level := self.api_params.get("notificationlevel"): + self.api_params["notificationlevel"] = level.split("_")[1] + + if nolog := self.api_params.get("nolog"): + if nolog == "True": + self.api_params["nolog"] = 1 + else: + self.api_params["nolog"] = 0 + + +class Notification(object): + def __init__(self, context, message_format, message_length_limit): + self._logger = logging.getLogger("ecall_plugin") + self._context = context + self._message_format = message_format + self._length_limit = message_length_limit + self.contact = context["CONTACTPAGER"] + + def _limit(self, text): + self._logger.info("Limiting message length to at most %i characters", self._length_limit) + return text[: self._length_limit] + + @property + def message(self): + if self._context["WHAT"] == "SERVICE": + message = substitute_context(self._message_format.service, self._context) + else: + message = substitute_context(self._message_format.host, self._context) + + self._logger.debug("Unlimited message is '%s'", message) + + return self._limit(message) + + +class BulkNotification(Notification): + def __init__(self, context_list, message_format, message_length_limit, bulk_message_prefix): + # call super to get shared properties + # just use the first context as reference for contact + # the official plugins also assume that contact is the same for every context + super(BulkNotification, self).__init__( + context_list[0], message_format, message_length_limit + ) + self._prefix = bulk_message_prefix + self._context_list = context_list + + def __format_message(self): + prefixes = [] + affected_hosts = [] # TODO: This is currently not being used + service_states = {} + host_states = {} + + self._logger.info("Processing bulk context list") + for context in self._context_list: + self._logger.debug("Processing context %s", context) + affected_host = context["HOSTNAME"] + + # keep track of prefixes (if any) + if self._prefix: + # prefix is either HOSTGROUPNAMES or SERVICEGROUPNAMES + # Both are lists of group names separated by whitespace + prefixes += context[self._prefix].split(" ") + + # fill list of affected hosts + if affected_host not in affected_hosts: + affected_hosts.append(affected_host) + + # keep track of states + # TODO: We could code logic here to only send the most recent, worst state for each item + if context["WHAT"] == "SERVICE": + if context["PARSED_STATE"] not in service_states: + service_states[context["PARSED_STATE"]] = 1 + else: + service_states[context["PARSED_STATE"]] += 1 + else: + if context["PARSED_STATE"] not in host_states: + host_states[context["PARSED_STATE"]] = 1 + else: + host_states[context["PARSED_STATE"]] += 1 + + # consolidate prefixes + prefixes = list(set(prefixes)) + prefix_info = "" + + if prefixes: + prefix_info = ", ".join(prefixes) + self._logger.debug("Got the following prefix for bulk message: %s", prefix_info) + prefix_info += ": " + + # generate message + self._logger.debug("Raw service states are: %s", service_states) + self._logger.debug("Raw host states are: %s", host_states) + + service_info = map(lambda i: "%s: %i" % (i[0].capitalize(), i[1]), service_states.items()) + host_info = map(lambda i: "%s: %i" % (i[0].capitalize(), i[1]), host_states.items()) + + message_items = [] + if host_info: + self._logger.debug("Processed host info is: %s", host_info) + message_items.append("Hosts %s" % ", ".join(host_info)) + if service_info: + self._logger.debug("Processed service info is: %s", service_info) + message_items.append("Services %s" % ", ".join(service_info)) + + return prefix_info + ", ".join(message_items) + + @property + def message(self): + # use base class when there is only one notification in bulk context + if len(self._context_list) == 1: + self._logger.info( + "Only one context in bulk notification, use default notification method" + ) + return super(BulkNotification, self).message + + return self._limit(self.__format_message()) + + +class ContextParser(object): + "Parse variable inputs from checkmk notifier" + + def __init__(self): + argument_parser = argparse.ArgumentParser( + description="Send eCall SMS from checkmk monitoring notifications." + ) + argument_parser.add_argument( + "--bulk", help="Enable checkmk bulk mode", action="store_true", default=False + ) + args = argument_parser.parse_args() + + # TODO: maybe this should be configurable in the GUI. + # right now, the only notificationtype left is "PROBLEM". + # Only for "PROBLEM" the message format is actually being + # used. This is not optimal. May be let the user set a + # default message format and format and state per type of + # notification? This would be more flexible. + self.__special_states = { + "FLAPPINGSTART": ("WARNING", "started flapping"), + "FLAPPINGSTOP": ("OK", "stopped flapping"), + "ACKNOWLEDGEMENT": ("OK", "acknowledged"), + "RECOVERY": ("OK", "recovered"), + "DOWNTIMESTART": ("OK", "downtime started"), + "DOWNTIMECANCELLED": ("OK", "downtime cancelled"), + "DOWNTIMEEND": ("OK", "downtime ended"), + } + + self.bulk = args.bulk + + if args.bulk: + self.params, self.context = self.__parse_bulk() + else: + self.params, self.context = self.__parse_rulebased() + + def __parse_filter_context(self, context, params, is_bulk=False): + "Add custom macros to the context and strip out any parameters" + what = context["WHAT"] + notification_type = context["NOTIFICATIONTYPE"] + + context["PARSED_ITEM"] = context["HOSTNAME"] + + if what == "SERVICE": + if params.get("SHOW_HOST_IF_SERVICE", "True") == "True": + context["PARSED_ITEM"] = "%s on %s" % ( + context["SERVICEDESC"], + context["PARSED_ITEM"], + ) + else: + context["PARSED_ITEM"] = context["SERVICEDESC"] + + if notification_type in self.__special_states: + context["PARSED_STATE"], context["PARSED_OUTPUT"] = self.__special_states[ + notification_type + ] + + if is_bulk: + # Since we are merely counting the notifications for each state + # it makes no sense to keep the original OK/WARN/CRIT/UP/DOWN here. + # Instead, we overwrite it with the pretty output (e.g. "started flapping") + # so that this is counted in the bulk message. + # e.g. Host Started flapping: 1, Downtime ended: 2, Down: 2, Service OK: 3, acknowledged: 4 + context["PARSED_STATE"] = context["PARSED_OUTPUT"] + else: + context["PARSED_OUTPUT"] = context["%sOUTPUT" % what] + context["PARSED_STATE"] = context["%sSTATE" % what] + + # return new context without parameters (since they would expose passwords) + return dict(filter(lambda i: not i[0].startswith("PARAMETER_"), context.items())) + + def __parse_parameters(self, context): + params = {} + for param_key in filter(lambda k: k.startswith("PARAMETER_"), context.keys()): + params[param_key[10::]] = context[param_key] + + return params + + def __parse_rulebased(self): + context = collect_context() + params = self.__parse_parameters(context) + return params, self.__parse_filter_context(context, params) + + def __parse_bulk(self): + params, context_list = read_bulk_contexts() + params = self.__parse_parameters(params) + + # add custom fields to bulk contexts + # when there are no more than 1 contexts in the list, do not use bulk mode (--> is_bulk=False) + enriched_contexts = [ + self.__parse_filter_context(context, params, is_bulk=len(context_list) > 1) + for context in context_list + ] + + return params, enriched_contexts + + +class Plugin(object): + """ + Check_MK notification plugin to notify events using eCall's HTTP/S API. + """ + + def __init__(self): + parser = ContextParser() + settings = Settings(parser.params) + + self._logger = logging.getLogger("ecall_plugin") + self.__configure_logging(settings.log_level, settings.log_file, settings.log_stdout) + + self._logger.info("Starting eCall notification process") + self._logger.debug("Notification context is %s", parser.context) + + if parser.bulk: + notification = BulkNotification( + parser.context, + settings.message_format, + settings.message_length_limit, + settings.bulk_message_prefix, + ) + else: + notification = Notification( + parser.context, settings.message_format, settings.message_length_limit + ) + + self._logger.debug("Formatted message is %s", notification.message) + + self._logger.debug("Initialising eCall API Object") + self.api = settings.api_class( + settings.username, + settings.password, + notification.contact, + notification.message, + requests_proxy_hosts=settings.proxy, + requests_verify_cert=not settings.ssl_skip_verify, + **settings.api_params, + ) + self._logger.debug("Initialisation done") + + def __configure_logging(self, log_level, log_file, log_stdout): + self._logger.setLevel(log_level) + log_format = logging.Formatter("%(asctime)s - %(levelname)s | %(message)s") + + loghandler_file = TimedRotatingFileHandler( + log_file, when="midnight", interval=1, backupCount=10 + ) + loghandler_file.setFormatter(log_format) + loghandler_file.setLevel(log_level) + + self._logger.addHandler(loghandler_file) + + if log_stdout: + logger_console = logging.StreamHandler() + logger_console.setFormatter(log_format) + logger_console.setLevel(log_level) + self._logger.addHandler(logger_console) + + def notify(self): + """ + Prepare input data and initiate notification sending + """ + self._logger.info("Notification process started") + self.api.send() + self._logger.info("Notification process finished") + + +Plugin().notify() diff --git a/ecall/umb_ecall_notify-2.2.0.mkp b/ecall/umb_ecall_notify-2.2.0.mkp new file mode 100755 index 0000000000000000000000000000000000000000..b40c2be4b3cc3a7659dc2923e49bfb8df474954c GIT binary patch literal 9915 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 literal 0 HcmV?d00001 diff --git a/ecall/umb_ecall_notify-2.3.0.mkp b/ecall/umb_ecall_notify-2.3.0.mkp new file mode 100755 index 0000000000000000000000000000000000000000..31c7b53c0589edba97042bb1297d9e17a966eb35 GIT binary patch literal 9963 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 BR73y( literal 0 HcmV?d00001 diff --git a/ecall/umb_ecall_notify-2.4.0.mkp b/ecall/umb_ecall_notify-2.4.0.mkp new file mode 100755 index 0000000000000000000000000000000000000000..b49606568fe899161c80475b212b91dff2341c63 GIT binary patch literal 8765 zcmaKxWm6jrfP|6a(xSnoxI=L%Qe29=L!r1^kmBx8+}%A8DDLj=?h@Ps+&lOA=DzIg z><@TmXO}z*873*G*9z|A;`@d38eihAo>I@R#0IX)X?I$u8uiMR`(?RZ;-Lo}xdt_U zKvaow)UU|-M0FA)R;@?O$IwUWr41Mv80!0-W!c}UW$9^O1EdOPj@f(=W+b6~-%D9! z1eJ1UjbskDE-p&-L~KRYHdnmBZ_p8(@n#SR^cK`~-=`&W%XN!gb`$0|d1uv^Y4MkD zHX!w~h2DaT?_2%|tg>O#!WrqGtlOfz?<}&mko{p0B7{=kAe%BVyLnhb z9+&=iGA&ghjn^H@o)gC#cJo*wV)DLlcgi8nw4s3GcPn)L)vtm7em0sWb3E*&JTi*H zjG_CzaYw0E-$4j5%RuFeVb@k(Ri=q!=g9<|g?mtO3atubI=o{_ai&Bfaq4C(C);%i z`m_wzdOD(mI2D;Rj8&7<17Uqk9tAesi#W_)d^v+oe8}DX#F^2j5Dm+MRQfZdGJV%= zoTGA3ai?pE*goy~HD-YE&^RM!pbvlhkL-pRtb6aG+s(}gXe&QZAs**6a|b6EEs z6_Wa?YVwRb13Hr1$xADeKZ4I)_t0k-@pq9ePw>L^O_hql!o*h z0y%^wRq3Eb^!&?!L$X!RkH z?tyV0m02*t<+N>&Bdj+6=_U^zPLj6H*|Np78{-=ah3cB{EA%*tT&*J zeXHTpZ~AWAi08lo(;KLb@u=cE0D$R3)-f*zu zp@g}5{d_q-)vvV%KDm^|_Cp>V7kHfQZWbcvLMjK?OP4NI&K#TJd=P$(Zm0hqy%KWJ zDu!!Tvo?8glN167Nyc;}qaOnj8R+)eJYD|W zZ)md-^)G%X*;&3QBa+1|vsE0thz)$j#dKy)+;^s9or~?aMV+MvKj^h|TTo-~mS$J& z9Ui%?Sn8a>FoB~YQleqlkLzIbTk3*ygvS7RP98$V3PuI-CsrDzni6GB9dfw1q=o5n zW^Kw;EPPCLSj?g)xC>#qqcsKzXU`{y_0oB7qn(y=)6z&uk8y6c$${Mc_B)X-az2 zYBiSe{<8B0P<%YM-78H}gee+d`cI3M7*V>@@1B?wl?zd z z7Gt+WlEtR`Zc^09^b0J7*4C8%?V&!fE_MsTSCd!&Wkl$t(fgLi+!)Bhs1G&1d z!$=gB!JOcu$!ui*j`o+6m)ix0lNVLP#ZeeO^Yiv?!lKitCff)x;XF)^+pwrB6h$B; z^8{D=#OuBp_csoU4>6K(+Y*l|Mzh@k5;K(M0!sDz!PK7snfUN)1%arbA4hs?Q{Wu` zj}K^RcJ|=MN9enV$5sABtQp2pA2?`r?x)506zLz~)`y;BxQIg<-VNv@h1oI1HK30f z_UBmcXHhEu>3tDJuCu39KFoB$#ZOC$xyq$mcC>DYMWH_XrDyqN{xxUthj__h4YcT& zDx+GaTdo06`BjfiqPb1&C$>+`*Ae8xQ<6z0{K==e1XHuD{h%m6;sH>1ZFNar`)v;z z{1w7(f7x}sWa`R3g~0vS6B&|;neVek_2|cc+_2?ecUX{+2oi`S(Tn<}FuXBiNyDn- z?65zpTKkE@xm68`%q1C7CiSfIc1-fYF;T)z@uT=Kv)>DcGB{j0yqvCQwy>>rS)P`o1;YEk0|OEOleC5iQfu5A4oe!+3+0=cG5(>D0a;5fK%(VrR&0>oGTS*aX3 z3(iZ%+MwYvwP%~A0fuMJRdQQ8?EZ?lkcw7^4@*XaM9eOa+U=Q|z6meScJ9P@3+KwJ z4#=bvIhziJM_NhoXv$)oXrYE$tEt!|AX_*^3oQKJ`!v`b<^GFYT0ogHmNjt9vo{>I zomsak-D9BYme>kO3qF(P{3~)_C!^0QPlho!f-_@htwB|t3?mr8>Le;&rqsk{h?+lx zt}r2BX5&>FS+7}P{kf4>X3YA%LSD+Rx^56Y%^A4?z)Uq3WzW1-rI9_`9~){v^~c+r z0g9(wL&(^){I2KYTi0d#sd|}T-=o&BeO{I%UD1ODIcu;+*}dw+P|d+EYn3K_b&J-M z)>mllG^^ThDw4iBZ9=oj8%qVs+M0*jR69$#rne;@M`9{;Tl4a(99ySNi_gpEVFb0I zi?zBM6~7WW62DE){ZW(MH9huJP2!}AAV^IZtvi%z?b-|OcUo%K9wlj`lc+<%lR;{r zjpJO0n!~Ga`H_qFC+64ji~%bmV_Q2SY1kHQv!VfG4?P?<1dTz*im_WdTc|Yc@!!jY zJOg&h!K}FYyUT0-1_x9{dwSCi<>59M9e5wI1o*IplJU4grx$5RY%Zig&=PZ?_5>%U z!dK|?kc6s9hFf*F2_`T#X<%q1K22$L8jj1gRW*xqIFO+)#Of0U{IX^5oDgi9oAPVg z4pCF>G>*lYd96EXkr~8?lO;Okify~Wmu*lvc512K-(A3fDslEbly=5G$q=;feS#V= zAripwBG5syF~0n0?k`u=S-mpi){iwg9_rp~Mzv=XWs2%iGz|R1ChDM6C(t46_2=_K zHnmu^a+}6NzT~zJaeFm!!9#5=qXfXUGr!B7z-ZGwoEZhVhOeQKA)Jjsmk#CFpl*LZ znp^UI4ka#Z(qb012aof}sU6H(aV~26#Cy^{6$N7UuiKtWm0}qPhMYRdX9&L|Me8>x zL}_Qh$~y>QmSMC67DgDpI~AmQFnWE4t>-t2eDzBK zqo!uQq4Ut6=ozj$q=0wtxIILV9rr?v=ayM=n95$}^Pg-6IT-q5`JWMV0#;J?TpV^U z)`0pR_HpWsy;;J%iymXx;0RN?b9WrTZy%+9msk7vddnSphLaL#DyB6V)i39=1L0qA zzipr)xqqn7RuoztXm8!s?jz?Bkk)f)t}&uQ4kpgCVnbmSErkhNUaNLM?BcB*0{cZB z8S8E3-R&9-dHm`V6p4Vf>}^d+)m=glv$w9&?k#o?0NjjQ)!rX5zdrAbKu#R%F3GwG zv)c*3LvLx@&vb@vZF!~~X#)^o#JLEFSl7my7ot}b6u9~mI(x{L%&e}$3O8m%1Id5W zBb?^f!af>v0a<#;%xvZ4V8!OX6-S{RTC_OlDjE0SY&oxJ*bHK4Hk`smrL^iCpy3J4 zlb3Q%8BxWn*_~KTfTJh%COyzNnW2_c#fzNRowhI;V)xhD8z+G`vWo6lk5%7cF6-26 zz5Pw<$qs>hKtVpDe}!E!WZ)8 z6{cD%`upS>FcQ4Io-gRhFA82PQx3tsT$gX@SEI!|e$awVBi^s3Qh)P(XG_E5SdT*{>P_Req%z?MkJVGxv3G}Brw6Ohj4j9Z z^slkhBj}>3>Wa5X8R;NG^LrQT=@4zfb2W0X=*rE_-P6R#4CJgQ{xIT>)f4_#T>ptV z_LK!f#+N{O%{ZHREjFNjU!Hmg$zyZ>_W48MM&4wH+@`G0&o+XM?cR=L0z(_qWBJmc zc|~0}Ta5gOf1V+M2KD-u<63A>ZA9G7NywK^ro>;+uA^`2Z*@tkIdU;*6Lh-Et2CqDSwxV|+;J4{-kJCuQ^e!h<_gun6aXVaN*&Rg&; z<0AZ7(_?~zW`Wqf^HXb6yirvr7@?}81GMnZd}`M4{J`Mf!+wDYwI~fW6TYg`{Z5Ve zekj8BZU#1EC&EBCGZ0cd&~H~MY0vNZS5_K-i7gK71wDM|Kttx_ zd^%hp;F53{R)X=f32Ctvh>sC5po8|ViP%MCZpVhA!Fe#bR8H%#U;tE98QD(5S@Jk3 z-85H=L0;X~vTGwaVMWvmOiJbBx`wxa461d8pt^C^0d zm<_2&oY9>@rM50Thbf?u9t4={yRjAjXUti5ZOATfCBEvmV}QM|gD*@2a%454@u zP<6a}O9gc6-Ar6~x!4Y&qWa;tN{`XO5^-=`7z3-*@M#Z1aY*irq>qi|5-pdL?^!kM z3vKszr`AolaGM(sM;m)Zkdx3RB$*pQEkJf_icoKRZaYkJ3g z?}#n`N&g@aj8Vh8qt@0q{R#g4NiGt{x1D4Z=LPyxL(<;SRks?7%f>6HWUvip5;y?8 zw6iKoOjh~mkcFw&Q(t|&ANf`PoyCtsk9#?t3ox3Txjx<==-GrEZ$DWs+iBE=dQsry zOfIbJ%oWjcm&^XNR{fhX|9@G0Iy?E14Wl;&tLwL^De&l{hy(AVh+nb9t@@)r(#Fl( zxz5C!()5B-?#R0z_|^>R#n0w-W$y63v^K#S;)e}*?%gsv+|ih7HDM4EdB3hd7y_<} zPGwjC;y&=U8vFH+uI`n$wni=iB^^B8K*Ye;7p;4sZKs;p=C2^mI^eI}<@J5q+MI{P zvGmlU@%9W|XmpccIq4|lrS!|I&?LbJ^QA~OaE8xp08g&acyzGT>2QZN1t2zTc#S?P zsm*A(3)!Dfw-B>j@7o_T8HSD*Zz$eORs?(gN(Ey_E$uGSwZE&d3cJ=b0=#-+a5`KC zM3g`jnHE*REe2(mj%$MAW(hgwRoHJpkmy%w0L&~_*z=p>u6{D0u~fgyq;1z(*cVP# z%U)_9pgEU=>&0v!I&rJ_M`aqy2RSN~b=s{=C=<`h@+_Lzr0@t85O!Pd;edA`eI0L**C1EvXQw zR<3-ZqSO1im%v`xOyaWDx}x3EF!9N)8d1%I)wa4F^Y0~>l2$vV;cuhS{uhc#t>5$# zS+GkB`>GK5E>m7paS}3)TS0m(eZbxj$UnwguCro|`rt<5T_S3v)igWC>wpiSqKUc( zxWw}}0-j`g;W=MioQxI8t@(12WbJO+5q~+GH*Dx6nH+&QK0%&bbNf{4v|>xQ;m80p z#lSjryDy57**)|^PvCKNTe__6mjPVltAL2xSEqjoxd`O#3Ru*MpKn9qKTde3u~KIa ztj&EHjcP-i5pUJ9s3nG0KMyYc6q>S`O2FaeiXLjPb2ZK26HZ=z2CTIhP4o-W-DU5v zP}T%Bx88M-d1OoWhZ@=yeyc;zNswVS2pUzV8_p=4?^Ggrmpb zyT4xPHzBv^-j|UMb_POc|2R4Fl`)TK8K*#;hU_p_F^&dReh1vVZ66QRUq9a^QLtCZrp3jgEm1#RBa7qadd z+~Jyg4&tGJL&+1!!Fnepy(AUS))iJQvKEFbJEDXa3v%;`^lVh<-%g9rW%vf^B6nkINK72s01I|R?j8D9YT=qh<(=Ksf6Ats z@=KXH3ZoCiJ|tw6iKoCZTFx!w6nW&~6){Pl>QHL$KkD-Bh4gJri|5+yot}HmE_NHB z*PPNqP-O(dZ|u%}#{Btto=fajWmd(@isL?)h2Atzp+QWSPQ|e*HrlK{hlN3|mV$5| zFShTt2ONBGuQ5H1UVx#^IcqH`^EGAt%jR$kCX?o6$?_dn6r}+NmLE?O!=kZWdeJzj zKkRv`!eceNyQu++zR|rWd!|8qJ@8T9+RxVyX2Pd|8SA9wjQ!7|(ilH==vr4BE(t<6_&BE0^Rw6B$x~F+g7=#G z%+5Dg|E1s`b|)W}Wj5o!6~7b<_N-2f=8$I-`0SZ37 zgE5m6hxMd^rJCBF>KP6x{%ilLfZ72Enc_9x8r$9!n` z42sufE;(7+7nkxA@%OB$!7;atFUkDh^9oi z)oG4Gv|!WPuSYrtW|Hbg#QGy^Z;*u=^%>P7%~A7cr(`(bMa8Cba6jZ!!wsMQ;r>i? zK&>LL_19p_RFz#a2-<=cE(MO*&!`wpvtF|YMd(wjwa?-L*5n$vj>cznI7Pw=80rh9# zP*0lGrtkE3-<|3Gs%sNKib#@E;vzH%LnY@NjsR8O#T6$;j;oxQEt8&KqZRW+^2o~a zCz>mB_33g06EWx=$RJ+stz%bGxHVlBM51B+5W)LL$r6!~K>v`L|JBdvSk}{@V}Rh9 zm-_h__|cMj>HBbmV()_KAfX?@`b(~9n+rn}z?NbjS}q#)rGgw^7W9|;0rpRozhY;S ze&A0Hv(|kaD6RyadWG`L;IX=OVmw*CBQr`IcCtEUYzZC0CyZKh@od{r@BTL?^uvRG zhal0rg3Lrv?Tv&EhDbb8h-pm?bO@xJBGGI(^yXZ!^)n%K0HpjvN+kN!(PDO z?SpmZswZ1L?mZy&wbixB-Ok;1YqK5rT~eIK!k6nbun(dzX+-R|cvjs(8ZVq(mH^dA_T!%jUnmqeL~qwl5m1e8A- zGe}$8%Olgu2W3VawPN&9sGF1*vYe6zq2~cU6dEX2BvcfAAh1vAy4|qRKYC72ThXRw zGhfcDa=a5=@U1tA1j1c0HV*vZi_-_^o{xFHr4x1@C{GdSEfTpU={c`RTt*Gc9r-0lKxmu=%I{;J%aOMGcH9fFyI5$M3qX3LG;e z1I_WL8B>2`VEQW5lQdRtPs0Us5URU&3&_aX&s?kLR|i(`fjgqB5zB11Y}#va=d4ev zPRwS4%R|mYnIZJU#2q*{{4$(7uKD1}Pi7_7rYBelWd`7KfwEXIAU;E(#rf$)TeDW% zw)odX+k7yGNM5(ll~88?UaOVZf`_iam+U3seheoSnn|OK8EQ4!V!_iAnS9>C9#`>!o0s>~hzUyxEGFw>C5*RfANPhq8T|;nHM_j7POF^SieH(xO1{J0nGiJwjqgt++z7Pz2 zm5dY9Hwvb3J~7U+H#phDVf2AAu>t=3T$JrSE|&`TJi>zBQiGO)3KLXu&;5?i;$zr! zP87bsy4HC>-5p;t6UoQD(Z{vSHbs0rxCCGT%npQX9I>@j$3ovU>fD+T9^K;}5X1b< zG)%UleDFijB0tA6qRm!gTT{*vmhQ+FG8J1))QoRwPSC0XTl78&dRA$$!;xoeH*V+% zu>FH8@>wV(IoFTBiSwcTfp*BCVu2zogF?<~fm<&R7!3FJduGve4v{9SE4YUHh9CEu z&(x5#7Vg5ggrkIp>lnuRQN@y6z)`xd@wu7s&Ak8tpuh{y2A}5$?T!C*MjnS28;(W~ zEi3-2k4r;@a||4dBEonO{X`lslX1R`_Rjk1hd?zrr)i_xzx z_r9uBUPoI>`lSilDEfuXxCc5{sNNnj6Wwr7)kG^0r~3Ylt(M4eohxqHjn%!LQ)L^! zchS1EKYJRBx0m|AhUuisJSi`KtGm}dwfdsZ-7<&!;|H&9b_3$LSY#|>DVGJy^zhp; z>VTSY2@@$dq92ml$pTPgvMfq=jpXI^qT29M;R3<@uzkP$1_7b?H~#3-mG(S?rX5RH z!JP}7<%@p4J_EGIWNu-MuwqmB2G2l(Hk_aBD+Tdi=7VaaGcNHy|Jz0_0{m^b&^@lM zO?k~cGffg;Qac69$ULGBuFje#EJNs1|0W-;REe^`E2!#WZ%BlAP?6)RkQ!#lHmmtd zp(q~YO>Ul5fiarw*eNKt+RmVb=$3aiSTiK&8!#`}1Co7*3oeCQ;10A?G%93&+#@W z+(4y~#l~a);O~(&+e@uZma$+oOZ0#NXy3Ej z+`v&tIm0$psI}2nH9uRe4WKGBRe)uiD*FjqvZzhNg)B;RkG`x~LUeyt+pX@FFzY|r z-?14HS};fFO=@6yjM`9F_>*=o!~f!=A7sK*J4=)BAF*tyuQcMKHX>iL*Kq&QiBbIG z(#cz#^5ncR6-ZK$ypiIG$!aLDJIiPwoV>zzI*`hZ@RXn!BX~{z6?8gf$_E`1; zo(kOz@izI4nof&$}}4Sn5s2zd%+p3V=_qQk4NVE*dT&5OtN+yHlOA zy`gEwnoXmpc{NT85aUj+6GTI0aWHgC*UizMl|hK^ZCWQ{*J$-Jd6W&Tq>;$l>aFx( z;KH4@9F87IN!sEuL@E0bMy5eO@%6WMq7`PKgU4bh5O%l(2X4h0WvzTKyo6VpmiPY~ Z68`Ub;eXQ;a@sc$(^P3%m@62V{{ej3Q_uha literal 0 HcmV?d00001