fix: block Podman on macOS and remove ghcr.io default

- Add platform check in PodmanCLI.__init__() that raises FuzzForgeError
  on macOS with instructions to use Docker instead
- Change RegistrySettings.url default from "ghcr.io/fuzzinglabs" to ""
  (empty string) for local-only mode since no images are published yet
- Update _ensure_module_image() to show helpful error when image not
  found locally and no registry configured
- Update tests to mock Linux platform for Podman tests
- Add root ruff.toml to fix broken configuration in fuzzforge-runner
This commit is contained in:
tduhamel42
2026-01-31 10:27:03 +01:00
parent e72c5fb201
commit ac6e8a8a34
6 changed files with 144 additions and 68 deletions
@@ -2,32 +2,41 @@
import os
import shutil
import sys
import uuid
from pathlib import Path
from unittest import mock
import pytest
from fuzzforge_common.exceptions import FuzzForgeError
from fuzzforge_common.sandboxes.engines.podman.cli import PodmanCLI, _is_running_under_snap
# Helper to mock Linux platform for testing (since Podman is Linux-only)
def _mock_linux_platform() -> mock._patch[str]:
"""Context manager to mock sys.platform as 'linux'."""
return mock.patch.object(sys, "platform", "linux")
@pytest.fixture
def podman_cli_engine() -> PodmanCLI:
"""Create a PodmanCLI engine with temporary storage.
Uses short paths in /tmp to avoid podman's 50-char runroot limit.
Simulates Snap environment to test custom storage paths.
Mocks Linux platform since Podman is Linux-only.
"""
short_id = str(uuid.uuid4())[:8]
graphroot = Path(f"/tmp/ff-{short_id}/storage")
runroot = Path(f"/tmp/ff-{short_id}/run")
# Simulate Snap environment for testing
with mock.patch.dict(os.environ, {"SNAP": "/snap/code/123"}):
# Simulate Snap environment for testing on Linux
with _mock_linux_platform(), mock.patch.dict(os.environ, {"SNAP": "/snap/code/123"}):
engine = PodmanCLI(graphroot=graphroot, runroot=runroot)
yield engine
# Cleanup
parent = graphroot.parent
if parent.exists():
@@ -48,21 +57,30 @@ def test_snap_detection_when_snap_not_set() -> None:
assert _is_running_under_snap() is False
def test_podman_cli_blocks_macos() -> None:
"""Test that PodmanCLI raises error on macOS."""
with mock.patch.object(sys, "platform", "darwin"):
with pytest.raises(FuzzForgeError) as exc_info:
PodmanCLI()
assert "Podman is not supported on macOS" in str(exc_info.value)
assert "Docker" in str(exc_info.value)
def test_podman_cli_creates_storage_directories_under_snap() -> None:
"""Test that PodmanCLI creates storage directories when under Snap."""
short_id = str(uuid.uuid4())[:8]
graphroot = Path(f"/tmp/ff-{short_id}/storage")
runroot = Path(f"/tmp/ff-{short_id}/run")
assert not graphroot.exists()
assert not runroot.exists()
with mock.patch.dict(os.environ, {"SNAP": "/snap/code/123"}):
engine = PodmanCLI(graphroot=graphroot, runroot=runroot)
with _mock_linux_platform(), mock.patch.dict(os.environ, {"SNAP": "/snap/code/123"}):
PodmanCLI(graphroot=graphroot, runroot=runroot)
assert graphroot.exists()
assert runroot.exists()
# Cleanup
shutil.rmtree(graphroot.parent, ignore_errors=True)
@@ -72,15 +90,15 @@ def test_podman_cli_base_cmd_under_snap() -> None:
short_id = str(uuid.uuid4())[:8]
graphroot = Path(f"/tmp/ff-{short_id}/storage")
runroot = Path(f"/tmp/ff-{short_id}/run")
with mock.patch.dict(os.environ, {"SNAP": "/snap/code/123"}):
with _mock_linux_platform(), mock.patch.dict(os.environ, {"SNAP": "/snap/code/123"}):
engine = PodmanCLI(graphroot=graphroot, runroot=runroot)
base_cmd = engine._base_cmd()
assert "podman" in base_cmd
assert "--root" in base_cmd
assert "--runroot" in base_cmd
# Cleanup
shutil.rmtree(graphroot.parent, ignore_errors=True)
@@ -90,25 +108,26 @@ def test_podman_cli_base_cmd_without_snap() -> None:
short_id = str(uuid.uuid4())[:8]
graphroot = Path(f"/tmp/ff-{short_id}/storage")
runroot = Path(f"/tmp/ff-{short_id}/run")
env = os.environ.copy()
env.pop("SNAP", None)
with mock.patch.dict(os.environ, env, clear=True):
with _mock_linux_platform(), mock.patch.dict(os.environ, env, clear=True):
engine = PodmanCLI(graphroot=graphroot, runroot=runroot)
base_cmd = engine._base_cmd()
assert base_cmd == ["podman"]
assert "--root" not in base_cmd
# Directories should NOT be created when not under Snap
assert not graphroot.exists()
def test_podman_cli_default_mode() -> None:
"""Test PodmanCLI without custom storage paths."""
engine = PodmanCLI() # No paths provided
base_cmd = engine._base_cmd()
with _mock_linux_platform():
engine = PodmanCLI() # No paths provided
base_cmd = engine._base_cmd()
assert base_cmd == ["podman"]
assert "--root" not in base_cmd
@@ -116,7 +135,7 @@ def test_podman_cli_default_mode() -> None:
def test_podman_cli_list_images_returns_list(podman_cli_engine: PodmanCLI) -> None:
"""Test that list_images returns a list (even if empty)."""
images = podman_cli_engine.list_images()
assert isinstance(images, list)
@@ -125,6 +144,6 @@ def test_podman_cli_can_pull_and_list_image(podman_cli_engine: PodmanCLI) -> Non
"""Test pulling an image and listing it."""
# Pull a small image
podman_cli_engine._run(["pull", "docker.io/library/alpine:latest"])
images = podman_cli_engine.list_images()
assert any("alpine" in img.identifier for img in images)