Compare commits
No commits in common. "c1863033bccc50f1b0d7209fc26eb89672894163" and "a6904ba37034e36ceae3dc3efa6e376ff20070a4" have entirely different histories.
c1863033bc
...
a6904ba370
6 changed files with 29 additions and 147 deletions
|
@ -1,69 +0,0 @@
|
||||||
{ lib }:
|
|
||||||
rec {
|
|
||||||
mkFqdn = _: cfg: cfg.networking.fqdn;
|
|
||||||
|
|
||||||
fromHive =
|
|
||||||
{
|
|
||||||
builder,
|
|
||||||
nodes,
|
|
||||||
excludes ? [ ],
|
|
||||||
}:
|
|
||||||
lib.mkMerge (
|
|
||||||
builtins.map (node: builder node nodes.${node}) (
|
|
||||||
lib.subtractLists excludes (builtins.attrNames nodes)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
pingProbesFromHive =
|
|
||||||
{
|
|
||||||
mkHost,
|
|
||||||
nodes,
|
|
||||||
prefix ? "Ping ",
|
|
||||||
excludes ? [ ],
|
|
||||||
tags ? [ ],
|
|
||||||
}:
|
|
||||||
fromHive {
|
|
||||||
builder = (
|
|
||||||
node: module: {
|
|
||||||
monitors = {
|
|
||||||
${prefix + node} = {
|
|
||||||
type = "ping";
|
|
||||||
inherit tags;
|
|
||||||
hostname = mkHost node module.config;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
inherit nodes excludes;
|
|
||||||
};
|
|
||||||
|
|
||||||
httpProbesFromConfig =
|
|
||||||
{
|
|
||||||
config,
|
|
||||||
excludes ? [ ],
|
|
||||||
prefix ? "",
|
|
||||||
type ? "keyword",
|
|
||||||
tags ? [ ],
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
filter = k: v: !builtins.elem k excludes && v.globalRedirect == null;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
monitors = lib.mapAttrs' (
|
|
||||||
vhostName: vhost:
|
|
||||||
let
|
|
||||||
hasSSL = vhost.onlySSL || vhost.enableSSL || vhost.addSSL || vhost.forceSSL;
|
|
||||||
serverName = if vhost.serverName != null then vhost.serverName else vhostName;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
name = prefix + serverName;
|
|
||||||
value = {
|
|
||||||
inherit type;
|
|
||||||
inherit tags;
|
|
||||||
url = "http${lib.optionalString hasSSL "s"}://${serverName}";
|
|
||||||
method = "get";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
) (lib.filterAttrs filter config.services.nginx.virtualHosts);
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -22,31 +22,29 @@ in
|
||||||
Extra arguments to use for executing `stateless-uptime-kuma`.
|
Extra arguments to use for executing `stateless-uptime-kuma`.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
lib = lib.mkOption { type = lib.types.raw; };
|
|
||||||
probesConfig = {
|
probesConfig = {
|
||||||
monitors = lib.mkOption {
|
monitors = lib.mkOption {
|
||||||
type = with lib.types; attrsOf probesFormat.type;
|
inherit (probesFormat) type;
|
||||||
default = { };
|
default = [ ];
|
||||||
};
|
};
|
||||||
tags = lib.mkOption {
|
tags = lib.mkOption {
|
||||||
type = with lib.types; attrsOf probesFormat.type;
|
inherit (probesFormat) type;
|
||||||
default = { };
|
default = [ ];
|
||||||
};
|
};
|
||||||
notifications = lib.mkOption {
|
notifications = lib.mkOption {
|
||||||
type = with lib.types; attrsOf probesFormat.type;
|
inherit (probesFormat) type;
|
||||||
default = { };
|
default = [ ];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
config.statelessUptimeKuma = {
|
config.statelessUptimeKuma = {
|
||||||
lib = import ../lib { inherit lib; };
|
|
||||||
build = {
|
build = {
|
||||||
json = probesFormat.generate "probes.json" cfg.probesConfig;
|
json = probesFormat.generate "probes.json" cfg.probesConfig;
|
||||||
script = pkgs.writeShellApplication {
|
script = pkgs.writeShellApplication {
|
||||||
name = "deploy-uptime-kuma-probes";
|
name = "deploy-uptime-kuma-probes";
|
||||||
runtimeInputs = [ pkgs.statelessUptimeKuma ];
|
runtimeInputs = [ pkgs.statelessUptimeKuma ];
|
||||||
text = ''
|
text = ''
|
||||||
stateless-uptime-kuma apply-json -f ${cfg.build.json} ${builtins.concatStringsSep " " cfg.extraFlags}
|
stateless-uptime-kuma apply-json -f ${cfg.build.json} ${cfg.extraFlags}
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -35,34 +35,19 @@ def cli():
|
||||||
help="Scrape keywords for http probe",
|
help="Scrape keywords for http probe",
|
||||||
default=False,
|
default=False,
|
||||||
)
|
)
|
||||||
@click.option(
|
def apply_json(file, scrape_http_keywords):
|
||||||
"--keywords-fallback/--no-keywords-fallback",
|
|
||||||
help="When scrapping keywords, fallback to http if no keywords found",
|
|
||||||
default=True,
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--no-autocreate-tags",
|
|
||||||
"-t",
|
|
||||||
is_flag=True,
|
|
||||||
help="Don't automatically create tags if not in tags section of input",
|
|
||||||
default=False,
|
|
||||||
)
|
|
||||||
def apply_json(file, scrape_http_keywords, no_autocreate_tags, keywords_fallback):
|
|
||||||
"""
|
"""
|
||||||
Apply json probes
|
Apply json probes
|
||||||
"""
|
"""
|
||||||
logger.debug(
|
|
||||||
f"Flags value:\n - scrape_http_keywords: {scrape_http_keywords}\n - no_autocreate_tags: {no_autocreate_tags}\n - keywords_fallback: {keywords_fallback}"
|
|
||||||
)
|
|
||||||
with UptimeKumaApi("http://localhost:3001") as api:
|
with UptimeKumaApi("http://localhost:3001") as api:
|
||||||
|
api.login("admin", "123456789a")
|
||||||
logging.debug("Reading json")
|
logging.debug("Reading json")
|
||||||
data = json.load(file)
|
data = json.load(file)
|
||||||
logging.debug("Parsing json")
|
logging.debug("Parsing json")
|
||||||
tree = from_dict(api, data, not no_autocreate_tags)
|
tree = from_dict(api, data)
|
||||||
if scrape_http_keywords:
|
if scrape_http_keywords:
|
||||||
hydrate_http_probes(tree)
|
hydrate_http_probes(tree)
|
||||||
logging.debug("Sync probes")
|
logging.debug("Sync probes")
|
||||||
api.login("admin", "123456789a")
|
|
||||||
Manager(api, tree).process()
|
Manager(api, tree).process()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -8,10 +8,10 @@ from uptime_kuma_api import MonitorType
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def hydrate_http_probes(tree, excludes=[], fallback_to_http = True):
|
def hydrate_http_probes(tree, excludes=[]):
|
||||||
for probe in tree.get("monitors", []):
|
for probe in tree.get("monitors", []):
|
||||||
if "type" not in probe.kwargs:
|
if "type" not in probe.kwargs:
|
||||||
logger.error(f"Fatal: probe {probe.name} must have a 'type' parameter")
|
logger.error("Fatal: probes must have a 'type' parameter")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
if (
|
if (
|
||||||
probe.kwargs["type"] == MonitorType.KEYWORD
|
probe.kwargs["type"] == MonitorType.KEYWORD
|
||||||
|
@ -28,20 +28,9 @@ def hydrate_http_probes(tree, excludes=[], fallback_to_http = True):
|
||||||
method = probe.kwargs["method"]
|
method = probe.kwargs["method"]
|
||||||
headers = probe.kwargs.get("headers")
|
headers = probe.kwargs.get("headers")
|
||||||
body = probe.kwargs.get("body")
|
body = probe.kwargs.get("body")
|
||||||
req = requests.request(
|
content = requests.request(method, url, headers=headers, data=body).text
|
||||||
method, url, headers=headers, data=body, allow_redirects=True
|
m = re.search("<title>(.*?)</title>", content)
|
||||||
)
|
|
||||||
req.encoding = req.apparent_encoding
|
|
||||||
if req.status_code not in probe.get_status_codes():
|
|
||||||
logger.error(f"{probe.name} is not returning the right status code")
|
|
||||||
m = re.search("(?s)<title[^>]*>(.*?)</title>", req.text)
|
|
||||||
if m is None:
|
if m is None:
|
||||||
logger.warn(
|
logger.info(f"Didn't find keywords for probe {probe.name}, skipping")
|
||||||
f"Didn't find keywords for probe {probe.name}, falling back on classic HTTP probe"
|
probe.kwargs["keyword"] = m.group(1)
|
||||||
)
|
logger.debug(f"New keyword: {m.group(1)}")
|
||||||
logging.debug(req.text)
|
|
||||||
if fallback_to_http:
|
|
||||||
probe.kwargs["type"] = "http"
|
|
||||||
else:
|
|
||||||
probe.kwargs["keyword"] = m.group(1).strip()
|
|
||||||
logger.debug(f"New keyword: {m.group(1).strip()}")
|
|
||||||
|
|
|
@ -17,33 +17,31 @@ def die_tag_format_error():
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
def from_dict(api, tree, autocreate_tags=True):
|
def from_dict(api, tree):
|
||||||
notif = tree.get("notifications", [])
|
notif = tree.get("notifications", [])
|
||||||
indexed_notifications = {
|
indexed_notifications = {n["name"]: Notification(api, **n) for n in notif}
|
||||||
name: Notification(api, name, **kwargs) for name, kwargs in notif.items()
|
|
||||||
}
|
|
||||||
tags = tree.get("tags", [])
|
tags = tree.get("tags", [])
|
||||||
indexed_tags = {name: Tag(api, name, **kwargs) for name, kwargs in tags.items()}
|
indexed_tags = {t["name"]: Tag(api, **t) for t in tags}
|
||||||
monitors = tree.get("monitors", [])
|
monitors = tree.get("monitors", [])
|
||||||
indexed_monitors = {}
|
indexed_monitors = {}
|
||||||
for monitor_name, monitor_kwargs in monitors.items():
|
for m in monitors:
|
||||||
associated_tags = []
|
associated_tags = []
|
||||||
for tag in monitor_kwargs.get("tags", []):
|
for tag in m.get("tags", []):
|
||||||
if not isinstance(tag, dict) or "name" not in tag:
|
if not isinstance(tag, dict) or "name" not in tag:
|
||||||
die_tag_format_error()
|
die_tag_format_error()
|
||||||
try:
|
try:
|
||||||
if autocreate_tags and tag["name"] not in indexed_tags:
|
|
||||||
indexed_tags[tag["name"]] = Tag(api, name=tag["name"])
|
|
||||||
associated_tags.append((indexed_tags[tag["name"]], tag.get("value")))
|
associated_tags.append((indexed_tags[tag["name"]], tag.get("value")))
|
||||||
except IndexError:
|
except IndexError:
|
||||||
die_tag_format_error()
|
die_tag_format_error()
|
||||||
monitor_kwargs["tags"] = associated_tags
|
m["tags"] = associated_tags
|
||||||
associated_notifications = [
|
associated_notifications = [
|
||||||
indexed_notifications[notif]
|
indexed_notifications[notif] for notif in m.get("notifications", [])
|
||||||
for notif in monitor_kwargs.get("notifications", [])
|
|
||||||
]
|
]
|
||||||
monitor_kwargs["notifications"] = associated_notifications
|
m["notifications"] = associated_notifications
|
||||||
indexed_monitors[monitor_name] = Monitor(api, monitor_name, **monitor_kwargs)
|
if "name" not in m:
|
||||||
|
logger.error("Fatal: All monitors must have a name")
|
||||||
|
sys.exit(1)
|
||||||
|
indexed_monitors[m["name"]] = Monitor(api, **m)
|
||||||
return {
|
return {
|
||||||
"monitors": indexed_monitors.values(),
|
"monitors": indexed_monitors.values(),
|
||||||
"tags": indexed_tags.values(),
|
"tags": indexed_tags.values(),
|
||||||
|
|
|
@ -3,7 +3,6 @@ Classes to make the needed operations to reach the specified state.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import sys
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -86,24 +85,6 @@ class Monitor(Item):
|
||||||
self.notifications = notifications
|
self.notifications = notifications
|
||||||
self.saved = False
|
self.saved = False
|
||||||
|
|
||||||
def get_status_codes(self):
|
|
||||||
if "accepted_statuscodes" not in self.kwargs:
|
|
||||||
return set(range(200, 300))
|
|
||||||
accepted_statuscodes = set()
|
|
||||||
for codes in self.kwargs["accepted_statuscodes"]:
|
|
||||||
if "-" in codes:
|
|
||||||
c = codes.split("-")
|
|
||||||
if len(c) != 2:
|
|
||||||
logger.error(
|
|
||||||
f"Fatal: status codes for {self} must be in the format 'number' or 'number-number'"
|
|
||||||
)
|
|
||||||
sys.exit(1)
|
|
||||||
a, b = int(c[0]), int(c[1])
|
|
||||||
accepted_statuscodes.update(range(a, b))
|
|
||||||
else:
|
|
||||||
accepted_statuscodes.add(int(codes))
|
|
||||||
return accepted_statuscodes
|
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
if self.saved:
|
if self.saved:
|
||||||
return
|
return
|
||||||
|
|
Loading…
Reference in a new issue