mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-09-13 23:48:53 +00:00
* fix(docker): allow aio DooD socket preflight on Windows Git Bash
* test(deploy): add regression tests for deploy.sh DooD socket preflight
* fix(docker): restrict Windows DooD socket bypass to default path
Limit the Windows socket bypass in docker.sh and deploy.sh to the default /var/run/docker.sock path, and add regression tests ensuring custom missing socket paths are rejected.
* test(docker): skip unreachable socket controls on hosts with live docker socket
Add @pytest.mark.skipif on Path('/var/run/docker.sock').is_socket() to prevent false test failures on daemon-live hosts.
This commit is contained in:
parent
81f2015fe6
commit
ce635b7de6
@ -2,6 +2,8 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
@ -316,3 +318,295 @@ require_compose_version
|
||||
|
||||
assert result.returncode == 1, result.stdout + result.stderr
|
||||
assert "too old" in result.stdout
|
||||
|
||||
|
||||
def test_aio_dood_socket_preflight_allows_windows_when_docker_reachable():
|
||||
"""Windows Git Bash without /var/run/docker.sock proceeds when Docker daemon is reachable."""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
tmp_root = Path(tmpdir)
|
||||
_seed_compose_file(tmp_root)
|
||||
_seed_env_examples(tmp_root)
|
||||
(tmp_root / "config.yaml").write_text(
|
||||
"sandbox:\n use: deerflow.community.aio_sandbox:AioSandboxProvider\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
command = f"""
|
||||
source '{SCRIPT_PATH}'
|
||||
PROJECT_ROOT='{tmp_root}'
|
||||
DOCKER_DIR='{tmp_root}'
|
||||
require_compose_version() {{ :; }}
|
||||
uname() {{ echo 'MINGW64_NT-10.0'; }}
|
||||
docker() {{
|
||||
if [ "$1" = info ]; then
|
||||
return 0
|
||||
fi
|
||||
return 0
|
||||
}}
|
||||
DEER_FLOW_DOCKER_SOCKET='/var/run/docker.sock'
|
||||
COMPOSE_CMD=echo
|
||||
start
|
||||
"""
|
||||
result = subprocess.run(
|
||||
[BASH_EXECUTABLE, "-lc", command],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
assert result.returncode == 0, result.stdout + result.stderr
|
||||
assert "docker-compose.dood.yaml" in result.stdout
|
||||
|
||||
|
||||
def test_aio_dood_socket_preflight_rejects_missing_socket_on_posix():
|
||||
"""POSIX hosts without a physical socket file must fail fast."""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
tmp_root = Path(tmpdir)
|
||||
_seed_compose_file(tmp_root)
|
||||
_seed_env_examples(tmp_root)
|
||||
(tmp_root / "config.yaml").write_text(
|
||||
"sandbox:\n use: deerflow.community.aio_sandbox:AioSandboxProvider\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
command = f"""
|
||||
source '{SCRIPT_PATH}'
|
||||
PROJECT_ROOT='{tmp_root}'
|
||||
DOCKER_DIR='{tmp_root}'
|
||||
require_compose_version() {{ :; }}
|
||||
uname() {{ echo 'Linux'; }}
|
||||
DEER_FLOW_DOCKER_SOCKET='/nonexistent/docker.sock'
|
||||
COMPOSE_CMD=echo
|
||||
start
|
||||
"""
|
||||
result = subprocess.run(
|
||||
[BASH_EXECUTABLE, "-lc", command],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
assert result.returncode == 1, result.stdout + result.stderr
|
||||
assert "Docker socket not found" in result.stdout
|
||||
|
||||
|
||||
def test_aio_dood_socket_preflight_rejects_missing_custom_socket_on_windows():
|
||||
"""Windows Git Bash must fail if a custom non-existent socket path is specified."""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
tmp_root = Path(tmpdir)
|
||||
_seed_compose_file(tmp_root)
|
||||
_seed_env_examples(tmp_root)
|
||||
(tmp_root / "config.yaml").write_text(
|
||||
"sandbox:\n use: deerflow.community.aio_sandbox:AioSandboxProvider\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
command = f"""
|
||||
source '{SCRIPT_PATH}'
|
||||
PROJECT_ROOT='{tmp_root}'
|
||||
DOCKER_DIR='{tmp_root}'
|
||||
require_compose_version() {{ :; }}
|
||||
uname() {{ echo 'MINGW64_NT-10.0'; }}
|
||||
docker() {{
|
||||
if [ "$1" = info ]; then
|
||||
return 0
|
||||
fi
|
||||
return 0
|
||||
}}
|
||||
DEER_FLOW_DOCKER_SOCKET='/nonexistent/docker.sock'
|
||||
COMPOSE_CMD=echo
|
||||
start
|
||||
"""
|
||||
result = subprocess.run(
|
||||
[BASH_EXECUTABLE, "-lc", command],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
assert result.returncode == 1, result.stdout + result.stderr
|
||||
assert "Docker socket not found" in result.stdout
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
Path("/var/run/docker.sock").is_socket(),
|
||||
reason="Host has real /var/run/docker.sock",
|
||||
)
|
||||
def test_aio_dood_socket_preflight_rejects_windows_when_docker_unreachable():
|
||||
"""Windows Git Bash must fail if Docker daemon is not reachable."""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
tmp_root = Path(tmpdir)
|
||||
_seed_compose_file(tmp_root)
|
||||
_seed_env_examples(tmp_root)
|
||||
(tmp_root / "config.yaml").write_text(
|
||||
"sandbox:\n use: deerflow.community.aio_sandbox:AioSandboxProvider\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
command = f"""
|
||||
source '{SCRIPT_PATH}'
|
||||
PROJECT_ROOT='{tmp_root}'
|
||||
DOCKER_DIR='{tmp_root}'
|
||||
require_compose_version() {{ :; }}
|
||||
uname() {{ echo 'MINGW64_NT-10.0'; }}
|
||||
docker() {{
|
||||
if [ "$1" = info ]; then
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}}
|
||||
DEER_FLOW_DOCKER_SOCKET='/var/run/docker.sock'
|
||||
COMPOSE_CMD=echo
|
||||
start
|
||||
"""
|
||||
result = subprocess.run(
|
||||
[BASH_EXECUTABLE, "-lc", command],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
assert result.returncode == 1, result.stdout + result.stderr
|
||||
assert "Docker socket not found" in result.stdout
|
||||
|
||||
|
||||
def _setup_deploy_worktree(tmp_path: Path) -> Path:
|
||||
worktree = tmp_path / "repo"
|
||||
shutil.copytree(REPO_ROOT / "scripts", worktree / "scripts")
|
||||
shutil.copytree(REPO_ROOT / "docker", worktree / "docker")
|
||||
(worktree / "backend").mkdir()
|
||||
(worktree / "config.yaml").write_text(
|
||||
"sandbox:\n use: deerflow.community.aio_sandbox:AioSandboxProvider\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
(worktree / "extensions_config.json").write_text("{}\n", encoding="utf-8")
|
||||
(worktree / ".env").write_text("TEST=1\n", encoding="utf-8")
|
||||
return worktree
|
||||
|
||||
|
||||
def test_aio_deploy_socket_preflight_allows_windows_when_docker_reachable(tmp_path):
|
||||
"""deploy.sh on Windows Git Bash proceeds past preflight when Docker daemon is reachable."""
|
||||
worktree = _setup_deploy_worktree(tmp_path)
|
||||
bin_dir = tmp_path / "bin"
|
||||
bin_dir.mkdir()
|
||||
capture_args = tmp_path / "docker_args.txt"
|
||||
docker = bin_dir / "docker"
|
||||
docker.write_text(
|
||||
f'#!/usr/bin/env sh\nif [ "$1" = "info" ]; then exit 0; fi\nfor arg in "$@"; do printf "%s\\n" "$arg"; done > "{capture_args}"\nexit 0\n',
|
||||
encoding="utf-8",
|
||||
)
|
||||
docker.chmod(0o755)
|
||||
|
||||
bash_env = tmp_path / "env.sh"
|
||||
bash_env.write_text("uname() { echo 'MINGW64_NT-10.0'; }\n", encoding="utf-8")
|
||||
|
||||
env = os.environ.copy()
|
||||
env["PATH"] = f"{bin_dir}{os.pathsep}{env['PATH']}"
|
||||
env["BASH_ENV"] = str(bash_env)
|
||||
env["DEER_FLOW_DOCKER_SOCKET"] = "/var/run/docker.sock"
|
||||
env["BETTER_AUTH_SECRET"] = "test-secret"
|
||||
env["DEER_FLOW_INTERNAL_AUTH_TOKEN"] = "test-token"
|
||||
env["UV_EXTRAS"] = "redis"
|
||||
|
||||
result = subprocess.run(
|
||||
[BASH_EXECUTABLE, str(worktree / "scripts" / "deploy.sh"), "start"],
|
||||
cwd=worktree,
|
||||
env=env,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
|
||||
assert result.returncode == 0, result.stdout + result.stderr
|
||||
assert "docker-compose.dood.yaml" in capture_args.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def test_aio_deploy_socket_preflight_rejects_missing_socket_on_posix(tmp_path):
|
||||
"""deploy.sh on POSIX hosts without a physical socket file fails fast."""
|
||||
worktree = _setup_deploy_worktree(tmp_path)
|
||||
bash_env = tmp_path / "env.sh"
|
||||
bash_env.write_text("uname() { echo 'Linux'; }\n", encoding="utf-8")
|
||||
|
||||
env = os.environ.copy()
|
||||
env["BASH_ENV"] = str(bash_env)
|
||||
env["DEER_FLOW_DOCKER_SOCKET"] = "/nonexistent/docker.sock"
|
||||
env["BETTER_AUTH_SECRET"] = "test-secret"
|
||||
env["DEER_FLOW_INTERNAL_AUTH_TOKEN"] = "test-token"
|
||||
env["UV_EXTRAS"] = "redis"
|
||||
|
||||
result = subprocess.run(
|
||||
[BASH_EXECUTABLE, str(worktree / "scripts" / "deploy.sh"), "start"],
|
||||
cwd=worktree,
|
||||
env=env,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
|
||||
assert result.returncode == 1, result.stdout + result.stderr
|
||||
assert "Docker socket not found" in result.stdout + result.stderr
|
||||
|
||||
|
||||
def test_aio_deploy_socket_preflight_rejects_missing_custom_socket_on_windows(tmp_path):
|
||||
"""deploy.sh on Windows Git Bash fails if a custom non-existent socket path is specified."""
|
||||
worktree = _setup_deploy_worktree(tmp_path)
|
||||
bin_dir = tmp_path / "bin"
|
||||
bin_dir.mkdir()
|
||||
docker = bin_dir / "docker"
|
||||
docker.write_text(
|
||||
'#!/usr/bin/env sh\nif [ "$1" = "info" ]; then exit 0; fi\nexit 0\n',
|
||||
encoding="utf-8",
|
||||
)
|
||||
docker.chmod(0o755)
|
||||
|
||||
bash_env = tmp_path / "env.sh"
|
||||
bash_env.write_text("uname() { echo 'MINGW64_NT-10.0'; }\n", encoding="utf-8")
|
||||
|
||||
env = os.environ.copy()
|
||||
env["PATH"] = f"{bin_dir}{os.pathsep}{env['PATH']}"
|
||||
env["BASH_ENV"] = str(bash_env)
|
||||
env["DEER_FLOW_DOCKER_SOCKET"] = "/nonexistent/docker.sock"
|
||||
env["BETTER_AUTH_SECRET"] = "test-secret"
|
||||
env["DEER_FLOW_INTERNAL_AUTH_TOKEN"] = "test-token"
|
||||
env["UV_EXTRAS"] = "redis"
|
||||
|
||||
result = subprocess.run(
|
||||
[BASH_EXECUTABLE, str(worktree / "scripts" / "deploy.sh"), "start"],
|
||||
cwd=worktree,
|
||||
env=env,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
|
||||
assert result.returncode == 1, result.stdout + result.stderr
|
||||
assert "Docker socket not found" in result.stdout + result.stderr
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
Path("/var/run/docker.sock").is_socket(),
|
||||
reason="Host has real /var/run/docker.sock",
|
||||
)
|
||||
def test_aio_deploy_socket_preflight_rejects_windows_when_docker_unreachable(tmp_path):
|
||||
"""deploy.sh on Windows Git Bash fails if Docker daemon is not reachable."""
|
||||
worktree = _setup_deploy_worktree(tmp_path)
|
||||
bin_dir = tmp_path / "bin"
|
||||
bin_dir.mkdir()
|
||||
docker = bin_dir / "docker"
|
||||
docker.write_text('#!/usr/bin/env sh\nif [ "$1" = "info" ]; then exit 1; fi\nexit 0\n', encoding="utf-8")
|
||||
docker.chmod(0o755)
|
||||
|
||||
bash_env = tmp_path / "env.sh"
|
||||
bash_env.write_text("uname() { echo 'MINGW64_NT-10.0'; }\n", encoding="utf-8")
|
||||
|
||||
env = os.environ.copy()
|
||||
env["PATH"] = f"{bin_dir}{os.pathsep}{env['PATH']}"
|
||||
env["BASH_ENV"] = str(bash_env)
|
||||
env["DEER_FLOW_DOCKER_SOCKET"] = "/var/run/docker.sock"
|
||||
env["BETTER_AUTH_SECRET"] = "test-secret"
|
||||
env["DEER_FLOW_INTERNAL_AUTH_TOKEN"] = "test-token"
|
||||
env["UV_EXTRAS"] = "redis"
|
||||
|
||||
result = subprocess.run(
|
||||
[BASH_EXECUTABLE, str(worktree / "scripts" / "deploy.sh"), "start"],
|
||||
cwd=worktree,
|
||||
env=env,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
|
||||
assert result.returncode == 1, result.stdout + result.stderr
|
||||
assert "Docker socket not found" in result.stdout + result.stderr
|
||||
|
||||
@ -373,9 +373,15 @@ fi
|
||||
|
||||
if [ "$sandbox_mode" = "aio" ]; then
|
||||
if [ ! -S "$DEER_FLOW_DOCKER_SOCKET" ]; then
|
||||
echo -e "${RED}⚠ Docker socket not found at $DEER_FLOW_DOCKER_SOCKET${NC}"
|
||||
echo " AioSandboxProvider (DooD) will not work."
|
||||
exit 1
|
||||
# On Windows (Git Bash / MSYS), Docker Desktop mounts the default
|
||||
# /var/run/docker.sock into containers even though no host socket file exists.
|
||||
if [ "$DEER_FLOW_DOCKER_SOCKET" = "/var/run/docker.sock" ] && [[ "$(uname -s)" =~ ^(MINGW|MSYS|CYGWIN) ]] && docker info >/dev/null 2>&1; then
|
||||
:
|
||||
else
|
||||
echo -e "${RED}⚠ Docker socket not found at $DEER_FLOW_DOCKER_SOCKET${NC}"
|
||||
echo " AioSandboxProvider (DooD) will not work."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
echo -e "${GREEN}✓ Docker socket: $DEER_FLOW_DOCKER_SOCKET${NC}"
|
||||
echo -e "${YELLOW} Mounting host Docker socket into gateway (DooD = host root-equivalent). See SECURITY.md.${NC}"
|
||||
|
||||
@ -335,8 +335,14 @@ start() {
|
||||
if [ "$sandbox_mode" = "aio" ]; then
|
||||
local docker_socket="${DEER_FLOW_DOCKER_SOCKET:-/var/run/docker.sock}"
|
||||
if [ ! -S "$docker_socket" ]; then
|
||||
echo -e "${YELLOW}⚠ Docker socket not found at $docker_socket — AioSandboxProvider (DooD) will not work.${NC}"
|
||||
exit 1
|
||||
# On Windows (Git Bash / MSYS), Docker Desktop mounts the default
|
||||
# /var/run/docker.sock into containers even though no host socket file exists.
|
||||
if [ "$docker_socket" = "/var/run/docker.sock" ] && [[ "$(uname -s)" =~ ^(MINGW|MSYS|CYGWIN) ]] && docker info >/dev/null 2>&1; then
|
||||
:
|
||||
else
|
||||
echo -e "${YELLOW}⚠ Docker socket not found at $docker_socket — AioSandboxProvider (DooD) will not work.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
echo -e "${YELLOW}Mounting host Docker socket into gateway (DooD = host root-equivalent). See SECURITY.md.${NC}"
|
||||
COMPOSE_CMD="$COMPOSE_CMD -f docker-compose.dood.yaml"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user