fix: make cicd in-cluster mode explicit

This commit is contained in:
Codex
2026-07-03 13:01:15 +00:00
parent 4b1b173b3f
commit 7c43716b3c
6 changed files with 30 additions and 18 deletions
+1 -1
View File
@@ -18,7 +18,7 @@ export function renderControllerReconcileJob(registry: BranchFollowerRegistry, o
...(options.followerId === null ? ["--all"] : ["--follower", options.followerId]),
mode.dryRun ? "--dry-run" : "--confirm",
"--wait",
"--controller",
"--in-cluster",
"--config",
"config/cicd-branch-followers.yaml",
"--timeout-seconds",
+1 -1
View File
@@ -23,7 +23,7 @@ export interface ParsedOptions {
confirm: boolean;
dryRun: boolean;
wait: boolean;
controller: boolean;
inCluster: boolean;
live: boolean;
noLive: boolean;
full: boolean;
+22 -15
View File
@@ -117,8 +117,11 @@ function parseOptions(args: string[]): ParsedOptions {
options.dryRun = true;
} else if (arg === "--wait") {
options.wait = true;
} else if (arg === "--in-cluster") {
options.inCluster = true;
} else if (arg === "--controller") {
options.controller = true;
if (isInClusterRuntime()) options.inCluster = true;
else if (options.action === "status") options.live = true;
} else if (arg === "--live") {
options.live = true;
} else if (arg === "--no-live") {
@@ -162,6 +165,10 @@ function parseOptions(args: string[]): ParsedOptions {
return options;
}
function isInClusterRuntime(): boolean {
return Boolean(process.env.KUBERNETES_SERVICE_HOST && process.env.KUBERNETES_SERVICE_PORT);
}
function defaultOptions(action: BranchFollowerAction, _args: string[]): ParsedOptions {
return {
action,
@@ -171,7 +178,7 @@ function defaultOptions(action: BranchFollowerAction, _args: string[]): ParsedOp
confirm: false,
dryRun: false,
wait: false,
controller: false,
inCluster: false,
live: false,
noLive: false,
full: false,
@@ -525,9 +532,9 @@ async function applyController(registry: BranchFollowerRegistry, options: Parsed
async function buildStatus(registry: BranchFollowerRegistry, options: ParsedOptions): Promise<Record<string, unknown>> {
let k8s = readK8sState(registry, options);
const wantsLive = options.live || (!options.noLive && Object.keys(k8s.stateByFollower).length === 0);
const refresh = wantsLive && !options.controller ? runControllerReconcileJob(registry, options, { dryRun: true, wait: true, recordState: true }) : null;
const refresh = wantsLive && !options.inCluster ? runControllerReconcileJob(registry, options, { dryRun: true, wait: true, recordState: true }) : null;
if (refresh !== null) k8s = readK8sState(registry, options);
const shouldLive = wantsLive && options.controller;
const shouldLive = wantsLive && options.inCluster;
const selected = selectFollowers(registry, options, { includeDisabled: true });
const followers = [];
for (const follower of selected) {
@@ -553,7 +560,7 @@ async function buildStatus(registry: BranchFollowerRegistry, options: ParsedOpti
}
async function runOnce(registry: BranchFollowerRegistry, options: ParsedOptions): Promise<Record<string, unknown>> {
if (!options.controller) {
if (!options.inCluster) {
const refresh = runControllerReconcileJob(registry, options, { dryRun: options.dryRun, wait: true, recordState: true });
const k8s = readK8sState(registry, options);
const selected = selectFollowers(registry, options, { includeDisabled: false });
@@ -599,7 +606,7 @@ async function runOnce(registry: BranchFollowerRegistry, options: ParsedOptions)
dryRun: options.dryRun,
confirm: options.confirm,
wait: options.wait,
controller: options.controller,
controller: options.inCluster,
registry: registrySummary(registry),
followers: results,
warnings: stateWriteWarnings,
@@ -663,7 +670,7 @@ async function runFollowerDrillDown(registry: BranchFollowerRegistry, options: P
}
const follower = registry.followers.find((item) => item.id === options.followerId);
if (follower === undefined) throw new Error(`unknown follower ${options.followerId}`);
if (!options.controller) {
if (!options.inCluster) {
const refresh = runControllerReconcileJob(registry, options, { dryRun: true, wait: true, recordState: true });
const k8s = readK8sState(registry, options);
const stored = k8s.stateByFollower[follower.id] ?? {};
@@ -781,7 +788,7 @@ async function decideAndMaybeTrigger(
if (options.confirm && (phase === "PendingTrigger" || phase === "Superseded" || (phase === "Observed" && observedSha !== null))) {
const trigger = await executeTrigger(registry, follower, observedSha, options);
triggerCommand = trigger.command;
phase = trigger.ok ? (options.wait || options.controller ? "ClosingOut" : "Triggering") : "Failed";
phase = trigger.ok ? (options.wait || options.inCluster ? "ClosingOut" : "Triggering") : "Failed";
decision = trigger.ok ? `trigger submitted for ${shortSha(observedSha)}` : `trigger failed for ${shortSha(observedSha)}`;
inFlightJob = trigger.jobId ?? live.inFlightJob;
lastTriggeredSha = observedSha;
@@ -839,7 +846,7 @@ async function decideAndMaybeTrigger(
inFlightJob,
budgetSource: follower.budgets,
controller: {
mode: options.controller ? "k8s-controller" : "local-cli",
mode: options.inCluster ? "k8s-controller" : "local-cli",
stateConfigMap: registry.controller.stateConfigMapName,
leaseName: registry.controller.leaseName,
},
@@ -861,16 +868,16 @@ async function decideAndMaybeTrigger(
async function executeTrigger(registry: BranchFollowerRegistry, follower: FollowerSpec, observedSha: string | null, options: ParsedOptions): Promise<TriggerResult> {
const spec = follower.commands.trigger;
const timeoutSeconds = options.timeoutSeconds ?? spec.timeoutSeconds;
if (follower.adapter === "hwlab-node-runtime" && options.controller) {
if (follower.adapter === "hwlab-node-runtime" && options.inCluster) {
return await executeNativeHwlabNodeTrigger(registry, follower, observedSha, options, timeoutSeconds);
}
if (follower.adapter === "agentrun-yaml-lane" && options.controller) {
if (follower.adapter === "agentrun-yaml-lane" && options.inCluster) {
return await executeNativeAgentRunTrigger(registry, follower, observedSha, options, timeoutSeconds);
}
if (follower.adapter === "web-probe-sentinel-cicd" && options.controller) {
if (follower.adapter === "web-probe-sentinel-cicd" && options.inCluster) {
return await executeNativeSentinelTrigger(registry, follower, observedSha, options, timeoutSeconds);
}
if (!options.wait && !options.controller) {
if (!options.wait && !options.inCluster) {
const job = startJob(`cicd_branch_follower_${safeJobSegment(follower.id)}`, spec.argv, `Trigger ${follower.id} for observed sha ${observedSha ?? "unknown"}`);
return {
ok: true,
@@ -1957,7 +1964,7 @@ function kubePodList(registry: BranchFollowerRegistry, options: ParsedOptions, s
}
function runKubeScript(registry: BranchFollowerRegistry, options: ParsedOptions, script: string, input: string, timeoutMs: number): CommandResult {
if (options.controller) {
if (options.inCluster) {
return runCommand(["sh", "-lc", script], repoRoot, { input, timeoutMs });
}
return runCommand([transPath(), registry.controller.kubeRoute, "sh"], repoRoot, { input: `${script}\n`, timeoutMs });
@@ -2349,7 +2356,7 @@ function roundSeconds(value: number): number {
function writeFollowerState(registry: BranchFollowerRegistry, state: FollowerState, options: ParsedOptions): CommandResult {
const json = JSON.stringify(compactFollowerStateForConfigMap(state));
const dataPatch = JSON.stringify({ data: { [state.id]: json, _updatedAt: new Date().toISOString(), _specRef: SPEC_REF } });
if (options.controller) {
if (options.inCluster) {
const patchBase64 = Buffer.from(dataPatch, "utf8").toString("base64");
const createBase64 = Buffer.from(JSON.stringify({
metadata: {