diff --git a/README.md b/README.md index aca7786..c85023d 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,8 @@ Features : - [x] Change the naming and use service/agent. - [x] Packaging (and `argos agent` / `argos service` commands) - [x] Endpoints are protected by an authentication token -- [ ] Find a way to define when the task should be checked again (via config ? stored on the tasks themselves ?) +- [ ] Task frequency can be defined in the configuration +- [ ] Add a command to generate new authentication tokens - [ ] Local task for database cleanup (to run periodically) - [ ] Handles multiple alerting backends (email, sms, gotify) ; - [ ] Exposes a simple read-only website. diff --git a/argos/schemas/config.py b/argos/schemas/config.py index f3ba29c..f039740 100644 --- a/argos/schemas/config.py +++ b/argos/schemas/config.py @@ -7,6 +7,8 @@ import yaml from pydantic import BaseModel, Field, HttpUrl, validator from yamlinclude import YamlIncludeConstructor +from argos.schemas.utils import string_to_duration + # XXX Find a way to check without having cirular imports # This file contains the pydantic schemas. For the database models, check in argos.model. @@ -21,17 +23,9 @@ class SSL(BaseModel): @validator("thresholds", each_item=True, pre=True) def parse_threshold(cls, value): for duration_str, severity in value.items(): - num = int("".join(filter(str.isdigit, duration_str))) - if "d" in duration_str: - num = num - elif "w" in duration_str: - num = num * 7 - elif "m" in duration_str: - num = num * 30 - else: - raise ValueError("Invalid duration value") + days = string_to_duration(duration_str, "days") # Return here because it's one-item dicts. - return (num, severity) + return (days, severity) class WebsiteCheck(BaseModel): @@ -63,8 +57,7 @@ class WebsitePath(BaseModel): @validator("checks", each_item=True, pre=True) def parse_checks(cls, value): - from argos.checks import \ - get_registered_checks # To avoid circular imports + from argos.checks import get_registered_checks # To avoid circular imports available_names = get_registered_checks().keys() @@ -95,6 +88,10 @@ class General(BaseModel): frequency: str alerts: Alert + @validator("frequency", pre=True) + def parse_frequency(cls, value): + return string_to_duration(value, "hours") + class Config(BaseModel): general: General diff --git a/argos/schemas/utils.py b/argos/schemas/utils.py new file mode 100644 index 0000000..a7e7699 --- /dev/null +++ b/argos/schemas/utils.py @@ -0,0 +1,30 @@ +from typing import Literal + + +def string_to_duration(value: str, target: Literal["days", "hours"]): + """Convert a string to a number of hours, or days""" + num = int("".join(filter(str.isdigit, value))) + + if target == "hours": + reconvert = True + + if "h" in value: + if target == "days": + raise ValueError("Invalid duration value", value) + num = num + reconvert = False + elif "d" in value: + num = num + elif "w" in value: + num = num * 7 + elif "m" in value: + num = num * 30 + elif "y" in value: + num = num * 365 + else: + raise ValueError("Invalid duration value", value) + + if target == "hours" and reconvert: + num = num * 24 + + return num diff --git a/config.yaml b/config.yaml index c52a5a8..4118503 100644 --- a/config.yaml +++ b/config.yaml @@ -19,4 +19,75 @@ ssl: - "1d": critical "5d": warning -websites: !include websites.yaml +# It's also possible to define the checks in another file +# with the include syntax: +# +# websites: !include websites.yaml +# +websites: + - domain: "https://mypads.framapad.org" + paths: + - path: "/mypads/" + checks: + - status-is: 200 + - body-contains: '
' + - ssl-certificate-expiration: "on-check" + - path: "/admin/" + checks: + - status-is: 401 + - domain: "https://munin.framasoft.org" + paths: + - path: "/" + checks: + - status-is: 301 + - path: "/munin/" + checks: + - status-is: 401 + - domain: "https://framagenda.org" + paths: + - path: "/status.php" + checks: + - status-is: 200 + # Là, idéalement, il faudrait un json-contains, + # qui serait une table de hachage + - body-contains: '"maintenance":false' + - ssl-certificate-expiration: "on-check" + - path: "/" + checks: + - status-is: 302 + - path: "/login" + checks: + - status-is: 200 + - domain: "https://framadrive.org" + paths: + - path: "/status.php" + checks: + - status-is: 200 + - body-contains: '"maintenance":false' + - ssl-certificate-expiration: "on-check" + - path: "/" + checks: + - status-is: 302 + - path: "/login" + checks: + - status-is: 200 + - domain: "https://cloud.framabook.org" + paths: + - path: "/status.php" + checks: + - status-is: 200 + - body-contains: '"maintenance":false' + - ssl-certificate-expiration: "on-check" + - path: "/" + checks: + - status-is: 302 + - path: "/login" + checks: + - status-is: 200 + - domain: "https://framasoft.org" + paths: + - path: "/" + checks: + - status-is: 200 + - ssl-certificate-expiration: "on-check" + diff --git a/tests/test_schemas_utils.py b/tests/test_schemas_utils.py new file mode 100644 index 0000000..0732b3a --- /dev/null +++ b/tests/test_schemas_utils.py @@ -0,0 +1,21 @@ +import pytest + +from argos.schemas.utils import string_to_duration + + +def test_string_to_duration_days(): + assert string_to_duration("1d", target="days") == 1 + assert string_to_duration("1w", target="days") == 7 + assert string_to_duration("3w", target="days") == 21 + assert string_to_duration("3m", target="days") == 90 + assert string_to_duration("1y", target="days") == 365 + with pytest.raises(ValueError): + string_to_duration("3h", target="days") + + +def test_string_to_duration_hours(): + assert string_to_duration("1h", target="hours") == 1 + assert string_to_duration("1d", target="hours") == 24 + assert string_to_duration("1w", target="hours") == 7 * 24 + assert string_to_duration("3w", target="hours") == 21 * 24 + assert string_to_duration("3m", target="hours") == 3 * 30 * 24