mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
fix(ci): require live docker credentials by resource
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user