This is an automated email from the ASF dual-hosted git repository.
sbp pushed a commit to branch sbp
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git
The following commit(s) were added to refs/heads/sbp by this push:
new 7815a1c Add a form for admins to run checks again without using the
cache
7815a1c is described below
commit 7815a1c9cb08c2620a6d4caeb473204523659a17
Author: Sean B. Palmer <[email protected]>
AuthorDate: Thu Jan 29 20:32:25 2026 +0000
Add a form for admins to run checks again without using the cache
---
atr/models/sql.py | 2 ++
atr/post/draft.py | 28 +++++++++++++++++++++
atr/shared/web.py | 12 +++++++--
atr/storage/writers/revision.py | 2 ++
atr/tasks/checks/__init__.py | 19 ++++++++++++++
atr/templates/check-selected.html | 6 ++++-
atr/templates/report-selected-path.html | 30 +++++++++++++++++-----
migrations/versions/0044_2026.01.29_24d82084.py | 33 +++++++++++++++++++++++++
8 files changed, 123 insertions(+), 9 deletions(-)
diff --git a/atr/models/sql.py b/atr/models/sql.py
index 0b8d8a4..e178e2f 100644
--- a/atr/models/sql.py
+++ b/atr/models/sql.py
@@ -936,6 +936,7 @@ class CheckResult(sqlmodel.SQLModel, table=True):
sa_column=sqlalchemy.Column(sqlalchemy.JSON), **example({"expected":
"...", "found": "..."})
)
input_hash: str | None = sqlmodel.Field(default=None, index=True,
**example("blake3:7f83b1657ff1fc..."))
+ cached: bool = sqlmodel.Field(default=False, **example(False))
class CheckResultIgnore(sqlmodel.SQLModel, table=True):
@@ -1180,6 +1181,7 @@ class Revision(sqlmodel.SQLModel, table=True):
description: str | None = sqlmodel.Field(default=None, **example("This is
a description"))
tag: str | None = sqlmodel.Field(default=None, **example("rc1"))
+ use_check_cache: bool = sqlmodel.Field(default=True, **example(True))
def model_post_init(self, _context):
if isinstance(self.created, str):
diff --git a/atr/post/draft.py b/atr/post/draft.py
index c7fd259..3ca61bc 100644
--- a/atr/post/draft.py
+++ b/atr/post/draft.py
@@ -146,6 +146,34 @@ async def hashgen(session: web.Committer, project_name:
str, version_name: str,
)
[email protected]("/draft/recheck/<project_name>/<version_name>")
[email protected]()
+async def recheck(session: web.Committer, project_name: str, version_name:
str) -> web.WerkzeugResponse:
+ """Start a new draft revision to rerun all checks without using caches."""
+ await session.check_access(project_name)
+ if not session.is_admin:
+ raise base.ASFQuartException("Admin access required", errorcode=403)
+
+ description = "Empty revision to restart all checks without cache for the
whole release candidate draft"
+ async with storage.write(session) as write:
+ wacp = await write.as_project_committee_participant(project_name)
+ async with wacp.revision.create_and_manage(
+ project_name,
+ version_name,
+ session.uid,
+ description=description,
+ use_check_cache=False,
+ ) as _creating:
+ pass
+
+ return await session.redirect(
+ get.compose.selected,
+ project_name=project_name,
+ version_name=version_name,
+ success="All checks restarted without cache",
+ )
+
+
@post.committer("/draft/sbomgen/<project_name>/<version_name>/<path:file_path>")
@post.empty()
async def sbomgen(session: web.Committer, project_name: str, version_name:
str, file_path: str) -> web.WerkzeugResponse:
diff --git a/atr/shared/web.py b/atr/shared/web.py
index 66c4bc0..a63190f 100644
--- a/atr/shared/web.py
+++ b/atr/shared/web.py
@@ -112,12 +112,19 @@ async def check(
),
)
- empty_form = form.render(
+ fresh_form = form.render(
model_cls=form.Empty,
action=util.as_url(post.draft.fresh,
project_name=release.project.name, version_name=release.version),
submit_label="Restart all checks",
submit_classes="btn btn-primary",
)
+ recheck_form = form.render(
+ model_cls=form.Empty,
+ action=util.as_url(post.draft.recheck,
project_name=release.project.name, version_name=release.version),
+ submit_label="Recheck all without cache",
+ submit_classes="btn btn-outline-secondary",
+ # confirm="Restart all checks without using cached results? This
creates a new revision.",
+ )
vote_task_warnings = _warnings_from_vote_result(vote_task)
has_files = await util.has_files(release)
@@ -152,7 +159,8 @@ async def check(
vote_task=vote_task,
archive_url=archive_url,
vote_task_warnings=vote_task_warnings,
- empty_form=empty_form,
+ fresh_form=fresh_form,
+ recheck_form=recheck_form,
csrf_input=str(form.csrf_input()),
resolve_form=resolve_form,
has_files=has_files,
diff --git a/atr/storage/writers/revision.py b/atr/storage/writers/revision.py
index dc08d43..0bc1f11 100644
--- a/atr/storage/writers/revision.py
+++ b/atr/storage/writers/revision.py
@@ -111,6 +111,7 @@ class CommitteeParticipant(FoundationCommitter):
version_name: str,
asf_uid: str,
description: str | None = None,
+ use_check_cache: bool = True,
) -> AsyncGenerator[types.Creating]:
"""Manage the creation and symlinking of a mutable release revision."""
# Get the release
@@ -177,6 +178,7 @@ class CommitteeParticipant(FoundationCommitter):
created=datetime.datetime.now(datetime.UTC),
phase=release.phase,
description=description,
+ use_check_cache=use_check_cache,
)
# Acquire the write lock and add the row
diff --git a/atr/tasks/checks/__init__.py b/atr/tasks/checks/__init__.py
index 9f3d5c5..a83e8f9 100644
--- a/atr/tasks/checks/__init__.py
+++ b/atr/tasks/checks/__init__.py
@@ -65,6 +65,7 @@ class Recorder:
afresh: bool
__cached: bool
__input_hash: str | None
+ __use_check_cache: bool | None
def __init__(
self,
@@ -86,6 +87,7 @@ class Recorder:
self.member_problems: dict[sql.CheckResultStatus, int] = {}
self.__cached = False
self.__input_hash = None
+ self.__use_check_cache = None
self.project_name = project_name
self.version_name = version_name
@@ -139,6 +141,7 @@ class Recorder:
status=status,
message=message,
data=data,
+ cached=False,
input_hash=self.__input_hash,
)
@@ -208,6 +211,9 @@ class Recorder:
if config.get().DISABLE_CHECK_CACHE:
return False
+ if not await self.use_check_cache():
+ return False
+
no_cache_file = self.abs_path_base() / ".atr-no-cache"
if await aiofiles.os.path.exists(no_cache_file):
return False
@@ -245,6 +251,7 @@ class Recorder:
status=cached.status,
message=cached.message,
data=cached.data,
+ cached=True,
input_hash=self.__input_hash,
)
data.add(new_result)
@@ -302,6 +309,18 @@ class Recorder:
member_rel_path=member_rel_path,
)
+ async def use_check_cache(self) -> bool:
+ if self.__use_check_cache is not None:
+ return self.__use_check_cache
+
+ async with db.session() as data:
+ revision = await data.revision(release_name=self.release_name,
number=self.revision_number).get()
+ if revision is None:
+ self.__use_check_cache = True
+ return True
+ self.__use_check_cache = revision.use_check_cache
+ return self.__use_check_cache
+
async def warning(
self, message: str, data: Any, primary_rel_path: str | None = None,
member_rel_path: str | None = None
) -> sql.CheckResult:
diff --git a/atr/templates/check-selected.html
b/atr/templates/check-selected.html
index 77b793d..7a77cc4 100644
--- a/atr/templates/check-selected.html
+++ b/atr/templates/check-selected.html
@@ -198,7 +198,11 @@
<div class="mb-2">
<p>The following form is for debugging purposes only. It will create a
new revision.</p>
</div>
- <div class="mb-3">{{ empty_form|safe }}</div>
+ <div class="mb-2">{{ fresh_form|safe }}</div>
+ {% if is_viewing_as_admin_fn(current_user.uid) %}
+ <div class="mb-2">{{ recheck_form|safe }}</div>
+ <p class="text-muted small mb-3">Rechecks without using cached
results.</p>
+ {% endif %}
<h3 id="delete-draft" class="mt-4">Delete this draft</h3>
<p>Permanently delete this release candidate draft and all associated
files. This action cannot be undone.</p>
diff --git a/atr/templates/report-selected-path.html
b/atr/templates/report-selected-path.html
index 9040529..c6ddc96 100644
--- a/atr/templates/report-selected-path.html
+++ b/atr/templates/report-selected-path.html
@@ -212,9 +212,18 @@
{% endif %}
</td>
<td class="align-middle text-center atr-sans">
- <span class="badge rounded-pill {% if
primary_result.status.value == "success" %}bg-success {% elif
primary_result.status.value == "failure" %}bg-danger {% elif
primary_result.status.value == "warning" %}bg-warning {% elif
primary_result.status.value == "exception" %}bg-danger {% else %}bg-secondary{%
endif %}">
- {{ primary_result.status.value|title }}
- </span>
+ <div class="d-inline-flex flex-column align-items-center
gap-1">
+ <span class="badge rounded-pill {% if
primary_result.status.value == "success" %}bg-success {% elif
primary_result.status.value == "failure" %}bg-danger {% elif
primary_result.status.value == "warning" %}bg-warning {% elif
primary_result.status.value == "exception" %}bg-danger {% else %}bg-secondary{%
endif %}">
+ {{ primary_result.status.value|title }}
+ </span>
+ {% if primary_result.cached %}
+ <span class="badge rounded-pill bg-secondary-subtle
text-secondary-emphasis border border-secondary-subtle small"
+ title="Cached result"
+ aria-label="Cached result">
+ <i class="bi bi-database me-1"></i>Cached
+ </span>
+ {% endif %}
+ </div>
</td>
</tr>
{% endfor %}
@@ -286,9 +295,18 @@
{% if member_result.message %}{{ member_result.message }}{%
endif %}
</td>
<td class="align-middle text-center atr-sans">
- <span class="badge rounded-pill {% if
member_result.status.value == "success" %}bg-success {% elif
member_result.status.value == "failure" %}bg-danger {% elif
member_result.status.value == "warning" %}bg-warning {% elif
member_result.status.value == "exception" %}bg-danger {% else %}bg-secondary{%
endif %}">
- {{ member_result.status.value|title }}
- </span>
+ <div class="d-inline-flex flex-column align-items-center
gap-1">
+ <span class="badge rounded-pill {% if
member_result.status.value == "success" %}bg-success {% elif
member_result.status.value == "failure" %}bg-danger {% elif
member_result.status.value == "warning" %}bg-warning {% elif
member_result.status.value == "exception" %}bg-danger {% else %}bg-secondary{%
endif %}">
+ {{ member_result.status.value|title }}
+ </span>
+ {% if member_result.cached %}
+ <span class="badge rounded-pill bg-secondary-subtle
text-secondary-emphasis border border-secondary-subtle small"
+ title="Cached result"
+ aria-label="Cached result">
+ <i class="bi bi-database me-1"></i>Cached
+ </span>
+ {% endif %}
+ </div>
</td>
</tr>
{% endfor %}
diff --git a/migrations/versions/0044_2026.01.29_24d82084.py
b/migrations/versions/0044_2026.01.29_24d82084.py
new file mode 100644
index 0000000..7903c70
--- /dev/null
+++ b/migrations/versions/0044_2026.01.29_24d82084.py
@@ -0,0 +1,33 @@
+"""Record check cache status and allow status to be set
+
+Revision ID: 0044_2026.01.29_24d82084
+Revises: 0043_2026.01.29_d7d89670
+Create Date: 2026-01-29 20:16:47.043872+00:00
+"""
+
+from collections.abc import Sequence
+
+import sqlalchemy as sa
+from alembic import op
+
+# Revision identifiers, used by Alembic
+revision: str = "0044_2026.01.29_24d82084"
+down_revision: str | None = "0043_2026.01.29_d7d89670"
+branch_labels: str | Sequence[str] | None = None
+depends_on: str | Sequence[str] | None = None
+
+
+def upgrade() -> None:
+ with op.batch_alter_table("checkresult", schema=None) as batch_op:
+ batch_op.add_column(sa.Column("cached", sa.Boolean(), nullable=False,
server_default=sa.false()))
+
+ with op.batch_alter_table("revision", schema=None) as batch_op:
+ batch_op.add_column(sa.Column("use_check_cache", sa.Boolean(),
nullable=False, server_default=sa.true()))
+
+
+def downgrade() -> None:
+ with op.batch_alter_table("revision", schema=None) as batch_op:
+ batch_op.drop_column("use_check_cache")
+
+ with op.batch_alter_table("checkresult", schema=None) as batch_op:
+ batch_op.drop_column("cached")
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]