[mod] source
This commit is contained in:
parent
e168077ef9
commit
cf9c4f009d
|
|
@ -17,6 +17,8 @@
|
||||||
"help.args.expose_full_order": "nur den Pfad zur Datenbank-Datei zur Standard-Ausgabe schreiben und beenden (nützlich für Fehlersuche)",
|
"help.args.expose_full_order": "nur den Pfad zur Datenbank-Datei zur Standard-Ausgabe schreiben und beenden (nützlich für Fehlersuche)",
|
||||||
"help.args.show_version": "nur die Version zur Standard-Ausgabe schreiben und beenden",
|
"help.args.show_version": "nur die Version zur Standard-Ausgabe schreiben und beenden",
|
||||||
"help.args.verbosity": "Schwellwert für Log-Ausgaben",
|
"help.args.verbosity": "Schwellwert für Log-Ausgaben",
|
||||||
|
"checks.script.execution_failed": "Ausführung gescheitert",
|
||||||
|
"checks.script.invalid_return_code": "ungültiger Rückgabe-Code",
|
||||||
"checks.file_state.exists": "Datei existiert (soll aber nicht)",
|
"checks.file_state.exists": "Datei existiert (soll aber nicht)",
|
||||||
"checks.file_state.missing": "Datei existiert nicht (soll aber)",
|
"checks.file_state.missing": "Datei existiert nicht (soll aber)",
|
||||||
"checks.file_state.timestamp_implausible": "Datei ist scheinbar aus der Zukunft",
|
"checks.file_state.timestamp_implausible": "Datei ist scheinbar aus der Zukunft",
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@
|
||||||
"help.args.expose_full_order": "only print the extended order to stdout and exit (useful for debugging)",
|
"help.args.expose_full_order": "only print the extended order to stdout and exit (useful for debugging)",
|
||||||
"help.args.show_version": "only print the version to stdout and exit",
|
"help.args.show_version": "only print the version to stdout and exit",
|
||||||
"help.args.verbosity": "threshold for log outputs",
|
"help.args.verbosity": "threshold for log outputs",
|
||||||
|
"checks.script.execution_failed": "execution failed",
|
||||||
|
"checks.script.invalid_return_code": "invalid return code",
|
||||||
"checks.file_state.exists": "file exists (but shall not)",
|
"checks.file_state.exists": "file exists (but shall not)",
|
||||||
"checks.file_state.missing": "file does not exist (but shall)",
|
"checks.file_state.missing": "file does not exist (but shall)",
|
||||||
"checks.file_state.timestamp_implausible": "file is apparently from the future",
|
"checks.file_state.timestamp_implausible": "file is apparently from the future",
|
||||||
|
|
|
||||||
|
|
@ -1,125 +0,0 @@
|
||||||
class implementation_notification_channel_email(interface_notification_channel):
|
|
||||||
|
|
||||||
'''
|
|
||||||
[implementation]
|
|
||||||
'''
|
|
||||||
def parameters_schema(self):
|
|
||||||
return {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": False,
|
|
||||||
"properties": {
|
|
||||||
"access": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": False,
|
|
||||||
"properties": {
|
|
||||||
"host": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"port": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"username": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"password": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"host",
|
|
||||||
"port",
|
|
||||||
"username",
|
|
||||||
"password"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"sender": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"receivers": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tags": {
|
|
||||||
"description": "list of strings, which will be placed in the e-mail subject",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"default": []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"access",
|
|
||||||
"sender",
|
|
||||||
"receivers"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
'''
|
|
||||||
[implementation]
|
|
||||||
'''
|
|
||||||
def normalize_order_node(self, node):
|
|
||||||
return dict_merge(
|
|
||||||
{
|
|
||||||
},
|
|
||||||
node
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
'''
|
|
||||||
[implementation]
|
|
||||||
'''
|
|
||||||
def notify(self, parameters, name, data, state, info):
|
|
||||||
datetime = _datetime.datetime.utcnow()
|
|
||||||
smtp_connection = _smtplib.SMTP(
|
|
||||||
parameters["access"]["host"]
|
|
||||||
)
|
|
||||||
smtp_connection.login(
|
|
||||||
parameters["access"]["username"],
|
|
||||||
parameters["access"]["password"]
|
|
||||||
)
|
|
||||||
message = MIMEText(
|
|
||||||
_json.dumps(info, indent = "\t", ensure_ascii = False)
|
|
||||||
)
|
|
||||||
message["Subject"] = string_coin(
|
|
||||||
"{{tags}} {{title}}",
|
|
||||||
{
|
|
||||||
"tags": " ".join(
|
|
||||||
map(
|
|
||||||
lambda tag: ("[%s]" % tag.upper()),
|
|
||||||
(
|
|
||||||
parameters["tags"]
|
|
||||||
+
|
|
||||||
[condition_show(state["condition"])]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
"title": data["name"],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
message["From"] = parameters["sender"]
|
|
||||||
message["To"] = ",".join(parameters["receivers"])
|
|
||||||
message["Date"] = string_coin(
|
|
||||||
"{{day_of_week}}, {{day_of_month}} {{month}} {{year}} {{hour}}:{{minute}}:{{second}} {{time_offset}}",
|
|
||||||
{
|
|
||||||
"day_of_week": datetime.strftime("%a"),
|
|
||||||
"day_of_month": datetime.strftime("%d"),
|
|
||||||
"month": datetime.strftime("%b"),
|
|
||||||
"year": datetime.strftime("%Y"),
|
|
||||||
"hour": datetime.strftime("%H"),
|
|
||||||
"minute": datetime.strftime("%M"),
|
|
||||||
"second": datetime.strftime("%S"),
|
|
||||||
# "time_offset": datetime.strftime("%z"),
|
|
||||||
"time_offset": "+0000",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
smtp_connection.sendmail(
|
|
||||||
parameters["sender"],
|
|
||||||
parameters["receivers"],
|
|
||||||
message.as_string()
|
|
||||||
)
|
|
||||||
smtp_connection.quit()
|
|
||||||
|
|
||||||
|
|
@ -1,90 +0,0 @@
|
||||||
class implementation_notification_channel_libnotify(interface_notification_channel):
|
|
||||||
|
|
||||||
'''
|
|
||||||
[implementation]
|
|
||||||
'''
|
|
||||||
def parameters_schema(self):
|
|
||||||
return {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": False,
|
|
||||||
"properties": {
|
|
||||||
"icon": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
'''
|
|
||||||
[implementation]
|
|
||||||
'''
|
|
||||||
def normalize_order_node(self, node):
|
|
||||||
return dict_merge(
|
|
||||||
{
|
|
||||||
"icon": "/usr/local/share/icons/heimdall.png",
|
|
||||||
},
|
|
||||||
node
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
'''
|
|
||||||
[implementation]
|
|
||||||
'''
|
|
||||||
def notify(self, parameters, name, data, state, info):
|
|
||||||
def condition_translate(condition):
|
|
||||||
return {
|
|
||||||
enum_condition.unknown: "normal",
|
|
||||||
enum_condition.ok: "low",
|
|
||||||
enum_condition.concerning: "normal",
|
|
||||||
enum_condition.critical: "critical",
|
|
||||||
}[condition]
|
|
||||||
parts = []
|
|
||||||
parts.append(
|
|
||||||
"notify-send"
|
|
||||||
)
|
|
||||||
## app name
|
|
||||||
parts.append(
|
|
||||||
string_coin(
|
|
||||||
"--app-name={{app_name}}",
|
|
||||||
{
|
|
||||||
"app_name": "heimdall",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
## urgency
|
|
||||||
parts.append(
|
|
||||||
string_coin(
|
|
||||||
"--urgency={{urgency}}",
|
|
||||||
{
|
|
||||||
"urgency": condition_translate(state["condition"]),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
## icon
|
|
||||||
if ("icon" not in parameters):
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
parts.append(
|
|
||||||
string_coin(
|
|
||||||
"--icon={{icon}}",
|
|
||||||
{
|
|
||||||
"icon": parameters["icon"],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
## summary
|
|
||||||
parts.append(
|
|
||||||
string_coin(
|
|
||||||
"{{condition}} | {{title}}",
|
|
||||||
{
|
|
||||||
"title": data["title"],
|
|
||||||
"condition": condition_show(state["condition"]).upper(),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
## body
|
|
||||||
parts.append(_json.dumps(info, ensure_ascii = False))
|
|
||||||
_subprocess.run(parts)
|
|
||||||
|
|
||||||
|
|
@ -1,178 +0,0 @@
|
||||||
class implementation_check_kind_generic_remote(interface_check_kind):
|
|
||||||
|
|
||||||
'''
|
|
||||||
[implementation]
|
|
||||||
'''
|
|
||||||
def parameters_schema(self):
|
|
||||||
return {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": False,
|
|
||||||
"properties": {
|
|
||||||
"host" : {
|
|
||||||
"type" : "string"
|
|
||||||
},
|
|
||||||
"ssh_port": {
|
|
||||||
"type": ["null", "integer"],
|
|
||||||
"default": None
|
|
||||||
},
|
|
||||||
"ssh_user" : {
|
|
||||||
"type" : ["null", "string"],
|
|
||||||
"default": None,
|
|
||||||
},
|
|
||||||
"ssh_key" : {
|
|
||||||
"type" : ["null", "string"],
|
|
||||||
"default": None,
|
|
||||||
},
|
|
||||||
"mount_point" : {
|
|
||||||
"type" : "string",
|
|
||||||
"default" : "/"
|
|
||||||
},
|
|
||||||
"threshold" : {
|
|
||||||
"type" : "integer",
|
|
||||||
"default" : 95,
|
|
||||||
"description" : "maximaler Füllstand in Prozent"
|
|
||||||
},
|
|
||||||
"critical": {
|
|
||||||
"description": "whether a violation of this check shall be leveled as critical instead of concerning",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": True
|
|
||||||
},
|
|
||||||
"strict": {
|
|
||||||
"deprecated": True,
|
|
||||||
"description": "alias for 'critical'",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": True
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"host",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
'''
|
|
||||||
[implementation]
|
|
||||||
'''
|
|
||||||
def normalize_order_node(self, node):
|
|
||||||
version = (
|
|
||||||
"v1"
|
|
||||||
if (not ("critical" in node)) else
|
|
||||||
"v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
if (version == "v1"):
|
|
||||||
if (not "host" in node):
|
|
||||||
raise ValueError("mandatory parameter \"host\" missing")
|
|
||||||
else:
|
|
||||||
node_ = dict_merge(
|
|
||||||
{
|
|
||||||
"ssh_port": None,
|
|
||||||
"ssh_user": None,
|
|
||||||
"ssh_key": None,
|
|
||||||
"mount_point": "/",
|
|
||||||
"threshold": 95,
|
|
||||||
"strict": False,
|
|
||||||
},
|
|
||||||
node
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
"ssh_port": node_["ssh_port"],
|
|
||||||
"ssh_user": node_["ssh_user"],
|
|
||||||
"ssh_key": node_["ssh_key"],
|
|
||||||
"mount_point": node_["ssh_path"],
|
|
||||||
"threshold": node_["ssh_threshold"],
|
|
||||||
"critical": node_["strict"],
|
|
||||||
}
|
|
||||||
elif (version == "v2"):
|
|
||||||
if (not "host" in node):
|
|
||||||
raise ValueError("mandatory parameter \"host\" missing")
|
|
||||||
else:
|
|
||||||
node_ = dict_merge(
|
|
||||||
{
|
|
||||||
"ssh_port": None,
|
|
||||||
"ssh_user": None,
|
|
||||||
"ssh_key": None,
|
|
||||||
"mount_point": "/",
|
|
||||||
"threshold": 95,
|
|
||||||
"critical": False,
|
|
||||||
},
|
|
||||||
node
|
|
||||||
)
|
|
||||||
return node_
|
|
||||||
else:
|
|
||||||
raise ValueError("unhandled")
|
|
||||||
|
|
||||||
|
|
||||||
'''
|
|
||||||
[implementation]
|
|
||||||
'''
|
|
||||||
def run(self, parameters):
|
|
||||||
inner_command = string_coin(
|
|
||||||
"df {{mount_point}} | tr -s \" \"",
|
|
||||||
{
|
|
||||||
"mount_point": parameters["mount_point"],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
outer_command_parts = []
|
|
||||||
if True:
|
|
||||||
outer_command_parts.append("ssh");
|
|
||||||
if True:
|
|
||||||
outer_command_parts.append(string_coin("{{host}}", {"host": parameters["host"]}));
|
|
||||||
if (parameters["ssh_port"] is not None):
|
|
||||||
outer_command_parts.append(string_coin("-p {{port}}", {"port": ("%u" % parameters["ssh_port"])}));
|
|
||||||
if (parameters["ssh_user"] is not None):
|
|
||||||
outer_command_parts.append(string_coin("-l {{user}}", {"user": parameters["ssh_user"]}));
|
|
||||||
if (parameters["ssh_key"] is not None):
|
|
||||||
outer_command_parts.append(string_coin("-i {{key}}", {"key": parameters["ssh_key"]}));
|
|
||||||
if True:
|
|
||||||
outer_command_parts.append(string_coin("-o BatchMode=yes", {}))
|
|
||||||
if True:
|
|
||||||
outer_command_parts.append(string_coin("'{{inner_command}}'", {"inner_command": inner_command}))
|
|
||||||
outer_command = " ".join(outer_command_parts)
|
|
||||||
|
|
||||||
result = shell_command(outer_command)
|
|
||||||
|
|
||||||
if (result["return_code"] > 0):
|
|
||||||
return {
|
|
||||||
"condition": enum_condition.unknown,
|
|
||||||
"info": {
|
|
||||||
"error": result["stderr"],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
stuff = result["stdout"].split("\n")[-2].split(" ")
|
|
||||||
data = {
|
|
||||||
"device": stuff[0],
|
|
||||||
"used": int(stuff[2]),
|
|
||||||
"avail": int(stuff[3]),
|
|
||||||
"perc": int(stuff[4][:-1]),
|
|
||||||
}
|
|
||||||
faults = []
|
|
||||||
if (data["perc"] > parameters["threshold"]):
|
|
||||||
faults.append(translation_get("checks.generic_remote.overflow"))
|
|
||||||
else:
|
|
||||||
pass
|
|
||||||
return {
|
|
||||||
"condition": (
|
|
||||||
enum_condition.ok
|
|
||||||
if (len(faults) <= 0) else
|
|
||||||
(
|
|
||||||
enum_condition.critical
|
|
||||||
if parameters["strict"] else
|
|
||||||
enum_condition.concerning
|
|
||||||
)
|
|
||||||
),
|
|
||||||
"info": {
|
|
||||||
"data": {
|
|
||||||
"host": parameters["host"],
|
|
||||||
"device": data["device"],
|
|
||||||
"mount_point": parameters["mount_point"],
|
|
||||||
"used": format_bytes(data["used"]),
|
|
||||||
"available": format_bytes(data["avail"]),
|
|
||||||
"percentage": (str(data["perc"]) + "%"),
|
|
||||||
},
|
|
||||||
"faults": faults
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,118 +0,0 @@
|
||||||
def dict_merge(core_dict, mantle_dict, recursive = False):
|
|
||||||
result_dict = {}
|
|
||||||
for current_dict in [core_dict, mantle_dict]:
|
|
||||||
for (key, value, ) in current_dict.items():
|
|
||||||
if (not (key in result_dict)):
|
|
||||||
result_dict[key] = value
|
|
||||||
else:
|
|
||||||
if (recursive and (type(result_dict[key]) == dict) and (type(value) == dict)):
|
|
||||||
result_dict[key] = dict_merge(result_dict[key], value)
|
|
||||||
elif (recursive and (type(result_dict[key]) == list) and (type(value) == list)):
|
|
||||||
result_dict[key] = (result_dict[key] + value)
|
|
||||||
else:
|
|
||||||
result_dict[key] = value
|
|
||||||
return result_dict
|
|
||||||
|
|
||||||
|
|
||||||
def file_read(path):
|
|
||||||
handle = open(path, "r")
|
|
||||||
content = handle.read()
|
|
||||||
handle.close()
|
|
||||||
return content
|
|
||||||
|
|
||||||
|
|
||||||
def file_write(path, content, options = None):
|
|
||||||
options = dict_merge(
|
|
||||||
{
|
|
||||||
"append": False,
|
|
||||||
},
|
|
||||||
({} if (options is None) else options)
|
|
||||||
)
|
|
||||||
handle = open(path, "a" if options["append"] else "w")
|
|
||||||
handle.write(content)
|
|
||||||
handle.close()
|
|
||||||
|
|
||||||
|
|
||||||
def string_coin(template, arguments):
|
|
||||||
result = template
|
|
||||||
for (key, value, ) in arguments.items():
|
|
||||||
result = result.replace("{{%s}}" % key, value)
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def get_current_timestamp():
|
|
||||||
return int(round(_time.time(), 0))
|
|
||||||
|
|
||||||
|
|
||||||
def env_get_language():
|
|
||||||
try:
|
|
||||||
env_lang = _os.environ.get("LANG")
|
|
||||||
locale = env_lang.split(".")[0]
|
|
||||||
language = locale.split("_")[0]
|
|
||||||
return language
|
|
||||||
except Exception as error:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def shell_command(command):
|
|
||||||
result = _subprocess.run(
|
|
||||||
command,
|
|
||||||
capture_output = True,
|
|
||||||
shell = True,
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
"return_code": result.returncode,
|
|
||||||
"stdout": result.stdout.decode(),
|
|
||||||
"stderr": result.stderr.decode(),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def format_bytes(bytes_):
|
|
||||||
units = [
|
|
||||||
{"label": "B", "digits": 0},
|
|
||||||
{"label": "KB", "digits": 1},
|
|
||||||
{"label": "MB", "digits": 1},
|
|
||||||
{"label": "GB", "digits": 1},
|
|
||||||
{"label": "TB", "digits": 1},
|
|
||||||
{"label": "PB", "digits": 1},
|
|
||||||
]
|
|
||||||
number = bytes_
|
|
||||||
index = 0
|
|
||||||
while ((number >= 1000) and (index < (len(units) - 1))):
|
|
||||||
number /= 1000
|
|
||||||
index += 1
|
|
||||||
return (
|
|
||||||
("%." + ("%u" % units[index]["digits"]) + "f %s")
|
|
||||||
% (
|
|
||||||
number,
|
|
||||||
units[index]["label"],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def sqlite_query_set(database_path, template, arguments):
|
|
||||||
connection = _sqlite3.connect(database_path)
|
|
||||||
cursor = connection.cursor()
|
|
||||||
result = cursor.execute(template, arguments)
|
|
||||||
connection.commit()
|
|
||||||
connection.close()
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def sqlite_query_put(database_path, template, arguments):
|
|
||||||
connection = _sqlite3.connect(database_path)
|
|
||||||
cursor = connection.cursor()
|
|
||||||
result = cursor.execute(template, arguments)
|
|
||||||
connection.commit()
|
|
||||||
connection.close()
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def sqlite_query_get(database_path, template, arguments):
|
|
||||||
connection = _sqlite3.connect(database_path)
|
|
||||||
cursor = connection.cursor()
|
|
||||||
result = cursor.execute(template, arguments)
|
|
||||||
rows = result.fetchall()
|
|
||||||
connection.close()
|
|
||||||
return rows
|
|
||||||
|
|
||||||
|
|
@ -1,388 +0,0 @@
|
||||||
def main():
|
|
||||||
## setup translation for the first time
|
|
||||||
translation_initialize("en", env_get_language())
|
|
||||||
|
|
||||||
## args
|
|
||||||
argumentparser = _argparse.ArgumentParser(
|
|
||||||
description = translation_get("help.title"),
|
|
||||||
formatter_class = _argparse.ArgumentDefaultsHelpFormatter
|
|
||||||
)
|
|
||||||
argumentparser.add_argument(
|
|
||||||
type = str,
|
|
||||||
default = "monitoring.hmdl.json",
|
|
||||||
dest = "order_path",
|
|
||||||
metavar = "<order-path>",
|
|
||||||
help = translation_get("help.args.order_path"),
|
|
||||||
)
|
|
||||||
argumentparser.add_argument(
|
|
||||||
"-x",
|
|
||||||
"--erase-state",
|
|
||||||
action = "store_true",
|
|
||||||
default = False,
|
|
||||||
dest = "erase_state",
|
|
||||||
help = translation_get("help.args.erase_state"),
|
|
||||||
)
|
|
||||||
argumentparser.add_argument(
|
|
||||||
"-s",
|
|
||||||
"--show-schema",
|
|
||||||
action = "store_true",
|
|
||||||
default = False,
|
|
||||||
dest = "show_schema",
|
|
||||||
help = translation_get("help.args.show_schema"),
|
|
||||||
)
|
|
||||||
argumentparser.add_argument(
|
|
||||||
"-e",
|
|
||||||
"--expose-full-order",
|
|
||||||
action = "store_true",
|
|
||||||
default = False,
|
|
||||||
dest = "expose_full_order",
|
|
||||||
help = translation_get("help.args.expose_full_order"),
|
|
||||||
)
|
|
||||||
### v conf stuff v
|
|
||||||
argumentparser.add_argument(
|
|
||||||
"-d",
|
|
||||||
"--database-path",
|
|
||||||
type = str,
|
|
||||||
default = None,
|
|
||||||
dest = "database_path",
|
|
||||||
metavar = "<database-path>",
|
|
||||||
help = translation_get("help.args.database_path"),
|
|
||||||
)
|
|
||||||
argumentparser.add_argument(
|
|
||||||
"-m",
|
|
||||||
"--mutex-path",
|
|
||||||
type = str,
|
|
||||||
default = "/tmp/heimdall.lock",
|
|
||||||
dest = "mutex_path",
|
|
||||||
metavar = "<mutex-path>",
|
|
||||||
help = translation_get("help.args.mutex_path"),
|
|
||||||
)
|
|
||||||
argumentparser.add_argument(
|
|
||||||
"-y",
|
|
||||||
"--send-ok-notifications",
|
|
||||||
action = "store_true",
|
|
||||||
default = False,
|
|
||||||
dest = "send_ok_notifications",
|
|
||||||
help = translation_get("help.args.send_ok_notifications", {"condition_name": translation_get("conditions.ok")}),
|
|
||||||
)
|
|
||||||
argumentparser.add_argument(
|
|
||||||
"-l",
|
|
||||||
"--language",
|
|
||||||
type = str,
|
|
||||||
choices = localization_data.keys(),
|
|
||||||
default = None,
|
|
||||||
dest = "language",
|
|
||||||
metavar = "<language>",
|
|
||||||
help = translation_get("help.args.language"),
|
|
||||||
)
|
|
||||||
argumentparser.add_argument(
|
|
||||||
"-t",
|
|
||||||
"--time-to-live",
|
|
||||||
type = int,
|
|
||||||
default = (60 * 60 * 24 * 7),
|
|
||||||
dest = "time_to_live",
|
|
||||||
metavar = "<time-to-live>",
|
|
||||||
help = translation_get("help.args.time_to_live"),
|
|
||||||
)
|
|
||||||
args = argumentparser.parse_args()
|
|
||||||
|
|
||||||
## vars
|
|
||||||
id_ = _hashlib.sha256(_os.path.abspath(args.order_path).encode("ascii")).hexdigest()[:8]
|
|
||||||
database_path = (
|
|
||||||
args.database_path
|
|
||||||
if (args.database_path is not None) else
|
|
||||||
_os.path.join(
|
|
||||||
_tempfile.gettempdir(),
|
|
||||||
string_coin("monitoring-state-{{id}}.sqlite", {"id": id_})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
## exec
|
|
||||||
|
|
||||||
### setup translation for the second time
|
|
||||||
if (args.language is not None):
|
|
||||||
translation_initialize("en", args.language)
|
|
||||||
|
|
||||||
### load check kind implementations
|
|
||||||
check_kind_implementations = {
|
|
||||||
"script": implementation_check_kind_script(),
|
|
||||||
"file_state": implementation_check_kind_file_state(),
|
|
||||||
"tls_certificate": implementation_check_kind_tls_certificate(),
|
|
||||||
"http_request": implementation_check_kind_http_request(),
|
|
||||||
"generic_remote" : implementation_check_kind_generic_remote(),
|
|
||||||
}
|
|
||||||
|
|
||||||
### load notification channel implementations
|
|
||||||
notification_channel_implementations = {
|
|
||||||
"console": implementation_notification_channel_console(),
|
|
||||||
"email": implementation_notification_channel_email(),
|
|
||||||
"libnotify": implementation_notification_channel_libnotify(),
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args.show_schema):
|
|
||||||
_sys.stdout.write(
|
|
||||||
_json.dumps(
|
|
||||||
order_schema_root(
|
|
||||||
check_kind_implementations,
|
|
||||||
notification_channel_implementations
|
|
||||||
),
|
|
||||||
indent = "\t"
|
|
||||||
)
|
|
||||||
+
|
|
||||||
"\n"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
### get order data
|
|
||||||
order = order_load(
|
|
||||||
check_kind_implementations,
|
|
||||||
notification_channel_implementations,
|
|
||||||
_os.path.abspath(args.order_path)
|
|
||||||
)
|
|
||||||
|
|
||||||
if (args.expose_full_order):
|
|
||||||
_sys.stdout.write(_json.dumps(order, indent = "\t") + "\n")
|
|
||||||
_sys.exit(1)
|
|
||||||
else:
|
|
||||||
_sys.stderr.write(
|
|
||||||
string_coin(
|
|
||||||
"[info] {{label}}: {{path}}\n",
|
|
||||||
{
|
|
||||||
"label": translation_get("misc.state_file_path"),
|
|
||||||
"path": database_path,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
### mutex check
|
|
||||||
if (_os.path.exists(args.mutex_path)):
|
|
||||||
_sys.stderr.write(
|
|
||||||
string_coin(
|
|
||||||
"[error] {{message}} ({{path}})\n",
|
|
||||||
{
|
|
||||||
"message": translation_get("misc.still_running"),
|
|
||||||
"path": args.mutex_path,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
_sys.exit(2)
|
|
||||||
else:
|
|
||||||
### setup database
|
|
||||||
sqlite_query_set(
|
|
||||||
database_path,
|
|
||||||
"CREATE TABLE IF NOT EXISTS results(check_name TEXT NOT NULL, timestamp INTEGER NOT NULL, condition TEXT NOT NULL, notification_sent BOOLEAN NOT NULL, info TEXT NOT NULL);",
|
|
||||||
{}
|
|
||||||
)
|
|
||||||
|
|
||||||
### clean database
|
|
||||||
result = sqlite_query_put(
|
|
||||||
database_path,
|
|
||||||
"DELETE FROM results WHERE ((timestamp < :timestamp_min) OR :erase_state);",
|
|
||||||
{
|
|
||||||
"timestamp_min": (get_current_timestamp() - args.time_to_live),
|
|
||||||
"erase_state": args.erase_state,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
_sys.stderr.write(
|
|
||||||
string_coin(
|
|
||||||
"[info] {{text}}\n",
|
|
||||||
{
|
|
||||||
"text": translation_get(
|
|
||||||
"misc.cleanup_info",
|
|
||||||
{
|
|
||||||
"count": ("%u" % result.rowcount),
|
|
||||||
}
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
file_write(args.mutex_path, "", {"append": True})
|
|
||||||
|
|
||||||
### iterate through checks
|
|
||||||
for check_data in order["checks"]:
|
|
||||||
if (not check_data["active"]):
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
### get old state and examine whether the check shall be executed
|
|
||||||
rows1 = sqlite_query_get(
|
|
||||||
database_path,
|
|
||||||
"SELECT MAX(timestamp) FROM results WHERE (check_name = :check_name) AND (notification_sent = TRUE);",
|
|
||||||
{
|
|
||||||
"check_name": check_data["name"],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
rows2 = sqlite_query_get(
|
|
||||||
database_path,
|
|
||||||
"SELECT timestamp, condition, notification_sent FROM results WHERE (check_name = :check_name) ORDER BY timestamp DESC LIMIT :limit;",
|
|
||||||
{
|
|
||||||
"check_name": check_data["name"],
|
|
||||||
"limit": (check_data["threshold"] + 1),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
if (len(rows2) <= 0):
|
|
||||||
old_item_state = None
|
|
||||||
else:
|
|
||||||
count = 1
|
|
||||||
for row in rows2[1:]:
|
|
||||||
if (row[1] == rows2[0][1]):
|
|
||||||
count += 1
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
if (count > check_data["threshold"]):
|
|
||||||
count = None
|
|
||||||
else:
|
|
||||||
pass
|
|
||||||
old_item_state = {
|
|
||||||
"timestamp": rows2[0][0],
|
|
||||||
"condition": condition_decode(rows2[0][1]),
|
|
||||||
"count": count,
|
|
||||||
"last_notification_timestamp": rows1[0][0],
|
|
||||||
}
|
|
||||||
|
|
||||||
timestamp = get_current_timestamp()
|
|
||||||
due = (
|
|
||||||
(old_item_state is None)
|
|
||||||
or
|
|
||||||
(old_item_state["condition"] != enum_condition.ok)
|
|
||||||
or
|
|
||||||
((timestamp - old_item_state["timestamp"]) >= check_data["schedule"]["regular_interval"])
|
|
||||||
or
|
|
||||||
(
|
|
||||||
(old_item_state["count"] is not None)
|
|
||||||
and
|
|
||||||
((timestamp - old_item_state["timestamp"]) >= check_data["schedule"]["attentive_interval"])
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if (not due):
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
_sys.stderr.write(
|
|
||||||
string_coin(
|
|
||||||
"-- {{check_name}}\n",
|
|
||||||
{
|
|
||||||
"check_name": check_data["name"],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
### execute check and set new state
|
|
||||||
try:
|
|
||||||
result = check_kind_implementations[check_data["kind"]].run(check_data["parameters"])
|
|
||||||
except Exception as error:
|
|
||||||
result = {
|
|
||||||
"condition": enum_condition.unknown,
|
|
||||||
"info": {
|
|
||||||
"cause": translation_get("misc.check_procedure_failed"),
|
|
||||||
"error": str(error),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
count = (
|
|
||||||
1
|
|
||||||
if (
|
|
||||||
(old_item_state is None)
|
|
||||||
or
|
|
||||||
(old_item_state["condition"] != result["condition"])
|
|
||||||
) else
|
|
||||||
(
|
|
||||||
(old_item_state["count"] + 1)
|
|
||||||
if (
|
|
||||||
(old_item_state["count"] is not None)
|
|
||||||
and
|
|
||||||
((old_item_state["count"] + 1) <= check_data["threshold"])
|
|
||||||
) else
|
|
||||||
None
|
|
||||||
)
|
|
||||||
)
|
|
||||||
shall_send_notification = (
|
|
||||||
(
|
|
||||||
(
|
|
||||||
(count is not None)
|
|
||||||
and
|
|
||||||
(count == check_data["threshold"])
|
|
||||||
)
|
|
||||||
or
|
|
||||||
(
|
|
||||||
(count is None)
|
|
||||||
and
|
|
||||||
check_data["annoy"]
|
|
||||||
)
|
|
||||||
or
|
|
||||||
(
|
|
||||||
(count is None)
|
|
||||||
and
|
|
||||||
(
|
|
||||||
(old_item_state is not None)
|
|
||||||
and
|
|
||||||
(old_item_state["last_notification_timestamp"] is not None)
|
|
||||||
and
|
|
||||||
(check_data["schedule"]["reminding_interval"] is not None)
|
|
||||||
and
|
|
||||||
(
|
|
||||||
(timestamp - old_item_state["last_notification_timestamp"])
|
|
||||||
>=
|
|
||||||
check_data["schedule"]["reminding_interval"]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
and
|
|
||||||
(
|
|
||||||
(result["condition"] != enum_condition.ok)
|
|
||||||
or
|
|
||||||
args.send_ok_notifications
|
|
||||||
)
|
|
||||||
)
|
|
||||||
new_item_state = {
|
|
||||||
"timestamp": timestamp,
|
|
||||||
"condition": result["condition"],
|
|
||||||
"count": count,
|
|
||||||
"last_notification_timestamp": (
|
|
||||||
timestamp
|
|
||||||
if shall_send_notification else
|
|
||||||
(
|
|
||||||
None
|
|
||||||
if (old_item_state is None) else
|
|
||||||
old_item_state["last_notification_timestamp"]
|
|
||||||
)
|
|
||||||
),
|
|
||||||
}
|
|
||||||
sqlite_query_put(
|
|
||||||
database_path,
|
|
||||||
"INSERT INTO results(check_name, timestamp, condition, notification_sent, info) VALUES (:check_name, :timestamp, :condition, :notification_sent, :info);",
|
|
||||||
{
|
|
||||||
"check_name": check_data["name"],
|
|
||||||
"timestamp": timestamp,
|
|
||||||
"condition": condition_encode(result["condition"]),
|
|
||||||
"notification_sent": shall_send_notification,
|
|
||||||
"info": _json.dumps(result["info"]),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
### send notifications
|
|
||||||
if (not shall_send_notification):
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
for notification in check_data["notifications"]:
|
|
||||||
notification_channel_implementations[notification["kind"]].notify(
|
|
||||||
notification["parameters"],
|
|
||||||
check_data["name"],
|
|
||||||
check_data,
|
|
||||||
new_item_state,
|
|
||||||
dict_merge(
|
|
||||||
(
|
|
||||||
{}
|
|
||||||
if (check_data["custom"] is None) else
|
|
||||||
{"custom": check_data["custom"]}
|
|
||||||
),
|
|
||||||
result["info"]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if (not _os.path.exists(args.mutex_path)):
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
_os.remove(args.mutex_path)
|
|
||||||
|
|
||||||
|
|
||||||
main()
|
|
||||||
|
|
||||||
|
|
@ -1,3 +1,9 @@
|
||||||
|
/**
|
||||||
|
* @todo outsource
|
||||||
|
*/
|
||||||
|
declare var __dirname;
|
||||||
|
|
||||||
|
|
||||||
namespace _heimdall
|
namespace _heimdall
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -111,11 +111,12 @@ namespace _heimdall.check_kinds.generic_remote
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
|
"host": node_["host"],
|
||||||
"ssh_port": node_["ssh_port"],
|
"ssh_port": node_["ssh_port"],
|
||||||
"ssh_user": node_["ssh_user"],
|
"ssh_user": node_["ssh_user"],
|
||||||
"ssh_key": node_["ssh_key"],
|
"ssh_key": node_["ssh_key"],
|
||||||
"mount_point": node_["ssh_path"],
|
"mount_point": node_["mount_point"],
|
||||||
"threshold": node_["ssh_threshold"],
|
"threshold": node_["threshold"],
|
||||||
"critical": node_["strict"],
|
"critical": node_["strict"],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -185,49 +186,21 @@ namespace _heimdall.check_kinds.generic_remote
|
||||||
}
|
}
|
||||||
const outer_command : string = outer_command_parts.join(" ");
|
const outer_command : string = outer_command_parts.join(" ");
|
||||||
|
|
||||||
type type_result = {
|
const shell_exec_result : _heimdall.helpers.misc.type_shell_exec_result = await _heimdall.helpers.misc.shell_exec(
|
||||||
return_code : int;
|
outer_command_parts[0],
|
||||||
stdout : string;
|
outer_command_parts.slice(1)
|
||||||
stderr : string;
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* @see https://nodejs.org/api/child_process.html#child_processspawncommand-args-options
|
|
||||||
*/
|
|
||||||
const result : type_result = await new Promise<type_result>(
|
|
||||||
(resolve, reject) => {
|
|
||||||
nm_child_process.spawnSync(
|
|
||||||
outer_command,
|
|
||||||
[],
|
|
||||||
{
|
|
||||||
},
|
|
||||||
(result) => {
|
|
||||||
if (result.error) {
|
|
||||||
reject(result.error);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
resolve(
|
|
||||||
{
|
|
||||||
"return_code": result.status,
|
|
||||||
"stdout": result.stdout,
|
|
||||||
"stderr": result.stdin,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.return_code > 0) {
|
if (shell_exec_result.return_code > 0) {
|
||||||
return {
|
return {
|
||||||
"condition": _heimdall.enum_condition.unknown,
|
"condition": _heimdall.enum_condition.unknown,
|
||||||
"info": {
|
"info": {
|
||||||
"error": result.stderr,
|
"error": shell_exec_result.stderr,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const stuff : Array<string> = result.stdout.split("\n").slice(-2)[0].split(" ");
|
const stuff : Array<string> = shell_exec_result.stdout.split("\n").slice(-2)[0].split(" ");
|
||||||
const data = {
|
const data = {
|
||||||
"device": stuff[0],
|
"device": stuff[0],
|
||||||
"used": parseInt(stuff[2]),
|
"used": parseInt(stuff[2]),
|
||||||
|
|
|
||||||
|
|
@ -194,12 +194,21 @@ namespace _heimdall.check_kinds.http_request
|
||||||
) : Promise<_heimdall.type_result>
|
) : Promise<_heimdall.type_result>
|
||||||
{
|
{
|
||||||
let error : (null | Error);
|
let error : (null | Error);
|
||||||
|
const url : URL = new URL(parameters.request.target);
|
||||||
const http_request : lib_plankton.http.type_request = {
|
const http_request : lib_plankton.http.type_request = {
|
||||||
"target": parameters.request.target,
|
"version": "HTTP/1.1",
|
||||||
|
"scheme": (
|
||||||
|
(url.protocol.slice(0, -1) === "https")
|
||||||
|
? "https"
|
||||||
|
: "http"
|
||||||
|
),
|
||||||
|
"host": url.host,
|
||||||
"method": {
|
"method": {
|
||||||
"GET": lib_plankton.http.enum_method.get,
|
"GET": lib_plankton.http.enum_method.get,
|
||||||
"POST": lib_plankton.http.enum_method.post,
|
"POST": lib_plankton.http.enum_method.post,
|
||||||
}[parameters.request.method],
|
}[parameters.request.method],
|
||||||
|
"path": url.pathname,
|
||||||
|
"query": url.search,
|
||||||
"headers": {},
|
"headers": {},
|
||||||
"body": "",
|
"body": "",
|
||||||
};
|
};
|
||||||
|
|
@ -208,9 +217,9 @@ namespace _heimdall.check_kinds.http_request
|
||||||
http_response = await lib_plankton.http.call(
|
http_response = await lib_plankton.http.call(
|
||||||
http_request,
|
http_request,
|
||||||
{
|
{
|
||||||
"timeout": /*parameters.timeout*/20.0,
|
"timeout": parameters.timeout,
|
||||||
"follow_redirects": parameters.follow_redirects,
|
"follow_redirects": parameters.follow_redirects,
|
||||||
"implementation": "http_module",
|
"implementation": "fetch",
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
error = null;
|
error = null;
|
||||||
|
|
@ -247,12 +256,12 @@ namespace _heimdall.check_kinds.http_request
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const status_code_expected : int = (parameters.response.status_code as int);
|
const status_code_expected : int = (parameters.response.status_code as int);
|
||||||
if (! (http_response.statuscode === status_code_expected)) {
|
if (! (http_response.status_code === status_code_expected)) {
|
||||||
faults.push(
|
faults.push(
|
||||||
lib_plankton.translate.get(
|
lib_plankton.translate.get(
|
||||||
"checks.http_request.status_code_mismatch",
|
"checks.http_request.status_code_mismatch",
|
||||||
{
|
{
|
||||||
"status_code_actual": http_response.statuscode.toFixed(0),
|
"status_code_actual": http_response.status_code.toFixed(0),
|
||||||
"status_code_expected": status_code_expected.toFixed(0),
|
"status_code_expected": status_code_expected.toFixed(0),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -327,7 +336,7 @@ namespace _heimdall.check_kinds.http_request
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const body_part : string = (parameters.response.body_part as string);
|
const body_part : string = (parameters.response.body_part as string);
|
||||||
if (! http_response.body.includes(body_part)) {
|
if (! http_response.body.toString().includes(body_part)) {
|
||||||
faults.push(
|
faults.push(
|
||||||
lib_plankton.translate.get(
|
lib_plankton.translate.get(
|
||||||
"checks.http_request.body_misses_part",
|
"checks.http_request.body_misses_part",
|
||||||
|
|
@ -356,7 +365,7 @@ namespace _heimdall.check_kinds.http_request
|
||||||
"info": {
|
"info": {
|
||||||
"request": parameters.request,
|
"request": parameters.request,
|
||||||
"response": {
|
"response": {
|
||||||
"status_code": http_response.statuscode,
|
"status_code": http_response.status_code,
|
||||||
"headers": http_response.headers,
|
"headers": http_response.headers,
|
||||||
// "body": http_response.body,
|
// "body": http_response.body,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -54,73 +54,63 @@ namespace _heimdall.check_kinds.script
|
||||||
parameters
|
parameters
|
||||||
) : Promise<_heimdall.type_result>
|
) : Promise<_heimdall.type_result>
|
||||||
{
|
{
|
||||||
const nm_child_process = require("child_process");
|
let shell_exec_result : (null | _heimdall.helpers.misc.type_shell_exec_result);
|
||||||
|
let error : any;
|
||||||
type type_result = {
|
try {
|
||||||
return_code : int;
|
shell_exec_result = await _heimdall.helpers.misc.shell_exec(
|
||||||
stdout : string;
|
parameters["path"],
|
||||||
stderr : string;
|
parameters["arguments"],
|
||||||
};
|
);
|
||||||
/**
|
error = null;
|
||||||
* @see https://nodejs.org/api/child_process.html#child_processspawncommand-args-options
|
}
|
||||||
*/
|
catch (error_) {
|
||||||
const result : type_result = await new Promise<type_result>(
|
shell_exec_result = null;
|
||||||
(resolve, reject) => {
|
error = error_;
|
||||||
nm_child_process.spawnSync(
|
}
|
||||||
parameters["path"],
|
|
||||||
parameters["arguments"],
|
|
||||||
{
|
|
||||||
},
|
|
||||||
(result) => {
|
|
||||||
if (result.error) {
|
|
||||||
reject(result.error);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
resolve(
|
|
||||||
{
|
|
||||||
"return_code": result.status,
|
|
||||||
"stdout": result.stdout,
|
|
||||||
"stderr": result.stdin,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
let condition : _heimdall.enum_condition;
|
let condition : _heimdall.enum_condition;
|
||||||
switch (result.return_code) {
|
if ((error !== null) || (shell_exec_result === null)) {
|
||||||
default: {
|
lib_plankton.log.notice(
|
||||||
lib_plankton.log.notice(
|
lib_plankton.translate.get("checks.script.execution_failed"),
|
||||||
lib_plankton.translate.get("check_kind_script_invalid_return_code"),
|
{
|
||||||
{
|
"error": String(error),
|
||||||
"return_code": result.return_code,
|
}
|
||||||
}
|
);
|
||||||
);
|
condition = _heimdall.enum_condition.unknown;
|
||||||
condition = _heimdall.enum_condition.unknown;
|
}
|
||||||
break;
|
else {
|
||||||
}
|
switch (shell_exec_result.return_code) {
|
||||||
case 0: {
|
default: {
|
||||||
condition = _heimdall.enum_condition.ok;
|
lib_plankton.log.notice(
|
||||||
break;
|
lib_plankton.translate.get("checks.script.invalid_return_code"),
|
||||||
}
|
{
|
||||||
case 1: {
|
"return_code": shell_exec_result.return_code,
|
||||||
condition = _heimdall.enum_condition.unknown;
|
}
|
||||||
break;
|
);
|
||||||
}
|
condition = _heimdall.enum_condition.unknown;
|
||||||
case 2: {
|
break;
|
||||||
condition = _heimdall.enum_condition.concerning;
|
}
|
||||||
break;
|
case 0: {
|
||||||
}
|
condition = _heimdall.enum_condition.ok;
|
||||||
case 3: {
|
break;
|
||||||
condition = _heimdall.enum_condition.critical;
|
}
|
||||||
break;
|
case 1: {
|
||||||
|
condition = _heimdall.enum_condition.unknown;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 2: {
|
||||||
|
condition = _heimdall.enum_condition.concerning;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 3: {
|
||||||
|
condition = _heimdall.enum_condition.critical;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
"condition": condition,
|
"condition": condition,
|
||||||
"info": {
|
"info": {
|
||||||
"result": result,
|
"result": shell_exec_result,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,14 @@ namespace _heimdall.helpers.json_schema
|
||||||
}
|
}
|
||||||
&
|
&
|
||||||
(
|
(
|
||||||
|
{
|
||||||
|
type : "any";
|
||||||
|
default ?: any;
|
||||||
|
}
|
||||||
|
|
|
||||||
{
|
{
|
||||||
type : "null";
|
type : "null";
|
||||||
|
default ?: null;
|
||||||
}
|
}
|
||||||
|
|
|
|
||||||
{
|
{
|
||||||
|
|
@ -97,11 +103,13 @@ namespace _heimdall.helpers.json_schema
|
||||||
}
|
}
|
||||||
|
|
|
|
||||||
{
|
{
|
||||||
anyOf ?: Array<type_schema>;
|
anyOf : Array<type_schema>;
|
||||||
|
default ?: any;
|
||||||
}
|
}
|
||||||
|
|
|
|
||||||
{
|
{
|
||||||
allOf ?: Array<type_schema>;
|
allOf : Array<type_schema>;
|
||||||
|
default ?: any;
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -29,4 +29,59 @@ namespace _heimdall.helpers.misc
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
export type type_shell_exec_result = {return_code : int; stdout : string; stderr : string;};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see https://nodejs.org/api/child_process.html#child_processspawncommand-args-options
|
||||||
|
*/
|
||||||
|
export function shell_exec(
|
||||||
|
head : string,
|
||||||
|
args : Array<string>
|
||||||
|
) : Promise<type_shell_exec_result>
|
||||||
|
{
|
||||||
|
const nm_child_process = require("child_process");
|
||||||
|
|
||||||
|
return new Promise<type_shell_exec_result>(
|
||||||
|
(resolve, reject) => {
|
||||||
|
let stdout : string = "";
|
||||||
|
let stderr : string = "";
|
||||||
|
const proc = nm_child_process.spawn(
|
||||||
|
head,
|
||||||
|
args,
|
||||||
|
{
|
||||||
|
},
|
||||||
|
);
|
||||||
|
proc.stdout.on(
|
||||||
|
"data",
|
||||||
|
(data) => {
|
||||||
|
stdout += data;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
proc.stderr.on(
|
||||||
|
"data",
|
||||||
|
(data) => {
|
||||||
|
stderr += data;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
proc.on(
|
||||||
|
"close",
|
||||||
|
(return_code) => {
|
||||||
|
resolve(
|
||||||
|
{
|
||||||
|
"return_code": return_code,
|
||||||
|
"stdout": stdout,
|
||||||
|
"stderr": stderr,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ async function main(
|
||||||
{
|
{
|
||||||
// consts
|
// consts
|
||||||
const version : string = "0.8";
|
const version : string = "0.8";
|
||||||
|
const workdir : string = __dirname;
|
||||||
|
|
||||||
// init translations
|
// init translations
|
||||||
// TODO: use env language
|
// TODO: use env language
|
||||||
|
|
@ -12,8 +13,8 @@ async function main(
|
||||||
"order": ["de", "en"],
|
"order": ["de", "en"],
|
||||||
"packages": await Promise.all(
|
"packages": await Promise.all(
|
||||||
[
|
[
|
||||||
{"identifier": "de", "path": "localization/de.json"},
|
{"identifier": "de", "path": (workdir + "/localization/de.json")},
|
||||||
{"identifier": "en", "path": "localization/en.json"},
|
{"identifier": "en", "path": (workdir + "/localization/en.json")},
|
||||||
]
|
]
|
||||||
.map(
|
.map(
|
||||||
(entry) => (
|
(entry) => (
|
||||||
|
|
@ -65,7 +66,7 @@ async function main(
|
||||||
"default": false,
|
"default": false,
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"indicators_long": ["version"],
|
"indicators_long": ["version"],
|
||||||
"indicators_short": ["v"],
|
"indicators_short": ["r"],
|
||||||
},
|
},
|
||||||
"info": lib_plankton.translate.get("help.args.show_version"),
|
"info": lib_plankton.translate.get("help.args.show_version"),
|
||||||
}
|
}
|
||||||
|
|
@ -243,6 +244,8 @@ async function main(
|
||||||
else {
|
else {
|
||||||
const notification_kind_implementations : Record<string, _heimdall.notification_kinds.type_notification_kind> = {
|
const notification_kind_implementations : Record<string, _heimdall.notification_kinds.type_notification_kind> = {
|
||||||
"console": _heimdall.notification_kinds.console.notification_kind_implementation(),
|
"console": _heimdall.notification_kinds.console.notification_kind_implementation(),
|
||||||
|
"email": _heimdall.notification_kinds.email.notification_kind_implementation(),
|
||||||
|
"libnotify": _heimdall.notification_kinds.libnotify.notification_kind_implementation(),
|
||||||
};
|
};
|
||||||
const check_kind_implementations : Record<string, _heimdall.check_kinds.type_check_kind> = {
|
const check_kind_implementations : Record<string, _heimdall.check_kinds.type_check_kind> = {
|
||||||
"script": _heimdall.check_kinds.script.check_kind_implementation(),
|
"script": _heimdall.check_kinds.script.check_kind_implementation(),
|
||||||
|
|
@ -341,208 +344,206 @@ async function main(
|
||||||
// create mutex file
|
// create mutex file
|
||||||
await lib_plankton.file.write(args["mutex_path"], "");
|
await lib_plankton.file.write(args["mutex_path"], "");
|
||||||
|
|
||||||
order.checks.forEach(
|
for await (const check of order.checks) {
|
||||||
async (check) => {
|
if (! check.active) {
|
||||||
if (! check.active) {
|
// do nothing
|
||||||
// do nothing
|
}
|
||||||
|
else {
|
||||||
|
let old_item_state : (null | _heimdall.type_item_state);
|
||||||
|
|
||||||
|
const last_notification_timestamp : (null | int) = await _heimdall.state_repository.get_last_notification_timestamp(
|
||||||
|
database_path,
|
||||||
|
check.name
|
||||||
|
);
|
||||||
|
|
||||||
|
const rows : Array<
|
||||||
|
{
|
||||||
|
timestamp : int;
|
||||||
|
condition : _heimdall.enum_condition;
|
||||||
|
notification_sent : boolean;
|
||||||
|
}
|
||||||
|
> = await _heimdall.state_repository.probe(
|
||||||
|
database_path,
|
||||||
|
check.name,
|
||||||
|
(check.threshold + 1),
|
||||||
|
);
|
||||||
|
if (rows.length <= 0) {
|
||||||
|
old_item_state = null;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
let old_item_state : (null | _heimdall.type_item_state);
|
let count : int = 1;
|
||||||
|
rows.slice(1).some(
|
||||||
const last_notification_timestamp : (null | int) = await _heimdall.state_repository.get_last_notification_timestamp(
|
(row) => {
|
||||||
database_path,
|
if (row.condition === rows[0].condition) {
|
||||||
check.name
|
count += 1;
|
||||||
);
|
return true;
|
||||||
|
|
||||||
const rows : Array<
|
|
||||||
{
|
|
||||||
timestamp : int;
|
|
||||||
condition : _heimdall.enum_condition;
|
|
||||||
notification_sent : boolean;
|
|
||||||
}
|
|
||||||
> = await _heimdall.state_repository.probe(
|
|
||||||
database_path,
|
|
||||||
check.name,
|
|
||||||
(check.threshold + 1),
|
|
||||||
);
|
|
||||||
if (rows.length <= 0) {
|
|
||||||
old_item_state = null;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
let count : int = 1;
|
|
||||||
rows.slice(1).some(
|
|
||||||
(row) => {
|
|
||||||
if (row.condition === rows[0].condition) {
|
|
||||||
count += 1;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
if (count > check.threshold) {
|
|
||||||
count = null;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
old_item_state = {
|
|
||||||
"timestamp": rows[0].timestamp,
|
|
||||||
"condition": rows[0].condition,
|
|
||||||
"count": count,
|
|
||||||
"last_notification_timestamp": last_notification_timestamp,
|
|
||||||
}
|
|
||||||
|
|
||||||
const timestamp : int = _heimdall.get_current_timestamp();
|
|
||||||
const due : boolean = (
|
|
||||||
(old_item_state === null)
|
|
||||||
||
|
|
||||||
(old_item_state.condition !== _heimdall.enum_condition.ok)
|
|
||||||
||
|
|
||||||
((timestamp - old_item_state.timestamp) >= check.schedule.regular_interval)
|
|
||||||
||
|
|
||||||
(
|
|
||||||
(! (old_item_state.count === null))
|
|
||||||
&&
|
|
||||||
((timestamp - old_item_state.timestamp) >= check.schedule.attentive_interval)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
if (! due) {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
process.stderr.write(
|
|
||||||
lib_plankton.string.coin(
|
|
||||||
"-- {{check_name}}\n",
|
|
||||||
{
|
|
||||||
"check_name": check.name,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// execute check and set new state
|
|
||||||
let result;
|
|
||||||
try {
|
|
||||||
result = await check_kind_implementations[check.kind].run(check.parameters);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
result = {
|
|
||||||
"condition": _heimdall.enum_condition.unknown,
|
|
||||||
"info": {
|
|
||||||
"cause": lib_plankton.translate.get("misc.check_procedure_failed"),
|
|
||||||
"error": error.toString(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const count : (null | int) = (
|
|
||||||
(
|
|
||||||
(old_item_state === null)
|
|
||||||
||
|
|
||||||
(old_item_state.condition !== result.condition)
|
|
||||||
)
|
|
||||||
? 1
|
|
||||||
: (
|
|
||||||
(
|
|
||||||
(! (old_item_state.count === null))
|
|
||||||
&&
|
|
||||||
((old_item_state.count + 1) <= check.threshold)
|
|
||||||
)
|
|
||||||
? (old_item_state.count + 1)
|
|
||||||
: null
|
|
||||||
)
|
|
||||||
)
|
|
||||||
const shall_send_notification : boolean = (
|
|
||||||
(
|
|
||||||
(
|
|
||||||
(! (count === null))
|
|
||||||
&&
|
|
||||||
(count == check.threshold)
|
|
||||||
)
|
|
||||||
||
|
|
||||||
(
|
|
||||||
(count === null)
|
|
||||||
&&
|
|
||||||
check.annoy
|
|
||||||
)
|
|
||||||
||
|
|
||||||
(
|
|
||||||
(count === null)
|
|
||||||
&&
|
|
||||||
(
|
|
||||||
(! (old_item_state === null))
|
|
||||||
&&
|
|
||||||
(! (old_item_state.last_notification_timestamp === null))
|
|
||||||
&&
|
|
||||||
(! (check.schedule.reminding_interval === null))
|
|
||||||
&&
|
|
||||||
(
|
|
||||||
(timestamp - old_item_state.last_notification_timestamp)
|
|
||||||
>=
|
|
||||||
check.schedule.reminding_interval
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
&&
|
|
||||||
(
|
|
||||||
(result.condition !== _heimdall.enum_condition.ok)
|
|
||||||
||
|
|
||||||
args["send_ok_notifications"]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
const new_item_state : _heimdall.type_item_state = {
|
|
||||||
"timestamp": timestamp,
|
|
||||||
"condition": result.condition,
|
|
||||||
"count": count,
|
|
||||||
"last_notification_timestamp": (
|
|
||||||
shall_send_notification
|
|
||||||
? timestamp
|
|
||||||
: (
|
|
||||||
(old_item_state === null)
|
|
||||||
? null
|
|
||||||
: old_item_state.last_notification_timestamp
|
|
||||||
)
|
|
||||||
),
|
|
||||||
}
|
|
||||||
await _heimdall.state_repository.feed(
|
|
||||||
database_path,
|
|
||||||
check.name,
|
|
||||||
timestamp,
|
|
||||||
result.condition,
|
|
||||||
shall_send_notification,
|
|
||||||
result.info
|
|
||||||
);
|
|
||||||
|
|
||||||
// send notifications
|
|
||||||
if (! shall_send_notification) {
|
|
||||||
// do nothing
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
check.notifications.forEach(
|
return false;
|
||||||
notification => {
|
|
||||||
notification_kind_implementations[notification.kind].notify(
|
|
||||||
notification.parameters,
|
|
||||||
check.name,
|
|
||||||
check,
|
|
||||||
new_item_state,
|
|
||||||
Object.assign(
|
|
||||||
(
|
|
||||||
(check.custom === null)
|
|
||||||
? {}
|
|
||||||
: {"custom": check.custom}
|
|
||||||
),
|
|
||||||
result.info
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
if (count > check.threshold) {
|
||||||
|
count = null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
old_item_state = {
|
||||||
|
"timestamp": rows[0].timestamp,
|
||||||
|
"condition": rows[0].condition,
|
||||||
|
"count": count,
|
||||||
|
"last_notification_timestamp": last_notification_timestamp,
|
||||||
|
}
|
||||||
|
|
||||||
|
const timestamp : int = _heimdall.get_current_timestamp();
|
||||||
|
const due : boolean = (
|
||||||
|
(old_item_state === null)
|
||||||
|
||
|
||||||
|
(old_item_state.condition !== _heimdall.enum_condition.ok)
|
||||||
|
||
|
||||||
|
((timestamp - old_item_state.timestamp) >= check.schedule.regular_interval)
|
||||||
|
||
|
||||||
|
(
|
||||||
|
(! (old_item_state.count === null))
|
||||||
|
&&
|
||||||
|
((timestamp - old_item_state.timestamp) >= check.schedule.attentive_interval)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (! due) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
process.stderr.write(
|
||||||
|
lib_plankton.string.coin(
|
||||||
|
"-- {{check_name}}\n",
|
||||||
|
{
|
||||||
|
"check_name": check.name,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// execute check and set new state
|
||||||
|
let result;
|
||||||
|
try {
|
||||||
|
result = await check_kind_implementations[check.kind].run(check.parameters);
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
result = {
|
||||||
|
"condition": _heimdall.enum_condition.unknown,
|
||||||
|
"info": {
|
||||||
|
"cause": lib_plankton.translate.get("misc.check_procedure_failed"),
|
||||||
|
"error": error.toString(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const count : (null | int) = (
|
||||||
|
(
|
||||||
|
(old_item_state === null)
|
||||||
|
||
|
||||||
|
(old_item_state.condition !== result.condition)
|
||||||
|
)
|
||||||
|
? 1
|
||||||
|
: (
|
||||||
|
(
|
||||||
|
(! (old_item_state.count === null))
|
||||||
|
&&
|
||||||
|
((old_item_state.count + 1) <= check.threshold)
|
||||||
|
)
|
||||||
|
? (old_item_state.count + 1)
|
||||||
|
: null
|
||||||
|
)
|
||||||
|
)
|
||||||
|
const shall_send_notification : boolean = (
|
||||||
|
(
|
||||||
|
(
|
||||||
|
(! (count === null))
|
||||||
|
&&
|
||||||
|
(count == check.threshold)
|
||||||
|
)
|
||||||
|
||
|
||||||
|
(
|
||||||
|
(count === null)
|
||||||
|
&&
|
||||||
|
check.annoy
|
||||||
|
)
|
||||||
|
||
|
||||||
|
(
|
||||||
|
(count === null)
|
||||||
|
&&
|
||||||
|
(
|
||||||
|
(! (old_item_state === null))
|
||||||
|
&&
|
||||||
|
(! (old_item_state.last_notification_timestamp === null))
|
||||||
|
&&
|
||||||
|
(! (check.schedule.reminding_interval === null))
|
||||||
|
&&
|
||||||
|
(
|
||||||
|
(timestamp - old_item_state.last_notification_timestamp)
|
||||||
|
>=
|
||||||
|
check.schedule.reminding_interval
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
&&
|
||||||
|
(
|
||||||
|
(result.condition !== _heimdall.enum_condition.ok)
|
||||||
|
||
|
||||||
|
args["send_ok_notifications"]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
const new_item_state : _heimdall.type_item_state = {
|
||||||
|
"timestamp": timestamp,
|
||||||
|
"condition": result.condition,
|
||||||
|
"count": count,
|
||||||
|
"last_notification_timestamp": (
|
||||||
|
shall_send_notification
|
||||||
|
? timestamp
|
||||||
|
: (
|
||||||
|
(old_item_state === null)
|
||||||
|
? null
|
||||||
|
: old_item_state.last_notification_timestamp
|
||||||
|
)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
await _heimdall.state_repository.feed(
|
||||||
|
database_path,
|
||||||
|
check.name,
|
||||||
|
timestamp,
|
||||||
|
result.condition,
|
||||||
|
shall_send_notification,
|
||||||
|
result.info
|
||||||
|
);
|
||||||
|
|
||||||
|
// send notifications
|
||||||
|
if (! shall_send_notification) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
check.notifications.forEach(
|
||||||
|
notification => {
|
||||||
|
notification_kind_implementations[notification.kind].notify(
|
||||||
|
notification.parameters,
|
||||||
|
check.name,
|
||||||
|
check,
|
||||||
|
new_item_state,
|
||||||
|
Object.assign(
|
||||||
|
(
|
||||||
|
(check.custom === null)
|
||||||
|
? {}
|
||||||
|
: {"custom": check.custom}
|
||||||
|
),
|
||||||
|
result.info
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
}
|
||||||
|
|
||||||
// drop mutex file
|
// drop mutex file
|
||||||
await (
|
await (
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ namespace _heimdall.notification_kinds.console
|
||||||
lib_plankton.string.coin(
|
lib_plankton.string.coin(
|
||||||
"[{{title}}] <{{condition}}> {{info}}\n",
|
"[{{title}}] <{{condition}}> {{info}}\n",
|
||||||
{
|
{
|
||||||
"title": check.title,
|
"title": check.name,
|
||||||
"condition": _heimdall.condition_show(state.condition),
|
"condition": _heimdall.condition_show(state.condition),
|
||||||
"info": lib_plankton.json.encode(info, true),
|
"info": lib_plankton.json.encode(info, true),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
155
source/logic/notification_kinds/email.ts
Normal file
155
source/logic/notification_kinds/email.ts
Normal file
|
|
@ -0,0 +1,155 @@
|
||||||
|
namespace _heimdall.notification_kinds.email
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
function parameters_schema(
|
||||||
|
) : _heimdall.helpers.json_schema.type_schema
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"access": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"host": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"port": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"host",
|
||||||
|
"port",
|
||||||
|
"username",
|
||||||
|
"password"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"sender": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"receivers": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": {
|
||||||
|
"description": "list of strings, which will be placed in the e-mail subject",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"default": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"access",
|
||||||
|
"sender",
|
||||||
|
"receivers"
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
function normalize_order_node(
|
||||||
|
node : any
|
||||||
|
) : any
|
||||||
|
{
|
||||||
|
return Object.assign(
|
||||||
|
{
|
||||||
|
},
|
||||||
|
node
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
async function notify(
|
||||||
|
parameters : any,
|
||||||
|
name : string,
|
||||||
|
check : type_check,
|
||||||
|
state : type_item_state,
|
||||||
|
info : any
|
||||||
|
) : Promise<void>
|
||||||
|
{
|
||||||
|
const nm_nodemailer = require("nodemailer");
|
||||||
|
|
||||||
|
// datetime = _datetime.datetime.utcnow()
|
||||||
|
const now : Date = new Date(Date.now());
|
||||||
|
|
||||||
|
const transporter = nm_nodemailer.createTransport(
|
||||||
|
{
|
||||||
|
"host": parameters.access.host,
|
||||||
|
"port": parameters.access.port,
|
||||||
|
"secure": true,
|
||||||
|
"auth": {
|
||||||
|
"user": parameters.access.username,
|
||||||
|
"pass": parameters.access.password,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const send_result = await transporter.sendMail(
|
||||||
|
{
|
||||||
|
"from": parameters.sender,
|
||||||
|
"to": parameters.receivers.join(","),
|
||||||
|
"subject": lib_plankton.string.coin(
|
||||||
|
"{{tags}} {{title}}",
|
||||||
|
{
|
||||||
|
"tags": (
|
||||||
|
parameters.tags
|
||||||
|
.concat([_heimdall.condition_show(state.condition)])
|
||||||
|
.map(tag => lib_plankton.string.coin("[{{tag}}]", tag.toUpperCase()))
|
||||||
|
.join(" ")
|
||||||
|
),
|
||||||
|
"title": check.name,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
"text": JSON.stringify(info, undefined, "\t"),
|
||||||
|
"headers": {
|
||||||
|
"Date": lib_plankton.string.coin(
|
||||||
|
"{{day_of_week}}, {{day_of_month}} {{month}} {{year}} {{hour}}:{{minute}}:{{second}} {{time_offset}}",
|
||||||
|
{
|
||||||
|
"day_of_week": now.getDay().toFixed(0),
|
||||||
|
"day_of_month": now.getDate().toFixed(0),
|
||||||
|
"month": (now.getMonth() + 1).toFixed(0),
|
||||||
|
"year": now.getFullYear().toFixed(0),
|
||||||
|
"hour": now.getHours().toFixed(0),
|
||||||
|
"minute": now.getMinutes().toFixed(0),
|
||||||
|
"second": now.getSeconds().toFixed(0),
|
||||||
|
// "time_offset": (now.getTimezoneOffset() / 60).toFixed(0),
|
||||||
|
"time_offset": "+0000",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return Promise.resolve<void>(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
export function notification_kind_implementation(
|
||||||
|
) : type_notification_kind
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
"parameters_schema": parameters_schema,
|
||||||
|
"normalize_order_node": normalize_order_node,
|
||||||
|
"notify": notify,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
127
source/logic/notification_kinds/libnotify.ts
Normal file
127
source/logic/notification_kinds/libnotify.ts
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
namespace _heimdall.notification_kinds.libnotify
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
function parameters_schema(
|
||||||
|
) : _heimdall.helpers.json_schema.type_schema
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"icon": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
function normalize_order_node(
|
||||||
|
node : any
|
||||||
|
) : any
|
||||||
|
{
|
||||||
|
return Object.assign(
|
||||||
|
{
|
||||||
|
"icon": "/usr/local/share/icons/heimdall.png",
|
||||||
|
},
|
||||||
|
node
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
async function notify(
|
||||||
|
parameters : any,
|
||||||
|
name : string,
|
||||||
|
check : type_check,
|
||||||
|
state : type_item_state,
|
||||||
|
info : any
|
||||||
|
) : Promise<void>
|
||||||
|
{
|
||||||
|
const condition_translate = function (condition : _heimdall.enum_condition): string
|
||||||
|
{
|
||||||
|
switch (condition) {
|
||||||
|
case _heimdall.enum_condition.unknown: return "normal";
|
||||||
|
case _heimdall.enum_condition.ok: return "low";
|
||||||
|
case _heimdall.enum_condition.concerning: return "normal";
|
||||||
|
case _heimdall.enum_condition.critical: return "critical";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let parts : Array<string> = [];
|
||||||
|
parts.push(
|
||||||
|
"notify-send"
|
||||||
|
);
|
||||||
|
// app name
|
||||||
|
parts.push(
|
||||||
|
lib_plankton.string.coin(
|
||||||
|
"--app-name={{app_name}}",
|
||||||
|
{
|
||||||
|
"app_name": "heimdall",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
// urgency
|
||||||
|
parts.push(
|
||||||
|
lib_plankton.string.coin(
|
||||||
|
"--urgency={{urgency}}",
|
||||||
|
{
|
||||||
|
"urgency": condition_translate(state.condition),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
// icon
|
||||||
|
if (! ("icon" in parameters)) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
parts.push(
|
||||||
|
lib_plankton.string.coin(
|
||||||
|
"--icon={{icon}}",
|
||||||
|
{
|
||||||
|
"icon": parameters.icon,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// summary
|
||||||
|
parts.push(
|
||||||
|
lib_plankton.string.coin(
|
||||||
|
"{{condition}} | {{title}}",
|
||||||
|
{
|
||||||
|
"title": check.name,
|
||||||
|
"condition": _heimdall.condition_show(state.condition).toUpperCase(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
// body
|
||||||
|
parts.push(
|
||||||
|
JSON.stringify(info)
|
||||||
|
);
|
||||||
|
|
||||||
|
await _heimdall.helpers.misc.shell_exec(parts[0], parts.slice(1));
|
||||||
|
|
||||||
|
return Promise.resolve<void>(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
export function notification_kind_implementation(
|
||||||
|
) : type_notification_kind
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
"parameters_schema": parameters_schema,
|
||||||
|
"normalize_order_node": normalize_order_node,
|
||||||
|
"notify": notify,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -219,6 +219,7 @@ namespace _heimdall.order
|
||||||
},
|
},
|
||||||
"parameters": value.parameters_schema(),
|
"parameters": value.parameters_schema(),
|
||||||
"custom": {
|
"custom": {
|
||||||
|
"type": "any",
|
||||||
"description": "custom data, which shall be attached to notifications",
|
"description": "custom data, which shall be attached to notifications",
|
||||||
"default": null,
|
"default": null,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue