mirror of
https://github.com/tiennm99/ccs.git
synced 2026-08-18 06:25:36 +00:00
hotfix: avoid Docker smoke port collisions (#1412)
This commit is contained in:
+2
-2
@@ -25,8 +25,8 @@ services:
|
|||||||
CCS_DOCKER_LEGACY_KEY_GRACE_DAYS: "${CCS_DOCKER_LEGACY_KEY_GRACE_DAYS:-}"
|
CCS_DOCKER_LEGACY_KEY_GRACE_DAYS: "${CCS_DOCKER_LEGACY_KEY_GRACE_DAYS:-}"
|
||||||
CCS_DOCKER_RESTORE_LEGACY_API_KEY: "${CCS_DOCKER_RESTORE_LEGACY_API_KEY:-}"
|
CCS_DOCKER_RESTORE_LEGACY_API_KEY: "${CCS_DOCKER_RESTORE_LEGACY_API_KEY:-}"
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
- "${CCS_DASHBOARD_PORT:-3000}:3000"
|
||||||
- "8317:8317"
|
- "${CCS_CLIPROXY_PORT:-8317}:8317"
|
||||||
volumes:
|
volumes:
|
||||||
# /root/.ccs matches the HOME used inside the integrated image.
|
# /root/.ccs matches the HOME used inside the integrated image.
|
||||||
# entrypoint-integrated.sh runs as root (supervisord user=root) and
|
# entrypoint-integrated.sh runs as root (supervisord user=root) and
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Unit test for network-contract.sh Docker Compose env and args.
|
||||||
|
# Uses a fake docker binary; no Docker daemon required.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
SCRIPT="${SCRIPT_DIR}/network-contract.sh"
|
||||||
|
|
||||||
|
PASS=0
|
||||||
|
FAIL=0
|
||||||
|
MOCK_DIR="$(mktemp -d)"
|
||||||
|
LOG_FILE="${MOCK_DIR}/docker.log"
|
||||||
|
COMPOSE_FILE="${MOCK_DIR}/compose.yaml"
|
||||||
|
trap 'rm -rf "$MOCK_DIR"' EXIT
|
||||||
|
|
||||||
|
cat > "$COMPOSE_FILE" <<'YAML'
|
||||||
|
services:
|
||||||
|
ccs:
|
||||||
|
image: ${CCS_IMAGE:-ghcr.io/kaitranntt/ccs:latest}
|
||||||
|
YAML
|
||||||
|
|
||||||
|
cat > "${MOCK_DIR}/docker" <<'MOCK'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
printf 'cmd=%s env_image=%s env_dashboard=%s env_cliproxy=%s args=%s\n' \
|
||||||
|
"$1" "${CCS_IMAGE:-}" "${CCS_DASHBOARD_PORT:-}" "${CCS_CLIPROXY_PORT:-}" "$*" \
|
||||||
|
>> "$DOCKER_MOCK_LOG"
|
||||||
|
|
||||||
|
if [[ "$1" == "compose" ]]; then
|
||||||
|
if printf '%s\n' "$*" | grep -q 'ps --format json'; then
|
||||||
|
printf '[{"Service":"ccs","Health":"healthy"}]\n'
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$1" == "network" && "$2" == "inspect" && "$3" == "ccs-net" ]]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$1" == "run" ]]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 1
|
||||||
|
MOCK
|
||||||
|
chmod +x "${MOCK_DIR}/docker"
|
||||||
|
|
||||||
|
run_test() {
|
||||||
|
local name="$1"
|
||||||
|
shift
|
||||||
|
|
||||||
|
if "$@"; then
|
||||||
|
echo "[OK] ${name}"
|
||||||
|
(( PASS++ )) || true
|
||||||
|
else
|
||||||
|
echo "[X] ${name}"
|
||||||
|
(( FAIL++ )) || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
run_contract() {
|
||||||
|
PATH="${MOCK_DIR}:${PATH}" \
|
||||||
|
DOCKER_MOCK_LOG="$LOG_FILE" \
|
||||||
|
bash "$SCRIPT" "$COMPOSE_FILE" "ghcr.io/kaitranntt/ccs:test" >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Running network-contract.sh env tests..."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
run_contract
|
||||||
|
|
||||||
|
run_test "compose up receives safe host ports and image override" \
|
||||||
|
grep -q 'env_image=ghcr.io/kaitranntt/ccs:test env_dashboard=13001 env_cliproxy=18318 args=compose -f .* up -d --remove-orphans' "$LOG_FILE"
|
||||||
|
|
||||||
|
run_test "compose ps receives the same safe host ports" \
|
||||||
|
grep -q 'env_image=ghcr.io/kaitranntt/ccs:test env_dashboard=13001 env_cliproxy=18318 args=compose -f .* ps --format json' "$LOG_FILE"
|
||||||
|
|
||||||
|
run_test "compose down removes volumes and orphans through cleanup trap" \
|
||||||
|
grep -q 'env_image=ghcr.io/kaitranntt/ccs:test env_dashboard=13001 env_cliproxy=18318 args=compose -f .* down -v --remove-orphans' "$LOG_FILE"
|
||||||
|
|
||||||
|
run_test "sibling probes keep the ccs-net DNS contract" \
|
||||||
|
grep -q 'args=run --rm --network ccs-net curlimages/curl:latest -fsS --max-time 10 http://ccs:8317/' "$LOG_FILE"
|
||||||
|
|
||||||
|
run_test "dashboard sibling probe keeps internal port 3000" \
|
||||||
|
grep -q 'args=run --rm --network ccs-net curlimages/curl:latest -fsS --max-time 10 http://ccs:3000/' "$LOG_FILE"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Results: ${PASS} passed, ${FAIL} failed"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [[ "$FAIL" -gt 0 ]]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@@ -19,6 +19,8 @@ set -euo pipefail
|
|||||||
|
|
||||||
COMPOSE_FILE="${1:-docker/compose.yaml}"
|
COMPOSE_FILE="${1:-docker/compose.yaml}"
|
||||||
IMAGE_OVERRIDE="${2:-}"
|
IMAGE_OVERRIDE="${2:-}"
|
||||||
|
DASHBOARD_HOST_PORT="${CCS_NETWORK_CONTRACT_DASHBOARD_PORT:-${CCS_DASHBOARD_PORT:-13001}}"
|
||||||
|
CLIPROXY_HOST_PORT="${CCS_NETWORK_CONTRACT_CLIPROXY_PORT:-${CCS_CLIPROXY_PORT:-18318}}"
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Helpers
|
# Helpers
|
||||||
@@ -27,22 +29,35 @@ log() { printf '[i] %s\n' "$*"; }
|
|||||||
ok() { printf '[OK] %s\n' "$*"; }
|
ok() { printf '[OK] %s\n' "$*"; }
|
||||||
err() { printf '[X] %s\n' "$*" >&2; }
|
err() { printf '[X] %s\n' "$*" >&2; }
|
||||||
|
|
||||||
|
compose() {
|
||||||
|
if [[ -n "$IMAGE_OVERRIDE" ]]; then
|
||||||
|
CCS_IMAGE="$IMAGE_OVERRIDE" \
|
||||||
|
CCS_DASHBOARD_PORT="$DASHBOARD_HOST_PORT" \
|
||||||
|
CCS_CLIPROXY_PORT="$CLIPROXY_HOST_PORT" \
|
||||||
|
docker compose -f "$COMPOSE_FILE" "$@"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
CCS_DASHBOARD_PORT="$DASHBOARD_HOST_PORT" \
|
||||||
|
CCS_CLIPROXY_PORT="$CLIPROXY_HOST_PORT" \
|
||||||
|
docker compose -f "$COMPOSE_FILE" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
log "Tearing down stack..."
|
||||||
|
compose down -v --remove-orphans 2>/dev/null || true
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Bring stack up; register teardown on any exit
|
# Bring stack up; register teardown on any exit
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
log "Bringing CCS stack up: $COMPOSE_FILE"
|
log "Bringing CCS stack up: $COMPOSE_FILE"
|
||||||
|
log "Using host ports: dashboard=${DASHBOARD_HOST_PORT}, cliproxy=${CLIPROXY_HOST_PORT}"
|
||||||
if [[ -n "$IMAGE_OVERRIDE" ]]; then
|
if [[ -n "$IMAGE_OVERRIDE" ]]; then
|
||||||
log "Overriding image with: $IMAGE_OVERRIDE"
|
log "Overriding image with: $IMAGE_OVERRIDE"
|
||||||
CCS_IMAGE="$IMAGE_OVERRIDE" docker compose -f "$COMPOSE_FILE" up -d
|
|
||||||
else
|
|
||||||
docker compose -f "$COMPOSE_FILE" up -d
|
|
||||||
fi
|
fi
|
||||||
|
compose up -d --remove-orphans
|
||||||
cleanup() {
|
|
||||||
log "Tearing down stack..."
|
|
||||||
docker compose -f "$COMPOSE_FILE" down -v 2>/dev/null || true
|
|
||||||
}
|
|
||||||
trap cleanup EXIT
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Wait for healthcheck (max 90s) — use jq instead of python3 for CI portability
|
# Wait for healthcheck (max 90s) — use jq instead of python3 for CI portability
|
||||||
@@ -52,7 +67,7 @@ WAIT_MAX=45 # 45 x 2s = 90s
|
|||||||
HEALTHY=0
|
HEALTHY=0
|
||||||
for _i in $(seq 1 "$WAIT_MAX"); do
|
for _i in $(seq 1 "$WAIT_MAX"); do
|
||||||
STATUS=$(
|
STATUS=$(
|
||||||
docker compose -f "$COMPOSE_FILE" ps --format json 2>/dev/null \
|
compose ps --format json 2>/dev/null \
|
||||||
| jq -r 'if type == "array" then .[] else . end | select(.Service != null and (.Service | contains("ccs"))) | .Health // "unknown"' \
|
| jq -r 'if type == "array" then .[] else . end | select(.Service != null and (.Service | contains("ccs"))) | .Health // "unknown"' \
|
||||||
2>/dev/null | head -1 || echo "unknown"
|
2>/dev/null | head -1 || echo "unknown"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { describe, expect, test } from 'bun:test';
|
import { describe, expect, test } from 'bun:test';
|
||||||
|
import { spawnSync } from 'node:child_process';
|
||||||
import { existsSync, readFileSync } from 'node:fs';
|
import { existsSync, readFileSync } from 'node:fs';
|
||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
|
|
||||||
@@ -43,4 +44,24 @@ describe('docker release workflow context', () => {
|
|||||||
expect(dockerfile).toContain('127.0.0.1:3000');
|
expect(dockerfile).toContain('127.0.0.1:3000');
|
||||||
expect(dockerfile).toContain('127.0.0.1:8317');
|
expect(dockerfile).toContain('127.0.0.1:8317');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('lets network-contract smoke tests avoid fixed host port collisions', () => {
|
||||||
|
const compose = readFileSync(join(repoRoot, 'docker/compose.yaml'), 'utf8');
|
||||||
|
const contractScript = readFileSync(join(repoRoot, 'tests/docker/network-contract.sh'), 'utf8');
|
||||||
|
|
||||||
|
expect(compose).toContain('${CCS_DASHBOARD_PORT:-3000}:3000');
|
||||||
|
expect(compose).toContain('${CCS_CLIPROXY_PORT:-8317}:8317');
|
||||||
|
expect(contractScript).toContain('CCS_NETWORK_CONTRACT_DASHBOARD_PORT:-${CCS_DASHBOARD_PORT:-13001}');
|
||||||
|
expect(contractScript).toContain('CCS_NETWORK_CONTRACT_CLIPROXY_PORT:-${CCS_CLIPROXY_PORT:-18318}');
|
||||||
|
expect(contractScript).toContain('up -d --remove-orphans');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('passes collision-safe compose env through network-contract calls', () => {
|
||||||
|
const result = spawnSync('bash', ['tests/docker/network-contract-env.test.sh'], {
|
||||||
|
cwd: repoRoot,
|
||||||
|
encoding: 'utf8',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.status, result.stdout + result.stderr).toBe(0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user