Add automatic agent updates with verified rollback

This commit is contained in:
2026-10-04 10:40:45 -04:00
parent 020894b816
commit 8d1331c67d
26 changed files with 2568 additions and 110 deletions
+13 -1
View File
@@ -57,6 +57,9 @@ class UninstallTests(unittest.TestCase):
"UNINSTALL_EXECUTABLE": self.bin_dir / "electric-cherry-uninstall.py",
"UNINSTALL_UNIT": self.unit_dir / "electric-cherry-uninstall.service",
"UNINSTALL_REQUEST": self.config_dir / "decommission.json",
"UPDATE_EXECUTABLE": self.bin_dir / "electric-cherry-update.py",
"UPDATE_UNIT": self.unit_dir / "electric-cherry-update.service",
"UPDATE_TIMER": self.unit_dir / "electric-cherry-update.timer",
}
self.patches = [patch.object(module, key, value) for key, value in replacements.items()]
# Unit tests run without root privileges. Keep actual path, symlink,
@@ -74,8 +77,10 @@ class UninstallTests(unittest.TestCase):
self.config = {"hostId": "host-one", "controller": "https://controller.example", "credential": "secret", "root": str(self.root), "configPath": str(module.AGENT_CONFIG)}
self.agent = module.Agent(self.config)
self.docker = CleanupDocker()
for path in (module.AGENT_EXECUTABLE, module.AGENT_UNIT, module.UNINSTALL_EXECUTABLE, module.UNINSTALL_UNIT):
for path in (module.AGENT_EXECUTABLE, module.AGENT_UNIT, module.UNINSTALL_EXECUTABLE, module.UNINSTALL_UNIT, module.UPDATE_EXECUTABLE, module.UPDATE_UNIT, module.UPDATE_TIMER):
path.write_text("installed")
for path in (module.AGENT_EXECUTABLE, module.UNINSTALL_EXECUTABLE):
path.with_suffix(".py.update").write_text("interrupted update")
module.atomic_json(module.AGENT_CONFIG, self.config)
module.atomic_json(self.root / module.ROOT_MARKER, {"version": 1, "root": str(self.root), "hostId": "host-one", "controller": "https://controller.example"})
self.game = self.root / "libraries" / "t6" / "library" / "content" / "zone" / "all" / "dlc.ff"
@@ -108,6 +113,11 @@ class UninstallTests(unittest.TestCase):
self.assertFalse(module.AGENT_CONFIG.exists())
self.assertFalse(module.AGENT_EXECUTABLE.exists())
self.assertFalse(module.AGENT_UNIT.exists())
self.assertFalse(module.UPDATE_EXECUTABLE.exists())
self.assertFalse(module.UPDATE_UNIT.exists())
self.assertFalse(module.UPDATE_TIMER.exists())
self.assertFalse(module.AGENT_EXECUTABLE.with_suffix(".py.update").exists())
self.assertFalse(module.UNINSTALL_EXECUTABLE.with_suffix(".py.update").exists())
if stage == "cleaned":
self.assertTrue(module.UNINSTALL_REQUEST.exists())
self.assertTrue(module.UNINSTALL_EXECUTABLE.exists())
@@ -120,6 +130,8 @@ class UninstallTests(unittest.TestCase):
self.assertEqual(stages, ["cleaned", "completed"])
self.assertEqual((self.base / "unrelated.txt").read_text(), "keep")
module.systemctl.assert_any_call("disable", "--now", "electric-cherry-agent.service")
module.systemctl.assert_any_call("disable", "--now", "electric-cherry-update.timer")
module.systemctl.assert_any_call("stop", "electric-cherry-update.service")
module.systemctl.assert_any_call("disable", "electric-cherry-uninstall.service")
def test_handoff_is_durable_and_never_reports_operation_completed(self):