test(docker): align package harness image

This commit is contained in:
Peter Steinberger
2026-04-27 01:22:50 +01:00
parent 732a5842ee
commit 7ca2f9fed5
7 changed files with 41 additions and 92 deletions

View File

@@ -65,14 +65,10 @@ model calls must not export `StreamAbandoned` on successful turns; raw diagnosti
`openclaw.content.*` attributes must stay out of the trace. It writes
`otel-smoke-summary.json` next to the QA suite artifacts.
The normal Docker aggregate and release-path core chunk also run an
observability lane. It reuses the shared package-installed functional Docker
image, mounts the QA harness files read-only, runs the OTEL trace smoke inside
the container, then runs the `docker-prometheus-smoke` QA scenario with the
`diagnostics-prometheus` plugin enabled. Set
`OPENCLAW_DOCKER_OBSERVABILITY_LOOPS=<count>` to repeat both checks inside one
Docker run while preserving per-loop artifacts under
`.artifacts/docker-observability/...`.
Observability QA stays source-checkout only. The npm tarball intentionally omits
QA Lab, so package Docker release lanes do not run `qa` commands. Use
`pnpm qa:otel:smoke` from a built source checkout when changing diagnostics
instrumentation.
For a transport-real Matrix smoke lane, run:

View File

@@ -617,7 +617,7 @@ The live-model Docker runners also bind-mount only the needed CLI auth homes (or
- CLI backend smoke: `pnpm test:docker:live-cli-backend` (script: `scripts/test-live-cli-backend-docker.sh`)
- Codex app-server harness smoke: `pnpm test:docker:live-codex-harness` (script: `scripts/test-live-codex-harness-docker.sh`)
- Gateway + dev agent: `pnpm test:docker:live-gateway` (script: `scripts/test-live-gateway-models-docker.sh`)
- Docker observability smoke: included in `pnpm test:docker:all`, `pnpm test:docker:local:all`, and the release-path `core` chunk (script: `scripts/e2e/docker-observability-smoke.sh`). It runs QA-lab OTEL and Prometheus diagnostics checks inside the shared package-installed functional Docker image, with only QA harness files mounted read-only. Set `OPENCLAW_DOCKER_OBSERVABILITY_LOOPS=<count>` to repeat both checks in one container run.
- Observability smoke: `pnpm qa:otel:smoke` is a private QA source-checkout lane. It is intentionally not part of package Docker release lanes because the npm tarball omits QA Lab.
- Open WebUI live smoke: `pnpm test:docker:openwebui` (script: `scripts/e2e/openwebui-docker.sh`)
- Onboarding wizard (TTY, full scaffolding): `pnpm test:docker:onboard` (script: `scripts/e2e/onboard-docker.sh`)
- Npm tarball onboarding/channel/agent smoke: `pnpm test:docker:npm-onboard-channel-agent` installs the packed OpenClaw tarball globally in Docker, configures OpenAI via env-ref onboarding plus Telegram by default, verifies doctor repairs activated plugin runtime deps, and runs one mocked OpenAI agent turn. Reuse a prebuilt tarball with `OPENCLAW_CURRENT_PACKAGE_TGZ=/path/to/openclaw-*.tgz`, skip the host rebuild with `OPENCLAW_NPM_ONBOARD_HOST_BUILD=0`, or switch channel with `OPENCLAW_NPM_ONBOARD_CHANNEL=discord`.

View File

@@ -6,8 +6,10 @@
FROM node:24-bookworm-slim@sha256:e8e2e91b1378f83c5b2dd15f0247f34110e2fe895f6ca7719dbb780f929368eb AS e2e-runner
# python3 covers package/plugin install paths that execute helper scripts while
# staying below a full build-essential toolchain.
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates git \
&& apt-get install -y --no-install-recommends ca-certificates git python3 \
&& rm -rf /var/lib/apt/lists/*
RUN corepack enable
@@ -40,10 +42,14 @@ FROM bare AS functional
# The app under test enters through the named BuildKit context, not by copying
# checkout sources into the image.
COPY --from=openclaw_package --chown=appuser:appuser openclaw-current.tgz /tmp/openclaw-current.tgz
# Preserve package self-reference imports such as openclaw/plugin-sdk/* after
# copying the installed package out of npm's global node_modules tree.
RUN npm install -g --prefix /tmp/openclaw-prefix /tmp/openclaw-current.tgz --no-fund --no-audit \
&& cp -a /tmp/openclaw-prefix/lib/node_modules/openclaw/. /app/ \
&& mkdir -p "$HOME/.local/bin" \
&& ln -sf /app/openclaw.mjs "$HOME/.local/bin/openclaw" \
&& mkdir -p /app/node_modules \
&& ln -sf /app /app/node_modules/openclaw \
&& rm -rf /tmp/openclaw-prefix /tmp/openclaw-current.tgz
CMD ["bash"]

View File

@@ -1,61 +0,0 @@
#!/usr/bin/env bash
# Runs QA diagnostics smoke checks inside the shared package-installed Docker
# E2E image. The OpenClaw app under test comes from the prepared npm tarball;
# only QA harness files are mounted read-only.
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
source "$ROOT_DIR/scripts/lib/docker-e2e-image.sh"
IMAGE_NAME="$(docker_e2e_resolve_image "openclaw-docker-observability-e2e:local" OPENCLAW_DOCKER_OBSERVABILITY_E2E_IMAGE OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE)"
SKIP_BUILD="${OPENCLAW_DOCKER_OBSERVABILITY_E2E_SKIP_BUILD:-0}"
LOOPS="${OPENCLAW_DOCKER_OBSERVABILITY_LOOPS:-1}"
OUTPUT_DIR="${OPENCLAW_DOCKER_OBSERVABILITY_OUTPUT_DIR:-$ROOT_DIR/.artifacts/docker-observability/$(date +%Y%m%d-%H%M%S)}"
if ! [[ "$LOOPS" =~ ^[1-9][0-9]*$ ]]; then
echo "OPENCLAW_DOCKER_OBSERVABILITY_LOOPS must be a positive integer, got: $LOOPS" >&2
exit 1
fi
mkdir -p "$OUTPUT_DIR"
docker_e2e_build_or_reuse "$IMAGE_NAME" docker-observability "$ROOT_DIR/scripts/e2e/Dockerfile" "$ROOT_DIR" "" "$SKIP_BUILD"
docker_e2e_harness_mount_args
echo "Running Docker observability smoke with $LOOPS loop(s)..."
run_logged docker-observability docker run --rm \
-e "OPENCLAW_DOCKER_OBSERVABILITY_LOOPS=$LOOPS" \
"${DOCKER_E2E_HARNESS_ARGS[@]}" \
-v "$ROOT_DIR/scripts/qa-otel-smoke.ts:/app/scripts/qa-otel-smoke.ts:ro" \
-v "$ROOT_DIR/qa:/app/qa:ro" \
-v "$OUTPUT_DIR:/app/.artifacts/docker-observability-current" \
"$IMAGE_NAME" \
bash -lc '
set -euo pipefail
loops="${OPENCLAW_DOCKER_OBSERVABILITY_LOOPS:-1}"
artifact_root=".artifacts/docker-observability-current"
mkdir -p "$artifact_root"
for i in $(seq 1 "$loops"); do
iteration_dir="$artifact_root/loop-$i"
mkdir -p "$iteration_dir"
echo "== docker observability loop $i/$loops: otel =="
# The functional image has a global tsx runner for mounted harness files; the
# published package intentionally does not ship tsx as an app dependency.
tsx scripts/qa-otel-smoke.ts \
--provider-mode mock-openai \
--output-dir "$iteration_dir/otel"
echo "== docker observability loop $i/$loops: prometheus =="
node openclaw.mjs qa suite \
--provider-mode mock-openai \
--scenario docker-prometheus-smoke \
--concurrency 1 \
--fast \
--output-dir "$iteration_dir/prometheus"
done
'
echo "Docker observability smoke passed. Artifacts: $OUTPUT_DIR"

View File

@@ -184,13 +184,6 @@ export const mainLanes = [
{ resources: ["service"], weight: 3 },
),
serviceLane("gateway-network", "OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:gateway-network"),
serviceLane(
"observability",
"OPENCLAW_SKIP_DOCKER_BUILD=1 bash scripts/e2e/docker-observability-smoke.sh",
{
weight: 3,
},
),
serviceLane(
"agents-delete-shared-workspace",
"OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:agents-delete-shared-workspace",
@@ -345,13 +338,6 @@ const releasePathChunks = {
"pi-bundle-mcp-tools",
"OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:pi-bundle-mcp-tools",
),
serviceLane(
"observability",
"OPENCLAW_SKIP_DOCKER_BUILD=1 bash scripts/e2e/docker-observability-smoke.sh",
{
weight: 3,
},
),
serviceLane("mcp-channels", "OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:mcp-channels", {
resources: ["npm"],
weight: 3,

View File

@@ -287,14 +287,10 @@ function startLocalOtlpTraceReceiver() {
}
function openClawEntryArgs(): string[] {
if (
existsSync(path.join(process.cwd(), "openclaw.mjs")) &&
(existsSync(path.join(process.cwd(), "dist", "entry.js")) ||
existsSync(path.join(process.cwd(), "dist", "entry.mjs")))
) {
return ["openclaw.mjs"];
if (existsSync(path.join(process.cwd(), "scripts", "run-node.mjs"))) {
return ["scripts/run-node.mjs"];
}
return ["scripts/run-node.mjs"];
return ["openclaw.mjs"];
}
function spawnOpenClaw(args: string[], env: NodeJS.ProcessEnv): ChildProcess {

View File

@@ -234,7 +234,32 @@ function buildCoreDistEntries(): Record<string, string> {
};
}
function buildDockerE2eHarnessEntries(): Record<string, string> {
return {
// Mounted Docker harnesses run against the npm tarball image, so any
// internal module they assert must have a stable package dist entry.
"agents/pi-bundle-mcp-materialize": "src/agents/pi-bundle-mcp-materialize.ts",
"agents/pi-bundle-mcp-runtime": "src/agents/pi-bundle-mcp-runtime.ts",
"agents/pi-embedded-runner/effective-tool-policy":
"src/agents/pi-embedded-runner/effective-tool-policy.ts",
"agents/pi-embedded-runner/run/runtime-context-prompt":
"src/agents/pi-embedded-runner/run/runtime-context-prompt.ts",
"auto-reply/reply/commands-crestodian": "src/auto-reply/reply/commands-crestodian.ts",
"cli/run-main": "src/cli/run-main.ts",
"config/config": "src/config/config.ts",
"crestodian/crestodian": "src/crestodian/crestodian.ts",
"crestodian/rescue-message": "src/crestodian/rescue-message.ts",
"gateway/protocol/index": "src/gateway/protocol/index.ts",
"infra/errors": "src/infra/errors.ts",
"infra/ws": "src/infra/ws.ts",
"plugin-sdk/provider-onboard": "src/plugin-sdk/provider-onboard.ts",
"plugins/tools": "src/plugins/tools.ts",
"shared/string-coerce": "src/shared/string-coerce.ts",
};
}
const coreDistEntries = buildCoreDistEntries();
const dockerE2eHarnessEntries = buildDockerE2eHarnessEntries();
const stagedBundledPluginBuildEntries = bundledPluginBuildEntries.filter(({ packageJson }) =>
shouldStageBundledPluginRuntimeDependencies(packageJson),
);
@@ -247,6 +272,7 @@ const rootBundledPluginBuildEntries = bundledPluginBuildEntries.filter(
function buildUnifiedDistEntries(): Record<string, string> {
return {
...coreDistEntries,
...dockerE2eHarnessEntries,
// Internal compat artifact for the root-alias.cjs lazy loader.
"plugin-sdk/compat": "src/plugin-sdk/compat.ts",
...Object.fromEntries(