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-releases-client.git


The following commit(s) were added to refs/heads/main by this push:
     new 4203994  Add a command for listing OpenPGP keys by user
4203994 is described below

commit 4203994a0b405576904d0341faaebcc299df268b
Author: Sean B. Palmer <[email protected]>
AuthorDate: Wed Jul 16 16:26:06 2025 +0100

    Add a command for listing OpenPGP keys by user
---
 pyproject.toml              |  4 ++--
 src/atrclient/client.py     | 18 ++++++++++++++++++
 src/atrclient/models/api.py | 35 ++++++++++++++++++++++++++++-------
 src/atrclient/models/sql.py | 10 ++++++++++
 tests/cli_keys.t            | 17 +++++++++++++++++
 uv.lock                     |  4 ++--
 6 files changed, 77 insertions(+), 11 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index 8aa01cb..3158a05 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -11,7 +11,7 @@ build-backend = "hatchling.build"
 
 [project]
 name            = "apache-trusted-releases"
-version         = "0.20250716.1448"
+version         = "0.20250716.1524"
 description     = "ATR CLI and Python API"
 readme          = "README.md"
 requires-python = ">=3.13"
@@ -72,4 +72,4 @@ select = [
 ]
 
 [tool.uv]
-exclude-newer = "2025-07-16T14:48:00Z"
+exclude-newer = "2025-07-16T15:24:00Z"
diff --git a/src/atrclient/client.py b/src/atrclient/client.py
index 591c972..fb54b07 100755
--- a/src/atrclient/client.py
+++ b/src/atrclient/client.py
@@ -164,6 +164,12 @@ def api_draft_delete(api: ApiPost, args: 
models.api.DraftDeleteArgs) -> models.a
     return models.api.validate_draft_delete(response)
 
 
+@api_get("/keys/user")
+def api_keys_user(api: ApiGet, asf_uid: str) -> models.api.KeysUserResults:
+    response = api.get(asf_uid)
+    return models.api.validate_keys_user(response)
+
+
 @api_get("/list")
 def api_list(api: ApiGet, project: str, version: str) -> 
models.api.ListResults:
     response = api.get(project, version)
@@ -520,6 +526,18 @@ def app_jwt_show() -> None:
     return app_show("tokens.jwt")
 
 
+@APP_KEYS.command(name="user", help="List OpenPGP keys for a user.")
+def app_keys_user(asf_uid: str | None = None) -> None:
+    if asf_uid is None:
+        with config_lock() as config:
+            asf_uid = config_get(config, ["asf", "uid"])
+    if asf_uid is None:
+        show_error_and_exit("No ASF UID provided and asf.uid not configured.")
+    keys_user = api_keys_user(asf_uid)
+    for key in keys_user.keys:
+        print(key.model_dump_json(indent=None))
+
+
 @APP.command(name="list", help="List all files within a release.")
 def app_list(project: str, version: str, revision: str | None = None, /) -> 
None:
     list_results = api_list(project, version, revision)
diff --git a/src/atrclient/models/api.py b/src/atrclient/models/api.py
index 171b3f6..4d51105 100644
--- a/src/atrclient/models/api.py
+++ b/src/atrclient/models/api.py
@@ -101,11 +101,6 @@ class JwtResults(schema.Strict):
     jwt: str
 
 
-class KeyResults(schema.Strict):
-    endpoint: Literal["/key"] = schema.Field(alias="endpoint")
-    key: sql.PublicSigningKey
-
-
 @dataclasses.dataclass
 class KeysQuery:
     offset: int = 0
@@ -118,6 +113,21 @@ class KeysResults(schema.Strict):
     count: int
 
 
+class KeysCommitteeResults(schema.Strict):
+    endpoint: Literal["/keys/committee"] = schema.Field(alias="endpoint")
+    keys: Sequence[sql.PublicSigningKey]
+
+
+class KeysGetResults(schema.Strict):
+    endpoint: Literal["/keys/get"] = schema.Field(alias="endpoint")
+    key: sql.PublicSigningKey
+
+
+class KeysUserResults(schema.Strict):
+    endpoint: Literal["/keys/user"] = schema.Field(alias="endpoint")
+    keys: Sequence[sql.PublicSigningKey]
+
+
 class ProjectResults(schema.Strict):
     endpoint: Literal["/project"] = schema.Field(alias="endpoint")
     project: sql.Project
@@ -238,6 +248,11 @@ class TasksResults(schema.Strict):
     count: int
 
 
+class UsersListResults(schema.Strict):
+    endpoint: Literal["/users/list"] = schema.Field(alias="endpoint")
+    users: Sequence[str]
+
+
 class VoteResolveArgs(schema.Strict):
     project: str
     version: str
@@ -288,8 +303,10 @@ Results = Annotated[
     | CommitteesProjectsResults
     | DraftDeleteResults
     | JwtResults
-    | KeyResults
     | KeysResults
+    | KeysGetResults
+    | KeysCommitteeResults
+    | KeysUserResults
     | ListResults
     | ProjectResults
     | ProjectReleasesResults
@@ -305,6 +322,7 @@ Results = Annotated[
     | SshDeleteResults
     | SshListResults
     | TasksResults
+    | UsersListResults
     | VoteResolveResults
     | VoteStartResults
     | UploadResults,
@@ -333,8 +351,10 @@ validate_committees_list = validator(CommitteesListResults)
 validate_committees_projects = validator(CommitteesProjectsResults)
 validate_draft_delete = validator(DraftDeleteResults)
 validate_jwt = validator(JwtResults)
-validate_key = validator(KeyResults)
 validate_keys = validator(KeysResults)
+validate_keys_committee = validator(KeysCommitteeResults)
+validate_keys_get = validator(KeysGetResults)
+validate_keys_user = validator(KeysUserResults)
 validate_list = validator(ListResults)
 validate_project = validator(ProjectResults)
 validate_project_releases = validator(ProjectReleasesResults)
@@ -350,6 +370,7 @@ validate_ssh_add = validator(SshAddResults)
 validate_ssh_delete = validator(SshDeleteResults)
 validate_ssh_list = validator(SshListResults)
 validate_tasks = validator(TasksResults)
+validate_users_list = validator(UsersListResults)
 validate_vote_resolve = validator(VoteResolveResults)
 validate_vote_start = validator(VoteStartResults)
 validate_upload = validator(UploadResults)
diff --git a/src/atrclient/models/sql.py b/src/atrclient/models/sql.py
index 90b6584..31d2de5 100644
--- a/src/atrclient/models/sql.py
+++ b/src/atrclient/models/sql.py
@@ -705,6 +705,16 @@ class PublicSigningKey(sqlmodel.SQLModel, table=True):
     # M-M: Committee -> [PublicSigningKey]
     committees: list[Committee] = 
sqlmodel.Relationship(back_populates="public_signing_keys", link_model=KeyLink)
 
+    def model_post_init(self, _context):
+        if isinstance(self.created, str):
+            self.created = 
datetime.datetime.fromisoformat(self.created.rstrip("Z"))
+
+        if isinstance(self.latest_self_signature, str):
+            self.latest_self_signature = 
datetime.datetime.fromisoformat(self.latest_self_signature.rstrip("Z"))
+
+        if isinstance(self.expires, str):
+            self.expires = 
datetime.datetime.fromisoformat(self.expires.rstrip("Z"))
+
 
 # ReleasePolicy: Project
 class ReleasePolicy(sqlmodel.SQLModel, table=True):
diff --git a/tests/cli_keys.t b/tests/cli_keys.t
new file mode 100644
index 0000000..4b1b953
--- /dev/null
+++ b/tests/cli_keys.t
@@ -0,0 +1,17 @@
+$ atr set atr.host 127.0.0.1:8080
+Set atr.host to "127.0.0.1:8080".
+
+$ atr dev user
+<?user?>
+
+$ atr set asf.uid <!user!>
+Set asf.uid to "<!user!>".
+
+$ atr dev pat
+<?pat?>
+
+$ atr set tokens.pat <!pat!>
+Set tokens.pat to "<!pat!>".
+
+$ atr keys user
+<.etc.>
diff --git a/uv.lock b/uv.lock
index e05994a..86207c3 100644
--- a/uv.lock
+++ b/uv.lock
@@ -2,7 +2,7 @@ version = 1
 requires-python = ">=3.13"
 
 [options]
-exclude-newer = "2025-07-16T14:48:00Z"
+exclude-newer = "2025-07-16T15:24:00Z"
 
 [[package]]
 name = "aiohappyeyeballs"
@@ -83,7 +83,7 @@ wheels = [
 
 [[package]]
 name = "apache-trusted-releases"
-version = "0.20250716.1448"
+version = "0.20250716.1524"
 source = { editable = "." }
 dependencies = [
     { name = "aiohttp" },


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

Reply via email to