fix(ci): require live docker credentials by resource

This commit is contained in:
Vincent Koc
2026-05-23 12:36:06 +02:00
parent e0bafc588c
commit 1e21121021
8 changed files with 167 additions and 70 deletions
+26 -12
View File
@@ -140,19 +140,33 @@ runs:
run: |
set -euo pipefail
credentials=",$CREDENTIALS,"
if [[ "$credentials" == *",openai,"* ]]; then
[[ -n "${OPENAI_API_KEY:-}" ]] || {
echo "OPENAI_API_KEY is required for selected Docker E2E lanes." >&2
exit 1
}
fi
if [[ "$credentials" == *",anthropic,"* && -z "${ANTHROPIC_API_TOKEN:-}" && -z "${ANTHROPIC_API_KEY:-}" ]]; then
echo "ANTHROPIC_API_TOKEN or ANTHROPIC_API_KEY is required for selected Docker E2E lanes." >&2
require_any() {
local label="$1"
shift
local key
for key in "$@"; do
if [[ -n "${!key:-}" ]]; then
return 0
fi
done
echo "Missing credential for ${label}: expected one of $*" >&2
exit 1
}
if [[ "$credentials" == *",openai,"* ]]; then
require_any OpenAI OPENAI_API_KEY
fi
if [[ "$credentials" == *",codex,"* ]]; then
require_any Codex OPENCLAW_CODEX_AUTH_JSON
fi
if [[ "$credentials" == *",anthropic,"* ]]; then
require_any Anthropic ANTHROPIC_API_TOKEN ANTHROPIC_API_KEY OPENCLAW_CLAUDE_CREDENTIALS_JSON OPENCLAW_CLAUDE_JSON
fi
if [[ "$credentials" == *",factory,"* ]]; then
[[ -n "${FACTORY_API_KEY:-}" ]] || {
echo "FACTORY_API_KEY is required for selected Docker E2E lanes." >&2
exit 1
}
require_any Factory FACTORY_API_KEY
fi
if [[ "$credentials" == *",gemini,"* ]]; then
require_any Gemini GEMINI_API_KEY GOOGLE_API_KEY OPENCLAW_GEMINI_SETTINGS_JSON
fi
if [[ "$credentials" == *",opencode,"* ]]; then
require_any OpenCode OPENCODE_API_KEY OPENCODE_ZEN_API_KEY
fi
@@ -844,21 +844,35 @@ jobs:
run: |
set -euo pipefail
credentials=",$CREDENTIALS,"
if [[ "$credentials" == *",openai,"* ]]; then
[[ -n "${OPENAI_API_KEY:-}" ]] || {
echo "OPENAI_API_KEY is required for selected Docker E2E lanes." >&2
exit 1
}
fi
if [[ "$credentials" == *",anthropic,"* && -z "${ANTHROPIC_API_TOKEN:-}" && -z "${ANTHROPIC_API_KEY:-}" ]]; then
echo "ANTHROPIC_API_TOKEN or ANTHROPIC_API_KEY is required for selected Docker E2E lanes." >&2
require_any() {
local label="$1"
shift
local key
for key in "$@"; do
if [[ -n "${!key:-}" ]]; then
return 0
fi
done
echo "Missing credential for ${label}: expected one of $*" >&2
exit 1
}
if [[ "$credentials" == *",openai,"* ]]; then
require_any OpenAI OPENAI_API_KEY
fi
if [[ "$credentials" == *",codex,"* ]]; then
require_any Codex OPENCLAW_CODEX_AUTH_JSON
fi
if [[ "$credentials" == *",anthropic,"* ]]; then
require_any Anthropic ANTHROPIC_API_TOKEN ANTHROPIC_API_KEY OPENCLAW_CLAUDE_CREDENTIALS_JSON OPENCLAW_CLAUDE_JSON
fi
if [[ "$credentials" == *",factory,"* ]]; then
[[ -n "${FACTORY_API_KEY:-}" ]] || {
echo "FACTORY_API_KEY is required for selected Docker E2E lanes." >&2
exit 1
}
require_any Factory FACTORY_API_KEY
fi
if [[ "$credentials" == *",gemini,"* ]]; then
require_any Gemini GEMINI_API_KEY GOOGLE_API_KEY OPENCLAW_GEMINI_SETTINGS_JSON
fi
if [[ "$credentials" == *",opencode,"* ]]; then
require_any OpenCode OPENCODE_API_KEY OPENCODE_ZEN_API_KEY
fi
- name: Run Docker E2E chunk
@@ -1088,21 +1102,35 @@ jobs:
run: |
set -euo pipefail
credentials=",$CREDENTIALS,"
if [[ "$credentials" == *",openai,"* ]]; then
[[ -n "${OPENAI_API_KEY:-}" ]] || {
echo "OPENAI_API_KEY is required for selected Docker E2E lanes." >&2
exit 1
}
fi
if [[ "$credentials" == *",anthropic,"* && -z "${ANTHROPIC_API_TOKEN:-}" && -z "${ANTHROPIC_API_KEY:-}" ]]; then
echo "ANTHROPIC_API_TOKEN or ANTHROPIC_API_KEY is required for selected Docker E2E lanes." >&2
require_any() {
local label="$1"
shift
local key
for key in "$@"; do
if [[ -n "${!key:-}" ]]; then
return 0
fi
done
echo "Missing credential for ${label}: expected one of $*" >&2
exit 1
}
if [[ "$credentials" == *",openai,"* ]]; then
require_any OpenAI OPENAI_API_KEY
fi
if [[ "$credentials" == *",codex,"* ]]; then
require_any Codex OPENCLAW_CODEX_AUTH_JSON
fi
if [[ "$credentials" == *",anthropic,"* ]]; then
require_any Anthropic ANTHROPIC_API_TOKEN ANTHROPIC_API_KEY OPENCLAW_CLAUDE_CREDENTIALS_JSON OPENCLAW_CLAUDE_JSON
fi
if [[ "$credentials" == *",factory,"* ]]; then
[[ -n "${FACTORY_API_KEY:-}" ]] || {
echo "FACTORY_API_KEY is required for selected Docker E2E lanes." >&2
exit 1
}
require_any Factory FACTORY_API_KEY
fi
if [[ "$credentials" == *",gemini,"* ]]; then
require_any Gemini GEMINI_API_KEY GOOGLE_API_KEY OPENCLAW_GEMINI_SETTINGS_JSON
fi
if [[ "$credentials" == *",opencode,"* ]]; then
require_any OpenCode OPENCODE_API_KEY OPENCODE_ZEN_API_KEY
fi
- name: Run targeted Docker E2E lanes
+16 -9
View File
@@ -325,6 +325,7 @@ export function findLaneByName(name) {
}
function laneCredentialRequirements(poolLane) {
const resources = laneResources(poolLane);
const credentials = [];
if (poolLane.name === "install-e2e-openai") {
credentials.push("openai");
@@ -332,17 +333,23 @@ function laneCredentialRequirements(poolLane) {
if (poolLane.name === "install-e2e-anthropic") {
credentials.push("anthropic");
}
if (poolLane.name === "live-acp-bind-droid") {
if (resources.includes("live:openai")) {
credentials.push("openai");
}
if (resources.includes("live:codex")) {
credentials.push("codex");
}
if (resources.includes("live:claude")) {
credentials.push("anthropic");
}
if (resources.includes("live:droid")) {
credentials.push("factory");
}
if (
poolLane.name === "openwebui" ||
poolLane.name === "openai-chat-tools" ||
poolLane.name === "openai-web-search-minimal" ||
poolLane.name === "live-codex-npm-plugin" ||
poolLane.name === "live-plugin-tool"
) {
credentials.push("openai");
if (resources.includes("live:gemini")) {
credentials.push("gemini");
}
if (resources.includes("live:opencode")) {
credentials.push("opencode");
}
return credentials;
}
+4 -4
View File
@@ -485,7 +485,7 @@ export const tailLanes = [
),
liveLane("live-codex-harness", liveDockerScriptCommand("test-live-codex-harness-docker.sh"), {
cacheKey: "codex-harness",
provider: "openai",
provider: "codex-cli",
resources: ["npm"],
timeoutMs: LIVE_ACP_TIMEOUT_MS,
weight: 3,
@@ -523,7 +523,7 @@ export const tailLanes = [
),
{
cacheKey: "codex-harness",
provider: "openai",
provider: "codex-cli",
resources: ["npm"],
timeoutMs: LIVE_ACP_TIMEOUT_MS,
weight: 3,
@@ -547,8 +547,8 @@ export const tailLanes = [
liveDockerScriptCommand("test-live-acp-bind-docker.sh", "OPENCLAW_LIVE_ACP_BIND_AGENT=codex"),
{
cacheKey: "acp-bind-codex",
provider: "openai",
resources: ["npm"],
provider: "codex-cli",
resources: ["live:openai", "npm"],
timeoutMs: LIVE_ACP_TIMEOUT_MS,
weight: 3,
},
+2 -2
View File
@@ -267,8 +267,8 @@ if ! "$NPM_CONFIG_PREFIX/bin/codex" exec \
--skip-git-repo-check \
"Reply exactly: $codex_preflight_token" >"$codex_preflight_log" 2>&1; then
if grep -q "Failed to extract accountId from token" "$codex_preflight_log"; then
echo "SKIP: Codex auth cannot extract accountId from the available token; skipping live Codex harness lane."
exit 0
echo "ERROR: Codex auth cannot extract accountId from the available token; refresh OPENCLAW_CODEX_AUTH_JSON or use OPENCLAW_LIVE_CODEX_HARNESS_AUTH=api-key." >&2
exit 1
fi
cat "$codex_preflight_log" >&2
exit 1
+23
View File
@@ -660,6 +660,7 @@ describe("scripts/lib/docker-e2e-plan", () => {
it("plans a live-only selected lane without package e2e images", () => {
const plan = planFor({ selectedLaneNames: ["live-models"] });
expect(plan.credentials).toEqual(["anthropic", "gemini"]);
expect(plan.lanes.map((lane) => lane.name)).toEqual(["live-models"]);
expect(plan.needs).toEqual({
bareImage: false,
@@ -670,6 +671,28 @@ describe("scripts/lib/docker-e2e-plan", () => {
});
});
it("derives live Docker credentials from lane resources", () => {
const cases = [
{ credentials: ["anthropic", "gemini"], name: "live-models" },
{ credentials: ["anthropic", "gemini"], name: "live-gateway" },
{ credentials: ["anthropic"], name: "live-cli-backend-claude" },
{ credentials: ["gemini"], name: "live-cli-backend-gemini" },
{ credentials: ["codex"], name: "live-codex-harness" },
{ credentials: ["openai"], name: "live-codex-media-path" },
{ credentials: ["openai"], name: "live-subagent-announce" },
{ credentials: ["codex"], name: "live-codex-bind" },
{ credentials: ["anthropic"], name: "live-acp-bind-claude" },
{ credentials: ["codex", "openai"], name: "live-acp-bind-codex" },
{ credentials: ["factory"], name: "live-acp-bind-droid" },
{ credentials: ["gemini"], name: "live-acp-bind-gemini" },
{ credentials: ["opencode"], name: "live-acp-bind-opencode" },
] as const;
for (const { credentials, name } of cases) {
expect(planFor({ selectedLaneNames: [name] }).credentials, name).toEqual(credentials);
}
});
it("plans the Codex npm plugin live lane as package-backed OpenAI proof", () => {
const plan = planFor({ selectedLaneNames: ["live-codex-npm-plugin"] });
@@ -633,7 +633,7 @@ describe("package artifact reuse", () => {
);
});
it("plumbs Factory credentials through planned Docker E2E live lanes", () => {
it("plumbs live credentials through planned Docker E2E live lanes", () => {
const reusableWorkflow = readFileSync(LIVE_E2E_WORKFLOW, "utf8");
const releaseChecksWorkflow = readFileSync(RELEASE_CHECKS_WORKFLOW, "utf8");
const scheduledWorkflow = readFileSync(SCHEDULED_LIVE_CHECKS_WORKFLOW, "utf8");
@@ -645,9 +645,20 @@ describe("package artifact reuse", () => {
expect(hydrateScript).toContain(" FACTORY_API_KEY \\");
expect(dockerPlanAction).toContain('if [[ "$credentials" == *",factory,"* ]]; then');
expect(dockerPlanAction).toContain(
"FACTORY_API_KEY is required for selected Docker E2E lanes.",
);
expectTextToIncludeAll(dockerPlanAction, [
'if [[ "$credentials" == *",openai,"* ]]; then',
"require_any OpenAI OPENAI_API_KEY",
'if [[ "$credentials" == *",codex,"* ]]; then',
"require_any Codex OPENCLAW_CODEX_AUTH_JSON",
'if [[ "$credentials" == *",anthropic,"* ]]; then',
"require_any Anthropic ANTHROPIC_API_TOKEN ANTHROPIC_API_KEY OPENCLAW_CLAUDE_CREDENTIALS_JSON OPENCLAW_CLAUDE_JSON",
'if [[ "$credentials" == *",factory,"* ]]; then',
"require_any Factory FACTORY_API_KEY",
'if [[ "$credentials" == *",gemini,"* ]]; then',
"require_any Gemini GEMINI_API_KEY GOOGLE_API_KEY OPENCLAW_GEMINI_SETTINGS_JSON",
'if [[ "$credentials" == *",opencode,"* ]]; then',
"require_any OpenCode OPENCODE_API_KEY OPENCODE_ZEN_API_KEY",
]);
for (const workflow of [
reusableWorkflow,
releaseChecksWorkflow,
@@ -660,7 +671,16 @@ describe("package artifact reuse", () => {
}
expect(reusableWorkflow).toContain("FACTORY_API_KEY:\n required: false");
expect(packageAcceptanceWorkflow).toContain("FACTORY_API_KEY:\n required: false");
expect(reusableWorkflow).toContain('if [[ "$credentials" == *",factory,"* ]]; then');
expectTextToIncludeAll(reusableWorkflow, [
'if [[ "$credentials" == *",openai,"* ]]; then',
"require_any OpenAI OPENAI_API_KEY",
'if [[ "$credentials" == *",codex,"* ]]; then',
"require_any Codex OPENCLAW_CODEX_AUTH_JSON",
'if [[ "$credentials" == *",gemini,"* ]]; then',
"require_any Gemini GEMINI_API_KEY GOOGLE_API_KEY OPENCLAW_GEMINI_SETTINGS_JSON",
'if [[ "$credentials" == *",opencode,"* ]]; then',
"require_any OpenCode OPENCODE_API_KEY OPENCODE_ZEN_API_KEY",
]);
});
it("allows the Telegram lane to run from reusable package acceptance artifacts", () => {
@@ -41,23 +41,17 @@ describe("scripts/test-live-codex-harness-docker.sh", () => {
it("forwards API-key auth through both OpenAI and Codex env names", () => {
const script = fs.readFileSync(SCRIPT_PATH, "utf8");
expect(script).toContain('printf \'OPENAI_API_KEY=%s\\n\' "${OPENAI_API_KEY}"');
expect(script).toContain('printf \'CODEX_API_KEY=%s\\n\' "${CODEX_API_KEY:-$OPENAI_API_KEY}"');
expect(script.indexOf("OPENAI_API_KEY=%s")).toBeLessThan(
script.indexOf("CODEX_API_KEY=%s"),
);
expect(script).toContain("printf 'OPENAI_API_KEY=%s\\n' \"${OPENAI_API_KEY}\"");
expect(script).toContain("printf 'CODEX_API_KEY=%s\\n' \"${CODEX_API_KEY:-$OPENAI_API_KEY}\"");
expect(script.indexOf("OPENAI_API_KEY=%s")).toBeLessThan(script.indexOf("CODEX_API_KEY=%s"));
});
it("keeps API-key runs on the ephemeral Docker home", () => {
const script = fs.readFileSync(SCRIPT_PATH, "utf8");
expect(script).toContain('DOCKER_USER="$(id -u):$(id -g)"');
expect(script).toContain(
'if [[ "$CODEX_HARNESS_AUTH_MODE" == "api-key" ]]; then',
);
expect(script).toContain(
'if [[ -z "${DOCKER_HOME_DIR:-}" ]]; then',
);
expect(script).toContain('if [[ "$CODEX_HARNESS_AUTH_MODE" == "api-key" ]]; then');
expect(script).toContain('if [[ -z "${DOCKER_HOME_DIR:-}" ]]; then');
expect(script).not.toContain('DOCKER_USER="0:0"');
expect(script).toContain(
'DOCKER_HOME_DIR="$(mktemp -d "${RUNNER_TEMP:-/tmp}/openclaw-docker-home.XXXXXX")"',
@@ -74,9 +68,7 @@ describe("scripts/test-live-codex-harness-docker.sh", () => {
expect(script).toContain(
'chmod 0777 "$DOCKER_HOME_DIR" "$CONFIG_DIR" "$WORKSPACE_DIR" || true',
);
expect(script).toContain(
'if [[ "$CODEX_HARNESS_AUTH_MODE" != "api-key" ]]; then',
);
expect(script).toContain('if [[ "$CODEX_HARNESS_AUTH_MODE" != "api-key" ]]; then');
expect(script.indexOf('PROFILE_STATUS="api-key-env"')).toBeLessThan(
script.indexOf("openclaw_live_append_array DOCKER_RUN_ARGS PROFILE_MOUNT"),
);
@@ -97,4 +89,17 @@ describe("scripts/test-live-codex-harness-docker.sh", () => {
'-e OPENCLAW_LIVE_CODEX_BIND_PROVIDER="${OPENCLAW_LIVE_CODEX_BIND_PROVIDER:-}"',
);
});
it("fails instead of skipping when Codex auth cannot identify an account", () => {
const script = fs.readFileSync(SCRIPT_PATH, "utf8");
expect(script).toContain("Failed to extract accountId from token");
expect(script).toContain(
"ERROR: Codex auth cannot extract accountId from the available token; refresh OPENCLAW_CODEX_AUTH_JSON or use OPENCLAW_LIVE_CODEX_HARNESS_AUTH=api-key.",
);
expect(script).not.toContain(
"SKIP: Codex auth cannot extract accountId from the available token; skipping live Codex harness lane.",
);
expect(script).not.toMatch(/Failed to extract accountId from token[\s\S]{0,180}exit 0/u);
});
});