diff --git a/CHANGELOG.md b/CHANGELOG.md index 54e11ad..d7e9d3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - 💄 — Correctly show results on small screens - 📝💄 — Add opengraph tags to documentation site (#62) - 🔨 — Add a small web server to browse documentation when developing +- ✨ — Add new check type: http-to-https (#61) ## 0.4.1 diff --git a/argos/checks/checks.py b/argos/checks/checks.py index d1fc530..e42046f 100644 --- a/argos/checks/checks.py +++ b/argos/checks/checks.py @@ -4,6 +4,7 @@ import json import re from datetime import datetime +from httpx import URL from jsonpointer import resolve_pointer, JsonPointerException from argos.checks.base import ( @@ -55,6 +56,33 @@ class HTTPStatusIn(BaseCheck): ) +class HTTPToHTTPS(BaseCheck): + """Checks that the HTTP to HTTPS redirection status code is the expected one.""" + + config = "http-to-https" + expected_cls = ExpectedStringValue + + async def run(self) -> dict: + task = self.task + url = URL(task.url).copy_with(scheme="http") + response = await self.http_client.request(method="get", url=url, timeout=60) + + expected_dict = json.loads(self.expected) + expected = range(300, 400) + if "range" in expected_dict: + expected = range(expected_dict["range"][0], expected_dict["range"][1]) + if "value" in expected_dict: + expected = range(expected_dict["value"], expected_dict["value"] + 1) + if "list" in expected_dict: + expected = expected_dict["list"] + + return self.response( + status=response.status_code in expected, + expected=self.expected, + retrieved=response.status_code, + ) + + class HTTPHeadersContain(BaseCheck): """Checks that response headers contains the expected headers (without checking their values)""" diff --git a/argos/config-example.yaml b/argos/config-example.yaml index 763367c..2999e12 100644 --- a/argos/config-example.yaml +++ b/argos/config-example.yaml @@ -107,6 +107,21 @@ websites: - headers-contain: - "content-encoding" - "content-type" + # Check that there is a HTTP to HTTPS redirection with 3xx status code + - http-to-https: true + # Check that there is a HTTP to HTTPS redirection with 301 status code + - http-to-https: 301 + # Check that there is a HTTP to HTTPS redirection with a status code + # in the provided range (stop value excluded) + - http-to-https: + start: 301 + stop: 308 + # Check that there is a HTTP to HTTPS redirection with a status code + # in the provided list + - http-to-https: + - 301 + - 302 + - 307 - path: "/admin/" checks: # Check that the return HTTP status is one of those diff --git a/argos/schemas/config.py b/argos/schemas/config.py index 520613a..7cc29d1 100644 --- a/argos/schemas/config.py +++ b/argos/schemas/config.py @@ -79,12 +79,26 @@ def parse_checks(value): if name not in available_names: msg = f"Check should be one of f{available_names}. ({name} given)" raise ValueError(msg) - if isinstance(expected, int): - expected = str(expected) - if isinstance(expected, list): - expected = json.dumps(expected) - if isinstance(expected, dict): - expected = json.dumps(expected) + if name == "http-to-https": + if isinstance(expected, int) and expected in range(300, 400): + expected = json.dumps({"value": expected}) + elif isinstance(expected, list): + expected = json.dumps({"list": expected}) + elif ( + isinstance(expected, dict) + and "start" in expected + and "stop" in expected + ): + expected = json.dumps({"range": [expected["start"], expected["stop"]]}) + else: + expected = json.dumps({"range": [300, 400]}) + else: + if isinstance(expected, int): + expected = str(expected) + if isinstance(expected, list): + expected = json.dumps(expected) + if isinstance(expected, dict): + expected = json.dumps(expected) return (name, expected) diff --git a/docs/checks.md b/docs/checks.md index dcbe764..8e961e2 100644 --- a/docs/checks.md +++ b/docs/checks.md @@ -11,17 +11,18 @@ These checks are the most basic ones. They simply check that the response from t | Check | Description | Configuration | | --- | --- | --- | -| `status-is` | Check that the returned status code matches what you expect. | `status-is: "200"` | +| `status-is` | Check that the returned status code matches what you expect. |
status-is: \"200\"
| | `status-in` | Check that the returned status code is in the list of codes you expect. |
status-in:
- 200
- 302
| -| `body-contains` | Check that the returned body contains a given string. | `body-contains: "Hello world"` | -| `body-like` | Check that the returned body matches a given regex. | `body-like: "Hel+o w.*"` | +| `body-contains` | Check that the returned body contains a given string. |
body-contains: "Hello world"
| +| `body-like` | Check that the returned body matches a given regex. |
body-like: "Hel+o w.*"
| | `headers-contain` | Check that the response contains the expected headers. |
headers-contain:
- "content-encoding"
- "content-type"
| | `headers-have` | Check that the response contains the expected headers with the expected value. |
headers-have:
content-encoding: "gzip"
content-type: "text/html"
| | `headers-like` | Check that response headers contains the expected headers and that the values matches the provided regexes. |
headers-like:
content-encoding: "gzip\|utf"
content-type: "text/(html\|css)"
| | `json-contains` | Check that JSON response contains the expected structure. |
json-contains:
- /foo/bar/0
- /timestamp
| | `json-has` | Check that JSON response contains the expected structure and values. |
json-has:
/maintenance: false
/productname: "Nextcloud"
| | `json-like` | Check that JSON response contains the expected structure and that the values matches the provided regexes. |
json-like:
/productname: ".\*cloud"
/versionstring: "29\\\\..\*"
| -| `json-is` | Check that JSON response is the exact expected JSON object. | `json-is: '{"foo": "bar", "baz": 42}'`| +| `json-is` | Check that JSON response is the exact expected JSON object. |
json-is: '{"foo": "bar", "baz": 42}'
| +| `http-to-https` | Check that the HTTP version of the domain redirects to HTTPS. Multiple choices of configuration. |
http-to-https: true
http-to-https: 301
http-to-https:
start: 301
stop: 308
http-to-https:
- 301
- 302
- 307
| ```{code-block} yaml --- @@ -37,6 +38,21 @@ caption: argos-config.yaml - headers-contain: - "content-encoding" - "content-type" + # Check that there is a HTTP to HTTPS redirection with 3xx status code + - http-to-https: true + # Check that there is a HTTP to HTTPS redirection with 301 status code + - http-to-https: 301 + # Check that there is a HTTP to HTTPS redirection with a status code + # in the provided range (stop value excluded) + - http-to-https: + start: 301 + stop: 308 + # Check that there is a HTTP to HTTPS redirection with a status code + # in the provided list + - http-to-https: + - 301 + - 302 + - 307 - path: "/foobar" checks: - status-in: diff --git a/docs/conf.py b/docs/conf.py index eb532d6..e7b8ae2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -35,6 +35,8 @@ html_sidebars = { # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output +smartquotes = False + if "CI_JOB_ID" in environ: html_baseurl = "https://argos-monitoring.framasoft.org" diff --git a/docs/developer/new-check.md b/docs/developer/new-check.md index 69d205f..2d80a5a 100644 --- a/docs/developer/new-check.md +++ b/docs/developer/new-check.md @@ -41,3 +41,7 @@ If that's your case, you can implement the `finalize` method, and return some ex # You can use the extra_arg here to determine the severity return Status.SUCCESS, Severity.OK ``` + +## Document the new check + +Please, document the use of the new check in `docs/checks.md` and `argos/config-example.yaml`.