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

View file

@ -2,6 +2,14 @@
## [Unreleased] ## [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 ## 0.4.1
Date: 2024-09-18 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 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/search.html public/genindex.html
sed -e 's@https://unpkg.com/mermaid[^"]*"@../mermaid.min.js"@' -i public/developer/models.html public/developer/overview.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. cog: ## Run cog, to integrate the CLI options to the docs.
venv/bin/cog -r docs/*.md venv/bin/cog -r docs/*.md
test: venv ## Run the tests test: venv ## Run the tests

View file

@ -4,6 +4,7 @@ import json
import re import re
from datetime import datetime from datetime import datetime
from httpx import URL
from jsonpointer import resolve_pointer, JsonPointerException from jsonpointer import resolve_pointer, JsonPointerException
from argos.checks.base import ( 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): class HTTPHeadersContain(BaseCheck):
"""Checks that response headers contains the expected headers """Checks that response headers contains the expected headers
(without checking their values)""" (without checking their values)"""
@ -313,6 +341,8 @@ class SSLCertificateExpiration(BaseCheck):
@classmethod @classmethod
async def finalize(cls, config, result, **context): async def finalize(cls, config, result, **context):
if result.status == Status.ERROR:
return result.status, Severity.UNKNOWN
if result.status != Status.ON_CHECK: if result.status != Status.ON_CHECK:
return result.status, Severity.WARNING return result.status, Severity.WARNING

View file

@ -107,6 +107,21 @@ websites:
- headers-contain: - headers-contain:
- "content-encoding" - "content-encoding"
- "content-type" - "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/" - path: "/admin/"
checks: checks:
# Check that the return HTTP status is one of those # 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: if name not in available_names:
msg = f"Check should be one of f{available_names}. ({name} given)" msg = f"Check should be one of f{available_names}. ({name} given)"
raise ValueError(msg) raise ValueError(msg)
if isinstance(expected, int): if name == "http-to-https":
expected = str(expected) if isinstance(expected, int) and expected in range(300, 400):
if isinstance(expected, list): expected = json.dumps({"value": expected})
expected = json.dumps(expected) elif isinstance(expected, list):
if isinstance(expected, dict): expected = json.dumps({"list": expected})
expected = json.dumps(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) return (name, expected)

View file

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

View file

@ -1,5 +1,22 @@
@import url("pico.min.css"); @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 { code {
white-space: pre-wrap; white-space: pre-wrap;
} }

View file

@ -44,7 +44,29 @@
</ul> </ul>
</nav> </nav>
<div class="container"> <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> <article>
<header title="Unknown"> <header title="Unknown">

View file

@ -3,7 +3,11 @@
{% block content %} {% block content %}
<dl> <dl>
<dt>Task</dt> <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> <dt>Submitted at</dt>
<dd>{{ result.submitted_at }}</dd> <dd>{{ result.submitted_at }}</dd>
<dt>Status</dt> <dt>Status</dt>
@ -11,6 +15,26 @@
<dt>Severity</dt> <dt>Severity</dt>
<dd>{{ result.severity }}</dd> <dd>{{ result.severity }}</dd>
<dt>Context</dt> <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> </dl>
{% endblock content %} {% endblock content %}

View file

@ -14,10 +14,34 @@
<tbody> <tbody>
{% for result in results %} {% for result in results %}
<tr id="{{ result.id }}"> <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.status }}</td>
<td>{{ result.severity }}</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> </tr>
{% endfor %} {% endfor %}
</tbody> </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 # The HTTP API
Argos exposes a website and an API. The website is available at "/" and the API at "/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 ```{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 # 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 ## Simple checks
@ -8,17 +11,18 @@ These checks are the most basic ones. They simply check that the response from t
| Check | Description | Configuration | | 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> | | `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-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. | `body-like: "Hel+o w.*"` | | `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-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-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> | | `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-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-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-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 ```{code-block} yaml
--- ---
@ -34,6 +38,21 @@ caption: argos-config.yaml
- headers-contain: - headers-contain:
- "content-encoding" - "content-encoding"
- "content-type" - "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" - path: "/foobar"
checks: checks:
- status-in: - status-in:

View file

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

View file

@ -6,9 +6,11 @@
# -- Project information ----------------------------------------------------- # -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
# pylint: disable-msg=invalid-name,redefined-builtin # pylint: disable-msg=invalid-name,redefined-builtin
from os import environ
import argos import argos
project = "Argos" project = "Argos monitoring"
copyright = "2023, Alexis Métaireau, Framasoft" copyright = "2023, Alexis Métaireau, Framasoft"
author = "Alexis Métaireau, Framasoft" author = "Alexis Métaireau, Framasoft"
release = argos.VERSION release = argos.VERSION
@ -33,6 +35,15 @@ html_sidebars = {
# -- Options for HTML output ------------------------------------------------- # -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#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_theme = "shibuya"
html_static_path = ["_static"] 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 # 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. 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 # Using Nginx as reverse proxy
Here is a example for Nginx configuration: 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 # Using systemd
Here are the systemd files that can be used to deploy the server and the agents. 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 # Main dependencies used by Argos
## Python packages ## Python packages

View file

@ -1,3 +1,6 @@
---
description: All you need to know to develop on Argos.
---
# Installing for development # Installing for development
To install all what you need to develop on Argos, do: 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 # License
Argos is licensed under the terms of the GNU AFFERO GPLv3. 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 # Adding a database migration
We are using [Alembic](https://alembic.sqlalchemy.org) to handle the database 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 # The data model
```{mermaid} ```{mermaid}
@ -25,6 +28,19 @@ class Result{
- severity - severity
- context - 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 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 # Implementing a new check
## Creating a new check class ## 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): async def finalize(cls, config, result, extra_arg):
# You can use the extra_arg here to determine the severity # You can use the extra_arg here to determine the severity
return Status.SUCCESS, Severity.OK 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 # Add a notification way
Adding a new notification way is quite simple. Adding a new notification way is quite simple.

View file

@ -1,3 +1,6 @@
---
description: An agent and a server, thats all.
---
# Technical overview # 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. 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 # Releasing guide
Once in a while, we release this package. Here is how. 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 # Requirements
Depending on your setup, you might need different tools to develop on argos. We try to list them here. 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) # This will explain how to add it to your path (to replace the default one)
brew info gnu-sed brew info gnu-sed
``` ```

View file

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

View file

@ -1,3 +1,6 @@
---
description: Soooo much questions…
---
# FAQ # FAQ
## How is it different than Nagios? ## 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 # Argos monitoring
A monitoring and status board for websites. A monitoring and status board for websites.

View file

@ -1,3 +1,6 @@
---
description: Install Argos, with all the explanations you want.
---
# Installation # Installation
NB: if you want a quick-installation guide, we [got you covered](tl-dr.md). 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 # Install and configure PostgreSQL
Here are a few steps for you to install PostgreSQL on your system: 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 # TL;DR: fast installation instructions
You want to install Argos fast? Ok, here we go. You want to install Argos fast? Ok, here we go.