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]

Reply via email to