feat: extend daemon model with daemon status tracking and list support
- Add daemon status tracking with state and metrics (idle, running, error) - Implement pfhub daemon list CLI command for viewing registered daemons - Extend auth schema and service for daemon lifecycle management - Add postgres repository queries for daemon state persistence - Expand test coverage for daemon registry and status transitions - Update rel-006 release document with daemon model verification results - Support daemon graceful shutdown and error recovery patterns
This commit is contained in:
@@ -67,7 +67,18 @@ class _DaemonRepo:
|
||||
return token
|
||||
|
||||
def get_service_token_by_name(self, name: str) -> AuthToken | None:
|
||||
return next((t for t in self._tokens.values() if t.name == name and t.token_type == "service"), None)
|
||||
return next(
|
||||
(
|
||||
t
|
||||
for t in sorted(
|
||||
self._tokens.values(),
|
||||
key=lambda token: token.created_at or datetime.min.replace(tzinfo=timezone.utc),
|
||||
reverse=True,
|
||||
)
|
||||
if t.name == name and t.token_type == "service" and t.revoked_at is None
|
||||
),
|
||||
None,
|
||||
)
|
||||
|
||||
def revoke_auth_token(self, user_id: str, token_id: str) -> None:
|
||||
token = self._tokens.get(token_id)
|
||||
@@ -142,6 +153,13 @@ class _DaemonRepo:
|
||||
self._tokens[new_token.id] = new_token
|
||||
return raw, new_token
|
||||
|
||||
def rebind_service_token(self, name: str, daemon_id: str) -> AuthToken:
|
||||
existing = self.get_service_token_by_name(name)
|
||||
if existing is None:
|
||||
raise ValueError(f"서비스 토큰을 찾을 수 없습니다: {name}")
|
||||
existing.daemon_id = daemon_id
|
||||
return existing
|
||||
|
||||
|
||||
def _repo_with_user(username: str = "alice") -> _DaemonRepo:
|
||||
repo = _DaemonRepo()
|
||||
@@ -284,3 +302,30 @@ def test_rotate_nonexistent_token_raises() -> None:
|
||||
|
||||
with pytest.raises(ValueError, match="nonexistent"):
|
||||
service.rotate_service_token("nonexistent")
|
||||
|
||||
|
||||
def test_rebind_service_token_updates_daemon_id_without_changing_runtime_id() -> None:
|
||||
repo = _repo_with_user()
|
||||
source_daemon = repo.create_daemon("user-1", daemon_slug="alpha-bot")
|
||||
target_daemon = repo.create_daemon("user-1", daemon_slug="beta-bot")
|
||||
service = _service(repo)
|
||||
|
||||
issued = service.issue_service_token("my-token", "alice", daemon_id=source_daemon.id)
|
||||
|
||||
previous_daemon_id, rebound = service.rebind_service_token("my-token", target_daemon.id)
|
||||
|
||||
assert previous_daemon_id == source_daemon.id
|
||||
assert rebound.daemon_id == target_daemon.id
|
||||
assert rebound.runtime_id == issued.auth_token.runtime_id
|
||||
|
||||
|
||||
def test_rebind_service_token_rejects_daemon_from_other_user() -> None:
|
||||
repo = _repo_with_user()
|
||||
repo._users["user-2"] = User(id="user-2", username="bob", permission="read")
|
||||
source_daemon = repo.create_daemon("user-1", daemon_slug="alpha-bot")
|
||||
foreign_daemon = repo.create_daemon("user-2", daemon_slug="bob-bot")
|
||||
service = _service(repo)
|
||||
service.issue_service_token("my-token", "alice", daemon_id=source_daemon.id)
|
||||
|
||||
with pytest.raises(ValueError, match="해당 토큰 사용자에 속하지 않습니다"):
|
||||
service.rebind_service_token("my-token", foreign_daemon.id)
|
||||
|
||||
Reference in New Issue
Block a user