fix(ansible): the swarm prune never runs a docker prune that touches images; it lives in its own folder (#6399)
CI / lint (push) Successful in 2s
CI / unit (push) Successful in 1m42s
CI / integration (push) Successful in 1m47s
CI / publish (push) Successful in 1m10s

The first applied run lost VDNT-Docker03 29 images (10.4 GB) before its
script ran. Its `docker system prune` removes dangling images, and Docker
counts an image with no tag as dangling: every image a swarm task pulled by
digest. Docker03 was left holding only its own tasks' images, as under the
old prune. Nothing went down, since a running container's image can't be
removed.

- Step 1 now prunes containers, networks, build cache and (prune_volumes)
  volumes one by one. Every image decision is left to the script, which
  keeps the images services are pinned to.
- A test guards against any image-pruning command in the playbook.
- The playbook moves to maintenance/swarm_prune/, with its script in that
  folder's files/, so a helper's name can't collide with another
  playbook's (the operator's direction for playbooks with helpers).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-25 11:22:41 -04:00
co-authored by Claude Opus 5.5
parent b40e672cda
commit b2f4dde288
3 changed files with 29 additions and 15 deletions
+9 -3
View File
@@ -16,7 +16,7 @@ BUILTIN = Path(get_sources({"sources": []})[0]["path"])
@pytest.fixture
def prune():
spec = importlib.util.spec_from_file_location(
"swarm_prune", BUILTIN / "maintenance" / "files" / "swarm_prune.py")
"swarm_prune", BUILTIN / "maintenance" / "swarm_prune" / "files" / "swarm_prune.py")
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
return mod
@@ -27,13 +27,19 @@ def _image(id_, tags=(), digests=(), size=100):
def test_the_playbook_is_listed_and_asks_before_it_runs():
assert "maintenance/swarm_prune.yml" in discover_playbooks(str(BUILTIN))
content = (BUILTIN / "maintenance" / "swarm_prune.yml").read_text()
assert "maintenance/swarm_prune/swarm_prune.yml" in discover_playbooks(str(BUILTIN))
content = (BUILTIN / "maintenance" / "swarm_prune" / "swarm_prune.yml").read_text()
meta = discover_playbook_meta(content)
assert meta["confirm"] is True and meta["category"] == "maintenance" and meta["description"]
play = yaml.safe_load(content)[0]
script = next(t for t in play["tasks"] if "ansible.builtin.script" in t)
assert script["check_mode"] is False and "--dry-run" in script["ansible.builtin.script"]["cmd"]
# No docker prune touches images: an image swarm pulled by digest has no
# tag, and Docker calls that dangling (on 2026-10-25 a `system prune`
# took 29 a node was keeping for the other's services).
commands = yaml.safe_dump([t for t in play["tasks"] if "ansible.builtin.command" in t])
assert "'system'" not in commands and "'image'" not in commands
assert "'container', 'prune'" in commands and "'builder', 'prune'" in commands
def test_services_are_matched_by_digest_or_else_by_tag(prune):