core: drop pre-migration read-compat now both nodes are current-format

Both machines (civil, primer) are on the current on-disk layout, so the
read-compat for older formats is dead weight. Remove it:

- config loader: only deployments/<kind>/<name>.yaml (drop flat deployments/*.yaml
  and the services/+jobs/ split); drop the run.runner→manager normalizer.
- registry loader: require composite <kind>/<name> keys + manager/kind (registry.yaml
  is regenerated every apply, so it's always current).
- manifest: drop the proxy/public→reach legacy input and the component→program alias
  (the derived proxy/public read accessors stay). reach field defaults already match.
- config_editor: parse incoming specs directly (frontend sends the current shape).
- save_config: drop the flat-file migration cleanup.

Bootstrap seeds move to bootstrap/deployments/<kind>/ and install.sh seeds that
per-kind tree directly. The legacy→current translation for terse test fixtures
moves into each test conftest (out of production). All suites green (362).
This commit is contained in:
2026-07-07 09:16:03 -07:00
parent 0226538229
commit 5beebfd739
17 changed files with 247 additions and 206 deletions

View File

@@ -9,18 +9,64 @@ import pytest
import yaml
def _modernize_deployment(spec: dict) -> dict:
"""Translate a test's terse legacy deployment dict to the current
manager-discriminated shape (production dropped this read-compat post-migration).
``proxy``/``public`` → ``reach``; ``run.runner`` → ``manager`` (+ ``run.launcher``)."""
d = dict(spec)
proxy = bool(d.pop("proxy", False))
public = bool(d.pop("public", False))
if "reach" not in d:
if public:
d["reach"] = "public"
elif proxy:
d["reach"] = "internal"
if "manager" not in d:
run = dict(d.pop("run", None) or {})
runner = run.get("runner")
if runner == "static":
d["manager"] = "caddy"
if run.get("root"):
d["root"] = run["root"]
elif runner == "path":
d["manager"] = "path"
elif runner == "remote":
d["manager"] = "none"
for k in ("base_url", "health_url"):
if run.get(k):
d[k] = run[k]
else:
launch = {k: v for k, v in run.items() if k != "runner"}
launch["launcher"] = runner
d["manager"] = "systemd"
d["run"] = launch
return d
def _store_for(spec: dict) -> str:
if spec.get("schedule"):
return "jobs"
return {"systemd": "services", "caddy": "statics", "path": "tools", "none": "references"}[
spec["manager"]
]
def _write_castle_config(root: Path, config: dict) -> None:
"""Scatter a nested castle config dict into the directory-per-resource layout."""
"""Scatter a nested castle config dict into the on-disk layout: castle.yaml globals,
programs/<name>.yaml, and deployments/<kind>/<name>.yaml (fields modernized)."""
globals_data = {k: v for k, v in config.items() if k in ("gateway", "repo")}
(root / "castle.yaml").write_text(yaml.dump(globals_data, default_flow_style=False))
for section in ("programs", "services", "jobs"):
entries = config.get(section) or {}
if not entries:
continue
section_dir = root / section
section_dir.mkdir(parents=True, exist_ok=True)
for name, spec in entries.items():
(section_dir / f"{name}.yaml").write_text(yaml.dump(spec, default_flow_style=False))
programs = config.get("programs") or {}
if programs:
(root / "programs").mkdir(parents=True, exist_ok=True)
for name, spec in programs.items():
(root / "programs" / f"{name}.yaml").write_text(yaml.dump(spec, default_flow_style=False))
for section in ("services", "jobs", "deployments"):
for name, spec in (config.get(section) or {}).items():
modern = _modernize_deployment(spec)
store_dir = root / "deployments" / _store_for(modern)
store_dir.mkdir(parents=True, exist_ok=True)
(store_dir / f"{name}.yaml").write_text(yaml.dump(modern, default_flow_style=False))
@pytest.fixture