feat: improve cli and scrapping (breaking change in json format)

This commit is contained in:
sinavir 2024-04-20 13:37:45 +02:00
parent 96f71648f5
commit c1863033bc
5 changed files with 76 additions and 29 deletions

View file

@ -25,16 +25,16 @@ in
lib = lib.mkOption { type = lib.types.raw; }; lib = lib.mkOption { type = lib.types.raw; };
probesConfig = { probesConfig = {
monitors = lib.mkOption { monitors = lib.mkOption {
inherit (probesFormat) type; type = with lib.types; attrsOf probesFormat.type;
default = [ ]; default = { };
}; };
tags = lib.mkOption { tags = lib.mkOption {
inherit (probesFormat) type; type = with lib.types; attrsOf probesFormat.type;
default = [ ]; default = { };
}; };
notifications = lib.mkOption { notifications = lib.mkOption {
inherit (probesFormat) type; type = with lib.types; attrsOf probesFormat.type;
default = [ ]; default = { };
}; };
}; };
}; };
@ -46,7 +46,7 @@ in
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} ${cfg.extraFlags} stateless-uptime-kuma apply-json -f ${cfg.build.json} ${builtins.concatStringsSep " " cfg.extraFlags}
''; '';
}; };
}; };

View file

@ -35,19 +35,34 @@ def cli():
help="Scrape keywords for http probe", help="Scrape keywords for http probe",
default=False, default=False,
) )
def apply_json(file, scrape_http_keywords): @click.option(
"--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) tree = from_dict(api, data, not no_autocreate_tags)
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()

View file

@ -8,10 +8,10 @@ from uptime_kuma_api import MonitorType
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def hydrate_http_probes(tree, excludes=[]): def hydrate_http_probes(tree, excludes=[], fallback_to_http = True):
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("Fatal: probes must have a 'type' parameter") logger.error(f"Fatal: probe {probe.name} must have a 'type' parameter")
sys.exit(1) sys.exit(1)
if ( if (
probe.kwargs["type"] == MonitorType.KEYWORD probe.kwargs["type"] == MonitorType.KEYWORD
@ -28,9 +28,20 @@ def hydrate_http_probes(tree, excludes=[]):
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")
content = requests.request(method, url, headers=headers, data=body).text req = requests.request(
m = re.search("<title>(.*?)</title>", content) method, url, headers=headers, data=body, allow_redirects=True
)
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.info(f"Didn't find keywords for probe {probe.name}, skipping") logger.warn(
probe.kwargs["keyword"] = m.group(1) f"Didn't find keywords for probe {probe.name}, falling back on classic HTTP probe"
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()}")

View file

@ -17,31 +17,33 @@ def die_tag_format_error():
sys.exit(1) sys.exit(1)
def from_dict(api, tree): def from_dict(api, tree, autocreate_tags=True):
notif = tree.get("notifications", []) notif = tree.get("notifications", [])
indexed_notifications = {n["name"]: Notification(api, **n) for n in notif} indexed_notifications = {
name: Notification(api, name, **kwargs) for name, kwargs in notif.items()
}
tags = tree.get("tags", []) tags = tree.get("tags", [])
indexed_tags = {t["name"]: Tag(api, **t) for t in tags} indexed_tags = {name: Tag(api, name, **kwargs) for name, kwargs in tags.items()}
monitors = tree.get("monitors", []) monitors = tree.get("monitors", [])
indexed_monitors = {} indexed_monitors = {}
for m in monitors: for monitor_name, monitor_kwargs in monitors.items():
associated_tags = [] associated_tags = []
for tag in m.get("tags", []): for tag in monitor_kwargs.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()
m["tags"] = associated_tags monitor_kwargs["tags"] = associated_tags
associated_notifications = [ associated_notifications = [
indexed_notifications[notif] for notif in m.get("notifications", []) indexed_notifications[notif]
for notif in monitor_kwargs.get("notifications", [])
] ]
m["notifications"] = associated_notifications monitor_kwargs["notifications"] = associated_notifications
if "name" not in m: indexed_monitors[monitor_name] = Monitor(api, monitor_name, **monitor_kwargs)
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(),

View file

@ -3,6 +3,7 @@ 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__)
@ -85,6 +86,24 @@ 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