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 b65a664 Improve email signatures and general formatting
b65a664 is described below
commit b65a664e52f76123e749b820897aaba778415e85
Author: Sean B. Palmer <[email protected]>
AuthorDate: Tue May 6 17:24:17 2025 +0100
Improve email signatures and general formatting
---
atr/construct.py | 22 +++++++++++++++-------
atr/routes/announce.py | 1 +
atr/routes/draft.py | 1 +
atr/routes/preview.py | 1 +
atr/routes/resolve.py | 5 ++++-
atr/routes/vote.py | 9 +++++++--
atr/routes/voting.py | 1 +
atr/tasks/vote.py | 2 ++
8 files changed, 32 insertions(+), 10 deletions(-)
diff --git a/atr/construct.py b/atr/construct.py
index 58feee9..a41f781 100644
--- a/atr/construct.py
+++ b/atr/construct.py
@@ -17,6 +17,7 @@
import dataclasses
+import aiofiles.os
import quart
import atr.config as config
@@ -28,6 +29,7 @@ import atr.util as util
@dataclasses.dataclass
class AnnounceReleaseOptions:
asfuid: str
+ fullname: str
project_name: str
version_name: str
@@ -35,6 +37,7 @@ class AnnounceReleaseOptions:
@dataclasses.dataclass
class StartVoteOptions:
asfuid: str
+ fullname: str
project_name: str
version_name: str
vote_duration: int
@@ -72,6 +75,7 @@ async def announce_release_body(body: str, options:
AnnounceReleaseOptions) -> s
body = body.replace("[PROJECT]", options.project_name)
body = body.replace("[VERSION]", options.version_name)
body = body.replace("[YOUR_ASF_ID]", options.asfuid)
+ body = body.replace("[YOUR_FULL_NAME]", options.fullname)
return body
@@ -100,14 +104,12 @@ Downloads are available from the following URL:
On behalf of the Apache [COMMITTEE] project team,
-[YOUR_ASF_ID]
+[YOUR_FULL_NAME] ([YOUR_ASF_ID])
"""
async def start_vote_body(body: str, options: StartVoteOptions) -> str:
async with db.session() as data:
- user_key = await
data.public_signing_key(apache_uid=options.asfuid).get()
- user_key_fingerprint = user_key.fingerprint if user_key else
"0000000000000000000000000000000000000000"
# Do not limit by phase, as it may be at RELEASE_CANDIDATE here if
called by the task
release = await data.release(
project_name=options.project_name,
@@ -125,6 +127,11 @@ async def start_vote_body(body: str, options:
StartVoteOptions) -> str:
committee_name = release.committee.display_name if release.committee else
release.project.display_name
project_short_display_name = release.project.short_display_name if
release.project else options.project_name
+ keys_file = None
+ keys_file_path = util.get_finished_dir() / options.project_name / "KEYS"
+ if await aiofiles.os.path.isfile(keys_file_path):
+ keys_file = f"https://{host}/downloads/{options.project_name}/KEYS"
+
checklist_content = ""
async with db.session() as data:
release_policy = await db.get_project_release_policy(data,
options.project_name)
@@ -135,12 +142,13 @@ async def start_vote_body(body: str, options:
StartVoteOptions) -> str:
# TODO: Handle the DURATION == 0 case
body = body.replace("[COMMITTEE]", committee_name)
body = body.replace("[DURATION]", str(options.vote_duration))
- body = body.replace("[KEY_FINGERPRINT]", user_key_fingerprint or "(No key
found)")
+ body = body.replace("[KEYS_FILE]", keys_file or "[Sorry, the KEYS file is
missing!]")
body = body.replace("[PROJECT]", project_short_display_name)
body = body.replace("[RELEASE_CHECKLIST]", checklist_content)
body = body.replace("[REVIEW_URL]", review_url)
body = body.replace("[VERSION]", options.version_name)
body = body.replace("[YOUR_ASF_ID]", options.asfuid)
+ body = body.replace("[YOUR_FULL_NAME]", options.fullname)
return body
@@ -164,9 +172,9 @@ The release candidate page, including downloads, can be
found at:
[REVIEW_URL]
-The release artifacts are signed with the GPG key with fingerprint:
+The release artifacts are signed with one or more GPG keys from:
- [KEY_FINGERPRINT]
+ [KEYS_FILE]
Please review the release candidate and vote accordingly.
@@ -180,5 +188,5 @@ This vote will remain open for [DURATION] hours.
[RELEASE_CHECKLIST]
Thanks,
-[YOUR_ASF_ID]
+[YOUR_FULL_NAME] ([YOUR_ASF_ID])
"""
diff --git a/atr/routes/announce.py b/atr/routes/announce.py
index c4c713e..d4963dd 100644
--- a/atr/routes/announce.py
+++ b/atr/routes/announce.py
@@ -147,6 +147,7 @@ async def selected_post(
body,
options=construct.AnnounceReleaseOptions(
asfuid=session.uid,
+ fullname=session.fullname,
project_name=project_name,
version_name=version_name,
),
diff --git a/atr/routes/draft.py b/atr/routes/draft.py
index c6ac1ae..c139e9b 100644
--- a/atr/routes/draft.py
+++ b/atr/routes/draft.py
@@ -487,6 +487,7 @@ async def vote_preview(
form_body,
construct.StartVoteOptions(
asfuid=asfuid,
+ fullname=session.fullname,
project_name=project_name,
version_name=version_name,
vote_duration=vote_duration,
diff --git a/atr/routes/preview.py b/atr/routes/preview.py
index 3eb731e..669b8f1 100644
--- a/atr/routes/preview.py
+++ b/atr/routes/preview.py
@@ -78,6 +78,7 @@ async def announce_preview(
# Construct options and generate body
options = construct.AnnounceReleaseOptions(
asfuid=session.uid,
+ fullname=session.fullname,
project_name=project_name,
version_name=version_name,
)
diff --git a/atr/routes/resolve.py b/atr/routes/resolve.py
index 07b3041..90b0b3e 100644
--- a/atr/routes/resolve.py
+++ b/atr/routes/resolve.py
@@ -16,6 +16,7 @@
# under the License.
import json
+import os
import quart
import werkzeug.wrappers.response as response
@@ -128,6 +129,8 @@ async def selected_post(
def task_mid_get(latest_vote_task: models.Task) -> str | None:
+ if "LOCAL_DEBUG" in os.environ:
+ return "[email protected]"
# TODO: Improve this
task_mid = None
@@ -184,7 +187,7 @@ async def _send_resolution(
email_recipient = latest_vote_task.task_args["email_to"]
email_sender = f"{session.uid}@apache.org"
subject = f"[VOTE] [RESULT] Release {release.project.display_name}
{release.version} {resolution.upper()}"
- body = f"{body}\n\n--{session.uid}"
+ body = f"{body}\n\n-- \n{session.fullname} ({session.uid})"
in_reply_to = vote_thread_mid
task = models.Task(
diff --git a/atr/routes/vote.py b/atr/routes/vote.py
index dd7fea6..a893bf9 100644
--- a/atr/routes/vote.py
+++ b/atr/routes/vote.py
@@ -129,7 +129,12 @@ async def _send_vote(
email_recipient = latest_vote_task.task_args["email_to"]
email_sender = f"{session.uid}@apache.org"
subject = f"Re: {original_subject}"
- body = f"{vote}{('\n\n' + comment) if comment else ''}\n\n--{session.uid}"
+ body = [f"{vote.lower()} ({session.uid}) {session.fullname}"]
+ if comment:
+ body.append(f"{comment}")
+ # Only include the signature if there is a comment
+ body.append(f"-- \n{session.fullname} ({session.uid})")
+ body_text = "\n\n".join(body)
in_reply_to = vote_thread_mid
task = models.Task(
@@ -139,7 +144,7 @@ async def _send_vote(
email_sender=email_sender,
email_recipient=email_recipient,
subject=subject,
- body=body,
+ body=body_text,
in_reply_to=in_reply_to,
).model_dump(),
release_name=release.name,
diff --git a/atr/routes/voting.py b/atr/routes/voting.py
index 7052ec9..6c26d5f 100644
--- a/atr/routes/voting.py
+++ b/atr/routes/voting.py
@@ -138,6 +138,7 @@ async def selected_revision(
email_to=email_to,
vote_duration=vote_duration_choice,
initiator_id=session.uid,
+ initiator_fullname=session.fullname,
subject=subject_data,
body=body_data,
).model_dump(),
diff --git a/atr/tasks/vote.py b/atr/tasks/vote.py
index 0ed5ee7..0e01322 100644
--- a/atr/tasks/vote.py
+++ b/atr/tasks/vote.py
@@ -41,6 +41,7 @@ class Initiate(pydantic.BaseModel):
email_to: str = pydantic.Field(..., description="The mailing list address
to send the vote email to")
vote_duration: int = pydantic.Field(..., description="Duration of the vote
in hours")
initiator_id: str = pydantic.Field(..., description="ASF ID of the vote
initiator")
+ initiator_fullname: str = pydantic.Field(..., description="Full name of
the vote initiator")
subject: str = pydantic.Field(..., description="Subject line for the vote
email")
body: str = pydantic.Field(..., description="Body content for the vote
email")
@@ -107,6 +108,7 @@ async def _initiate_core_logic(args: Initiate) -> dict[str,
Any]:
args.body,
construct.StartVoteOptions(
asfuid=args.initiator_id,
+ fullname=args.initiator_fullname,
project_name=release.project.name,
version_name=release.version,
vote_duration=args.vote_duration,
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]