diff --git a/CHANGELOG.md b/CHANGELOG.md index 311eda3..a38ec90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - ✅ — Add mypy test - ✨ — Add new check type: status-in - 🩹 — Close menu after rescheduling non-ok checks (#55) +- ✨ — Add new check types: headers-contain and headers-have ## 0.2.2 diff --git a/argos/checks/checks.py b/argos/checks/checks.py index a1e9c69..8894c84 100644 --- a/argos/checks/checks.py +++ b/argos/checks/checks.py @@ -52,6 +52,62 @@ class HTTPStatusIn(BaseCheck): ) +class HTTPHeadersContain(BaseCheck): + """Checks that response headers contains the expected headers + (without checking their values)""" + + config = "headers-contain" + expected_cls = ExpectedStringValue + + async def run(self) -> dict: + # XXX Get the method from the task + task = self.task + response = await self.http_client.request( + method="get", url=task.url, timeout=60 + ) + + status = True + for header in json.loads(self.expected): + if header not in response.headers: + status = False + break + + return self.response( + status=status, + expected=self.expected, + retrieved=json.dumps(list(dict(response.headers).keys())), + ) + + +class HTTPHeadersHave(BaseCheck): + """Checks that response headers contains the expected headers and values""" + + config = "headers-have" + expected_cls = ExpectedStringValue + + async def run(self) -> dict: + # XXX Get the method from the task + task = self.task + response = await self.http_client.request( + method="get", url=task.url, timeout=60 + ) + + status = True + for header, value in json.loads(self.expected).items(): + if header not in response.headers: + status = False + break + if response.headers[header] != value: + status = False + break + + return self.response( + status=status, + expected=self.expected, + retrieved=json.dumps(dict(response.headers)), + ) + + class HTTPBodyContains(BaseCheck): """Checks that the HTTP body contains the expected string.""" diff --git a/argos/config-example.yaml b/argos/config-example.yaml index 00a9eac..dacbed8 100644 --- a/argos/config-example.yaml +++ b/argos/config-example.yaml @@ -1,10 +1,14 @@ +--- general: db: - # The database URL, as defined in SQLAlchemy docs : https://docs.sqlalchemy.org/en/20/core/engines.html#database-urls + # The database URL, as defined in SQLAlchemy docs : + # https://docs.sqlalchemy.org/en/20/core/engines.html#database-urls # Example for SQLite: "sqlite:////tmp/argos.db" url: "postgresql://argos:argos@localhost/argos" - # You configure the size of the database pool of connection, and the max overflow (until when new connections are accepted ?) - # See https://docs.sqlalchemy.org/en/20/core/pooling.html#sqlalchemy.pool.QueuePool.params.pool_size for details + # You configure the size of the database pool of connection, and + # the max overflow (until when new connections are accepted ?) + # For details, see + # https://docs.sqlalchemy.org/en/20/core/pooling.html#sqlalchemy.pool.QueuePool.params.pool_size pool_size: 10 max_overflow: 20 # Can be "production", "dev", "test". @@ -77,13 +81,23 @@ websites: - body-contains: '
' # Check that the SSL certificate is no older than ssl.thresholds - ssl-certificate-expiration: "on-check" + # Check that the response contains this headers + # The comparison is case insensitive + - headers-contain: + - "content-encoding" + - "content-type" - path: "/admin/" checks: # Check that the return HTTP status is one of those # Similar to status-is, verify that you don’t mistyped it! - status-in: - - 401 - - 301 + - 401 + - 301 + # Check that the response contains this headers and values + # The name of the headers is case insensitive + - headers-have: + content-encoding: "gzip" + content-type: "text/html" - domain: "https://munin.example.org" frequency: "20m" paths: diff --git a/argos/schemas/config.py b/argos/schemas/config.py index 7022740..4bb9f1e 100644 --- a/argos/schemas/config.py +++ b/argos/schemas/config.py @@ -83,6 +83,8 @@ def parse_checks(value): 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 d3cb02d..2175f56 100644 --- a/docs/checks.md +++ b/docs/checks.md @@ -9,8 +9,10 @@ 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-in` | Check that the returned status code is in the list of codes you expect. |
status-in:
- 200
- 302
| +| `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"` | +| `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. |
headers-have:
content-encoding: "gzip"
content-type: "text/html"
| ```{code-block} yaml --- @@ -22,11 +24,18 @@ caption: argos-config.yaml checks: - status-is: 200 - body-contains: "Hello world" + - headers-contain: + - "content-encoding" + - "content-type" - path: "/foobar" checks: - status-in: - - 200 - - 302 + - 200 + - 302 + # It’s VERY important to respect the 4 spaces indentation here! + - headers-have: + content-encoding: "gzip" + content-type: "text/html" ``` ## SSL certificate expiration