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]