This is an automated email from the ASF dual-hosted git repository.
sbp pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-release.git
The following commit(s) were added to refs/heads/main by this push:
new 42b9650 Add an admin route to review all validation results, and a
disk validator
42b9650 is described below
commit 42b965006ef7fc8231b57fb255f28c2bee1d5281
Author: Sean B. Palmer <[email protected]>
AuthorDate: Wed Jun 25 15:55:50 2025 +0100
Add an admin route to review all validation results, and a disk validator
---
atr/blueprints/admin/admin.py | 16 +++++++++++
atr/blueprints/admin/templates/validation.html | 40 ++++++++++++++++++++++++++
atr/templates/includes/sidebar.html | 5 ++++
atr/validate.py | 16 +++++++++++
4 files changed, 77 insertions(+)
diff --git a/atr/blueprints/admin/admin.py b/atr/blueprints/admin/admin.py
index f4d0163..d2b04ef 100644
--- a/atr/blueprints/admin/admin.py
+++ b/atr/blueprints/admin/admin.py
@@ -48,6 +48,7 @@ import atr.routes.keys as keys
import atr.routes.mapping as mapping
import atr.template as template
import atr.util as util
+import atr.validate as validate
_LOGGER: Final = logging.getLogger(__name__)
@@ -632,6 +633,21 @@ async def admin_toggle_view() -> response.Response:
return quart.redirect(referrer or quart.url_for("admin.admin_data"))
[email protected]("/validate")
+async def admin_validate() -> str:
+ """Run validators and display any divergences."""
+
+ async with db.session() as data:
+ releases = await data.release().order_by(models.Release.name).all()
+
+ results = list(validate.releases(releases))
+
+ return await template.render(
+ "validation.html",
+ divergences=results,
+ )
+
+
async def _check_keys(fix: bool = False) -> str:
email_to_uid = await util.email_to_uid_map()
bad_keys = []
diff --git a/atr/blueprints/admin/templates/validation.html
b/atr/blueprints/admin/templates/validation.html
new file mode 100644
index 0000000..e028926
--- /dev/null
+++ b/atr/blueprints/admin/templates/validation.html
@@ -0,0 +1,40 @@
+{% extends "layouts/base.html" %}
+
+{% block title %}
+ Validation ~ ATR Admin
+{% endblock title %}
+
+{% block description %}
+ Results of running server data validators.
+{% endblock description %}
+
+{% block content %}
+ <h1>Validation</h1>
+
+ {% if divergences|length == 0 %}
+ <p>No validation errors were found.</p>
+ {% else %}
+ <table class="table table-striped table-hover">
+ <thead>
+ <tr>
+ <th>Source</th>
+ <th>Validator</th>
+ <th>Components</th>
+ <th>Expected</th>
+ <th>Actual</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for d in divergences %}
+ <tr>
+ <td>{{ d.source }}</td>
+ <td><code>{{ d.validator }}</code></td>
+ <td>{{ d.components | join(', ') }}</td>
+ <td><code>{{ d.divergence.expected }}</code></td>
+ <td><code>{{ d.divergence.actual }}</code></td>
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+ {% endif %}
+{% endblock content %}
diff --git a/atr/templates/includes/sidebar.html
b/atr/templates/includes/sidebar.html
index acef5be..324e280 100644
--- a/atr/templates/includes/sidebar.html
+++ b/atr/templates/includes/sidebar.html
@@ -176,6 +176,11 @@
<a href="{{ url_for('admin.admin_toggle_admin_view_page') }}"
{% if request.endpoint == 'admin.admin_toggle_admin_view_page'
%}class="active"{% endif %}>Toggle admin view</a>
</li>
+ <li>
+ <i class="bi bi-arrow-repeat"></i>
+ <a href="{{ url_for('admin.admin_validate') }}"
+ {% if request.endpoint == 'admin.admin_validate'
%}class="active"{% endif %}>Validate</a>
+ </li>
</ul>
{% endif %}
{% endif %}
diff --git a/atr/validate.py b/atr/validate.py
index 6f450c5..23d2740 100644
--- a/atr/validate.py
+++ b/atr/validate.py
@@ -16,10 +16,12 @@
# under the License.
import datetime
+import pathlib
from collections.abc import Callable, Generator, Iterable, Sequence
from typing import NamedTuple, TypeVar
import atr.db.models as models
+import atr.util as util
class Divergence(NamedTuple):
@@ -69,6 +71,7 @@ def release(r: models.Release) -> AnnotatedDivergences:
"""Check that a release is valid."""
yield from release_created(r)
yield from release_name(r)
+ yield from release_on_disk(r)
yield from release_package_managers(r)
yield from release_released(r)
yield from release_sboms(r)
@@ -115,6 +118,19 @@ def release_name(r: models.Release) -> Divergences:
yield from divergences(expected, actual)
+@release_components()
+def release_on_disk(r: models.Release) -> Divergences:
+ """Check that the release is on disk."""
+ path = util.release_directory(r)
+
+ def okay(p: pathlib.Path) -> bool:
+ # The release directory must exist and contain at least one entry
+ return p.exists() and any(p.iterdir())
+
+ expected = "directory to exist and contain files"
+ yield from divergences_predicate(okay, expected, path)
+
+
@release_components("Release.package_managers")
def release_package_managers(r: models.Release) -> Divergences:
"""Check that the release package managers are empty."""
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]