🔀 Merge branch 'develop'

This commit is contained in:
Luc Didry 2024-09-26 11:44:56 +02:00
commit 37bd7b0d8d
No known key found for this signature in database
GPG key ID: EA868E12D0257E3C
37 changed files with 304 additions and 24 deletions

View file

@ -76,8 +76,7 @@ pages:
<<: *pull_cache
stage: deploy
script:
- pwd
- ls
- sed -e "/Unreleased/,+1d" -i CHANGELOG.md
- make docs
- echo "https://framasoft.frama.io/framaspace/argos/* https://argos-monitoring.framasoft.org/:splat 301" > public/_redirects
artifacts:

View file

@ -2,6 +2,14 @@
## [Unreleased]
- 💄 — 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)
- 👷 — Remove Unreleased section from CHANGELOG when publishing documentation
- 🩹 — Severity of ssl-certificate-expirations errors is now UNKNOWN (#60)
- 💄 — Better display of results error details
## 0.4.1
Date: 2024-09-18

View file

@ -16,6 +16,8 @@ docs: cog ## Build the docs
if [ ! -e "public/mermaid.min.js" ]; then curl -sL $$(grep mermaid.min.js public/search.html | cut -f 2 -d '"') --output public/mermaid.min.js; fi
sed -e 's@https://unpkg.com/mermaid[^"]*"@mermaid.min.js"@' -i public/search.html public/genindex.html
sed -e 's@https://unpkg.com/mermaid[^"]*"@../mermaid.min.js"@' -i public/developer/models.html public/developer/overview.html
docs-webserver: docs
python3 -m http.server -d public -b 127.0.0.1 8001
cog: ## Run cog, to integrate the CLI options to the docs.
venv/bin/cog -r docs/*.md
test: venv ## Run the tests

View file

@ -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)"""
@ -313,6 +341,8 @@ class SSLCertificateExpiration(BaseCheck):
@classmethod
async def finalize(cls, config, result, **context):
if result.status == Status.ERROR:
return result.status, Severity.UNKNOWN
if result.status != Status.ON_CHECK:
return result.status, Severity.WARNING

View file

@ -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

View file

@ -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)

View file

@ -14,6 +14,7 @@ from passlib.context import CryptContext
from sqlalchemy import func
from sqlalchemy.orm import Session
from argos.checks.base import Status
from argos.schemas import Config
from argos.server import queries
from argos.server.models import Result, Task, User
@ -190,7 +191,7 @@ async def get_result_view(
"""Show the details of a result"""
result = db.query(Result).get(result_id)
return templates.TemplateResponse(
"result.html", {"request": request, "result": result}
"result.html", {"request": request, "result": result, "error": Status.ERROR}
)
@ -220,6 +221,7 @@ async def get_task_results_view(
"results": results,
"task": task,
"description": description,
"error": Status.ERROR,
},
)

View file

@ -1,5 +1,22 @@
@import url("pico.min.css");
.display-small {
display: none;
text-align: center;
}
@media (max-width: 767px) {
.display-large {
display: none !important;
}
.display-small {
display: block;
}
.display-small article {
display: inline-block;
width: 24%;
}
}
code {
white-space: pre-wrap;
}

View file

@ -44,7 +44,29 @@
</ul>
</nav>
<div class="container">
<div class="grid grid-index">
<div class="display-small">
<article title="Unknown">
<br>
{{ counts_dict['unknown'] }}
</article>
<article title="OK">
<br>
{{ counts_dict['ok'] }}
</article>
<article title="Warning">
⚠️
<br>
{{ counts_dict['warning'] }}
</article>
<article title="Critical">
<br>
{{ counts_dict['critical'] }}
</article>
</div>
<div class="grid grid-index display-large">
<article>
<header title="Unknown">

View file

@ -3,7 +3,11 @@
{% block content %}
<dl>
<dt>Task</dt>
<dd>{{ result.task }}</dd>
<dd>
<a href="{{ url_for('get_task_results_view', task_id=result.task.id) }}">
{{ result.task }}
</a>
</dd>
<dt>Submitted at</dt>
<dd>{{ result.submitted_at }}</dd>
<dt>Status</dt>
@ -11,6 +15,26 @@
<dt>Severity</dt>
<dd>{{ result.severity }}</dd>
<dt>Context</dt>
<dd>{{ result.context }}</dd>
<dd>
{% if result.status != error %}
{{ result.context }}
{% else %}
<dl>
{% if result.context['error_message'] %}
<dt>Error message</dt>
<dd>{{ result.context['error_message'] }}</dd>
{% endif %}
<dt>Error type</dt>
<dd>{{ result.context['error_type'] }}</dd>
<dt>Error details</dt>
<dd>
<details>
<summary>{{ result.context['error_details'] | truncate(120, False, '…') }} (click to expand)</summary>
<pre><code>{{ result.context['error_details'] | replace('\n', '<br>') | safe }}</code></pre>
</details>
</dd>
</dl>
{% endif %}
</dd>
</dl>
{% endblock content %}

View file

@ -14,10 +14,34 @@
<tbody>
{% for result in results %}
<tr id="{{ result.id }}">
<td>{{ result.submitted_at }}</td>
<td>
<a href="{{ url_for('get_result_view', result_id=result.id) }}" title="See details of result {{ result.id }}">
{{ result.submitted_at }}
</a>
</td>
<td>{{ result.status }}</td>
<td>{{ result.severity }}</td>
<td>{{ result.context }}</td>
<td>
{% if result.status != error %}
{{ result.context }}
{% else %}
<dl>
{% if result.context["error_message"] %}
<dt>Error message</dt>
<dd>{{ result.context["error_message"] }}</dd>
{% endif %}
<dt>Error type</dt>
<dd>{{ result.context["error_type"] }}</dd>
<dt>Error details</dt>
<dd>
<details>
<summary>{{ result.context["error_details"] | truncate(120, False, "…") }} (click to expand)</summary>
<pre><code>{{ result.context["error_details"] | replace("\n", "<br>") | safe }}</code></pre>
</details>
</dd>
</dl>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>

4
docs/_static/fix-nav.css vendored Normal file
View file

@ -0,0 +1,4 @@
.sy-head-brand img + strong {
display: inline;
margin-left: 1em;
}

1
docs/_static/logo.png vendored Symbolic link
View file

@ -0,0 +1 @@
../../argos/server/static/logo.png

View file

@ -1,3 +1,6 @@
---
description: Argos exposes a website and an API. This is how to use the API.
---
# The HTTP API
Argos exposes a website and an API. The website is available at "/" and the API at "/api".

View file

@ -1,2 +1,5 @@
---
description: Last changes in Argos.
---
```{include} ../CHANGELOG.md
```
```

View file

@ -1,6 +1,9 @@
---
description: Here are the checks that Argos proposes, with a description of what they do and how to configure them.
---
# Checks
At its core, argos runs checks and return the results to the service. Here are the implemented checks, with a description of what they do and how to configure them.
At its core, Argos runs checks and return the results to the service. Here are the implemented checks, with a description of what they do and how to configure them.
## Simple checks
@ -8,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. | <pre><code>status-is: \"200\"</code></pre> |
| `status-in` | Check that the returned status code is in the list of codes you expect. | <pre><code>status-in:<br> - 200<br> - 302</code></pre> |
| `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. | <pre><code>body-contains: "Hello world"</code></pre> |
| `body-like` | Check that the returned body matches a given regex. | <pre><code>body-like: "Hel+o w.*"</code></pre> |
| `headers-contain` | Check that the response contains the expected headers. | <pre><code>headers-contain:<br> - "content-encoding"<br> - "content-type"</code></pre> |
| `headers-have` | Check that the response contains the expected headers with the expected value. | <pre><code>headers-have:<br> content-encoding: "gzip"<br> content-type: "text/html"</code></pre> |
| `headers-like` | Check that response headers contains the expected headers and that the values matches the provided regexes. | <pre><code>headers-like:<br> content-encoding: "gzip\|utf"<br> content-type: "text/(html\|css)"</code></pre> |
| `json-contains` | Check that JSON response contains the expected structure. | <pre><code>json-contains:<br> - /foo/bar/0<br> - /timestamp</code></pre> |
| `json-has` | Check that JSON response contains the expected structure and values. | <pre><code>json-has:<br> /maintenance: false<br> /productname: "Nextcloud"</code></pre> |
| `json-like` | Check that JSON response contains the expected structure and that the values matches the provided regexes. | <pre><code>json-like:<br> /productname: ".\*cloud"<br> /versionstring: "29\\\\..\*"</code></pre> |
| `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. | <pre><code>json-is: '{"foo": "bar", "baz": 42}'</code></pre> |
| `http-to-https` | Check that the HTTP version of the domain redirects to HTTPS. Multiple choices of configuration. | <pre><code>http-to-https: true<br>http-to-https: 301<br>http-to-https:<br> start: 301<br> stop: 308<br>http-to-https:<br> - 301<br> - 302<br> - 307</code></pre> |
```{code-block} yaml
---
@ -34,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:

View file

@ -1,3 +1,6 @@
---
description: How to use Argos from the command line.
---
# Command-line interface
<!-- [[[cog

View file

@ -6,9 +6,11 @@
# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
# pylint: disable-msg=invalid-name,redefined-builtin
from os import environ
import argos
project = "Argos"
project = "Argos monitoring"
copyright = "2023, Alexis Métaireau, Framasoft"
author = "Alexis Métaireau, Framasoft"
release = argos.VERSION
@ -33,6 +35,15 @@ 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"
html_theme = "shibuya"
html_static_path = ["_static"]
html_css_files = ["fonts.css"]
html_css_files = ["fonts.css", "fix-nav.css"]
html_logo = "_static/logo.png"
html_theme_options = {
"og_image_url": "https://argos-monitoring.framasoft.org/_static/logo.png"
}

View file

@ -1,3 +1,6 @@
---
description: How to configure Argos.
---
# Configuration
Argos uses a simple YAML configuration file to define the servers configuration, the websites to monitor and the checks to run on these websites.

View file

@ -1,3 +1,6 @@
---
description: How to configure Nginx to use with Argos.
---
# Using Nginx as reverse proxy
Here is a example for Nginx configuration:

View file

@ -1,3 +1,6 @@
---
description: Here are the systemd files that can be used to deploy the server and the agents.
---
# Using systemd
Here are the systemd files that can be used to deploy the server and the agents.

View file

@ -1,3 +1,6 @@
---
description: Many thanks to their developers!
---
# Main dependencies used by Argos
## Python packages

View file

@ -1,3 +1,6 @@
---
description: All you need to know to develop on Argos.
---
# Installing for development
To install all what you need to develop on Argos, do:

View file

@ -1,3 +1,6 @@
---
description: Argos is licensed under the terms of the GNU AFFERO GPLv3.
---
# License
Argos is licensed under the terms of the GNU AFFERO GPLv3.

View file

@ -1,3 +1,6 @@
---
description: How to use Alambic to add a database migratation to Argos.
---
# Adding a database migration
We are using [Alembic](https://alembic.sqlalchemy.org) to handle the database

View file

@ -1,3 +1,6 @@
---
description: Whats in the database?
---
# The data model
```{mermaid}
@ -25,6 +28,19 @@ class Result{
- severity
- context
}
class ConfigCache {
- name
- val
- updated_at
}
class User {
- username
- password
- disabled
- created_at
- updated_at
- last_login_at
}
Result "*" o-- "1" Task : has many
```

View file

@ -1,3 +1,6 @@
---
description: Dont worry, creating a new check is quite easy.
---
# Implementing a new check
## Creating a new check class
@ -37,4 +40,8 @@ If that's your case, you can implement the `finalize` method, and return some ex
async def finalize(cls, config, result, extra_arg):
# 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`.

View file

@ -1,3 +1,6 @@
---
description: Adding a new notification way is quite simple.
---
# Add a notification way
Adding a new notification way is quite simple.

View file

@ -1,3 +1,6 @@
---
description: An agent and a server, thats all.
---
# Technical overview
Argos uses an agent and server architecture. The server is responsible for storing the configuration and the results of the checks. The agent is responsible for running the checks and sending the results to the server.

View file

@ -1,3 +1,6 @@
---
description: Once in a while, we release this package. Here is how.
---
# Releasing guide
Once in a while, we release this package. Here is how.

View file

@ -1,3 +1,6 @@
---
description: Depending on your setup, you might need different tools to develop on argos.
---
# Requirements
Depending on your setup, you might need different tools to develop on argos. We try to list them here.
@ -14,4 +17,4 @@ brew install gnu-sed
# This will explain how to add it to your path (to replace the default one)
brew info gnu-sed
```
```

View file

@ -1,3 +1,6 @@
---
description: Launch tests! Make linting tools happy!
---
# Tests and linting
## Tests
@ -19,3 +22,8 @@ You can launch all of them with:
```bash
make lint
```
To let `ruff` format the code, run:
```bash
make ruff-format
```

View file

@ -1,3 +1,6 @@
---
description: Soooo much questions…
---
# FAQ
## How is it different than Nagios?

View file

@ -1,3 +1,6 @@
---
description: A monitoring and status board for websites. Test how your websites respond to external checks, get notified when something goes wrong.
---
# Argos monitoring
A monitoring and status board for websites.

View file

@ -1,3 +1,6 @@
---
description: Install Argos, with all the explanations you want.
---
# Installation
NB: if you want a quick-installation guide, we [got you covered](tl-dr.md).

View file

@ -1,3 +1,6 @@
---
description: Here are a few steps for you to install PostgreSQL on your system.
---
# Install and configure PostgreSQL
Here are a few steps for you to install PostgreSQL on your system:

View file

@ -1,3 +1,6 @@
---
description: You want to install Argos fast? Ok, here we go.
---
# TL;DR: fast installation instructions
You want to install Argos fast? Ok, here we go.