fix: attach_command from one builder, no kill/start_command, row-less stop, one herdr name rule, registry format 2 (Issue #71) #73
No files matched your search
@@ -1,5 +1,6 @@
|
||||
import hashlib
|
||||
import re
|
||||
import shlex
|
||||
|
||||
def sanitize_herdr_agent_name(name: str) -> str:
|
||||
"""
|
||||
@@ -29,3 +30,14 @@ def sanitize_herdr_agent_name(name: str) -> str:
|
||||
|
||||
return s
|
||||
|
||||
|
||||
def attach_command(server, name):
|
||||
"""The registry row's attach_command (Issue #71).
|
||||
|
||||
A native herdr command, so it works from any shell: the server is always
|
||||
named (`default` too - herdr's env-free default), and the agent by the name
|
||||
herdr registered (sanitize_herdr_agent_name). create, resume and reconcile
|
||||
all write it from here.
|
||||
"""
|
||||
return "herdr --session %s agent attach %s" % (
|
||||
shlex.quote(server or "default"), shlex.quote(sanitize_herdr_agent_name(name)))
|
||||
@@ -4,6 +4,7 @@
|
||||
import os, sys, tempfile, shutil, glob, subprocess, json, sqlite3, fcntl
|
||||
from datetime import datetime, timezone
|
||||
import yaml
|
||||
from lib_py import registry_format
|
||||
|
||||
def atomic_dump_yaml_main():
|
||||
yaml_path = os.environ['YAML_PATH']
|
||||
@@ -114,6 +115,16 @@ def atomic_dump_yaml_main():
|
||||
elif 'herdr_sessions' not in d:
|
||||
d['herdr_sessions'] = []
|
||||
|
||||
# Issue #71: an older registry is migrated here, under the lock and
|
||||
# before the caller's mutation. It changes row fields only, so the
|
||||
# YAML is rewritten whether or not the mutation changes anything.
|
||||
from_version = registry_format.stored_version(d)
|
||||
migrated_rows = 0
|
||||
force_yaml = False
|
||||
if from_version < registry_format.CURRENT:
|
||||
d, migrated_rows = registry_format.migrate(d)
|
||||
force_yaml = True
|
||||
|
||||
old_sessions = get_all_sessions_status(d)
|
||||
# a mutation that replaces a row the monitor registered for a pane it
|
||||
# created itself (create_session.sh) drops that name from old_roles
|
||||
@@ -167,7 +178,7 @@ def atomic_dump_yaml_main():
|
||||
|
||||
conn.commit()
|
||||
|
||||
if new_sessions != old_sessions:
|
||||
if force_yaml or new_sessions != old_sessions:
|
||||
if os.path.exists(yaml_path):
|
||||
try:
|
||||
shutil.copy2(yaml_path, yaml_path + '.bak')
|
||||
@@ -207,6 +218,7 @@ def atomic_dump_yaml_main():
|
||||
_flock_f.close()
|
||||
except Exception:
|
||||
pass
|
||||
return from_version, registry_format.stored_version(d), migrated_rows
|
||||
|
||||
if __name__ == '__main__':
|
||||
atomic_dump_yaml_main()
|
||||
@@ -0,0 +1,92 @@
|
||||
"""Registry format version and its migration (Issue #71).
|
||||
|
||||
The session registry (agent-sessions.yaml + agent-sessions.db) carries a
|
||||
top-level `registry_format` key; it lives in the DB `state` row like every
|
||||
other top-level key. A registry without it is format 1.
|
||||
|
||||
1: rows may carry `kill_command` and `start_command`, and `attach_command`
|
||||
reads `HERDR_SESSION_NAME=<server> herdr agent attach <original name>`.
|
||||
2: no `kill_command` / `start_command`; `attach_command` is
|
||||
lib_py.agents.sanitize.attach_command(<row server>, <row name>).
|
||||
|
||||
The writer (lib_py.atomic_yaml, the only code that writes the registry)
|
||||
migrates an older registry under its lock before the caller's mutation, and
|
||||
then rewrites the YAML as well as the DB. Readers (lib_py.state) never
|
||||
migrate; they see the old rows until the first write.
|
||||
|
||||
Entry point (for #72, the update skill):
|
||||
|
||||
python -m lib_py.registry_format --yaml <agent-sessions.yaml>
|
||||
|
||||
runs that migration through the writer (same lock, YAML rewritten), prints one
|
||||
line `registry-format: <from> -> <to>, rows changed: <n>` and exits 0 when the
|
||||
registry was migrated or already current, nonzero on error. Run it with the
|
||||
skills directory on PYTHONPATH (as lib.sh does), e.g.
|
||||
`PYTHONPATH=.agents/skills python3 -m lib_py.registry_format --yaml .mam/agent-sessions.yaml`.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import copy
|
||||
import os
|
||||
import sys
|
||||
|
||||
from lib_py.agents.sanitize import attach_command
|
||||
|
||||
FORMAT_KEY = "registry_format"
|
||||
CURRENT = 2
|
||||
_DROPPED = ("kill_command", "start_command")
|
||||
|
||||
|
||||
def stored_version(d):
|
||||
"""The format a registry dict says it has (1 when it says nothing)."""
|
||||
v = d.get(FORMAT_KEY) if isinstance(d, dict) else None
|
||||
return v if isinstance(v, int) and not isinstance(v, bool) else 1
|
||||
|
||||
|
||||
def _row_server(s):
|
||||
return s.get("herdr_session") or s.get("herdr_server") or "default"
|
||||
|
||||
|
||||
def migrate(d):
|
||||
"""Return (new registry dict, rows changed). Pure and idempotent: `d` is
|
||||
not modified, and migrating the result again changes nothing. Every row is
|
||||
migrated, `stopped` and `terminated` ones included."""
|
||||
out = copy.deepcopy(d) if isinstance(d, dict) else {}
|
||||
changed = 0
|
||||
for s in out.get("herdr_sessions") or []:
|
||||
if not isinstance(s, dict) or not s.get("name"):
|
||||
continue
|
||||
before = dict(s)
|
||||
for k in _DROPPED:
|
||||
s.pop(k, None)
|
||||
s["attach_command"] = attach_command(_row_server(s), s["name"])
|
||||
if s != before:
|
||||
changed += 1
|
||||
out[FORMAT_KEY] = CURRENT
|
||||
return out, changed
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
ap = argparse.ArgumentParser(prog="python -m lib_py.registry_format",
|
||||
description="Migrate the session registry to the current format.")
|
||||
ap.add_argument("--yaml", required=True, help="path of agent-sessions.yaml")
|
||||
a = ap.parse_args(argv)
|
||||
yaml_path = os.path.abspath(a.yaml)
|
||||
db_path = os.path.splitext(yaml_path)[0] + ".db"
|
||||
if not (os.path.exists(yaml_path) or os.path.exists(db_path)):
|
||||
print("registry-format: error: no registry at %s" % yaml_path, file=sys.stderr)
|
||||
return 1
|
||||
from lib_py.atomic_yaml import atomic_dump_yaml_main
|
||||
os.environ["YAML_PATH"] = yaml_path
|
||||
os.environ["AGENT_SESSIONS_MUTATION"] = "pass"
|
||||
try:
|
||||
frm, to, n = atomic_dump_yaml_main()
|
||||
except (Exception, SystemExit) as exc: # the writer refuses with SystemExit
|
||||
print("registry-format: error: %s" % (exc,), file=sys.stderr)
|
||||
return 1
|
||||
print("registry-format: %d -> %d, rows changed: %d" % (frm, to, n), flush=True)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user