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 0dd29c63 Distinguish admins writing for committees from those writing 
generally
0dd29c63 is described below

commit 0dd29c6335d65681cffcdbac1572e0995dd7315f
Author: Sean B. Palmer <[email protected]>
AuthorDate: Wed Mar 4 19:39:40 2026 +0000

    Distinguish admins writing for committees from those writing generally
---
 atr/admin/__init__.py          |  6 ++---
 atr/api/__init__.py            |  4 +--
 atr/storage/__init__.py        | 60 +++++++++++++++++++++++++++++++++---------
 atr/storage/writers/keys.py    |  2 +-
 atr/storage/writers/release.py |  9 +++----
 atr/storage/writers/tokens.py  |  5 ++--
 scripts/keys_import.py         |  4 +--
 7 files changed, 60 insertions(+), 30 deletions(-)

diff --git a/atr/admin/__init__.py b/atr/admin/__init__.py
index 8fa120fd..6a759d93 100644
--- a/atr/admin/__init__.py
+++ b/atr/admin/__init__.py
@@ -742,7 +742,7 @@ async def revoke_user_tokens_post(
     target_uid = revoke_form.asf_uid
 
     async with storage.write(session) as write:
-        wafa = write.as_foundation_admin(session.asf_uid)
+        wafa = write.as_foundation_admin()
         count = await wafa.tokens.revoke_all_user_tokens(target_uid)
 
     if count > 0:
@@ -1014,8 +1014,8 @@ async def _delete_releases(session: web.Committer, 
releases_to_delete: list[str]
                 if release.committee is None:
                     raise RuntimeError(f"Release {release_name} has no 
committee")
             async with storage.write(session) as write:
-                wafa = write.as_foundation_admin(release.committee.name)
-                error = await wafa.release.delete(release.project.name, 
release.version)
+                waca = write.as_committee_admin(release.committee.name)
+                error = await waca.release.delete(release.project.name, 
release.version)
                 # Ensure that deletion errors are reported to the user
                 if error is not None:
                     raise RuntimeError(error)
diff --git a/atr/api/__init__.py b/atr/api/__init__.py
index ad2c628b..d911a7d9 100644
--- a/atr/api/__init__.py
+++ b/atr/api/__init__.py
@@ -912,8 +912,8 @@ async def release_delete(data: 
models.api.ReleaseDeleteArgs) -> DictResponse:
         raise exceptions.Forbidden("You do not have permission to delete a 
release")
 
     async with storage.write(asf_uid) as write:
-        wafa = write.as_foundation_admin(data.project)
-        error = await wafa.release.delete(data.project, data.version)
+        waca = await write.as_project_committee_admin(data.project)
+        error = await waca.release.delete(data.project, data.version)
         # Ensure that deletion errors are reported to the user
         if error is not None:
             raise RuntimeError(error)
diff --git a/atr/storage/__init__.py b/atr/storage/__init__.py
index 15314b26..4b8caa0f 100644
--- a/atr/storage/__init__.py
+++ b/atr/storage/__init__.py
@@ -220,13 +220,11 @@ class WriteAsCommitteeMember(WriteAsCommitteeParticipant):
         return self.__committee_name
 
 
-class WriteAsFoundationAdmin(WriteAsCommitteeMember):
-    def __init__(self, write: Write, data: db.Session, committee_name: str):
+class WriteAsFoundationAdmin(WriteAsFoundationCommitter):
+    def __init__(self, write: Write, data: db.Session):
         self.__asf_uid = write.authorisation.asf_uid
-        self.__committee_name = committee_name
-        self.keys = writers.keys.FoundationAdmin(write, self, data, 
committee_name)
-        self.release = writers.release.FoundationAdmin(write, self, data, 
committee_name)
-        self.tokens = writers.tokens.FoundationAdmin(write, self, data, 
committee_name)
+        self.release = writers.release.FoundationAdmin(write, self, data)
+        self.tokens = writers.tokens.FoundationAdmin(write, self, data)
 
     @property
     def asf_uid(self) -> str:
@@ -234,9 +232,11 @@ class WriteAsFoundationAdmin(WriteAsCommitteeMember):
             raise AccessError("Not authorized")
         return self.__asf_uid
 
-    @property
-    def committee_name(self) -> str:
-        return self.__committee_name
+
+class WriteAsCommitteeAdmin(WriteAsCommitteeMember):
+    def __init__(self, write: Write, data: db.Session, committee_name: str):
+        super().__init__(write, data, committee_name)
+        self.keys = writers.keys.FoundationAdmin(write, self, data, 
committee_name)
 
 
 # TODO: Could name this WriteDispatcher
@@ -251,6 +251,20 @@ class Write:
     def authorisation(self) -> principal.Authorisation:
         return self.__authorisation
 
+    def as_committee_admin(self, committee_name: str) -> WriteAsCommitteeAdmin:
+        return 
self.as_committee_admin_outcome(committee_name).result_or_raise()
+
+    def as_committee_admin_outcome(self, committee_name: str) -> 
outcome.Outcome[WriteAsCommitteeAdmin]:
+        if self.__authorisation.asf_uid is None:
+            return outcome.Error(AccessError("Not authorized"))
+        if not user.is_admin(self.__authorisation.asf_uid):
+            return outcome.Error(AccessError("Not an admin"))
+        try:
+            waca = WriteAsCommitteeAdmin(self, self.__data, committee_name)
+        except Exception as e:
+            return outcome.Error(e)
+        return outcome.Result(waca)
+
     def as_committee_member(self, committee_name: str) -> 
WriteAsCommitteeMember:
         return 
self.as_committee_member_outcome(committee_name).result_or_raise()
 
@@ -291,16 +305,16 @@ class Write:
             return outcome.Error(e)
         return outcome.Result(wafm)
 
-    def as_foundation_admin(self, committee_name: str) -> 
WriteAsFoundationAdmin:
-        return 
self.as_foundation_admin_outcome(committee_name).result_or_raise()
+    def as_foundation_admin(self) -> WriteAsFoundationAdmin:
+        return self.as_foundation_admin_outcome().result_or_raise()
 
-    def as_foundation_admin_outcome(self, committee_name: str) -> 
outcome.Outcome[WriteAsFoundationAdmin]:
+    def as_foundation_admin_outcome(self) -> 
outcome.Outcome[WriteAsFoundationAdmin]:
         if self.__authorisation.asf_uid is None:
             return outcome.Error(AccessError("Not authorized"))
         if not user.is_admin(self.__authorisation.asf_uid):
             return outcome.Error(AccessError("Not an admin"))
         try:
-            wafa = WriteAsFoundationAdmin(self, self.__data, committee_name)
+            wafa = WriteAsFoundationAdmin(self, self.__data)
         except Exception as e:
             return outcome.Error(e)
         return outcome.Result(wafa)
@@ -318,6 +332,26 @@ class Write:
     # async def as_key_owner(self) -> types.Outcome[WriteAsKeyOwner]:
     #     ...
 
+    async def as_project_committee_admin(self, project_name: str) -> 
WriteAsCommitteeAdmin:
+        write_as_outcome = await 
self.as_project_committee_admin_outcome(project_name)
+        return write_as_outcome.result_or_raise()
+
+    async def as_project_committee_admin_outcome(self, project_name: str) -> 
outcome.Outcome[WriteAsCommitteeAdmin]:
+        project = await self.__data.project(project_name, 
_committee=True).demand(
+            AccessError(f"Project not found: {project_name}")
+        )
+        if project.committee is None:
+            return outcome.Error(AccessError("No committee found for project - 
Invalid state"))
+        if self.__authorisation.asf_uid is None:
+            return outcome.Error(AccessError("Not authorized"))
+        if not user.is_admin(self.__authorisation.asf_uid):
+            return outcome.Error(AccessError("Not an admin"))
+        try:
+            waca = WriteAsCommitteeAdmin(self, self.__data, 
project.committee.name)
+        except Exception as e:
+            return outcome.Error(e)
+        return outcome.Result(waca)
+
     async def as_project_committee_member(self, project_name: str) -> 
WriteAsCommitteeMember:
         write_as_outcome = await 
self.as_project_committee_member_outcome(project_name)
         return write_as_outcome.result_or_raise()
diff --git a/atr/storage/writers/keys.py b/atr/storage/writers/keys.py
index ca1e4f58..b0cb6ee4 100644
--- a/atr/storage/writers/keys.py
+++ b/atr/storage/writers/keys.py
@@ -655,7 +655,7 @@ class FoundationAdmin(CommitteeMember):
     def __init__(
         self,
         write: storage.Write,
-        write_as: storage.WriteAsFoundationAdmin,
+        write_as: storage.WriteAsCommitteeAdmin,
         data: db.Session,
         committee_name: str,
     ):
diff --git a/atr/storage/writers/release.py b/atr/storage/writers/release.py
index 8f244172..10188b5f 100644
--- a/atr/storage/writers/release.py
+++ b/atr/storage/writers/release.py
@@ -779,11 +779,9 @@ class CommitteeMember(CommitteeParticipant):
         self.__committee_name = committee_name
 
 
-class FoundationAdmin(CommitteeMember):
-    def __init__(
-        self, write: storage.Write, write_as: storage.WriteAsFoundationAdmin, 
data: db.Session, committee_name: str
-    ) -> None:
-        super().__init__(write, write_as, data, committee_name)
+class FoundationAdmin(FoundationCommitter):
+    def __init__(self, write: storage.Write, write_as: 
storage.WriteAsFoundationAdmin, data: db.Session) -> None:
+        super().__init__(write, write_as, data)
         self.__write = write
         self.__write_as = write_as
         self.__data = data
@@ -791,4 +789,3 @@ class FoundationAdmin(CommitteeMember):
         if asf_uid is None:
             raise storage.AccessError("Not authorized")
         self.__asf_uid = asf_uid
-        self.__committee_name = committee_name
diff --git a/atr/storage/writers/tokens.py b/atr/storage/writers/tokens.py
index 620e9d3b..cfb28d1c 100644
--- a/atr/storage/writers/tokens.py
+++ b/atr/storage/writers/tokens.py
@@ -179,15 +179,14 @@ class CommitteeMember(CommitteeParticipant):
         self.__committee_name = committee_name
 
 
-class FoundationAdmin(CommitteeMember):
+class FoundationAdmin(FoundationCommitter):
     def __init__(
         self,
         write: storage.Write,
         write_as: storage.WriteAsFoundationAdmin,
         data: db.Session,
-        committee_name: str,
     ):
-        super().__init__(write, write_as, data, committee_name)
+        super().__init__(write, write_as, data)
         self.__write = write
         self.__write_as = write_as
         self.__data = data
diff --git a/scripts/keys_import.py b/scripts/keys_import.py
index c12d8b58..13c37709 100755
--- a/scripts/keys_import.py
+++ b/scripts/keys_import.py
@@ -179,9 +179,9 @@ async def keys_import(conf: config.AppConfig, asf_uid: str) 
-> None:
         # Parse the KEYS file and add it to the database
         # We use a separate storage.write() context for each committee to 
avoid transaction conflicts
         async with storage.write(asf_uid) as write:
-            wafa = write.as_foundation_admin(committee_name)
+            waca = write.as_committee_admin(committee_name)
             keys_file_text = content.decode("utf-8", errors="replace")
-            outcomes = await wafa.keys.ensure_associated(keys_file_text)
+            outcomes = await waca.keys.ensure_associated(keys_file_text)
             log_outcome_errors(outcomes, committee_name)
             yes = outcomes.result_count
             no = outcomes.error_count


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

Reply via email to