mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-10-26 05:06:59 +00:00
fix(scripts): select sandbox containers by anchored name (#7189)
This commit is contained in:
parent
702528e5c3
commit
c70dd685a9
@ -770,6 +770,10 @@ DeerFlow runs the agent runtime inside the Gateway API. Development mode enables
|
||||
| **Stop** | `./scripts/serve.sh --stop`<br/>`make stop` | `./scripts/docker.sh stop`<br/>`make docker-stop` | `./scripts/deploy.sh down`<br/>`make down` |
|
||||
| **Restart** | `./scripts/serve.sh --restart [flags]` | `./scripts/docker.sh restart` | — |
|
||||
|
||||
Local and Docker development stop commands also stop running sandbox containers
|
||||
whose names start with `deer-flow-sandbox-`. Containers with that text elsewhere
|
||||
in their names are left alone.
|
||||
|
||||
`make start` and `make start-daemon` rebuild the frontend with `next build` on
|
||||
every run. To reuse the last build instead, pass `SKIP_FRONTEND_BUILD=1` (or add
|
||||
`--skip-frontend-build` when calling `./scripts/serve.sh --prod` directly). This
|
||||
|
||||
62
backend/tests/test_cleanup_containers_script.py
Normal file
62
backend/tests/test_cleanup_containers_script.py
Normal file
@ -0,0 +1,62 @@
|
||||
"""Offline checks for the names selected by the sandbox cleanup helper."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
SCRIPT = Path(__file__).resolve().parents[2] / "scripts" / "cleanup-containers.sh"
|
||||
|
||||
|
||||
def _stub(path: Path, name: str, source: str) -> None:
|
||||
executable = path / name
|
||||
executable.write_text("#!/bin/sh\n" + source, encoding="utf-8", newline="\n")
|
||||
executable.chmod(0o755)
|
||||
|
||||
|
||||
@pytest.mark.skipif(os.name == "nt", reason="POSIX executable stubs")
|
||||
def test_docker_cleanup_stops_only_sandbox_prefix(tmp_path: Path) -> None:
|
||||
bin_dir = tmp_path / "bin"
|
||||
bin_dir.mkdir()
|
||||
stop_log = tmp_path / "stopped"
|
||||
_stub(
|
||||
bin_dir,
|
||||
"docker",
|
||||
'if [ "$1" = ps ]; then\n'
|
||||
' printf "other-id not-deer-flow-sandbox Up 3 days\\nvalid-id deer-flow-sandbox-abc Up 1 hour\\nbackup-id deer-flow-sandboxbackup Up 2 days\\n"\n'
|
||||
'elif [ "$1" = stop ]; then\n'
|
||||
' shift; printf "%s\\n" "$@" >> "$STOP_LOG"\n'
|
||||
"fi\n",
|
||||
)
|
||||
env = {**os.environ, "PATH": f"{bin_dir}:/usr/bin:/bin", "STOP_LOG": str(stop_log)}
|
||||
result = subprocess.run(["bash", str(SCRIPT), "deer-flow-sandbox"], env=env, capture_output=True, text=True, timeout=5)
|
||||
|
||||
assert result.returncode == 0, result.stderr
|
||||
assert stop_log.read_text(encoding="utf-8").splitlines() == ["valid-id"]
|
||||
assert "not-deer-flow-sandbox" not in result.stdout
|
||||
assert "deer-flow-sandbox-abc Up 1 hour" in result.stdout
|
||||
|
||||
|
||||
@pytest.mark.skipif(os.name == "nt", reason="POSIX executable stubs")
|
||||
def test_apple_cleanup_ignores_unrelated_names_and_bad_entries(tmp_path: Path) -> None:
|
||||
bin_dir = tmp_path / "bin"
|
||||
bin_dir.mkdir()
|
||||
stop_log = tmp_path / "stopped"
|
||||
entries = [
|
||||
{"configuration": None},
|
||||
{"configuration": {"id": "not-deer-flow-sandbox"}},
|
||||
{"configuration": {"id": "deer-flow-sandbox-abc"}},
|
||||
]
|
||||
payload = tmp_path / "containers.json"
|
||||
payload.write_text(json.dumps(entries), encoding="utf-8")
|
||||
_stub(bin_dir, "container", 'if [ "$1" = list ]; then cat "$CONTAINERS_JSON"; elif [ "$1" = stop ]; then printf "%s\\n" "$2" >> "$STOP_LOG"; fi\n')
|
||||
env = {**os.environ, "PATH": f"{bin_dir}:/usr/bin:/bin", "STOP_LOG": str(stop_log), "CONTAINERS_JSON": str(payload)}
|
||||
result = subprocess.run(["bash", str(SCRIPT), "deer-flow-sandbox"], env=env, capture_output=True, text=True, timeout=5)
|
||||
|
||||
assert result.returncode == 0, result.stderr
|
||||
assert stop_log.read_text(encoding="utf-8").splitlines() == ["deer-flow-sandbox-abc"]
|
||||
assert "not-deer-flow-sandbox" not in result.stdout
|
||||
@ -214,6 +214,11 @@ runs `check-data-not-in-use.sh` before `make stop` (which would stop a live
|
||||
stack's sandboxes) and refuses while that container runs; an absent or
|
||||
unreachable Docker passes. `make stop` must still run before the delete: it
|
||||
stops `deer-flow-sandbox*` containers, whose thread mounts live in that tree.
|
||||
`cleanup-containers.sh` must verify an anchored `<prefix>-` name after Docker's
|
||||
substring `name=` filter, and use the same rule for Apple Container IDs. Parse
|
||||
Apple Container JSON without interpolating the prefix into Python source; bad
|
||||
entries must not hide later valid containers. Offline stub coverage lives in
|
||||
`backend/tests/test_cleanup_containers_script.py`.
|
||||
`RUNTIME_DATA_CONTENTS` feeds both the help line and the deletion notice.
|
||||
|
||||
Host-side pnpm calls must go through `scripts/pnpm.py`. With native Windows
|
||||
|
||||
@ -22,14 +22,23 @@ echo "Cleaning up sandbox containers with prefix: ${PREFIX}"
|
||||
cleanup_docker() {
|
||||
if command -v docker &> /dev/null; then
|
||||
echo -n "Checking Docker containers... "
|
||||
DOCKER_CONTAINERS=$(docker ps -q --filter "name=${PREFIX}" 2>/dev/null || echo "")
|
||||
# Docker's name filter matches substrings. Check the returned names as
|
||||
# well so a container like not-deer-flow-sandbox is never stopped.
|
||||
DOCKER_LIST=$(docker ps --filter "name=${PREFIX}-" --format '{{.ID}} {{.Names}} {{.Status}}' 2>/dev/null || true)
|
||||
docker_ids=()
|
||||
docker_rows=()
|
||||
while read -r cid name status; do
|
||||
case "$name" in
|
||||
"$PREFIX"-*) docker_ids+=("$cid"); docker_rows+=("$cid $name $status") ;;
|
||||
esac
|
||||
done <<< "$DOCKER_LIST"
|
||||
|
||||
if [ -n "$DOCKER_CONTAINERS" ]; then
|
||||
if [ "${#docker_ids[@]}" -gt 0 ]; then
|
||||
echo ""
|
||||
echo "Found Docker containers to clean up:"
|
||||
docker ps --filter "name=${PREFIX}" --format "table {{.ID}}\t{{.Names}}\t{{.Status}}"
|
||||
printf ' %s\n' "${docker_rows[@]}"
|
||||
echo "Stopping Docker containers..."
|
||||
echo "$DOCKER_CONTAINERS" | xargs docker stop 2>/dev/null || true
|
||||
docker stop "${docker_ids[@]}" >/dev/null 2>&1 || true
|
||||
echo -e "${GREEN}✓ Docker containers stopped${NC}"
|
||||
else
|
||||
echo -e "${GREEN}none found${NC}"
|
||||
@ -49,21 +58,23 @@ cleanup_apple_container() {
|
||||
|
||||
if [ "$CONTAINER_LIST" != "[]" ] && [ -n "$CONTAINER_LIST" ]; then
|
||||
# Extract container IDs that match our prefix
|
||||
CONTAINER_IDS=$(echo "$CONTAINER_LIST" | python3 -c "
|
||||
CONTAINER_IDS=$(echo "$CONTAINER_LIST" | CLEANUP_CONTAINER_PREFIX="$PREFIX" python3 -c '
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
try:
|
||||
containers = json.load(sys.stdin)
|
||||
if isinstance(containers, list):
|
||||
prefix = os.environ["CLEANUP_CONTAINER_PREFIX"] + "-"
|
||||
for c in containers:
|
||||
if isinstance(c, dict):
|
||||
# Apple Container uses 'id' field which contains the container name
|
||||
cid = c.get('configuration').get('id', '')
|
||||
if '${PREFIX}' in cid:
|
||||
configuration = c.get("configuration")
|
||||
cid = configuration.get("id") if isinstance(configuration, dict) else None
|
||||
if isinstance(cid, str) and cid.startswith(prefix):
|
||||
print(cid)
|
||||
except:
|
||||
pass
|
||||
" 2>/dev/null || echo "")
|
||||
' 2>/dev/null || echo "")
|
||||
|
||||
if [ -n "$CONTAINER_IDS" ]; then
|
||||
echo ""
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user