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 be201ff  Allow only admins to use slow test files
be201ff is described below

commit be201ffed984cec0fcc4dd85dbdecfcca25e5158
Author: Sean B. Palmer <[email protected]>
AuthorDate: Thu Jul 24 15:45:04 2025 +0100

    Allow only admins to use slow test files
---
 atr/blueprints/api/api.py                       |  1 +
 atr/db/__init__.py                              |  3 ++
 atr/models/sql.py                               |  1 +
 atr/revision.py                                 |  2 +-
 atr/routes/announce.py                          |  9 +++---
 atr/routes/draft.py                             |  2 ++
 atr/routes/resolve.py                           |  2 ++
 atr/routes/vote.py                              |  1 +
 atr/routes/voting.py                            |  1 +
 atr/tasks/__init__.py                           | 42 ++++++++++++++-----------
 atr/tasks/checks/__init__.py                    |  1 +
 atr/tasks/checks/paths.py                       |  6 ++--
 atr/worker.py                                   |  1 +
 migrations/script.py.mako                       |  2 +-
 migrations/versions/0016_2025.07.24_07af24db.py | 27 ++++++++++++++++
 15 files changed, 74 insertions(+), 27 deletions(-)

diff --git a/atr/blueprints/api/api.py b/atr/blueprints/api/api.py
index de2b92f..e497b05 100644
--- a/atr/blueprints/api/api.py
+++ b/atr/blueprints/api/api.py
@@ -862,6 +862,7 @@ async def vote_start(data: models.api.VoteStartArgs) -> 
DictResponse:
                 subject=data.subject,
                 body=data.body,
             ).model_dump(),
+            asf_uid=asf_uid,
             project_name=data.project,
             version_name=data.version,
         )
diff --git a/atr/db/__init__.py b/atr/db/__init__.py
index c8d6f1a..1d1a5d2 100644
--- a/atr/db/__init__.py
+++ b/atr/db/__init__.py
@@ -559,6 +559,7 @@ class Session(sqlalchemy.ext.asyncio.AsyncSession):
         status: Opt[sql.TaskStatus] = NOT_SET,
         task_type: Opt[str] = NOT_SET,
         task_args: Opt[Any] = NOT_SET,
+        asf_uid: Opt[str] = NOT_SET,
         added: Opt[datetime.datetime] = NOT_SET,
         started: Opt[datetime.datetime | None] = NOT_SET,
         pid: Opt[int | None] = NOT_SET,
@@ -580,6 +581,8 @@ class Session(sqlalchemy.ext.asyncio.AsyncSession):
             query = query.where(sql.Task.task_type == task_type)
         if is_defined(task_args):
             query = query.where(sql.Task.task_args == task_args)
+        if is_defined(asf_uid):
+            query = query.where(sql.Task.asf_uid == asf_uid)
         if is_defined(added):
             query = query.where(sql.Task.added == added)
         if is_defined(started):
diff --git a/atr/models/sql.py b/atr/models/sql.py
index 31d2de5..20b8857 100644
--- a/atr/models/sql.py
+++ b/atr/models/sql.py
@@ -221,6 +221,7 @@ class Task(sqlmodel.SQLModel, table=True):
     status: TaskStatus = sqlmodel.Field(default=TaskStatus.QUEUED, index=True)
     task_type: TaskType
     task_args: Any = 
sqlmodel.Field(sa_column=sqlalchemy.Column(sqlalchemy.JSON))
+    asf_uid: str
     added: datetime.datetime = sqlmodel.Field(
         default_factory=lambda: datetime.datetime.now(datetime.UTC),
         sa_column=sqlalchemy.Column(UTCDateTime, index=True),
diff --git a/atr/revision.py b/atr/revision.py
index a1df2fd..fcf8ba5 100644
--- a/atr/revision.py
+++ b/atr/revision.py
@@ -164,7 +164,7 @@ async def create_and_manage(
             # It does, however, need a transaction to be created using 
data.begin()
             if release.phase == sql.ReleasePhase.RELEASE_CANDIDATE_DRAFT:
                 # Must use caller_data here because we acquired the write lock
-                await tasks.draft_checks(project_name, version_name, 
new_revision.number, caller_data=data)
+                await tasks.draft_checks(asf_uid, project_name, version_name, 
new_revision.number, caller_data=data)
 
 
 async def latest_info(project_name: str, version_name: str) -> tuple[str, str, 
datetime.datetime] | None:
diff --git a/atr/routes/announce.py b/atr/routes/announce.py
index 579fb06..2e59fc9 100644
--- a/atr/routes/announce.py
+++ b/atr/routes/announce.py
@@ -185,10 +185,10 @@ async def announce(
     subject: str,
     body: str,
     download_path_suffix: str,
-    uid: str,
+    asf_uid: str,
     fullname: str,
 ) -> None:
-    if recipient not in util.permitted_recipients(uid):
+    if recipient not in util.permitted_recipients(asf_uid):
         raise AnnounceError(f"You are not permitted to send announcements to 
{recipient}")
 
     unfinished_dir: str = ""
@@ -209,7 +209,7 @@ async def announce(
             body = await construct.announce_release_body(
                 body,
                 options=construct.AnnounceReleaseOptions(
-                    asfuid=uid,
+                    asfuid=asf_uid,
                     fullname=fullname,
                     project_name=project_name,
                     version_name=version_name,
@@ -219,12 +219,13 @@ async def announce(
                 status=sql.TaskStatus.QUEUED,
                 task_type=sql.TaskType.MESSAGE_SEND,
                 task_args=message.Send(
-                    email_sender=f"{uid}@apache.org",
+                    email_sender=f"{asf_uid}@apache.org",
                     email_recipient=recipient,
                     subject=subject,
                     body=body,
                     in_reply_to=None,
                 ).model_dump(),
+                asf_uid=asf_uid,
                 project_name=project_name,
                 version_name=version_name,
             )
diff --git a/atr/routes/draft.py b/atr/routes/draft.py
index fc17acc..4c0307c 100644
--- a/atr/routes/draft.py
+++ b/atr/routes/draft.py
@@ -316,6 +316,7 @@ async def sbomgen(
                     artifact_path=str(path_in_new_revision.resolve()),
                     output_path=str(sbom_path_in_new_revision.resolve()),
                 ).model_dump(),
+                asf_uid=util.unwrap(session.uid),
                 added=datetime.datetime.now(datetime.UTC),
                 status=sql.TaskStatus.QUEUED,
                 project_name=project_name,
@@ -376,6 +377,7 @@ async def svnload(session: routes.CommitterSession, 
project_name: str, version_n
             svn_import_task = sql.Task(
                 task_type=sql.TaskType.SVN_IMPORT_FILES,
                 task_args=task_args,
+                asf_uid=util.unwrap(session.uid),
                 added=datetime.datetime.now(datetime.UTC),
                 status=sql.TaskStatus.QUEUED,
                 project_name=project_name,
diff --git a/atr/routes/resolve.py b/atr/routes/resolve.py
index 6a4a808..612e23a 100644
--- a/atr/routes/resolve.py
+++ b/atr/routes/resolve.py
@@ -439,6 +439,7 @@ async def _send_resolution(
             body=body,
             in_reply_to=in_reply_to,
         ).model_dump(),
+        asf_uid=util.unwrap(session.uid),
         project_name=release.project.name,
         version_name=release.version,
     )
@@ -454,6 +455,7 @@ async def _send_resolution(
                 body=body,
                 in_reply_to=extra_destination[1],
             ).model_dump(),
+            asf_uid=util.unwrap(session.uid),
             project_name=release.project.name,
             version_name=release.version,
         )
diff --git a/atr/routes/vote.py b/atr/routes/vote.py
index 25d2f4e..36c05c7 100644
--- a/atr/routes/vote.py
+++ b/atr/routes/vote.py
@@ -209,6 +209,7 @@ async def _send_vote(
             body=body_text,
             in_reply_to=in_reply_to,
         ).model_dump(),
+        asf_uid=util.unwrap(session.uid),
         project_name=release.project.name,
         version_name=release.version,
     )
diff --git a/atr/routes/voting.py b/atr/routes/voting.py
index cf1f919..8d70e00 100644
--- a/atr/routes/voting.py
+++ b/atr/routes/voting.py
@@ -278,6 +278,7 @@ async def start_vote(
             subject=subject_data,
             body=body_data,
         ).model_dump(),
+        asf_uid=util.unwrap(session.uid),
         project_name=project_name,
         version_name=version_name,
     )
diff --git a/atr/tasks/__init__.py b/atr/tasks/__init__.py
index bf4fe38..c1b53c3 100644
--- a/atr/tasks/__init__.py
+++ b/atr/tasks/__init__.py
@@ -36,13 +36,14 @@ import atr.tasks.vote as vote
 import atr.util as util
 
 
-async def asc_checks(release: sql.Release, revision: str, signature_path: str) 
-> list[sql.Task]:
+async def asc_checks(asf_uid: str, release: sql.Release, revision: str, 
signature_path: str) -> list[sql.Task]:
     """Create signature check task for a .asc file."""
     tasks = []
 
     if release.committee:
         tasks.append(
             queued(
+                asf_uid,
                 sql.TaskType.SIGNATURE_CHECK,
                 release,
                 revision,
@@ -55,7 +56,7 @@ async def asc_checks(release: sql.Release, revision: str, 
signature_path: str) -
 
 
 async def draft_checks(
-    project_name: str, release_version: str, revision_number: str, 
caller_data: db.Session | None = None
+    asf_uid: str, project_name: str, release_version: str, revision_number: 
str, caller_data: db.Session | None = None
 ) -> int:
     """Core logic to analyse a draft revision and queue checks."""
     # Construct path to the specific revision
@@ -69,13 +70,13 @@ async def draft_checks(
         )
         for path in relative_paths:
             path_str = str(path)
-            task_function: Callable[[sql.Release, str, str], 
Awaitable[list[sql.Task]]] | None = None
+            task_function: Callable[[str, sql.Release, str, str], 
Awaitable[list[sql.Task]]] | None = None
             for suffix, func in TASK_FUNCTIONS.items():
                 if path.name.endswith(suffix):
                     task_function = func
                     break
             if task_function:
-                for task in await task_function(release, revision_number, 
path_str):
+                for task in await task_function(asf_uid, release, 
revision_number, path_str):
                     task.revision_number = revision_number
                     data.add(task)
 
@@ -84,7 +85,7 @@ async def draft_checks(
             if release.project.committee.is_podling:
                 is_podling = True
         path_check_task = queued(
-            sql.TaskType.PATHS_CHECK, release, revision_number, 
extra_args={"is_podling": is_podling}
+            asf_uid, sql.TaskType.PATHS_CHECK, release, revision_number, 
extra_args={"is_podling": is_podling}
         )
         data.add(path_check_task)
         if caller_data is None:
@@ -107,6 +108,7 @@ async def keys_import_file(
                     project_name=project_name,
                     version_name=version_name,
                 ).model_dump(),
+                asf_uid=asf_uid,
                 revision_number=revision_number,
                 primary_rel_path=None,
             )
@@ -115,6 +117,7 @@ async def keys_import_file(
 
 
 def queued(
+    asf_uid: str,
     task_type: sql.TaskType,
     release: sql.Release,
     revision_number: str,
@@ -125,6 +128,7 @@ def queued(
         status=sql.TaskStatus.QUEUED,
         task_type=task_type,
         task_args=extra_args or {},
+        asf_uid=asf_uid,
         project_name=release.project.name,
         version_name=release.version,
         revision_number=revision_number,
@@ -168,40 +172,40 @@ def resolve(task_type: sql.TaskType) -> Callable[..., 
Awaitable[results.Results
         # Otherwise we lose exhaustiveness checking
 
 
-async def sha_checks(release: sql.Release, revision: str, hash_file: str) -> 
list[sql.Task]:
+async def sha_checks(asf_uid: str, release: sql.Release, revision: str, 
hash_file: str) -> list[sql.Task]:
     """Create hash check task for a .sha256 or .sha512 file."""
     tasks = []
 
-    tasks.append(queued(sql.TaskType.HASHING_CHECK, release, revision, 
hash_file))
+    tasks.append(queued(asf_uid, sql.TaskType.HASHING_CHECK, release, 
revision, hash_file))
 
     return tasks
 
 
-async def tar_gz_checks(release: sql.Release, revision: str, path: str) -> 
list[sql.Task]:
+async def tar_gz_checks(asf_uid: str, release: sql.Release, revision: str, 
path: str) -> list[sql.Task]:
     """Create check tasks for a .tar.gz or .tgz file."""
     # This release has committee, as guaranteed in draft_checks
     is_podling = (release.project.committee is not None) and 
release.project.committee.is_podling
     tasks = [
-        queued(sql.TaskType.LICENSE_FILES, release, revision, path, 
extra_args={"is_podling": is_podling}),
-        queued(sql.TaskType.LICENSE_HEADERS, release, revision, path),
-        queued(sql.TaskType.RAT_CHECK, release, revision, path),
-        queued(sql.TaskType.TARGZ_INTEGRITY, release, revision, path),
-        queued(sql.TaskType.TARGZ_STRUCTURE, release, revision, path),
+        queued(asf_uid, sql.TaskType.LICENSE_FILES, release, revision, path, 
extra_args={"is_podling": is_podling}),
+        queued(asf_uid, sql.TaskType.LICENSE_HEADERS, release, revision, path),
+        queued(asf_uid, sql.TaskType.RAT_CHECK, release, revision, path),
+        queued(asf_uid, sql.TaskType.TARGZ_INTEGRITY, release, revision, path),
+        queued(asf_uid, sql.TaskType.TARGZ_STRUCTURE, release, revision, path),
     ]
 
     return tasks
 
 
-async def zip_checks(release: sql.Release, revision: str, path: str) -> 
list[sql.Task]:
+async def zip_checks(asf_uid: str, release: sql.Release, revision: str, path: 
str) -> list[sql.Task]:
     """Create check tasks for a .zip file."""
     # This release has committee, as guaranteed in draft_checks
     is_podling = (release.project.committee is not None) and 
release.project.committee.is_podling
     tasks = [
-        queued(sql.TaskType.LICENSE_FILES, release, revision, path, 
extra_args={"is_podling": is_podling}),
-        queued(sql.TaskType.LICENSE_HEADERS, release, revision, path),
-        queued(sql.TaskType.RAT_CHECK, release, revision, path),
-        queued(sql.TaskType.ZIPFORMAT_INTEGRITY, release, revision, path),
-        queued(sql.TaskType.ZIPFORMAT_STRUCTURE, release, revision, path),
+        queued(asf_uid, sql.TaskType.LICENSE_FILES, release, revision, path, 
extra_args={"is_podling": is_podling}),
+        queued(asf_uid, sql.TaskType.LICENSE_HEADERS, release, revision, path),
+        queued(asf_uid, sql.TaskType.RAT_CHECK, release, revision, path),
+        queued(asf_uid, sql.TaskType.ZIPFORMAT_INTEGRITY, release, revision, 
path),
+        queued(asf_uid, sql.TaskType.ZIPFORMAT_STRUCTURE, release, revision, 
path),
     ]
     return tasks
 
diff --git a/atr/tasks/checks/__init__.py b/atr/tasks/checks/__init__.py
index da7f429..c8a7a45 100644
--- a/atr/tasks/checks/__init__.py
+++ b/atr/tasks/checks/__init__.py
@@ -40,6 +40,7 @@ import atr.util as util
 @dataclasses.dataclass
 class FunctionArguments:
     recorder: Callable[[], Awaitable[Recorder]]
+    asf_uid: str
     project_name: str
     version_name: str
     revision_number: str
diff --git a/atr/tasks/checks/paths.py b/atr/tasks/checks/paths.py
index 58a4ece..f202d52 100644
--- a/atr/tasks/checks/paths.py
+++ b/atr/tasks/checks/paths.py
@@ -25,6 +25,7 @@ import atr.analysis as analysis
 import atr.log as log
 import atr.models.results as results
 import atr.tasks.checks as checks
+import atr.user as user
 import atr.util as util
 
 _ALLOWED_TOP_LEVEL = {"CHANGES", "LICENSE", "NOTICE", "README"}
@@ -79,6 +80,7 @@ async def check(args: checks.FunctionArguments) -> 
results.Results | None:
     for relative_path in relative_paths:
         # Delegate processing of each path to the helper function
         await _check_path_process_single(
+            args.asf_uid,
             base_path,
             relative_path,
             recorder_errors,
@@ -155,6 +157,7 @@ async def _check_metadata_rules(
 
 
 async def _check_path_process_single(
+    asf_uid: str,
     base_path: pathlib.Path,
     relative_path: pathlib.Path,
     recorder_errors: checks.Recorder,
@@ -168,8 +171,7 @@ async def _check_path_process_single(
     relative_path_str = str(relative_path)
 
     # For debugging and testing
-    # TODO: Scope this to admin users
-    if full_path.name == "deliberately_slow_ATR_task_filename.txt":
+    if user.is_admin(asf_uid) and (full_path.name == 
"deliberately_slow_ATR_task_filename.txt"):
         await asyncio.sleep(20)
 
     errors: list[str] = []
diff --git a/atr/worker.py b/atr/worker.py
index b5037ac..d6f9c7e 100644
--- a/atr/worker.py
+++ b/atr/worker.py
@@ -195,6 +195,7 @@ async def _task_process(task_id: int, task_type: str, 
task_args: list[str] | dic
 
             function_arguments = checks.FunctionArguments(
                 recorder=recorder_factory,
+                asf_uid=task_obj.asf_uid,
                 project_name=task_obj.project_name or "",
                 version_name=task_obj.version_name or "",
                 revision_number=task_obj.revision_number,
diff --git a/migrations/script.py.mako b/migrations/script.py.mako
index 66997e0..f4f51d8 100644
--- a/migrations/script.py.mako
+++ b/migrations/script.py.mako
@@ -9,7 +9,7 @@ from typing import Sequence, Union
 
 from alembic import op
 import sqlmodel
-import atr.db.models
+import atr.models.sql
 ${imports if imports else ""}
 
 # Revision identifiers, used by Alembic
diff --git a/migrations/versions/0016_2025.07.24_07af24db.py 
b/migrations/versions/0016_2025.07.24_07af24db.py
new file mode 100644
index 0000000..d3104f9
--- /dev/null
+++ b/migrations/versions/0016_2025.07.24_07af24db.py
@@ -0,0 +1,27 @@
+"""Add ASF UID to Task
+
+Revision ID: 0016_2025.07.24_07af24db
+Revises: 0015_2025.07.03_cb10d8d3
+Create Date: 2025-07-24 14:41:24.008407+00:00
+"""
+
+from collections.abc import Sequence
+
+import sqlalchemy as sa
+from alembic import op
+
+# Revision identifiers, used by Alembic
+revision: str = "0016_2025.07.24_07af24db"
+down_revision: str | None = "0015_2025.07.03_cb10d8d3"
+branch_labels: str | Sequence[str] | None = None
+depends_on: str | Sequence[str] | None = None
+
+
+def upgrade() -> None:
+    with op.batch_alter_table("task", schema=None) as batch_op:
+        batch_op.add_column(sa.Column("asf_uid", sa.String(), nullable=False, 
server_default=""))
+
+
+def downgrade() -> None:
+    with op.batch_alter_table("task", schema=None) as batch_op:
+        batch_op.drop_column("asf_uid")


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to