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 hashlib
|
||||||
import re
|
import re
|
||||||
|
import shlex
|
||||||
|
|
||||||
def sanitize_herdr_agent_name(name: str) -> str:
|
def sanitize_herdr_agent_name(name: str) -> str:
|
||||||
"""
|
"""
|
||||||
@@ -29,3 +30,14 @@ def sanitize_herdr_agent_name(name: str) -> str:
|
|||||||
|
|
||||||
return s
|
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
|
import os, sys, tempfile, shutil, glob, subprocess, json, sqlite3, fcntl
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
import yaml
|
import yaml
|
||||||
|
from lib_py import registry_format
|
||||||
|
|
||||||
def atomic_dump_yaml_main():
|
def atomic_dump_yaml_main():
|
||||||
yaml_path = os.environ['YAML_PATH']
|
yaml_path = os.environ['YAML_PATH']
|
||||||
@@ -114,6 +115,16 @@ def atomic_dump_yaml_main():
|
|||||||
elif 'herdr_sessions' not in d:
|
elif 'herdr_sessions' not in d:
|
||||||
d['herdr_sessions'] = []
|
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)
|
old_sessions = get_all_sessions_status(d)
|
||||||
# a mutation that replaces a row the monitor registered for a pane it
|
# a mutation that replaces a row the monitor registered for a pane it
|
||||||
# created itself (create_session.sh) drops that name from old_roles
|
# created itself (create_session.sh) drops that name from old_roles
|
||||||
@@ -167,7 +178,7 @@ def atomic_dump_yaml_main():
|
|||||||
|
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
if new_sessions != old_sessions:
|
if force_yaml or new_sessions != old_sessions:
|
||||||
if os.path.exists(yaml_path):
|
if os.path.exists(yaml_path):
|
||||||
try:
|
try:
|
||||||
shutil.copy2(yaml_path, yaml_path + '.bak')
|
shutil.copy2(yaml_path, yaml_path + '.bak')
|
||||||
@@ -207,6 +218,7 @@ def atomic_dump_yaml_main():
|
|||||||
_flock_f.close()
|
_flock_f.close()
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
return from_version, registry_format.stored_version(d), migrated_rows
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
atomic_dump_yaml_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