Files
computer-playbook/cli/administration/deploy/development/storage.py
T
kevinveenbirkenbachandClaude Opus 5 435ff42219 fix(storage): measure the docker root from the caller's namespace
docker info reports DockerRootDir as the DAEMON sees it, and
host_storage_constrained fed that string straight into shutil.disk_usage,
which statvfs()es it in the CALLING process's mount namespace. The two
coincide only when the caller is the daemon's host. In the nested act
container of the workspace jobs they do not, so os.statvfs('/var/lib/docker')
raised FileNotFoundError, matrix.py died, and because 00_one.sh invokes it
bare under set -euo pipefail the whole swarm drill went down with it. All
five workspace jobs of run 30560683608 failed this way, byte-identically,
across five base images and five nested distros.

docker_root_free_bytes() now takes the vantage point as a required keyword:
it measures the daemon's data root when that path resolves here, and the
caller's stated local path when it does not. matrix.py passes "/", which is
the same vantage it already trusts for its ENOSPC abort 270 lines earlier;
overlayfs passes statfs through to the upper filesystem, so a container of a
daemon measures that daemon's data-root filesystem through its own rootfs.

Nothing is caught and nothing is guessed. An unreachable daemon still raises
RuntimeError out of docker_data_root(), which is the guard this module exists
for, and local_vantage carries no default because only the caller knows where
it is running.

The compose-side twin loses its || true and its hardcoded /var/lib/docker
substitution, so an unresolvable root no longer measures a guessed filesystem.
Its compose.exec locality was already correct and is unchanged.

Verified against the pre-fix module under the CI condition: with the data root
stubbed to a path absent from this namespace, the old code raises
FileNotFoundError and the new code measures the local vantage; with the daemon
stubbed unreachable, the new code still raises RuntimeError.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 23:59:09 +02:00

45 lines
1.2 KiB
Python

from __future__ import annotations
from typing import TYPE_CHECKING
from utils.storage.constrained import is_constrained, required_storage_bytes
if TYPE_CHECKING:
from .compose import Compose
def detect_storage_constrained(
compose: Compose, app_ids: list[str], variants: dict[str, int] | None = None
) -> bool:
"""
Return True if the declared storage need of app_ids and their transitive
dependencies, at the given variant overlay, exceeds the free space on the
filesystem holding DockerRootDir.
We intentionally measure the DockerRootDir filesystem because this is where
images/volumes/build cache usually grow (especially in CI / Docker-in-Docker).
"""
cmd = [
"bash",
"-lc",
r"""
set -euo pipefail
root="$(docker info -f '{{.DockerRootDir}}')"
df -PB1 "${root}" | awk 'NR==2{print $4}'
""",
]
r = compose.exec(cmd, check=False, capture=True)
if r.returncode != 0:
return False
try:
free_bytes = int((r.stdout or "").strip())
except ValueError:
return False
return is_constrained(
free_bytes=free_bytes,
required_bytes=required_storage_bytes(app_ids, variants),
)