diff --git a/scripts/src/agentrun.ts b/scripts/src/agentrun.ts index abf8d735..783b0d3f 100644 --- a/scripts/src/agentrun.ts +++ b/scripts/src/agentrun.ts @@ -1677,6 +1677,7 @@ interface CleanupRunnersOptions extends ConfirmOptions { node: string | null; lane: string | null; timeoutSeconds: number; + forceActive: boolean; } interface CleanupRunsOptions extends ConfirmOptions { @@ -1935,13 +1936,14 @@ function parseGitMirrorOptions(args: string[]): GitMirrorOptions { } function parseCleanupRunnersOptions(args: string[]): CleanupRunnersOptions { - validateOptions(args, new Set(["--confirm", "--dry-run"]), new Set(["--timeout-seconds", "--node", "--lane"])); + validateOptions(args, new Set(["--confirm", "--dry-run", "--force-active"]), new Set(["--timeout-seconds", "--node", "--lane"])); const base = parseConfirmOptions(args); return { ...base, node: optionValue(args, "--node") ?? null, lane: optionValue(args, "--lane") ?? null, timeoutSeconds: positiveIntegerOption(args, "--timeout-seconds", 180, 600), + forceActive: args.includes("--force-active"), }; } @@ -2925,7 +2927,7 @@ async function cleanupRunners(config: UniDeskConfig, options: CleanupRunnersOpti dryRun: true, mutation: false, next: { - confirm: `bun scripts/cli.ts agentrun control-plane cleanup-runners --node ${spec.nodeId} --lane ${spec.lane} --confirm`, + confirm: `bun scripts/cli.ts agentrun control-plane cleanup-runners --node ${spec.nodeId} --lane ${spec.lane}${options.forceActive ? " --force-active" : ""} --confirm`, }, }; } @@ -2934,7 +2936,7 @@ async function cleanupRunners(config: UniDeskConfig, options: CleanupRunnersOpti dryRun: false, mutation: true, followUp: { - dryRun: `bun scripts/cli.ts agentrun control-plane cleanup-runners --node ${spec.nodeId} --lane ${spec.lane} --dry-run`, + dryRun: `bun scripts/cli.ts agentrun control-plane cleanup-runners --node ${spec.nodeId} --lane ${spec.lane}${options.forceActive ? " --force-active" : ""} --dry-run`, status: `bun scripts/cli.ts agentrun control-plane status --node ${spec.nodeId} --lane ${spec.lane}`, }, }; @@ -3030,6 +3032,7 @@ function cleanupRunnersScript(options: CleanupRunnersOptions, spec: AgentRunLane `age_based_cleanup_enabled=${retention.ageBasedCleanup.enabled ? "true" : "false"}`, `age_based_max_age_hours=${retention.ageBasedCleanup.maxAgeHours === null ? "" : String(retention.ageBasedCleanup.maxAgeHours)}`, `timeout_seconds=${String(options.timeoutSeconds)}`, + `force_active=${options.forceActive ? "true" : "false"}`, `match_labels_json_b64=${shQuote(matchLabelsB64)}`, `job_name_prefixes_json_b64=${shQuote(jobNamePrefixesB64)}`, "match_labels_json=$(printf '%s' \"$match_labels_json_b64\" | base64 -d)", @@ -3048,7 +3051,7 @@ function cleanupRunnersScript(options: CleanupRunnersOptions, spec: AgentRunLane "if [ \"$facts_exit\" -ne 0 ]; then", " printf '%s\\n' '{\"ok\":false,\"items\":[],\"failureKind\":\"manager-facts-unavailable\",\"valuesPrinted\":false}' > \"$tmp_dir/runner-facts.json\"", "fi", - "MATCH_LABELS_JSON=\"$match_labels_json\" JOB_NAME_PREFIXES_JSON=\"$job_name_prefixes_json\" MAX_RUNNERS=\"$max_runners\" CLEANUP_ORDER=\"$cleanup_order\" ACTIVE_HEARTBEAT_MAX_AGE_MS=\"$active_heartbeat_max_age_ms\" AGE_BASED_CLEANUP_ENABLED=\"$age_based_cleanup_enabled\" AGE_BASED_MAX_AGE_HOURS=\"$age_based_max_age_hours\" FACTS_EXIT=\"$facts_exit\" TMP_DIR=\"$tmp_dir\" NAMESPACE=\"$namespace\" node <<'NODE' > \"$tmp_dir/plan.json\"", + "MATCH_LABELS_JSON=\"$match_labels_json\" JOB_NAME_PREFIXES_JSON=\"$job_name_prefixes_json\" MAX_RUNNERS=\"$max_runners\" CLEANUP_ORDER=\"$cleanup_order\" ACTIVE_HEARTBEAT_MAX_AGE_MS=\"$active_heartbeat_max_age_ms\" AGE_BASED_CLEANUP_ENABLED=\"$age_based_cleanup_enabled\" AGE_BASED_MAX_AGE_HOURS=\"$age_based_max_age_hours\" FORCE_ACTIVE=\"$force_active\" FACTS_EXIT=\"$facts_exit\" TMP_DIR=\"$tmp_dir\" NAMESPACE=\"$namespace\" node <<'NODE' > \"$tmp_dir/plan.json\"", cleanupRunnersPlanNodeScript(), "NODE", "if [ " + shQuote(options.confirm && !options.dryRun ? "true" : "false") + " != true ]; then", @@ -4273,6 +4276,7 @@ const cleanupOrder = process.env.CLEANUP_ORDER || ""; const activeHeartbeatMaxAgeMs = Number(process.env.ACTIVE_HEARTBEAT_MAX_AGE_MS || 0); const ageBasedCleanupEnabled = process.env.AGE_BASED_CLEANUP_ENABLED === "true"; const ageBasedMaxAgeHours = process.env.AGE_BASED_MAX_AGE_HOURS ? Number(process.env.AGE_BASED_MAX_AGE_HOURS) : null; +const forceActive = process.env.FORCE_ACTIVE === "true"; const matchLabels = JSON.parse(process.env.MATCH_LABELS_JSON || "{}"); const jobNamePrefixes = JSON.parse(process.env.JOB_NAME_PREFIXES_JSON || "[]"); const now = Date.now(); @@ -4457,8 +4461,15 @@ if (ageBasedCleanupEnabled && ageBasedMaxAgeHours !== null) { } } } +if (forceActive) { + for (const item of runnerJobs) { + selectedByName.set(item.name, item); + selectionReasons.set(item.name, item.protectedActive ? "force-active-runner-cleanup" : "force-runner-cleanup"); + } +} -const selected = inactiveCandidates +const selectionPool = forceActive ? runnerJobs : inactiveCandidates; +const selected = selectionPool .filter((item) => selectedByName.has(item.name)) .map((item) => ({ ...item, selectionReason: selectionReasons.get(item.name) || "selected" })); const matchedPodNames = new Set(runnerJobs.map((item) => item.name)); @@ -4476,6 +4487,7 @@ console.log(JSON.stringify({ maxRunners, cleanupOrder, activeHeartbeatMaxAgeMs, + forceActive, selectors: { matchLabels, jobNamePrefixes }, ageBasedCleanup: { enabled: ageBasedCleanupEnabled, maxAgeHours: ageBasedMaxAgeHours }, },