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]