@outerlayer/cli 0.5.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/README.md +9 -0
- package/dist/{agent-setup-IFH3FB5X.js → agent-setup-USZ2SAXC.js} +2 -2
- package/dist/build-info.json +1 -1
- package/dist/{check-4Q33B5ZE.js → check-CCVPBN7A.js} +24 -14
- package/dist/{chunk-WOBNRVQT.js → chunk-3DEVYTNQ.js} +1 -1
- package/dist/{chunk-F4NU4H3V.js → chunk-43J3M6KB.js} +95 -11
- package/dist/{chunk-W7UTFZDH.js → chunk-4NLKY4WR.js} +2 -2
- package/dist/{lima-XN65D7GN.js → chunk-77MM5RYD.js} +0 -1
- package/dist/{chunk-D2KWEAN6.js → chunk-7DBI6SNT.js} +1 -1
- package/dist/{chunk-ZXBITXTS.js → chunk-AIW6GLET.js} +1 -1
- package/dist/{chunk-EX6M5JPR.js → chunk-ANK2B36Y.js} +131 -15
- package/dist/{chunk-RMAZ2NWA.js → chunk-BNMCXPG7.js} +14 -2
- package/dist/{chunk-UFFXNXLQ.js → chunk-FA2BOX4O.js} +31 -29
- package/dist/chunk-FBTE2A2X.js +38 -0
- package/dist/{chunk-DVDEBNQJ.js → chunk-GQD22KKN.js} +17 -6
- package/dist/{chunk-BCJNHMZT.js → chunk-HHJDN6AU.js} +18 -3
- package/dist/{chunk-JLDZTG3A.js → chunk-IASWYRVG.js} +1 -1
- package/dist/{install-cmd-7WXOIFDO.js → chunk-JWOQNNN2.js} +0 -1
- package/dist/{chunk-H4CK2YK4.js → chunk-MIGEDH5P.js} +16 -8
- package/dist/{chunk-CHLVJNTE.js → chunk-MXB5VB4Y.js} +17 -5
- package/dist/chunk-NQXFAGHU.js +211 -0
- package/dist/chunk-OUUINEYB.js +27 -0
- package/dist/{chunk-OR6AEEDO.js → chunk-PMAMOSTP.js} +3 -3
- package/dist/{chunk-RTXKYLCA.js → chunk-RBOMWNOG.js} +1 -1
- package/dist/{chunk-T52PKE2Y.js → chunk-RZSF5B46.js} +149 -7
- package/dist/chunk-T6OIJQRT.js +31 -0
- package/dist/{chunk-EGABNHZG.js → chunk-UMFWVGJO.js} +148 -18
- package/dist/{chunk-UWH5DSUP.js → chunk-VMJ3MMBX.js} +203 -80
- package/dist/chunk-ZILGTTY6.js +75 -0
- package/dist/{cli-LHSUYYMI.js → cli-7WCNPBYC.js} +134 -61
- package/dist/command-ARFK3OYF.js +162 -0
- package/dist/{connect-cmd-OJBFHZWP.js → connect-cmd-PCESZLTR.js} +4 -4
- package/dist/{context-adopt-UMA4O5NY.js → context-adopt-G5JAELE7.js} +2 -2
- package/dist/{context-materialize-G2FUFC72.js → context-materialize-3HLPZAF6.js} +2 -2
- package/dist/{dist-WDV7U7UJ.js → dist-RDLP4GGJ.js} +1 -1
- package/dist/{doctor-MOA3RIEP.js → doctor-VOMYOHQU.js} +32 -12
- package/dist/{emit-artifact-cmd-FDWYKURA.js → emit-artifact-cmd-TBF46VXZ.js} +6 -7
- package/dist/{emit-criteria-cmd-OCXHLK34.js → emit-criteria-cmd-3427XKFW.js} +3 -3
- package/dist/{emit-finding-cmd-POW7PKIA.js → emit-finding-cmd-GSI75K36.js} +3 -3
- package/dist/{emit-result-cmd-ID35SH53.js → emit-result-cmd-3PRQKB55.js} +6 -7
- package/dist/{hook-fast-EYENPK6F.js → hook-fast-NJBBZQ5Q.js} +1 -1
- package/dist/index.js +3 -2
- package/dist/{init-2IPOIXJV.js → init-OHYFIKZD.js} +26 -55
- package/dist/{init-FYDJAKTM.js → init-WVR4BZQ4.js} +8 -8
- package/dist/{init-cmd-KLIDRVXQ.js → init-cmd-HMFNIFPI.js} +7 -7
- package/dist/inspect-XAUSB7L3.js +63 -0
- package/dist/install-DSUZE2TJ.js +13 -0
- package/dist/install-cmd-453C7IZT.js +8 -0
- package/dist/lima-7NPCNGWT.js +6 -0
- package/dist/{loop-NEBUGD2Q.js → loop-UAJB34KA.js} +24 -11
- package/dist/{machine-ECU3QCZD.js → machine-2JOWGRSZ.js} +7 -7
- package/dist/session-sender-ED26V45B.js +28 -0
- package/dist/{status-CLATHCTQ.js → status-6WNXX2O7.js} +22 -8
- package/dist/{sync-cmd-SWQ5KLPL.js → sync-cmd-YONVGUYS.js} +6 -7
- package/dist/{watch-3YW6Z3DQ.js → watch-HHKAF26L.js} +6 -7
- package/dist/{work-claim-cmd-3OPWA77E.js → work-claim-cmd-LXBZTMJA.js} +5 -5
- package/dist/{work-cmd-NUR4CAG6.js → work-cmd-OVNF44FY.js} +3 -3
- package/dist/{work-comment-cmd-6TTTYGHM.js → work-comment-cmd-6DGQNTAH.js} +3 -3
- package/dist/{work-launch-YI4CJDAP.js → work-launch-R5HTAP6X.js} +1 -1
- package/dist/{work-open-pr-cmd-KZ7QQFLD.js → work-open-pr-cmd-FCZHFHZS.js} +4 -4
- package/dist/{work-pr-cmd-QNUBE6IQ.js → work-pr-cmd-QJJVHZDC.js} +2 -2
- package/package.json +2 -1
- package/dist/chunk-ZOC7JSYC.js +0 -83
- package/dist/{chunk-XDDW4FRS.js → chunk-OIRNH6UO.js} +1 -1
|
@@ -1,27 +1,29 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
|
+
import { writeJobFile, readJobFile } from './chunk-T6OIJQRT.js';
|
|
4
|
+
import { senderPidfile } from './chunk-ZILGTTY6.js';
|
|
3
5
|
import { DOCKERD_LOG } from './chunk-CBPPJSAR.js';
|
|
4
|
-
import { installScrubContext } from './chunk-
|
|
5
|
-
import { AssetError, describeCallFailure, obtainImage, ensureRootfs, gitRemoteFor, startServicesFor, createBuildDisk, machineConfig, refusalCode, resolveAgentUser, AgentUserError, runBuildSteps, removeStagedContext, stageGovernedContext, shellQuote,
|
|
6
|
+
import { installScrubContext } from './chunk-UMFWVGJO.js';
|
|
7
|
+
import { AssetError, describeCallFailure, obtainImage, ensureRootfs, gitRemoteFor, startServicesFor, createBuildDisk, machineConfig, refusalCode, resolveAgentUser, AgentUserError, runBuildSteps, removeStagedContext, stageGovernedContext, shellQuote, createRunnerLog, createContainerRuntime, stopContainer, syncInContainer, formatDuration, cleanupContainer, retrySend, formatClock, containerSenderCommand, GH_CONFIG_YML, ghHostsYml, ADDED_USER, isLiteralAddress, signalInContainer, deleteBuildDisk, senderSignalScript } from './chunk-RZSF5B46.js';
|
|
6
8
|
import { CLAUDE_CODE_VERSION } from './chunk-Z62HNKEJ.js';
|
|
7
9
|
import { isSameProcess, startTimeOf } from './chunk-N5FOE5PS.js';
|
|
8
10
|
import { childEnvironment } from './chunk-774HEQL6.js';
|
|
9
11
|
import { waitForAgent, vmExecArgs } from './chunk-W4SQCOZU.js';
|
|
10
12
|
import { socketPathProblem, vsockLinks, linkVsockSockets } from './chunk-B2G7JHHB.js';
|
|
11
|
-
import { WorkCommandError, postJson, isSignatureRefusal, getJson } from './chunk-
|
|
12
|
-
import { planArtifactUpload } from './chunk-
|
|
13
|
-
import { scrubText } from './chunk-
|
|
13
|
+
import { WorkCommandError, postJson, isSignatureRefusal, getJson } from './chunk-BNMCXPG7.js';
|
|
14
|
+
import { planArtifactUpload } from './chunk-3DEVYTNQ.js';
|
|
15
|
+
import { scrubText } from './chunk-ANK2B36Y.js';
|
|
14
16
|
import { isBuiltinContainer, isBuiltinProcess, isBuiltinVm } from './chunk-3DD6AOWC.js';
|
|
15
17
|
import { parseSize } from './chunk-F6GFZXZ3.js';
|
|
16
18
|
import { claudeCredential, buildEnvironment, JOB_MOUNT, CHECKOUT_DIR, parseIsolation, proxyEnvironment, containerLocalAddress, destinationEnvironment, CLAUDE_CREDENTIAL_VARIABLES, builtInEnvironment, withHooksPath, startBuildServices, BUILT_IN_NAMES, AGENT_CREDENTIAL_MISSING, sharedUserEnvironment, headerVariables, authorEnvironment, withTunnelRecord, commitIdentityRecord, RELAY_PORT } from './chunk-AUFG23AA.js';
|
|
17
19
|
import { readCloudConfig } from './chunk-EABW6AJQ.js';
|
|
18
20
|
import { resolveVersion } from './chunk-RA3O54FC.js';
|
|
19
21
|
import { signerFor } from './chunk-M5POMKKW.js';
|
|
20
|
-
import { runnerDir, buildDir, vsockPath,
|
|
22
|
+
import { runnerDir, buildDir, vsockPath, RUNNER_FILE_MODE } from './chunk-ZM2IMMYN.js';
|
|
21
23
|
import { homeFromEnv } from './chunk-VNVZDWO3.js';
|
|
22
24
|
import { spawn, spawnSync } from 'child_process';
|
|
23
25
|
import { randomBytes } from 'crypto';
|
|
24
|
-
import { mkdirSync, chmodSync, writeFileSync, appendFileSync, statSync, accessSync, constants, existsSync, readFileSync,
|
|
26
|
+
import { mkdirSync, chmodSync, writeFileSync, appendFileSync, statSync, accessSync, constants, openSync, existsSync, readFileSync, closeSync, rmSync, readdirSync } from 'fs';
|
|
25
27
|
import { join, isAbsolute, dirname, delimiter } from 'path';
|
|
26
28
|
|
|
27
29
|
createRequire(import.meta.url);
|
|
@@ -192,9 +194,15 @@ async function provisionMicroVm(input) {
|
|
|
192
194
|
return bail(`the build's disk and machine could not be made: ${describeCallFailure(err)}`);
|
|
193
195
|
}
|
|
194
196
|
if (input.aborted()) return bail("", true);
|
|
197
|
+
try {
|
|
198
|
+
state.cgroup = vm.cgroup.create(buildId, limits.memoryBytes);
|
|
199
|
+
} catch (err) {
|
|
200
|
+
return bail(`the build's memory limit could not be set: ${describeCallFailure(err)}`);
|
|
201
|
+
}
|
|
202
|
+
if (state.cgroup !== void 0) input.save(state, environment);
|
|
195
203
|
const consolePath = join(jobDir, "vm-console.log");
|
|
196
204
|
try {
|
|
197
|
-
state.firecracker = await vm.firecracker.spawn({ binary: assets.firecracker, configPath, consolePath });
|
|
205
|
+
state.firecracker = await vm.firecracker.spawn({ binary: assets.firecracker, configPath, consolePath, cgroup: state.cgroup });
|
|
198
206
|
} catch (err) {
|
|
199
207
|
return bail(`Firecracker did not start: ${describeCallFailure(err)}`);
|
|
200
208
|
}
|
|
@@ -315,7 +323,7 @@ function agentVersionOf(toolsJson) {
|
|
|
315
323
|
}
|
|
316
324
|
return CLAUDE_CODE_VERSION;
|
|
317
325
|
}
|
|
318
|
-
function
|
|
326
|
+
function vmExecFor(input, argv) {
|
|
319
327
|
const state = input.job.microvm;
|
|
320
328
|
const env = buildEnvironment({
|
|
321
329
|
config: input.config,
|
|
@@ -334,13 +342,17 @@ function microVmCommandLine(input) {
|
|
|
334
342
|
cwd: CHECKOUT_DIR,
|
|
335
343
|
env: { ...state.imageEnv, ...env.literals },
|
|
336
344
|
pass: Object.keys(env.passed).filter((name) => env.passed[name] !== void 0),
|
|
337
|
-
argv
|
|
338
|
-
};
|
|
339
|
-
return {
|
|
340
|
-
line: [process.execPath, input.cli, "vm-exec", ...vmExecArgs(options)].map(shellQuote).join(" "),
|
|
341
|
-
options,
|
|
342
|
-
env: { ...childEnvironment(input.runtime.hostEnv), ...env.passed }
|
|
345
|
+
argv
|
|
343
346
|
};
|
|
347
|
+
return { options, env: { ...childEnvironment(input.runtime.hostEnv), ...env.passed } };
|
|
348
|
+
}
|
|
349
|
+
function microVmCommandLine(input) {
|
|
350
|
+
const { options, env } = vmExecFor(input, ["/bin/sh", "-c", input.cmdline]);
|
|
351
|
+
return { line: [process.execPath, input.cli, "vm-exec", ...vmExecArgs(options)].map(shellQuote).join(" "), options, env };
|
|
352
|
+
}
|
|
353
|
+
function microVmSenderCommand(input) {
|
|
354
|
+
const { options, env } = vmExecFor(input, ["outerlayer", "session-sender"]);
|
|
355
|
+
return { command: process.execPath, args: [input.cli, "vm-exec", ...vmExecArgs(options)], env };
|
|
344
356
|
}
|
|
345
357
|
function isRunning(runtime, state) {
|
|
346
358
|
return state.firecracker !== void 0 && runtime.vm.firecracker.alive(state.firecracker);
|
|
@@ -391,6 +403,13 @@ async function cleanupMicroVm(runtime, state, jobDir) {
|
|
|
391
403
|
failures.push(`the microVM (pid ${state.firecracker.pid}) was not stopped: ${describeCallFailure(err)}`);
|
|
392
404
|
}
|
|
393
405
|
}
|
|
406
|
+
if (state.cgroup !== void 0) {
|
|
407
|
+
try {
|
|
408
|
+
runtime.vm.cgroup.remove(state.cgroup);
|
|
409
|
+
} catch (err) {
|
|
410
|
+
failures.push(`the build's cgroup ${state.cgroup} was not removed: ${describeCallFailure(err)}`);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
394
413
|
if (state.buildDisk !== void 0) deleteBuildDisk(state.buildDisk);
|
|
395
414
|
const tunnel = runtime.tunnels.get(state.buildId);
|
|
396
415
|
if (tunnel !== void 0) {
|
|
@@ -583,70 +602,6 @@ async function closeHostBuild(runtime, build) {
|
|
|
583
602
|
rmSync(build.buildDir, { recursive: true, force: true });
|
|
584
603
|
if (build.workDir !== void 0) rmSync(build.workDir, { recursive: true, force: true });
|
|
585
604
|
}
|
|
586
|
-
var ENDED = "ended before provision finished";
|
|
587
|
-
async function provisionProcess(input) {
|
|
588
|
-
const { config, runtime, item, jobDir } = input;
|
|
589
|
-
if (claudeCredential(runtime.hostEnv) === void 0) return { ok: false, reason: AGENT_CREDENTIAL_MISSING };
|
|
590
|
-
const repository = item.specRepository;
|
|
591
|
-
if (repository === void 0 || repository === null || repository === "") {
|
|
592
|
-
return { ok: false, reason: "the work item names no repository to clone" };
|
|
593
|
-
}
|
|
594
|
-
const buildId = randomBytes(6).toString("hex");
|
|
595
|
-
const buildDir2 = buildDir(config.configPath, buildId);
|
|
596
|
-
const workDir = join(jobDir, "work");
|
|
597
|
-
const state = { buildId, buildDir: buildDir2, workDir };
|
|
598
|
-
const environment = sharedUserEnvironment(input.cliVersion);
|
|
599
|
-
input.save(state, environment);
|
|
600
|
-
const bail = async (reason) => {
|
|
601
|
-
try {
|
|
602
|
-
await cleanupProcess(runtime, state);
|
|
603
|
-
} catch (err) {
|
|
604
|
-
input.log.error(`#${item.number} cleanup failed: ${describeCallFailure(err)}`);
|
|
605
|
-
}
|
|
606
|
-
return { ok: false, reason };
|
|
607
|
-
};
|
|
608
|
-
if (input.aborted()) return bail(ENDED);
|
|
609
|
-
mkdirSync(buildDir2, { recursive: true });
|
|
610
|
-
chmodSync(buildDir2, 493);
|
|
611
|
-
try {
|
|
612
|
-
await startHostServices({ runtime, config, build: state, jobDir, itemKey: input.itemKey, process: true });
|
|
613
|
-
} catch (err) {
|
|
614
|
-
return bail(`the tunnel could not start: ${describeCallFailure(err)}`);
|
|
615
|
-
}
|
|
616
|
-
input.save(state, environment);
|
|
617
|
-
if (input.aborted()) return bail(ENDED);
|
|
618
|
-
const checkout = join(workDir, "checkout");
|
|
619
|
-
const remote = runtime.remoteFor?.(repository) ?? `https://github.com/${repository}.git`;
|
|
620
|
-
const cloned = await runtime.clone({ remote, dest: checkout });
|
|
621
|
-
if (!cloned.ok) return bail(cloned.reason);
|
|
622
|
-
if (input.aborted()) return bail(ENDED);
|
|
623
|
-
return { ok: true, workdir: checkout, state, environment };
|
|
624
|
-
}
|
|
625
|
-
function cleanupProcess(runtime, state) {
|
|
626
|
-
return closeHostBuild(runtime, state);
|
|
627
|
-
}
|
|
628
|
-
function readJobFile(path) {
|
|
629
|
-
if (!existsSync(path)) return null;
|
|
630
|
-
try {
|
|
631
|
-
return JSON.parse(readFileSync(path, "utf8"));
|
|
632
|
-
} catch {
|
|
633
|
-
return null;
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
function readJobFileChecked(path) {
|
|
637
|
-
if (!existsSync(path)) return { kind: "missing" };
|
|
638
|
-
try {
|
|
639
|
-
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
640
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return { kind: "unreadable" };
|
|
641
|
-
return { kind: "ok", job: parsed };
|
|
642
|
-
} catch {
|
|
643
|
-
return { kind: "unreadable" };
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
function writeJobFile(path, job) {
|
|
647
|
-
mkdirSync(dirname(path), { recursive: true, mode: RUNNER_DIR_MODE });
|
|
648
|
-
writeFileSync(path, JSON.stringify(job, null, 2), { mode: RUNNER_FILE_MODE });
|
|
649
|
-
}
|
|
650
605
|
|
|
651
606
|
// src/runner/verdict.ts
|
|
652
607
|
var VERDICT_SETTLE_MS = 5 * 6e4;
|
|
@@ -720,6 +675,48 @@ async function settleVerdict(deps) {
|
|
|
720
675
|
await deps.sleep(everyMs);
|
|
721
676
|
}
|
|
722
677
|
}
|
|
678
|
+
var ENDED = "ended before provision finished";
|
|
679
|
+
async function provisionProcess(input) {
|
|
680
|
+
const { config, runtime, item, jobDir } = input;
|
|
681
|
+
if (claudeCredential(runtime.hostEnv) === void 0) return { ok: false, reason: AGENT_CREDENTIAL_MISSING };
|
|
682
|
+
const repository = item.specRepository;
|
|
683
|
+
if (repository === void 0 || repository === null || repository === "") {
|
|
684
|
+
return { ok: false, reason: "the work item names no repository to clone" };
|
|
685
|
+
}
|
|
686
|
+
const buildId = randomBytes(6).toString("hex");
|
|
687
|
+
const buildDir2 = buildDir(config.configPath, buildId);
|
|
688
|
+
const workDir = join(jobDir, "work");
|
|
689
|
+
const state = { buildId, buildDir: buildDir2, workDir };
|
|
690
|
+
const environment = sharedUserEnvironment(input.cliVersion);
|
|
691
|
+
input.save(state, environment);
|
|
692
|
+
const bail = async (reason) => {
|
|
693
|
+
try {
|
|
694
|
+
await cleanupProcess(runtime, state);
|
|
695
|
+
} catch (err) {
|
|
696
|
+
input.log.error(`#${item.number} cleanup failed: ${describeCallFailure(err)}`);
|
|
697
|
+
}
|
|
698
|
+
return { ok: false, reason };
|
|
699
|
+
};
|
|
700
|
+
if (input.aborted()) return bail(ENDED);
|
|
701
|
+
mkdirSync(buildDir2, { recursive: true });
|
|
702
|
+
chmodSync(buildDir2, 493);
|
|
703
|
+
try {
|
|
704
|
+
await startHostServices({ runtime, config, build: state, jobDir, itemKey: input.itemKey, process: true });
|
|
705
|
+
} catch (err) {
|
|
706
|
+
return bail(`the tunnel could not start: ${describeCallFailure(err)}`);
|
|
707
|
+
}
|
|
708
|
+
input.save(state, environment);
|
|
709
|
+
if (input.aborted()) return bail(ENDED);
|
|
710
|
+
const checkout = join(workDir, "checkout");
|
|
711
|
+
const remote = runtime.remoteFor?.(repository) ?? `https://github.com/${repository}.git`;
|
|
712
|
+
const cloned = await runtime.clone({ remote, dest: checkout });
|
|
713
|
+
if (!cloned.ok) return bail(cloned.reason);
|
|
714
|
+
if (input.aborted()) return bail(ENDED);
|
|
715
|
+
return { ok: true, workdir: checkout, state, environment };
|
|
716
|
+
}
|
|
717
|
+
function cleanupProcess(runtime, state) {
|
|
718
|
+
return closeHostBuild(runtime, state);
|
|
719
|
+
}
|
|
723
720
|
|
|
724
721
|
// src/runner/job.ts
|
|
725
722
|
var LEASE_SECONDS = 900;
|
|
@@ -1167,6 +1164,11 @@ async function finishJob(job, jobDirPath, outcome, config, deps, ending) {
|
|
|
1167
1164
|
const jobFilePath = join(jobDirPath, "job.json");
|
|
1168
1165
|
const logPath = join(jobDirPath, "command.log");
|
|
1169
1166
|
const tag = `#${job.item}`;
|
|
1167
|
+
if (job.sender !== void 0) {
|
|
1168
|
+
await stopSender({ job, deps: { spawnFn, runtime: deps.container, log } });
|
|
1169
|
+
delete job.sender;
|
|
1170
|
+
writeJobFile(jobFilePath, job);
|
|
1171
|
+
}
|
|
1170
1172
|
const build = job.container === void 0 ? void 0 : { state: job.container, runtime: deps.container ?? createContainerRuntime() };
|
|
1171
1173
|
const vmBuild = job.microvm === void 0 ? void 0 : { state: job.microvm, runtime: deps.container ?? createContainerRuntime() };
|
|
1172
1174
|
if (build !== void 0 && job.pid !== void 0 && isGroupAlive(job.pid)) {
|
|
@@ -1189,6 +1191,20 @@ async function finishJob(job, jobDirPath, outcome, config, deps, ending) {
|
|
|
1189
1191
|
job.reason = scrubReason(ending.reason, config);
|
|
1190
1192
|
writeJobFile(jobFilePath, job);
|
|
1191
1193
|
}
|
|
1194
|
+
let outOfMemory;
|
|
1195
|
+
if (vmBuild?.state.cgroup !== void 0 && outcome !== void 0 && outcome !== "ok" && ending?.reason === void 0) {
|
|
1196
|
+
try {
|
|
1197
|
+
if (vmBuild.runtime.vm.cgroup.oomKills(vmBuild.state.cgroup) > 0) {
|
|
1198
|
+
outOfMemory = `the build ran out of memory: the kernel killed its microVM at the limit of runner.build.memory (${config.build?.memory ?? "8g"}) plus 256 MiB for Firecracker. Raise runner.build.memory, or lower runner.concurrency`;
|
|
1199
|
+
}
|
|
1200
|
+
} catch (err) {
|
|
1201
|
+
log.error(`${tag} could not read the build's memory events: ${describeCallFailure(err)} \xB7 continuing`);
|
|
1202
|
+
}
|
|
1203
|
+
if (outOfMemory !== void 0) {
|
|
1204
|
+
job.reason = scrubReason(outOfMemory, config);
|
|
1205
|
+
writeJobFile(jobFilePath, job);
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1192
1208
|
const hookLimits = () => hookLimitsFor(config, now().getTime(), deps.now);
|
|
1193
1209
|
let tunnelRecord;
|
|
1194
1210
|
if (!job.cleanupRan) {
|
|
@@ -1352,6 +1368,8 @@ async function finishJob(job, jobDirPath, outcome, config, deps, ending) {
|
|
|
1352
1368
|
const fromHook = readReasonLine(jobDirPath, config);
|
|
1353
1369
|
if (verdictReason !== void 0) {
|
|
1354
1370
|
job.reason = verdictReason;
|
|
1371
|
+
} else if (outOfMemory !== void 0) {
|
|
1372
|
+
job.reason = scrubReason(outOfMemory, config);
|
|
1355
1373
|
} else if (ending?.reason === void 0) {
|
|
1356
1374
|
job.reason = (outcome === "ok" ? void 0 : held?.reason) ?? (isolated ? fromHook ?? job.reason : fromHook);
|
|
1357
1375
|
}
|
|
@@ -1470,4 +1488,109 @@ async function recoverLostJobs(jobsDirPath, config, deps) {
|
|
|
1470
1488
|
return recovered;
|
|
1471
1489
|
}
|
|
1472
1490
|
|
|
1473
|
-
|
|
1491
|
+
// src/runner/sender.ts
|
|
1492
|
+
var STOP_WAIT_MS = 3e4;
|
|
1493
|
+
var POLL_MS = 200;
|
|
1494
|
+
var BUILD_HOME = "/build/home";
|
|
1495
|
+
async function startSender(input) {
|
|
1496
|
+
const { config, job, jobDir, deps } = input;
|
|
1497
|
+
const tag = `#${job.item}`;
|
|
1498
|
+
if (job.itemKey === void 0) return void 0;
|
|
1499
|
+
const spawnFn = deps.spawnFn ?? spawn;
|
|
1500
|
+
try {
|
|
1501
|
+
const launch = launchFor(config, job, jobDir, deps);
|
|
1502
|
+
const fd = openSync(join(jobDir, "sender.log"), "a", RUNNER_FILE_MODE);
|
|
1503
|
+
let pid;
|
|
1504
|
+
try {
|
|
1505
|
+
const child = spawnFn(launch.command, launch.args, { env: launch.env, cwd: launch.cwd, detached: true, stdio: ["ignore", fd, fd] });
|
|
1506
|
+
child.on("error", (err) => deps.log.error(`${tag} session sender failed: ${describeCallFailure(err)}`));
|
|
1507
|
+
child.on("exit", (code, signal) => deps.log.line(`${tag} session sender exited \xB7 ${signal ?? `code ${code}`} \xB7 see sender.log`));
|
|
1508
|
+
child.unref?.();
|
|
1509
|
+
pid = child.pid;
|
|
1510
|
+
} finally {
|
|
1511
|
+
closeSync(fd);
|
|
1512
|
+
}
|
|
1513
|
+
if (pid === void 0) {
|
|
1514
|
+
deps.log.line(`${tag} session sender did not start: no pid`);
|
|
1515
|
+
return void 0;
|
|
1516
|
+
}
|
|
1517
|
+
deps.log.line(`${tag} session sender started \xB7 pid ${pid}`);
|
|
1518
|
+
return { pid, start: startTimeOf(pid) ?? void 0 };
|
|
1519
|
+
} catch (err) {
|
|
1520
|
+
deps.log.error(`${tag} session sender did not start: ${describeCallFailure(err)} \xB7 continuing`);
|
|
1521
|
+
return void 0;
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
function launchFor(config, job, jobDir, deps) {
|
|
1525
|
+
if (job.microvm !== void 0) {
|
|
1526
|
+
const runtime = deps.runtime ?? createContainerRuntime();
|
|
1527
|
+
const cli = deps.cliPath ?? join(runtime.cliBuild().distDir, "index.js");
|
|
1528
|
+
return { ...microVmSenderCommand({ job, runtime, config, cli }), cwd: jobDir };
|
|
1529
|
+
}
|
|
1530
|
+
if (job.container !== void 0) {
|
|
1531
|
+
return { ...containerSenderCommand({ job, runtime: deps.runtime ?? createContainerRuntime(), config }), cwd: jobDir };
|
|
1532
|
+
}
|
|
1533
|
+
const env = jobEnv(config, job.item, job.itemKey, jobDir, process.env, job.branch, job.commitIdentity?.author);
|
|
1534
|
+
const build = job.build;
|
|
1535
|
+
const hostEnv = build === void 0 ? env : hostCommandEnvironment(env, build, config, job.commitIdentity?.hooksPath);
|
|
1536
|
+
if (build?.exec !== void 0) {
|
|
1537
|
+
return { command: build.exec, args: execArgv(job.workdir, ["outerlayer", "session-sender"]), env: hostEnv, cwd: jobDir };
|
|
1538
|
+
}
|
|
1539
|
+
return { command: process.execPath, args: [deps.cliPath ?? process.argv[1] ?? "outerlayer", "session-sender"], env: hostEnv, cwd: job.workdir ?? jobDir };
|
|
1540
|
+
}
|
|
1541
|
+
function isLive(sender) {
|
|
1542
|
+
if (sender.start !== void 0) return isSameProcess(sender.pid, sender.start);
|
|
1543
|
+
try {
|
|
1544
|
+
process.kill(sender.pid, 0);
|
|
1545
|
+
return true;
|
|
1546
|
+
} catch {
|
|
1547
|
+
return false;
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
function signalHost(sender, signal) {
|
|
1551
|
+
if (!isLive(sender)) return;
|
|
1552
|
+
try {
|
|
1553
|
+
process.kill(-sender.pid, signal);
|
|
1554
|
+
} catch {
|
|
1555
|
+
try {
|
|
1556
|
+
process.kill(sender.pid, signal);
|
|
1557
|
+
} catch {
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
async function signalInGuest(runtime, state, pidfile, signal) {
|
|
1562
|
+
await runtime.vm.exec(state.vsockPath, {
|
|
1563
|
+
argv: ["/bin/sh", "-c", senderSignalScript(pidfile, signal)],
|
|
1564
|
+
user: state.agentUser ?? ADDED_USER,
|
|
1565
|
+
cwd: "/",
|
|
1566
|
+
env: { PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" }
|
|
1567
|
+
});
|
|
1568
|
+
}
|
|
1569
|
+
async function stopSender(input) {
|
|
1570
|
+
const { job, deps } = input;
|
|
1571
|
+
const sender = job.sender;
|
|
1572
|
+
if (sender === void 0) return;
|
|
1573
|
+
const tag = `#${job.item}`;
|
|
1574
|
+
const sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
1575
|
+
const inContainer = job.container === void 0 ? void 0 : { state: job.container, runtime: deps.runtime ?? createContainerRuntime(), pidfile: senderPidfile(BUILD_HOME, Number(job.item)) };
|
|
1576
|
+
const inGuest = job.microvm === void 0 ? void 0 : { state: job.microvm, runtime: deps.runtime ?? createContainerRuntime(), pidfile: senderPidfile(BUILD_HOME, Number(job.item)) };
|
|
1577
|
+
const signalInside = async (signal) => {
|
|
1578
|
+
if (inContainer !== void 0) await signalInContainer(inContainer.runtime, inContainer.state, inContainer.pidfile, signal).catch(() => void 0);
|
|
1579
|
+
if (inGuest !== void 0) await signalInGuest(inGuest.runtime, inGuest.state, inGuest.pidfile, signal).catch(() => void 0);
|
|
1580
|
+
};
|
|
1581
|
+
try {
|
|
1582
|
+
if (inContainer !== void 0 || inGuest !== void 0) await signalInside("TERM");
|
|
1583
|
+
else signalHost(sender, "SIGTERM");
|
|
1584
|
+
const deadline = (deps.waitMs ?? STOP_WAIT_MS) / POLL_MS;
|
|
1585
|
+
for (let waited = 0; isLive(sender) && waited < deadline; waited += 1) await sleep(POLL_MS);
|
|
1586
|
+
if (isLive(sender)) {
|
|
1587
|
+
deps.log.error(`${tag} session sender did not stop in time \xB7 killing it`);
|
|
1588
|
+
await signalInside("KILL");
|
|
1589
|
+
signalHost(sender, "SIGKILL");
|
|
1590
|
+
}
|
|
1591
|
+
} catch (err) {
|
|
1592
|
+
deps.log.error(`${tag} stopping the session sender failed: ${describeCallFailure(err)} \xB7 continuing`);
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
export { ALREADY_RELEASED, LEASE_SECONDS, RENEW_INTERVAL_SECONDS, deliverCommitHooks, describeHookEnd, execArgv, execCommandLine, finishJob, hookLimitsFor, hostCommandEnvironment, hostEnvironment, isGroupAlive, isIdle, isTimedOut, isUnfinished, jobEnv, listAdoptableJobs, listenOnBroker, microVmCommandLine, needsRenew, outcomeFromExitCode, provisionEnvironment, provisionMicroVm, provisionProcess, readCpuUsec, readProvisionOut, recoverLostJobs, releaseLease, renewLease, resolveHostBuild, retryFailedReleases, runCommand, runHook, setStage, startHostServices, startSender, stopOrphanedMicroVms, unfinishedJobs, workdirProblem };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { LiveUploader } from './chunk-UMFWVGJO.js';
|
|
4
|
+
import { CaptureDaemon } from './chunk-ANK2B36Y.js';
|
|
5
|
+
import { parseWorkItemEnv } from './chunk-GQD22KKN.js';
|
|
6
|
+
import { spoolDir } from './chunk-FA2BOX4O.js';
|
|
7
|
+
import { homeFromEnv } from './chunk-VNVZDWO3.js';
|
|
8
|
+
import { mkdirSync, writeFileSync, rmSync } from 'fs';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
|
|
11
|
+
createRequire(import.meta.url);
|
|
12
|
+
var SENDER_STOP_WAIT_MS = 25e3;
|
|
13
|
+
var RESCAN_MS = 3e4;
|
|
14
|
+
function senderPidfile(home, item) {
|
|
15
|
+
return join(spoolDir(home), `session-sender-${item}.pid`);
|
|
16
|
+
}
|
|
17
|
+
async function startSessionSender(opts = {}) {
|
|
18
|
+
const env = opts.env ?? process.env;
|
|
19
|
+
const stderr = opts.stderr ?? ((s) => void process.stderr.write(s));
|
|
20
|
+
const parsed = parseWorkItemEnv(env.OUTERLAYER_WORK);
|
|
21
|
+
const jobDir = env.OUTERLAYER_JOB_DIR;
|
|
22
|
+
if ("reason" in parsed || !jobDir) {
|
|
23
|
+
throw new Error("a session sender runs inside a runner job: OUTERLAYER_WORK must name the item and OUTERLAYER_JOB_DIR the job");
|
|
24
|
+
}
|
|
25
|
+
const home = opts.home ?? homeFromEnv(env);
|
|
26
|
+
const uploader = new LiveUploader({
|
|
27
|
+
home,
|
|
28
|
+
env,
|
|
29
|
+
fetchImpl: opts.fetchImpl,
|
|
30
|
+
sleepImpl: opts.sleepImpl,
|
|
31
|
+
debounceMs: opts.debounceMs,
|
|
32
|
+
stderr,
|
|
33
|
+
build: { item: parsed.number, jobDir },
|
|
34
|
+
onUnauthorized: opts.onFatal
|
|
35
|
+
});
|
|
36
|
+
const onLog = (event) => {
|
|
37
|
+
if (event.type === "changed" && event.file !== void 0) uploader.notify(event.file);
|
|
38
|
+
if (event.type === "error") stderr(`error ${event.file}: ${event.detail}
|
|
39
|
+
`);
|
|
40
|
+
};
|
|
41
|
+
const daemon = new CaptureDaemon({ ...opts.root === void 0 ? {} : { root: opts.root }, watchOnly: true, rescanMs: RESCAN_MS, onLog });
|
|
42
|
+
const pidfile = senderPidfile(home, parsed.number);
|
|
43
|
+
try {
|
|
44
|
+
mkdirSync(spoolDir(home), { recursive: true });
|
|
45
|
+
writeFileSync(pidfile, String(process.pid));
|
|
46
|
+
} catch {
|
|
47
|
+
}
|
|
48
|
+
await daemon.start();
|
|
49
|
+
let stopped;
|
|
50
|
+
return {
|
|
51
|
+
stop: () => stopped ??= (async () => {
|
|
52
|
+
daemon.rescanNow();
|
|
53
|
+
await uploader.stop(opts.stopWaitMs ?? SENDER_STOP_WAIT_MS);
|
|
54
|
+
await daemon.stop();
|
|
55
|
+
rmSync(pidfile, { force: true });
|
|
56
|
+
})()
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
async function runSessionSender() {
|
|
60
|
+
let sender;
|
|
61
|
+
const exit = async (code) => {
|
|
62
|
+
await sender?.stop().catch(() => void 0);
|
|
63
|
+
process.exit(code);
|
|
64
|
+
};
|
|
65
|
+
sender = await startSessionSender({ onFatal: () => void exit(1) });
|
|
66
|
+
process.on("SIGTERM", () => void exit(0));
|
|
67
|
+
process.on("SIGINT", () => void exit(0));
|
|
68
|
+
process.on("unhandledRejection", (reason) => {
|
|
69
|
+
process.stderr.write(`session-sender: unhandled error, continuing: ${String(reason)}
|
|
70
|
+
`);
|
|
71
|
+
});
|
|
72
|
+
await new Promise(() => void 0);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export { runSessionSender, senderPidfile, startSessionSender };
|