Close the nine open SDK tasks: one engine per directory, a tabbed dashboard, re-pairing, run recovery
Docs / docs (push) Successful in 27s
Playwright Tests / test-playwright (1, 2) (push) Failing after 17s
Playwright Tests / test-playwright (2, 2) (push) Failing after 12s
pre-commit / pre-commit (push) Failing after 1m59s
Test Backend / test-backend (push) Failing after 2m30s
Compose Smoke Test / test-compose (push) Failing after 13s
Playwright Tests / merge-reports (push) Failing after 2m19s
Docs / docs (push) Successful in 27s
Playwright Tests / test-playwright (1, 2) (push) Failing after 17s
Playwright Tests / test-playwright (2, 2) (push) Failing after 12s
pre-commit / pre-commit (push) Failing after 1m59s
Test Backend / test-backend (push) Failing after 2m30s
Compose Smoke Test / test-compose (push) Failing after 13s
Playwright Tests / merge-reports (push) Failing after 2m19s
serve: refuse a second engine for one data directory whatever port it was asked for, using the pidfile and a token this directory signed. The check runs before the database is touched and before the credential is written, which is what left every later CLI call pointing at a dead port. The terminal dashboard is three tabs (Overview, Runs, Logs) with the toolbar following the focused pane, the engine's output goes to serve.log rather than down a pipe, and closing the screen stops both reader threads so the prompt comes back. It adopts a running engine on every start, so stop/start and restart work on one it did not start, and a stop waits for the process to be gone before the next start. Enrolment reports itself in the modal. enroll: a new claim code replaces the pairing instead of being refused. The code is redeemed before anything is written, mappings to a portal being left are cleared, and a running engine redials when the stored enrolment changes. runs: an engine re-queues the runs left `queued` by the one before it, and `fluksio retry <id>` / `retry --group <sweep>` submits an interrupted run again with the same inputs and group, recorded through Run.parent_id. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U9BoNGq6V9MdRWAte7JBuC
This commit is contained in:
@@ -14,7 +14,7 @@ from typing import Any
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
import httpx
|
||||
from sqlmodel import Session, select
|
||||
from sqlmodel import Session, col, select
|
||||
|
||||
import fluksio
|
||||
from fluksio.cloud import config as cloud_config
|
||||
@@ -30,11 +30,6 @@ class EnrollError(Exception):
|
||||
self.detail = detail
|
||||
|
||||
|
||||
class AlreadyEnrolled(EnrollError):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(409, "This instance is already connected to a portal")
|
||||
|
||||
|
||||
def _is_local(url: str) -> bool:
|
||||
"""Whether the address is this machine or a compose-internal service."""
|
||||
host = urlsplit(url).hostname or ""
|
||||
@@ -83,10 +78,15 @@ def redeem_claim(
|
||||
def enroll(
|
||||
session: Session, user: User, portal_url: str, claim_code: str
|
||||
) -> cloud_config.CloudConfig:
|
||||
"""Redeem the code and write the config the connector dials with."""
|
||||
if cloud_config.exists():
|
||||
raise AlreadyEnrolled()
|
||||
"""Redeem the code and write the config the connector dials with.
|
||||
|
||||
An instance that is already paired is re-paired rather than refused: a new
|
||||
claim code is somebody asking for this, and refusing left deleting
|
||||
`cloud.json` by hand as the only way through. The code is redeemed before
|
||||
anything is written, so a code the portal rejects leaves a working
|
||||
connection working.
|
||||
"""
|
||||
previous = cloud_config.load()
|
||||
data = redeem_claim(portal_url, claim_code)
|
||||
config = cloud_config.CloudConfig(
|
||||
portal_url=portal_url.rstrip("/"),
|
||||
@@ -104,6 +104,17 @@ def enroll(
|
||||
cloud_config.save(config)
|
||||
|
||||
owner_id = str(data["owner_id"])
|
||||
if previous is not None and previous.issuer != config.issuer:
|
||||
# A mapping is a subject of the portal that issued it, and nothing in
|
||||
# the row says which portal that was — so against a different portal
|
||||
# the old rows would both block the new owner from being adopted and
|
||||
# resolve a stranger's subject onto a local account.
|
||||
for stale in session.exec(
|
||||
select(User).where(col(User.portal_sub).is_not(None))
|
||||
):
|
||||
stale.portal_sub = None
|
||||
session.add(stale)
|
||||
session.flush()
|
||||
# Re-enrolling from a different local account moves the mapping rather than
|
||||
# leaving two accounts claiming the same portal identity, which the unique
|
||||
# index would refuse and the lookup could not choose between anyway.
|
||||
|
||||
Reference in New Issue
Block a user