Files
felhom.eu/documentation/audits/ladder-2026-09-24/tools/liveA1.py
T

65 lines
2.9 KiB
Python

#!/usr/bin/env python3
"""Part A, first half — on controller v0.267.0 (the field's version): vikunja installed, seeded (A),
backed up by the product (the update's own `backing-up` leg, then a pull that fails: nothing moves),
RESTORED from its own unit — which recreates its volumes WITHOUT compose labels (R-658) — and seeded
again (B, after the restore). Evidence only; every act is a product endpoint."""
import json, re, sys, time
sys.path.insert(0, ".")
import walk as w
import fixtures as fx
ST = "../partA/state.json"
F = fx.Vikunja()
SUB = "vikunja-la"
out = {"controller": w.guest("cat /etc/felhom-controller-image").strip()}
w.login()
def drill_set(ref, msg):
import fcntl
with open(f"{w.SC}/drill.lock", "w") as lk:
fcntl.flock(lk, fcntl.LOCK_EX)
w.sh(["git", "-C", w.DRILL, "pull", "-q", "--rebase", "origin", "main"], timeout=120)
p = f"{w.DRILL}/templates/vikunja/docker-compose.yml"
s = open(p).read()
s = re.sub(r"image: vikunja/vikunja:\S+", f"image: vikunja/vikunja:{ref}", s)
open(p, "w").write(s)
w.sh(["git", "-C", w.DRILL, "commit", "-qam", f"LIVE-A vikunja {ref}: {msg}"])
r = w.sh(["git", "-C", w.DRILL, "push", "-q", "origin", "main"], timeout=120)
h = w.sh(["git", "-C", w.DRILL, "rev-parse", "--short=12", "HEAD"]).stdout.strip()
w.say(f"drill {h}: vikunja {ref} ({msg}) push rc={r.returncode}")
return h
def labels():
return w.guest("for v in $(docker volume ls -q | grep '^vikunja_'); do echo \"$v $(docker volume inspect $v --format '{{json .Labels}}')\"; done")
out["drill_A"] = drill_set("2.5.0", "the install version")
w.sync_rescan()
out["deployed"] = w.deploy("vikunja", SUB)
w.wait_app(SUB, "/", tries=60, delay=3)
A = F.seed(w, SUB, w.say)
out["seed_A"] = bool(A)
out["labels_after_install"] = labels()
w.say("labels after install:\n" + out["labels_after_install"])
out["drill_E"] = drill_set("2.5.99-notag", "a tag that does not exist: backing-up runs, the pull fails, nothing moves")
out["badge_wait"] = w.sync_rescan("vikunja", "vikunja/vikunja:2.5.99-notag")
out["press_backup"] = w.press_update("vikunja")
out["snapshots"] = w.snapshots("vikunja")
w.say("snapshots offered: " + json.dumps(out["snapshots"])[:400])
out["drill_back"] = drill_set("2.5.0", "back to the install version")
w.sync_rescan()
out["restore"] = w.restore("vikunja")
out["labels_after_restore_v0267"] = labels()
w.say("labels after the v0.267.0 restore:\n" + out["labels_after_restore_v0267"])
w.wait_app(SUB, "/", tries=60, delay=3)
out["A_after_restore"] = bool(F.verify(w, SUB, A, w.say))
B = F.seed(w, SUB, w.say)
out["seed_B_after_restore"] = bool(B)
json.dump({"A": A, "B": B, "sub": SUB}, open(ST, "w"))
json.dump(out, open("../partA/01-v0267-install-backup-restore.json", "w"), indent=2, ensure_ascii=False, default=str)
open("../partA/01-v0267-install-backup-restore.log", "w").write("\n".join(w.LOG) + "\n")