Flows and dashboards get ids: the store is addressed by id with a key index, the first start gives every document its key as id, fluksio store migrate --dry-run and ls; the rename routes are gone

This commit is contained in:
2026-11-02 16:54:11 +01:00
parent de13c48c90
commit fae85f5ecb
20 changed files with 1287 additions and 420 deletions
+81 -1
View File
@@ -1,4 +1,4 @@
"""`fluksio serve`, `fluksio enroll`, `fluksio worker`, `sync`, `run`, `runs`.
"""`fluksio serve`, `enroll`, `worker`, `store`, `sync`, `run`, `runs`.
The point of this module is a machine nobody can route to: a node on a cluster
where ports cannot be opened, or a laptop with no Docker. `fluksio serve`
@@ -686,6 +686,71 @@ def cmd_enroll(args: argparse.Namespace) -> int:
return result
def _store_dir(raw: str) -> Path | None:
path = Path(raw).expanduser()
if path.is_dir():
return path
print(f"{path} is not a directory", file=sys.stderr, flush=True)
return None
def cmd_store_migrate(args: argparse.Namespace) -> int:
"""Say what the engine's next start would do to a store's ids.
The same check the start makes, reading and nothing else: no store is
opened, so no repository is created and no other migration runs. Point it
at a copy of the store; the engine does the migration itself, with the
store to itself.
"""
from fluksio.flow.identity import plan_ids
root = _store_dir(args.dir)
if root is None:
return 2
plan = plan_ids(root)
flows = sum(1 for kind, _, _ in plan.objects if kind == "flow")
_say(f"{root}: {flows} flows, {len(plan.objects) - flows} dashboards")
if plan.problems:
_say("The engine would refuse to start, and change nothing, because:")
for problem in plan.problems:
_say(f" {problem}")
return 1
if plan.pending:
_say("An interrupted start gave these their id, and would commit them:")
for rel in sorted(plan.pending):
_say(f" {rel}")
if plan.stamps:
_say("The engine would give these their key as id, in one commit:")
for rel, version in sorted(plan.stamps.items()):
_say(f" {rel} (version {version})")
if not plan.pending and not plan.stamps:
_say("Nothing to do: every flow and dashboard has its id.")
return 0
def cmd_store_ls(args: argparse.Namespace) -> int:
"""Every flow and dashboard in a store, by id, key and kind."""
from fluksio.flow.identity import plan_ids
root = _store_dir(args.dir)
if root is None:
return 2
plan = plan_ids(root)
width = max([len("ID"), *(len(obj_id) for _, obj_id, _ in plan.objects)])
keys = max([len("KEY"), *(len(key) for _, _, key in plan.objects)])
_say(f"{'ID':<{width}} {'KEY':<{keys}} KIND")
for kind, obj_id, key in sorted(plan.objects):
_say(f"{obj_id:<{width}} {key:<{keys}} {kind}")
if plan.problems:
print(
f"{len(plan.problems)} problem(s): `fluksio store migrate --dry-run "
f"{root}` lists them",
file=sys.stderr,
flush=True,
)
return 0
def cmd_worker(rest: list[str]) -> int:
from fluksio_worker.agent import main as worker_main
@@ -804,6 +869,21 @@ def _parser() -> argparse.ArgumentParser:
add_help=False,
)
store = subparsers.add_parser("store", help="look into a flow store directory")
store_commands = store.add_subparsers(dest="store_command", required=True)
migrate = store_commands.add_parser(
"migrate", help="check what the next engine start migrates"
)
# Required: the engine migrates on its start, with the store to itself.
migrate.add_argument(
"--dry-run", action="store_true", required=True, help="change nothing"
)
migrate.add_argument("dir", help="the store, e.g. a copy of <data dir>/flows")
migrate.set_defaults(func=cmd_store_migrate)
ls = store_commands.add_parser("ls", help="list flows and dashboards by id")
ls.add_argument("dir", help="the store, e.g. <data dir>/flows")
ls.set_defaults(func=cmd_store_ls)
# The client half: talking to an engine rather than being one.
from fluksio.sdk.cli import add_parsers