mirror of
https://github.com/FuzzingLabs/fuzzforge_ai.git
synced 2026-02-12 20:32:46 +00:00
fix: block Podman on macOS and remove ghcr.io default (#39)
* 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 * rewrite guides for module architecture and update repo links --------- Co-authored-by: AFredefon <antoinefredefon@yahoo.fr>
This commit is contained in:
@@ -95,8 +95,8 @@ class DockerCLI(AbstractFuzzForgeSandboxEngine):
|
||||
continue
|
||||
|
||||
reference = f"{repo}:{tag}"
|
||||
|
||||
if filter_prefix and not reference.startswith(filter_prefix):
|
||||
|
||||
if filter_prefix and filter_prefix not in reference:
|
||||
continue
|
||||
|
||||
images.append(
|
||||
|
||||
@@ -31,14 +31,15 @@ def get_logger() -> BoundLogger:
|
||||
|
||||
def _is_running_under_snap() -> bool:
|
||||
"""Check if running under Snap environment.
|
||||
|
||||
|
||||
VS Code installed via Snap sets XDG_DATA_HOME to a version-specific path,
|
||||
causing Podman to look for storage in non-standard locations. When SNAP
|
||||
is set, we use custom storage paths to ensure consistency.
|
||||
|
||||
|
||||
Note: Snap only exists on Linux, so this also handles macOS implicitly.
|
||||
"""
|
||||
import os # noqa: PLC0415
|
||||
|
||||
return os.getenv("SNAP") is not None
|
||||
|
||||
|
||||
@@ -48,11 +49,11 @@ class PodmanCLI(AbstractFuzzForgeSandboxEngine):
|
||||
This implementation uses subprocess calls to the Podman CLI with --root
|
||||
and --runroot flags when running under Snap, providing isolation from
|
||||
system Podman storage.
|
||||
|
||||
|
||||
The custom storage is only used when:
|
||||
1. Running under Snap (SNAP env var is set) - to fix XDG_DATA_HOME issues
|
||||
2. Custom paths are explicitly provided
|
||||
|
||||
|
||||
Otherwise, uses default Podman storage which works for:
|
||||
- Native Linux installations
|
||||
- macOS (where Podman runs in a VM via podman machine)
|
||||
@@ -69,16 +70,24 @@ class PodmanCLI(AbstractFuzzForgeSandboxEngine):
|
||||
:param runroot: Path to container runtime state.
|
||||
|
||||
Custom storage is used when running under Snap AND paths are provided.
|
||||
|
||||
:raises FuzzForgeError: If running on macOS (Podman not supported).
|
||||
"""
|
||||
import sys # noqa: PLC0415
|
||||
|
||||
if sys.platform == "darwin":
|
||||
msg = (
|
||||
"Podman is not supported on macOS. Please use Docker instead:\n"
|
||||
" brew install --cask docker\n"
|
||||
" # Or download from https://docker.com/products/docker-desktop"
|
||||
)
|
||||
raise FuzzForgeError(msg)
|
||||
|
||||
AbstractFuzzForgeSandboxEngine.__init__(self)
|
||||
|
||||
|
||||
# Use custom storage only under Snap (to fix XDG_DATA_HOME issues)
|
||||
self.__use_custom_storage = (
|
||||
_is_running_under_snap()
|
||||
and graphroot is not None
|
||||
and runroot is not None
|
||||
)
|
||||
|
||||
self.__use_custom_storage = _is_running_under_snap() and graphroot is not None and runroot is not None
|
||||
|
||||
if self.__use_custom_storage:
|
||||
self.__graphroot = graphroot
|
||||
self.__runroot = runroot
|
||||
@@ -98,8 +107,10 @@ class PodmanCLI(AbstractFuzzForgeSandboxEngine):
|
||||
if self.__use_custom_storage and self.__graphroot and self.__runroot:
|
||||
return [
|
||||
"podman",
|
||||
"--root", str(self.__graphroot),
|
||||
"--runroot", str(self.__runroot),
|
||||
"--root",
|
||||
str(self.__graphroot),
|
||||
"--runroot",
|
||||
str(self.__runroot),
|
||||
]
|
||||
return ["podman"]
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user