R-880: build-step-bundle.py — the transition bundle for a release whose bundle adds paths (an installed felhom-os-apply refuses unknown paths, R16)
gates / gates (push) Successful in 21s
gates / gates (push) Successful in 21s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -667,5 +667,67 @@ class SelfupdateWrapperConfinement(unittest.TestCase):
|
||||
self.assertEqual(p.returncode, 1, p.stderr)
|
||||
self.assertIn("outside /var/lib/felhom-os-apply/agent-update", p.stderr)
|
||||
|
||||
|
||||
_sb = importlib.machinery.SourceFileLoader("stepbuild", str(REPO / "scripts" / "build-step-bundle.py"))
|
||||
_ss = importlib.util.spec_from_loader("stepbuild", _sb)
|
||||
stepbuild = importlib.util.module_from_spec(_ss)
|
||||
_sb.exec_module(stepbuild)
|
||||
NEW_IN_0146 = {"/usr/local/sbin/felhom-priv-apply", "/var/lib/vz/snippets/felhom-guest-hook.sh",
|
||||
"/usr/local/sbin/felhom-shared-parent.sh", "/etc/systemd/system/felhom-shared-parent.service"}
|
||||
|
||||
|
||||
class StepBundle(unittest.TestCase):
|
||||
"""R-880 (agent v0.146.1): an INSTALLED wrapper checks an incoming bundle's paths against its OWN table (R16), so a
|
||||
release that adds paths needs a step bundle: the boxes' current bundle with only felhom-os-apply replaced.
|
||||
RED-PROOF: deliver the full bundle to the old table → R16 (test_the_full_bundle_is_refused_by_an_old_table)."""
|
||||
|
||||
def old_world(self):
|
||||
"""The base bundle an older wrapper (no R-861 paths) installed, and that wrapper's table."""
|
||||
full = json.loads(builder.build("0.145.0"))
|
||||
full["files"] = [e for e in full["files"] if e["path"] not in NEW_IN_0146]
|
||||
old_wrapper = b'# the v0.145.0 wrapper stands in here\nBUNDLE_OP = "agent_config_update"\n'
|
||||
for e in full["files"]:
|
||||
if e["path"] == "/usr/local/sbin/felhom-os-apply":
|
||||
e["content_b64"], e["sha256"] = base64.b64encode(old_wrapper).decode(), hashlib.sha256(old_wrapper).hexdigest()
|
||||
base = (json.dumps(full, indent=1, sort_keys=True) + "\n").encode()
|
||||
old_dests = {k: v for k, v in osapply.BUNDLE_DESTS.items() if k not in NEW_IN_0146}
|
||||
return base, old_dests
|
||||
|
||||
def parse_with_table(self, data, dests, version):
|
||||
saved = osapply.BUNDLE_DESTS
|
||||
osapply.BUNDLE_DESTS = dests
|
||||
try:
|
||||
return osapply.Bundle(osapply.Apply(Box(b"{}", None), "")).parse(data, hashlib.sha256(data).hexdigest(), version)
|
||||
finally:
|
||||
osapply.BUNDLE_DESTS = saved
|
||||
|
||||
def test_the_full_bundle_is_refused_by_an_old_table(self):
|
||||
_, old_dests = self.old_world()
|
||||
full = builder.build("0.146.1")
|
||||
with self.assertRaises(osapply.Refused) as cm:
|
||||
self.parse_with_table(full, old_dests, "0.146.1")
|
||||
self.assertEqual(cm.exception.code, "R16")
|
||||
|
||||
def test_the_step_bundle_is_accepted_by_the_old_table_and_changes_only_the_wrapper(self):
|
||||
base, old_dests = self.old_world()
|
||||
new_wrapper = (HERE / "felhom-os-apply").read_bytes()
|
||||
step = stepbuild.build_step(base, "0.146.1-step1", new_wrapper)
|
||||
ver, files = self.parse_with_table(step, old_dests, "0.146.1-step1")
|
||||
self.assertEqual(ver, "0.146.1-step1")
|
||||
b, s_ = json.loads(base), json.loads(step)
|
||||
self.assertEqual(sorted(e["path"] for e in b["files"]), sorted(e["path"] for e in s_["files"]), "the paths must not change")
|
||||
changed = [e["path"] for e, f in zip(sorted(b["files"], key=lambda x: x["path"]), sorted(s_["files"], key=lambda x: x["path"]))
|
||||
if e != f]
|
||||
self.assertEqual(changed, ["/usr/local/sbin/felhom-os-apply"], "exactly the wrapper changes")
|
||||
installed = dict((d, c) for d, c, *_ in files)
|
||||
self.assertEqual(installed["/usr/local/sbin/felhom-os-apply"], new_wrapper)
|
||||
# and the NEW wrapper (now installed) knows every path the release's full bundle names
|
||||
self.assertTrue({e["path"] for e in json.loads(builder.build("0.146.1"))["files"]} <= set(osapply.BUNDLE_DESTS))
|
||||
|
||||
def test_a_step_version_must_carry_a_suffix(self):
|
||||
base, _ = self.old_world()
|
||||
with self.assertRaises(SystemExit):
|
||||
stepbuild.build_step(base, "0.146.1", b"x")
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user