You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tooling.apache.org by sb...@apache.org on 2025/09/09 18:42:33 UTC
(tooling-trusted-release) branch main updated: Allow vote initiation emails to be sent to actual committee mailing lists
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 4555e56 Allow vote initiation emails to be sent to actual committee mailing lists
4555e56 is described below
commit 4555e568448a6905585d4111aba19f1f2d2832b4
Author: Sean B. Palmer <se...@miscoranda.com>
AuthorDate: Tue Sep 9 19:41:45 2025 +0100
Allow vote initiation emails to be sent to actual committee mailing lists
---
atr/blueprints/api/api.py | 7 +++----
atr/models/sql.py | 4 +++-
atr/routes/announce.py | 9 ++++++---
atr/routes/voting.py | 21 ++++++++++++---------
atr/storage/writers/announce.py | 2 +-
atr/storage/writers/vote.py | 2 +-
atr/tasks/message.py | 8 +++++---
atr/tasks/vote.py | 2 +-
atr/templates/about.html | 8 ++++++++
atr/templates/announce-selected.html | 2 +-
atr/templates/voting-selected-revision.html | 2 +-
atr/util.py | 11 ++++++++++-
12 files changed, 52 insertions(+), 26 deletions(-)
diff --git a/atr/blueprints/api/api.py b/atr/blueprints/api/api.py
index 10e39cd..68cb927 100644
--- a/atr/blueprints/api/api.py
+++ b/atr/blueprints/api/api.py
@@ -1224,13 +1224,12 @@ async def vote_start(data: models.api.VoteStartArgs) -> DictResponse:
"""
asf_uid = _jwt_asf_uid()
- permitted_recipients = util.permitted_recipients(asf_uid)
- if data.email_to not in permitted_recipients:
- raise exceptions.Forbidden("Invalid mailing list choice")
-
try:
async with storage.write(asf_uid) as write:
wacm = await write.as_project_committee_member(data.project)
+ permitted_recipients = util.permitted_voting_recipients(asf_uid, wacm.committee_name)
+ if data.email_to not in permitted_recipients:
+ raise exceptions.Forbidden("Invalid mailing list choice")
# TODO: Get fullname and use instead of asf_uid
task = await wacm.vote.start(
data.email_to,
diff --git a/atr/models/sql.py b/atr/models/sql.py
index a5d9a9f..79a2ea2 100644
--- a/atr/models/sql.py
+++ b/atr/models/sql.py
@@ -552,7 +552,9 @@ On behalf of the Apache [COMMITTEE] project team,
return """Hello [COMMITTEE],
I'd like to call a vote on releasing the following artifacts as
-Apache [PROJECT] [VERSION].
+Apache [PROJECT] [VERSION]. This vote is being conducted using an
+Alpha version of the Apache Trusted Release(s) (ATR) platform.
+Please report any bugs or issues to the ASF Tooling team.
The release candidate page, including downloads, can be found at:
diff --git a/atr/routes/announce.py b/atr/routes/announce.py
index b04d348..dd14908 100644
--- a/atr/routes/announce.py
+++ b/atr/routes/announce.py
@@ -68,7 +68,7 @@ async def selected(session: routes.CommitterSession, project_name: str, version_
release = await session.release(
project_name, version_name, with_committee=True, phase=sql.ReleasePhase.RELEASE_PREVIEW
)
- announce_form = await _create_announce_form_instance(util.permitted_recipients(session.uid))
+ announce_form = await _create_announce_form_instance(util.permitted_announce_recipients(session.uid))
# Hidden fields
announce_form.preview_name.data = release.name
# There must be a revision to announce
@@ -112,8 +112,11 @@ async def selected_post(
"""Handle the announcement form submission and promote the preview to release."""
await session.check_access(project_name)
- permitted_recipients = util.permitted_recipients(session.uid)
- announce_form = await _create_announce_form_instance(permitted_recipients, data=await quart.request.form)
+ permitted_recipients = util.permitted_announce_recipients(session.uid)
+ announce_form = await _create_announce_form_instance(
+ permitted_recipients,
+ data=await quart.request.form,
+ )
if not (await announce_form.validate_on_submit()):
error_message = "Invalid submission"
diff --git a/atr/routes/voting.py b/atr/routes/voting.py
index 7d903f0..51f12d3 100644
--- a/atr/routes/voting.py
+++ b/atr/routes/voting.py
@@ -164,10 +164,11 @@ async def _form(
# Hidden field
form.release_name.data = release.name
# Description
- form.mailing_list.description = f"""\
-NOTE: The limited options above are provided for testing purposes.
-In the finished version of ATR, you will be able to send to your own specified mailing lists, i.e.
-{release_policy_mailto_addresses}."""
+ form.mailing_list.description = """\
+NOTE: The options to send to the user-tests mailing
+list and yourself are provided for testing purposes
+only, and will not be available in the finished
+version of ATR."""
return form
@@ -194,6 +195,10 @@ async def _selected_revision_data(
data: db.Session,
session: routes.CommitterSession,
) -> response.Response | str | VoteInitiateForm:
+ committee = release.committee
+ if committee is None:
+ raise base.ASFQuartException("Release has no associated committee", errorcode=400)
+
if release.project.policy_strict_checking:
if await interaction.has_failing_checks(release, revision, caller_data=data):
return await session.redirect(
@@ -207,7 +212,7 @@ async def _selected_revision_data(
# Check that the user is on the project committee for the release
# TODO: Consider relaxing this to all committers
# Otherwise we must not show the vote form
- if not (user.is_committee_member(release.committee, session.uid) or user.is_admin(session.uid)):
+ if not (user.is_committee_member(committee, session.uid) or user.is_admin(session.uid)):
return await session.redirect(
compose.selected,
error="You must be on the PMC of this project to start a vote",
@@ -217,7 +222,7 @@ async def _selected_revision_data(
)
# committee = util.unwrap(release.committee)
- permitted_recipients = util.permitted_recipients(session.uid)
+ permitted_recipients = util.permitted_voting_recipients(session.uid, committee.name)
if release.release_policy:
min_hours = release.release_policy.min_hours if (release.release_policy.min_hours is not None) else 72
else:
@@ -253,9 +258,7 @@ async def _selected_revision_data(
vote_duration_choice: int = util.unwrap(form.vote_duration.data)
subject_data: str = util.unwrap(form.subject.data)
body_data: str = util.unwrap(form.body.data)
- if release.committee is None:
- raise base.ASFQuartException("Release has no associated committee", errorcode=400)
- async with storage.write_as_committee_member(release.committee.name) as wacm:
+ async with storage.write_as_committee_member(committee.name) as wacm:
_task = await wacm.vote.start(
email_to,
project_name,
diff --git a/atr/storage/writers/announce.py b/atr/storage/writers/announce.py
index 2563178..094a427 100644
--- a/atr/storage/writers/announce.py
+++ b/atr/storage/writers/announce.py
@@ -109,7 +109,7 @@ class CommitteeMember(CommitteeParticipant):
asf_uid: str,
fullname: str,
) -> None:
- if recipient not in util.permitted_recipients(asf_uid):
+ if recipient not in util.permitted_announce_recipients(asf_uid):
raise storage.AccessError(f"You are not permitted to send announcements to {recipient}")
unfinished_dir: str = ""
diff --git a/atr/storage/writers/vote.py b/atr/storage/writers/vote.py
index 76eddc8..0d176cc 100644
--- a/atr/storage/writers/vote.py
+++ b/atr/storage/writers/vote.py
@@ -300,7 +300,7 @@ class CommitteeMember(CommitteeParticipant):
_committee=True,
).demand(storage.AccessError("Release not found"))
if permitted_recipients is None:
- permitted_recipients = util.permitted_recipients(asf_uid)
+ permitted_recipients = util.permitted_voting_recipients(asf_uid, self.__committee_name)
if email_to not in permitted_recipients:
# This will be checked again by tasks/vote.py for extra safety
log.info(f"Invalid mailing list choice: {email_to} not in {permitted_recipients}")
diff --git a/atr/tasks/message.py b/atr/tasks/message.py
index 1e9a78e..66d7088 100644
--- a/atr/tasks/message.py
+++ b/atr/tasks/message.py
@@ -20,7 +20,6 @@ import atr.mail as mail
import atr.models.results as results
import atr.models.schema as schema
import atr.tasks.checks as checks
-import atr.util as util
class Send(schema.Strict):
@@ -46,8 +45,11 @@ async def send(args: Send) -> results.Results | None:
else:
raise SendError(f"Invalid email sender: {args.email_sender}")
- if args.email_recipient not in util.permitted_recipients(sender_asf_uid):
- raise SendError(f"You are not permitted to send announcements to {args.email_recipient}")
+ recipient_domain = args.email_recipient.split("@")[-1]
+ sending_to_self = recipient_domain == f"{sender_asf_uid}@apache.org"
+ sending_to_committee = recipient_domain.endswith(".apache.org")
+ if not (sending_to_self or sending_to_committee):
+ raise SendError(f"You are not permitted to send emails to {args.email_recipient}")
message = mail.Message(
email_sender=args.email_sender,
diff --git a/atr/tasks/vote.py b/atr/tasks/vote.py
index be2fe09..267bd66 100644
--- a/atr/tasks/vote.py
+++ b/atr/tasks/vote.py
@@ -115,7 +115,7 @@ async def _initiate_core_logic(args: Initiate) -> results.Results | None:
),
)
- permitted_recipients = util.permitted_recipients(args.initiator_id)
+ permitted_recipients = util.permitted_voting_recipients(args.initiator_id, release.committee.name)
if args.email_to not in permitted_recipients:
log.error(f"Invalid mailing list choice: {args.email_to} not in {permitted_recipients}")
raise VoteInitiationError("Invalid mailing list choice")
diff --git a/atr/templates/about.html b/atr/templates/about.html
index 0bd74ce..f583b32 100644
--- a/atr/templates/about.html
+++ b/atr/templates/about.html
@@ -31,6 +31,14 @@
{% block content %}
<h1>About ATR</h1>
+ <h2>Release functionality</h2>
+
+ <p>
+ For Alpha v2, the ATR allows users to start votes by sending the vote initiation email to their own mailing lists. Announcements of finished releases, however, must still be performed manually using dist SVN as usual.
+ </p>
+
+ <h2>Release process</h2>
+
<p>
The following table compares the UI, CLI, and GitHub actions for each step in the release process. Variables <code>P</code>, <code>V</code>, and <code>R</code> represent the project, version, and revision of a release, respectively.
</p>
diff --git a/atr/templates/announce-selected.html b/atr/templates/announce-selected.html
index 1c1e451..feeeb79 100644
--- a/atr/templates/announce-selected.html
+++ b/atr/templates/announce-selected.html
@@ -74,7 +74,7 @@
<div class="row mb-3 pb-3 border-bottom">
{{ forms.label(announce_form.mailing_list, col="sm3-high") }}
<div class="col-md-9">
- <div class="d-flex gap-4 mb-3">
+ <div class="d-flex flex-wrap gap-2 mb-3">
{% for subfield in announce_form.mailing_list %}
<div class="form-check">
{{ forms.widget(subfield, classes="form-check-input") }}
diff --git a/atr/templates/voting-selected-revision.html b/atr/templates/voting-selected-revision.html
index 84a9f86..ae15bac 100644
--- a/atr/templates/voting-selected-revision.html
+++ b/atr/templates/voting-selected-revision.html
@@ -62,7 +62,7 @@
<div class="row mb-3 pb-3 border-bottom">
{{ forms.label(form.mailing_list, col="md3-high") }}
<div class="col-md-9">
- <div class="d-flex gap-4 mb-2">
+ <div class="d-flex flex-wrap gap-2 mb-2">
{% for subfield in form.mailing_list %}
<div class="form-check">
{{ subfield(class_='form-check-input') }}
diff --git a/atr/util.py b/atr/util.py
index 7da3259..021b7fd 100644
--- a/atr/util.py
+++ b/atr/util.py
@@ -642,7 +642,7 @@ async def paths_recursive_all(base_path: pathlib.Path) -> AsyncGenerator[pathlib
queue.append(entry_abs_path)
-def permitted_recipients(asf_uid: str) -> list[str]:
+def permitted_announce_recipients(asf_uid: str) -> list[str]:
return [
# f"dev@{committee.name}.apache.org",
# f"private@{committee.name}.apache.org",
@@ -651,6 +651,15 @@ def permitted_recipients(asf_uid: str) -> list[str]:
]
+def permitted_voting_recipients(asf_uid: str, committee_name: str) -> list[str]:
+ return [
+ f"dev@{committee_name}.apache.org",
+ f"private@{committee_name}.apache.org",
+ USER_TESTS_ADDRESS,
+ f"{asf_uid}@apache.org",
+ ]
+
+
async def read_file_for_viewer(full_path: pathlib.Path, max_size: int) -> tuple[str | None, bool, bool, str | None]:
"""Read file content for viewer."""
content: str | None = None
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@tooling.apache.org
For additional commands, e-mail: commits-help@tooling.apache.org