ci(docker): split bundled release lanes

This commit is contained in:
Peter Steinberger
2026-04-27 07:16:35 +01:00
parent db09f68ce5
commit a3fcb8db79
10 changed files with 32 additions and 14 deletions

View File

@@ -286,6 +286,10 @@ image. Release-path normal mode remains max three Docker chunk jobs:
OpenWebUI is folded into `plugins-integrations` for full release-path coverage
and keeps a standalone `openwebui` chunk only for OpenWebUI-only dispatches.
The bundled-channel runtime-dependency coverage inside `plugins-integrations`
uses the split `bundled-channel-*` and `bundled-channel-update-*` lanes rather
than the serial `bundled-channel-deps` lane, so failures produce cheap targeted
reruns for the exact channel/update scenario.
## Package Acceptance
@@ -448,7 +452,7 @@ these rough bands:
`session-runtime-context` ~20s, `gateway-network` ~34s, `qr` ~44s.
- Medium deterministic lanes, ~1-5 minutes:
`npm-onboard-channel-agent` ~96s, `openai-image-auth` ~99s,
bundled channel/update lanes usually ~90-300s, `openwebui` ~225s,
bundled channel/update lanes usually ~90-300s when split, `openwebui` ~225s,
`mcp-channels` ~274s.
- Heavy deterministic lanes, ~6-10 minutes:
`bundled-channel-root-owned` ~429s,

View File

@@ -483,6 +483,7 @@ jobs:
OPENCLAW_DOCKER_E2E_BARE_IMAGE: ${{ needs.prepare_docker_e2e_image.outputs.bare_image }}
OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE: ${{ needs.prepare_docker_e2e_image.outputs.functional_image }}
OPENCLAW_DOCKER_E2E_PACKAGE_ARTIFACT_NAME: ${{ inputs.package_artifact_name || 'docker-e2e-package' }}
OPENCLAW_DOCKER_E2E_SELECTED_SHA: ${{ needs.validate_selected_ref.outputs.selected_sha }}
OPENCLAW_CURRENT_PACKAGE_TGZ: .artifacts/docker-e2e-package/openclaw-current.tgz
OPENCLAW_SKIP_DOCKER_BUILD: "1"
INCLUDE_OPENWEBUI: ${{ inputs.include_openwebui }}
@@ -611,6 +612,7 @@ jobs:
OPENCLAW_DOCKER_E2E_BARE_IMAGE: ${{ needs.prepare_docker_e2e_image.outputs.bare_image }}
OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE: ${{ needs.prepare_docker_e2e_image.outputs.functional_image }}
OPENCLAW_DOCKER_E2E_PACKAGE_ARTIFACT_NAME: ${{ inputs.package_artifact_name || 'docker-e2e-package' }}
OPENCLAW_DOCKER_E2E_SELECTED_SHA: ${{ needs.validate_selected_ref.outputs.selected_sha }}
OPENCLAW_CURRENT_PACKAGE_TGZ: .artifacts/docker-e2e-package/openclaw-current.tgz
OPENCLAW_SKIP_DOCKER_BUILD: "1"
INCLUDE_OPENWEBUI: ${{ inputs.include_openwebui }}
@@ -698,6 +700,7 @@ jobs:
OPENCLAW_DOCKER_E2E_IMAGE: ${{ needs.prepare_docker_e2e_image.outputs.image }}
OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE: ${{ needs.prepare_docker_e2e_image.outputs.functional_image }}
OPENCLAW_DOCKER_E2E_PACKAGE_ARTIFACT_NAME: ${{ inputs.package_artifact_name || 'docker-e2e-package' }}
OPENCLAW_DOCKER_E2E_SELECTED_SHA: ${{ needs.validate_selected_ref.outputs.selected_sha }}
OPENCLAW_CURRENT_PACKAGE_TGZ: .artifacts/docker-e2e-package/openclaw-current.tgz
OPENCLAW_SKIP_DOCKER_BUILD: "1"
steps:

File diff suppressed because one or more lines are too long

View File

@@ -687,7 +687,7 @@ The live-model Docker runners also bind-mount only the needed CLI auth homes (or
Set `OPENCLAW_PLUGINS_E2E_CLAWHUB=0` to skip the live ClawHub block, or override the default package with `OPENCLAW_PLUGINS_E2E_CLAWHUB_SPEC` and `OPENCLAW_PLUGINS_E2E_CLAWHUB_ID`.
- Plugin update unchanged smoke: `pnpm test:docker:plugin-update` (script: `scripts/e2e/plugin-update-unchanged-docker.sh`)
- Config reload metadata smoke: `pnpm test:docker:config-reload` (script: `scripts/e2e/config-reload-source-docker.sh`)
- Bundled plugin runtime deps: `pnpm test:docker:bundled-channel-deps` builds a small Docker runner image by default, builds and packs OpenClaw once on the host, then mounts that tarball into each Linux install scenario. Reuse the image with `OPENCLAW_SKIP_DOCKER_BUILD=1`, skip the host rebuild after a fresh local build with `OPENCLAW_BUNDLED_CHANNEL_HOST_BUILD=0`, or point at an existing tarball with `OPENCLAW_CURRENT_PACKAGE_TGZ=/path/to/openclaw-*.tgz`. The full Docker aggregate pre-packs this tarball once, then shards bundled channel checks into independent lanes, including separate update lanes for Telegram, Discord, Slack, Feishu, memory-lancedb, and ACPX. Use `OPENCLAW_BUNDLED_CHANNELS=telegram,slack` to narrow the channel matrix when running the bundled lane directly, or `OPENCLAW_BUNDLED_CHANNEL_UPDATE_TARGETS=telegram,acpx` to narrow the update scenario. The lane also verifies that `channels.<id>.enabled=false` and `plugins.entries.<id>.enabled=false` suppress doctor/runtime-dependency repair.
- Bundled plugin runtime deps: `pnpm test:docker:bundled-channel-deps` builds a small Docker runner image by default, builds and packs OpenClaw once on the host, then mounts that tarball into each Linux install scenario. Reuse the image with `OPENCLAW_SKIP_DOCKER_BUILD=1`, skip the host rebuild after a fresh local build with `OPENCLAW_BUNDLED_CHANNEL_HOST_BUILD=0`, or point at an existing tarball with `OPENCLAW_CURRENT_PACKAGE_TGZ=/path/to/openclaw-*.tgz`. The full Docker aggregate and release-path `plugins-integrations` chunk pre-pack this tarball once, then shard bundled channel checks into independent lanes, including separate update lanes for Telegram, Discord, Slack, Feishu, memory-lancedb, and ACPX. Use `OPENCLAW_BUNDLED_CHANNELS=telegram,slack` to narrow the channel matrix when running the bundled lane directly, or `OPENCLAW_BUNDLED_CHANNEL_UPDATE_TARGETS=telegram,acpx` to narrow the update scenario. The lane also verifies that `channels.<id>.enabled=false` and `plugins.entries.<id>.enabled=false` suppress doctor/runtime-dependency repair.
- Narrow bundled plugin runtime deps while iterating by disabling unrelated scenarios, for example:
`OPENCLAW_BUNDLED_CHANNEL_SCENARIOS=0 OPENCLAW_BUNDLED_CHANNEL_UPDATE_SCENARIO=0 OPENCLAW_BUNDLED_CHANNEL_ROOT_OWNED_SCENARIO=0 OPENCLAW_BUNDLED_CHANNEL_SETUP_ENTRY_SCENARIO=0 pnpm test:docker:bundled-channel-deps`.

View File

@@ -321,6 +321,8 @@ Release Docker coverage includes:
- release-path Docker chunks: `core`, `package-update`, and
`plugins-integrations`
- OpenWebUI coverage inside the `plugins-integrations` chunk when requested
- split bundled-channel dependency lanes inside `plugins-integrations` instead
of the serial all-in-one bundled-channel lane
- live/E2E provider suites and Docker live model coverage when release checks
include live suites

View File

@@ -946,15 +946,17 @@ if (installRecords[pluginId]) {
throw new Error(`ClawHub install record still present after uninstall: ${pluginId}`);
}
const configPath = path.join(process.env.HOME, ".openclaw", "openclaw.json");
const config = fs.existsSync(configPath) ? JSON.parse(fs.readFileSync(configPath, "utf8")) : {};
if (config.plugins?.entries?.[pluginId]) {
const configAfterUninstallPath = path.join(process.env.HOME, ".openclaw", "openclaw.json");
const configAfterUninstall = fs.existsSync(configAfterUninstallPath)
? JSON.parse(fs.readFileSync(configAfterUninstallPath, "utf8"))
: {};
if (configAfterUninstall.plugins?.entries?.[pluginId]) {
throw new Error(`ClawHub config entry still present after uninstall: ${pluginId}`);
}
if ((config.plugins?.allow || []).includes(pluginId)) {
if ((configAfterUninstall.plugins?.allow || []).includes(pluginId)) {
throw new Error(`ClawHub allowlist entry still present after uninstall: ${pluginId}`);
}
if ((config.plugins?.deny || []).includes(pluginId)) {
if ((configAfterUninstall.plugins?.deny || []).includes(pluginId)) {
throw new Error(`ClawHub denylist entry still present after uninstall: ${pluginId}`);
}
if (fs.existsSync(installPath)) {

View File

@@ -388,11 +388,7 @@ const releasePathChunks = {
weight: 6,
}),
npmLane("plugin-update", "OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:plugin-update"),
npmLane(
"bundled-channel-deps",
"OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:bundled-channel-deps",
{ resources: ["service"], weight: 3 },
),
...bundledScenarioLanes,
serviceLane(
"cron-mcp-cleanup",
"OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:cron-mcp-cleanup",

View File

@@ -333,6 +333,7 @@ async function writeRunSummary(logDir, summary) {
process.env.GITHUB_SERVER_URL && process.env.GITHUB_REPOSITORY && process.env.GITHUB_RUN_ID
? `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`
: undefined,
selectedSha: process.env.OPENCLAW_DOCKER_E2E_SELECTED_SHA || undefined,
sha: process.env.GITHUB_SHA || undefined,
workflow: process.env.GITHUB_WORKFLOW || undefined,
},
@@ -344,7 +345,13 @@ async function writeRunSummary(logDir, summary) {
}
async function writeFailureIndex(logDir, summary) {
const ref = summary.github?.sha || summary.github?.ref || process.env.GITHUB_SHA || "HEAD";
const ref =
summary.github?.selectedSha ||
process.env.OPENCLAW_DOCKER_E2E_SELECTED_SHA ||
summary.github?.sha ||
summary.github?.ref ||
process.env.GITHUB_SHA ||
"HEAD";
const failures = Array.isArray(summary.failures)
? summary.failures
: (summary.lanes ?? []).filter((lane) => lane.status !== 0);

View File

@@ -42,6 +42,9 @@ describe("scripts/lib/docker-e2e-plan", () => {
expect(plan.credentials).toEqual(["anthropic", "openai"]);
expect(plan.lanes.map((lane) => lane.name)).toContain("install-e2e");
expect(plan.lanes.map((lane) => lane.name)).toContain("mcp-channels");
expect(plan.lanes.map((lane) => lane.name)).toContain("bundled-channel-feishu");
expect(plan.lanes.map((lane) => lane.name)).toContain("bundled-channel-update-acpx");
expect(plan.lanes.map((lane) => lane.name)).not.toContain("bundled-channel-deps");
expect(plan.lanes.map((lane) => lane.name)).not.toContain("openwebui");
});

View File

@@ -63,6 +63,7 @@ describe("package artifact reuse", () => {
expect(workflow).toContain("package_artifact_run_id:");
expect(workflow).toContain("docker_e2e_bare_image:");
expect(workflow).toContain("docker_e2e_functional_image:");
expect(workflow).toContain("OPENCLAW_DOCKER_E2E_SELECTED_SHA:");
expect(workflow).toContain("Download current-run OpenClaw Docker E2E package");
expect(workflow).toContain("Download previous-run OpenClaw Docker E2E package");
expect(workflow).toContain("inputs.package_artifact_name != ''");