feat: purge, and a way out of install ok unpacked
Both cases come from a fleet-wide Wazuh rollback. Of thirteen hosts carrying the agent, seven sat at `install ok unpacked` with the unit failed — an upgrade whose postinst could not reach a manager that had been decommissioned. `apt-get remove` cannot help there. apt configures a package before removing it, and configuring is precisely what was broken. On one host only `dpkg --purge --force-all` got it out. So `uninstall_package` takes `purge: bool = False`, and falls back to a forced dpkg purge **after apt has failed** — never as a routine second step. Forcing dpkg past its own consistency checks is a bigger hammer than apt, and a caller who reaches for it every time will eventually break something apt would rightly have refused. `purge` is off by default: configuration somebody may want back is not this function's to delete unless asked. It matters for more than tidiness — a package's apt source survives a plain remove, so the repository keeps being fetched on every update long after the package is gone, which is what the agent left behind on all thirteen. Found while writing the fallback test, and older than this change: the success check read apt's commonest failure as a success. `E: Sub-process /usr/bin/dpkg returned an error code (1)` contains neither "error:" nor "failed", so a removal that did not happen was reported as one that did — and the caller then records the package as gone. `_uninstall_failed` now also treats a line starting with `e: ` as failure, matched at line start because "note: " ends in "e: ". Reading success out of prose stays guesswork; the exit status is the real answer and `_sudo`'s `|| true` throws it away before anyone can read it. That is netork#267, deliberately not fixed here. apk and pacman have no separate purge. Asking for one there is not an error, it simply has nothing extra to do.
This commit is contained in:
@@ -912,3 +912,75 @@ class TestDockerBinHook:
|
||||
assert docker_cmds
|
||||
for cmd in docker_cmds:
|
||||
assert "/opt/cs/docker" in cmd, f"unconverted call site: {cmd}"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# uninstall_package – purge, and getting out of `install ok unpacked`
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestUninstallPackage:
|
||||
"""Removing a package that does not want to go.
|
||||
|
||||
Both cases here were found during a fleet-wide Wazuh rollback. Of thirteen
|
||||
hosts carrying the agent, seven sat at `install ok unpacked` with the unit
|
||||
failed — an upgrade whose postinst could not reach a manager that no longer
|
||||
existed. `apt-get remove` cannot help there: apt configures a package before
|
||||
removing it, and configuring is exactly what was broken.
|
||||
|
||||
And `remove` leaves the configuration behind by design, which for the Wazuh
|
||||
agent means its apt source keeps being fetched on every update, long after
|
||||
the package is gone.
|
||||
"""
|
||||
|
||||
def test_remove_is_still_the_default(self, driver):
|
||||
"""Callers that did not ask for a purge must not get one: configuration
|
||||
somebody may want back is not this function's to delete."""
|
||||
_mock_send(driver, "Removing wazuh-agent ...")
|
||||
|
||||
driver.uninstall_package("wazuh-agent")
|
||||
|
||||
sent = driver._device.send_command.call_args[0][0]
|
||||
assert "apt-get remove" in sent
|
||||
assert "purge" not in sent
|
||||
|
||||
def test_purge_is_asked_for_explicitly(self, driver):
|
||||
_mock_send(driver, "Purging configuration files for wazuh-agent ...")
|
||||
|
||||
driver.uninstall_package("wazuh-agent", purge=True)
|
||||
|
||||
assert "apt-get purge" in driver._device.send_command.call_args[0][0]
|
||||
|
||||
def test_a_half_configured_package_falls_back_to_dpkg(self, driver):
|
||||
"""`install ok unpacked` is the state apt cannot get out of. On one host
|
||||
only `dpkg --purge --force-all` removed it."""
|
||||
driver._device.send_command.side_effect = [
|
||||
"E: Sub-process /usr/bin/dpkg returned an error code (1)",
|
||||
"Removing wazuh-agent (4.14.7-1) ...",
|
||||
]
|
||||
|
||||
result = driver.uninstall_package("wazuh-agent", purge=True)
|
||||
|
||||
assert result["success"] is True
|
||||
second = driver._device.send_command.call_args_list[1][0][0]
|
||||
assert "dpkg --purge --force-all" in second
|
||||
|
||||
def test_the_fallback_is_not_tried_when_the_first_pass_worked(self, driver):
|
||||
"""A forced dpkg purge is a bigger hammer than apt and must stay a last
|
||||
resort, not a routine second step."""
|
||||
_mock_send(driver, "Removing wazuh-agent ...")
|
||||
|
||||
driver.uninstall_package("wazuh-agent", purge=True)
|
||||
|
||||
assert driver._device.send_command.call_count == 1
|
||||
|
||||
def test_a_package_manager_without_purge_still_removes(self, driver):
|
||||
"""apk and pacman have no separate purge; asking for one must not turn
|
||||
into a failure or a command they do not understand."""
|
||||
driver._pkg_manager = "apk"
|
||||
_mock_send(driver, "(1/1) Purging wazuh-agent")
|
||||
|
||||
result = driver.uninstall_package("wazuh-agent", purge=True)
|
||||
|
||||
assert result["success"] is True
|
||||
assert "apk del" in driver._device.send_command.call_args[0][0]
|
||||
|
||||
Reference in New Issue
Block a user