diff --git a/devenv/checks/colimaDiskSpace.py b/devenv/checks/colimaDiskSpace.py new file mode 100644 index 00000000..de5a1a86 --- /dev/null +++ b/devenv/checks/colimaDiskSpace.py @@ -0,0 +1,61 @@ +from __future__ import annotations + +from devenv.lib import colima +from devenv.lib import proc +from devenv.lib_check.types import checker +from devenv.lib_check.types import fixer + +tags: set[str] = {"builtin"} +name = "colima VM has sufficient disk space" + + +@checker +def check() -> tuple[bool, str]: + status = colima.check() + if status != colima.ColimaStatus.UP: + return True, "" + + try: + output = proc.run( + ("colima", "exec", "--", "df", "--output=pcent", "/"), stdout=True + ) + except RuntimeError: + return True, "" + + lines = output.strip().split("\n") + if len(lines) >= 2: + percent_str = lines[1].strip().rstrip("%") + try: + used_percent = int(percent_str) + if used_percent > 90: + return ( + False, + f"Colima VM disk is {used_percent}% full (less than 10% free space).\n" + "Consider resizing the disk or cleaning up unused Docker images/containers.", + ) + except ValueError: + pass + + return True, "" + + +@fixer +def fix() -> tuple[bool, str]: + return ( + False, + """ +First you should try to cleanup unused Docker resources: + docker system prune -a + +Failing that, you can resize the Colima VM's disk: + +1. Stop colima: + colima stop + +2. Install qemu: + brew install qemu + +2. Restart colima, resizing to a larger disk (e.g., 200GB): + colima start --disk 200 +""", + ) diff --git a/devenv/lib/colima.py b/devenv/lib/colima.py index f9c9e680..35a8f90c 100644 --- a/devenv/lib/colima.py +++ b/devenv/lib/colima.py @@ -186,6 +186,10 @@ def start(restart: bool = False) -> ColimaStatus: "colima", "start", "--verbose", + # default 60GiB disk is generally not enough over time + # since devs work with a lot of images + "--disk", + "128", # this effectively makes the vm's resolvectl status use: # DNS Servers: 8.8.8.8 1.1.1.1 192.168.5.2 # https://lima-vm.io/docs/config/network/user/ diff --git a/tests/checks/test_colimaDiskSpace.py b/tests/checks/test_colimaDiskSpace.py new file mode 100644 index 00000000..645a0f6a --- /dev/null +++ b/tests/checks/test_colimaDiskSpace.py @@ -0,0 +1,78 @@ +from __future__ import annotations + +from unittest import mock + +from devenv.checks import colimaDiskSpace +from devenv.lib import colima + + +def test_check_skipped_when_colima_down() -> None: + with mock.patch.object( + colima, "check", return_value=colima.ColimaStatus.DOWN + ): + assert colimaDiskSpace.check() == (True, "") + + +def test_check_skipped_when_colima_unhealthy() -> None: + with mock.patch.object( + colima, "check", return_value=colima.ColimaStatus.UNHEALTHY + ): + assert colimaDiskSpace.check() == (True, "") + + +def test_check_passes_when_disk_has_space() -> None: + with mock.patch.object( + colima, "check", return_value=colima.ColimaStatus.UP + ), mock.patch( + "devenv.checks.colimaDiskSpace.proc.run", return_value="Use%\n 50%" + ): + assert colimaDiskSpace.check() == (True, "") + + +def test_check_passes_at_90_percent() -> None: + with mock.patch.object( + colima, "check", return_value=colima.ColimaStatus.UP + ), mock.patch( + "devenv.checks.colimaDiskSpace.proc.run", return_value="Use%\n 90%" + ): + assert colimaDiskSpace.check() == (True, "") + + +def test_check_fails_when_disk_full() -> None: + with mock.patch.object( + colima, "check", return_value=colima.ColimaStatus.UP + ), mock.patch( + "devenv.checks.colimaDiskSpace.proc.run", return_value="Use%\n 95%" + ): + ok, msg = colimaDiskSpace.check() + assert ok is False + assert "95% full" in msg + assert "less than 10% free" in msg + + +def test_check_fails_at_91_percent() -> None: + with mock.patch.object( + colima, "check", return_value=colima.ColimaStatus.UP + ), mock.patch( + "devenv.checks.colimaDiskSpace.proc.run", return_value="Use%\n 91%" + ): + ok, msg = colimaDiskSpace.check() + assert ok is False + + +def test_check_skipped_on_df_error() -> None: + with mock.patch.object( + colima, "check", return_value=colima.ColimaStatus.UP + ), mock.patch( + "devenv.checks.colimaDiskSpace.proc.run", + side_effect=RuntimeError("command failed"), + ): + assert colimaDiskSpace.check() == (True, "") + + +def test_fix_returns_instructions() -> None: + ok, msg = colimaDiskSpace.fix() + assert ok is False + assert "colima stop" in msg + assert "colima start --disk 200" in msg + assert "docker system prune" in msg