86c4b9a0d8
gates / gates (push) Successful in 27s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
97 lines
4.6 KiB
Python
97 lines
4.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Part A, second half — on controller v0.268.0. vikunja's volumes are UNLABELLED (restored under v0.267.0,
|
|
liveA1). (e) a failing update (real move 2.5.0 -> 2.6.0 + a probe port the app does not answer): the undo
|
|
must copy BOTH volumes and seeds A and B must read back. (f) a restore under v0.268.0: the recreated
|
|
volumes carry compose's labels. (g) seed C, the same failing update again: undone, both copied, A B C read
|
|
back. (h) remove: volumes_removed names both, no applied-compose.yml / applied-meta/ left (R-651)."""
|
|
import json, re, sys, time
|
|
sys.path.insert(0, ".")
|
|
import walk as w
|
|
import fixtures as fx
|
|
|
|
S = json.load(open("../partA/state.json"))
|
|
F, SUB = fx.Vikunja(), S["sub"]
|
|
out = {"controller": w.guest("cat /etc/felhom-controller-image").strip()}
|
|
w.login()
|
|
|
|
|
|
def drill(edit, 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)
|
|
edit()
|
|
w.sh(["git", "-C", w.DRILL, "commit", "-qam", "LIVE-A " + 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}: {msg} push rc={r.returncode}")
|
|
return h
|
|
|
|
|
|
def set_vik(ref, port):
|
|
p = f"{w.DRILL}/templates/vikunja/docker-compose.yml"
|
|
s = re.sub(r"image: vikunja/vikunja:\S+", f"image: vikunja/vikunja:{ref}", open(p).read())
|
|
open(p, "w").write(s)
|
|
fy = f"{w.DRILL}/templates/vikunja/.felhom.yml"
|
|
f = open(fy).read()
|
|
f2 = re.sub(r"(healthcheck:\n(?:.*\n){0,8}?\s+port: )\d+", lambda m: m.group(1) + str(port), f, count=1)
|
|
assert f2 != f or str(port) in f, "probe port not found"
|
|
open(fy, "w").write(f2)
|
|
|
|
|
|
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")
|
|
|
|
|
|
def ctl_log(since):
|
|
return w.guest(f"docker logs --since {since} felhom-controller 2>&1 | grep -E 'vikunja' | grep -E 'undo copy will hold|carry no compose label|ladder|copied|UNDONE|UNDO|RestoreStack|Restoring Docker volume|was not created|volume .* already exists|created WITHOUT' | tail -30")
|
|
|
|
|
|
def readall(tag, seeds):
|
|
w.wait_app(SUB, "/", tries=60, delay=3)
|
|
r = {k: bool(F.verify(w, SUB, v, w.say)) for k, v in seeds.items()}
|
|
w.say(f"READBACK {tag}: {r}")
|
|
return r
|
|
|
|
|
|
PORT_REAL = int(re.search(r"port: (\d+)", open(f"{w.DRILL}/templates/vikunja/.felhom.yml").read().split("healthcheck:")[1]).group(1))
|
|
out["probe_port_real"] = PORT_REAL
|
|
out["labels_before"] = labels()
|
|
w.say("labels before (unlabelled after the v0.267.0 restore):\n" + out["labels_before"])
|
|
|
|
# (e)
|
|
t0 = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
|
out["drill_e"] = drill(lambda: set_vik("2.6.0", 8999), "vikunja 2.6.0 + probe 8999 (the failing edge)")
|
|
out["badge_e"] = w.sync_rescan("vikunja", "vikunja/vikunja:2.6.0")
|
|
out["press_e"] = w.press_update("vikunja")
|
|
out["log_e"] = ctl_log(t0)
|
|
w.say("controller log (e):\n" + out["log_e"])
|
|
out["read_e"] = readall("after the undo (e)", {"A": S["A"], "B": S["B"]})
|
|
out["obs_e"] = w.observables("vikunja")
|
|
|
|
# (f)
|
|
t1 = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
|
out["restore_f"] = w.restore("vikunja")
|
|
out["labels_after_restore_v0268"] = labels()
|
|
w.say("labels after the v0.268.0 restore:\n" + out["labels_after_restore_v0268"])
|
|
out["log_f"] = ctl_log(t1)
|
|
out["compose_warn_f"] = w.guest(f"docker logs --since {t1} felhom-controller 2>&1 | grep -iE 'not created by Docker Compose|already exists|Recreate' | tail -5")
|
|
w.say("compose warnings after the restore (want none): " + (out["compose_warn_f"].strip() or "(none)"))
|
|
out["read_f"] = readall("after the v0.268.0 restore (f)", {"A": S["A"]})
|
|
|
|
# (g)
|
|
C = F.seed(w, SUB, w.say)
|
|
t2 = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
|
out["press_g"] = w.press_update("vikunja")
|
|
out["log_g"] = ctl_log(t2)
|
|
w.say("controller log (g):\n" + out["log_g"])
|
|
out["read_g"] = readall("after the second undo (g)", {"A": S["A"], "C": C})
|
|
|
|
# (h)
|
|
out["drill_h"] = drill(lambda: set_vik("2.5.0", PORT_REAL), "vikunja back to 2.5.0 + its real probe")
|
|
out["remove_h"] = w.remove("vikunja")
|
|
out["leftovers_h"] = w.guest("ls -la /opt/docker/stacks/vikunja/; docker volume ls -q | grep -E '^vikunja' || echo 'no vikunja volumes'")
|
|
w.say("after remove:\n" + out["leftovers_h"])
|
|
json.dump(out, open("../partA/02-v0268-undo-after-restore.json", "w"), indent=2, ensure_ascii=False, default=str)
|
|
open("../partA/02-v0268-undo-after-restore.log", "w").write("\n".join(w.LOG) + "\n")
|