aglio-worker 0.23.0 → 0.28.21
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/agent.js +303 -43
- package/package.json +1 -1
package/agent.js
CHANGED
|
@@ -27344,6 +27344,119 @@ function validateRoutingLabels(input2) {
|
|
|
27344
27344
|
return labels;
|
|
27345
27345
|
}
|
|
27346
27346
|
|
|
27347
|
+
// ../../packages/shared/src/worker-capacity.ts
|
|
27348
|
+
var WORKER_DEFAULT_CAPACITY = 1;
|
|
27349
|
+
var WORKER_MAX_CAPACITY = 8;
|
|
27350
|
+
var WORKER_MIN_CAPACITY = 1;
|
|
27351
|
+
var WORKER_CAPACITY_OPTIONS = Array.from(
|
|
27352
|
+
{ length: WORKER_MAX_CAPACITY },
|
|
27353
|
+
(_, index) => index + 1
|
|
27354
|
+
);
|
|
27355
|
+
function workerCapacity(input2, fallback = WORKER_DEFAULT_CAPACITY) {
|
|
27356
|
+
const value = typeof input2 === "number" ? input2 : Number(input2);
|
|
27357
|
+
if (!Number.isFinite(value)) return fallback;
|
|
27358
|
+
return Math.min(WORKER_MAX_CAPACITY, Math.max(WORKER_MIN_CAPACITY, Math.trunc(value)));
|
|
27359
|
+
}
|
|
27360
|
+
function workerCapacityForMode(input2, mode) {
|
|
27361
|
+
return mode === "host" ? WORKER_MIN_CAPACITY : workerCapacity(input2);
|
|
27362
|
+
}
|
|
27363
|
+
|
|
27364
|
+
// ../../packages/shared/src/pool.ts
|
|
27365
|
+
var JOB_TARGETS = ["auto", "byow", "cloud"];
|
|
27366
|
+
var POOL_SIZES = ["small", "medium", "large", "xlarge"];
|
|
27367
|
+
var DEFAULT_POOL_SIZE = "small";
|
|
27368
|
+
var POOL_IDLE_TIMEOUT_MS = 5 * 6e4;
|
|
27369
|
+
var POOL_STALE_PROVISIONING_MS = 3 * 6e4;
|
|
27370
|
+
var POOL_SIZE_CONFIG = {
|
|
27371
|
+
small: {
|
|
27372
|
+
capacity: 2,
|
|
27373
|
+
cpuLimit: 2,
|
|
27374
|
+
memoryLimit: "2560m",
|
|
27375
|
+
instanceType: "c7i.xlarge",
|
|
27376
|
+
vcpu: 4,
|
|
27377
|
+
hourlyUsd: 0.1786,
|
|
27378
|
+
maxInstances: 4
|
|
27379
|
+
},
|
|
27380
|
+
medium: {
|
|
27381
|
+
capacity: 4,
|
|
27382
|
+
cpuLimit: 2,
|
|
27383
|
+
memoryLimit: "2560m",
|
|
27384
|
+
instanceType: "c7i.2xlarge",
|
|
27385
|
+
vcpu: 8,
|
|
27386
|
+
hourlyUsd: 0.3572,
|
|
27387
|
+
maxInstances: 2
|
|
27388
|
+
},
|
|
27389
|
+
large: {
|
|
27390
|
+
capacity: 8,
|
|
27391
|
+
cpuLimit: 2,
|
|
27392
|
+
memoryLimit: "2560m",
|
|
27393
|
+
instanceType: "c7i.4xlarge",
|
|
27394
|
+
vcpu: 16,
|
|
27395
|
+
hourlyUsd: 0.7144,
|
|
27396
|
+
// Documented only: the controller provisions small and medium machines.
|
|
27397
|
+
maxInstances: 0
|
|
27398
|
+
},
|
|
27399
|
+
xlarge: {
|
|
27400
|
+
capacity: 16,
|
|
27401
|
+
cpuLimit: 2,
|
|
27402
|
+
memoryLimit: "2560m",
|
|
27403
|
+
instanceType: "c7i.8xlarge",
|
|
27404
|
+
vcpu: 32,
|
|
27405
|
+
hourlyUsd: 1.4288,
|
|
27406
|
+
maxInstances: 0
|
|
27407
|
+
}
|
|
27408
|
+
};
|
|
27409
|
+
var POOL_REGION_LABEL = "region:us-east";
|
|
27410
|
+
function isPoolSize(value) {
|
|
27411
|
+
return typeof value === "string" && POOL_SIZES.includes(value);
|
|
27412
|
+
}
|
|
27413
|
+
function normalizePoolSize(value, fallback = DEFAULT_POOL_SIZE) {
|
|
27414
|
+
return isPoolSize(value) ? value : fallback;
|
|
27415
|
+
}
|
|
27416
|
+
function poolWorkerLabels(size) {
|
|
27417
|
+
return ["os:linux", "mode:docker", `size:${size}`, POOL_REGION_LABEL];
|
|
27418
|
+
}
|
|
27419
|
+
var POOL_HOST_RESERVE_BYTES = 1024 ** 3;
|
|
27420
|
+
var MEMORY_UNITS = {
|
|
27421
|
+
b: 1,
|
|
27422
|
+
k: 1024,
|
|
27423
|
+
m: 1024 ** 2,
|
|
27424
|
+
g: 1024 ** 3,
|
|
27425
|
+
t: 1024 ** 4
|
|
27426
|
+
};
|
|
27427
|
+
function parsePoolMemoryLimit(value) {
|
|
27428
|
+
const match = /^(\d+(?:\.\d+)?)\s*([bkmgt])?b?$/i.exec(value.trim());
|
|
27429
|
+
if (!match) return null;
|
|
27430
|
+
const unit = (match[2] ?? "").toLowerCase();
|
|
27431
|
+
const multiplier = unit === "" ? 1 : MEMORY_UNITS[unit];
|
|
27432
|
+
const amount = Number(match[1]);
|
|
27433
|
+
if (!multiplier || !Number.isFinite(amount) || amount <= 0) return null;
|
|
27434
|
+
return Math.round(amount * multiplier);
|
|
27435
|
+
}
|
|
27436
|
+
function poolBootCapCheck(input2) {
|
|
27437
|
+
const config2 = POOL_SIZE_CONFIG[input2.size];
|
|
27438
|
+
const capacity = workerCapacity(input2.capacity);
|
|
27439
|
+
const requiredCpu = capacity * config2.cpuLimit;
|
|
27440
|
+
const requiredMemoryBytes = capacity * (parsePoolMemoryLimit(config2.memoryLimit) ?? 0) + POOL_HOST_RESERVE_BYTES;
|
|
27441
|
+
const hostMemoryBytes = input2.hostMemoryBytes ?? null;
|
|
27442
|
+
const base = { requiredCpu, hostCpu: input2.hostCpu, requiredMemoryBytes, hostMemoryBytes };
|
|
27443
|
+
if (requiredCpu > input2.hostCpu) {
|
|
27444
|
+
return {
|
|
27445
|
+
...base,
|
|
27446
|
+
ok: false,
|
|
27447
|
+
reason: `capacity ${capacity} x ${config2.cpuLimit} vCPU exceeds the host ${input2.hostCpu} vCPU`
|
|
27448
|
+
};
|
|
27449
|
+
}
|
|
27450
|
+
if (hostMemoryBytes != null && requiredMemoryBytes > hostMemoryBytes) {
|
|
27451
|
+
return {
|
|
27452
|
+
...base,
|
|
27453
|
+
ok: false,
|
|
27454
|
+
reason: `capacity ${capacity} x ${config2.memoryLimit} plus the 1 GiB reserve (${Math.round(requiredMemoryBytes / 1024 ** 2)} MiB) exceeds the host ${Math.round(hostMemoryBytes / 1024 ** 2)} MiB`
|
|
27455
|
+
};
|
|
27456
|
+
}
|
|
27457
|
+
return { ...base, ok: true };
|
|
27458
|
+
}
|
|
27459
|
+
|
|
27347
27460
|
// ../../packages/shared/src/schemas.ts
|
|
27348
27461
|
var cardColumnSchema = external_exports.enum(CARD_COLUMNS);
|
|
27349
27462
|
var runStatusSchema = external_exports.enum(RUN_STATUSES_FOR_SCHEMA);
|
|
@@ -27429,8 +27542,11 @@ var projectSettingsSchema = external_exports.object({
|
|
|
27429
27542
|
/** Planning job labels. Empty means any online worker, so planning never waits on a build machine. */
|
|
27430
27543
|
planningRequiredWorkerLabels: routingLabelsSchema.default([]),
|
|
27431
27544
|
/** Undefined inherits the workspace switch. True or false overrides it. */
|
|
27432
|
-
postBuildReply: external_exports.boolean().optional()
|
|
27545
|
+
postBuildReply: external_exports.boolean().optional(),
|
|
27546
|
+
/** Default pool size for cloud runs started from this project. Auto picks one at dispatch. */
|
|
27547
|
+
cloudRunnerSize: external_exports.enum(POOL_SIZES).default("small")
|
|
27433
27548
|
});
|
|
27549
|
+
var jobTargetSchema = external_exports.enum(JOB_TARGETS);
|
|
27434
27550
|
var repoConfigSchema = external_exports.object({
|
|
27435
27551
|
id: external_exports.string(),
|
|
27436
27552
|
name: external_exports.string(),
|
|
@@ -27564,23 +27680,6 @@ var MAX_SCAN_MINUTES = 8 * 24 * 60;
|
|
|
27564
27680
|
// ../../packages/shared/src/workers.ts
|
|
27565
27681
|
import { createHash, randomBytes } from "node:crypto";
|
|
27566
27682
|
|
|
27567
|
-
// ../../packages/shared/src/worker-capacity.ts
|
|
27568
|
-
var WORKER_DEFAULT_CAPACITY = 1;
|
|
27569
|
-
var WORKER_MAX_CAPACITY = 8;
|
|
27570
|
-
var WORKER_MIN_CAPACITY = 1;
|
|
27571
|
-
var WORKER_CAPACITY_OPTIONS = Array.from(
|
|
27572
|
-
{ length: WORKER_MAX_CAPACITY },
|
|
27573
|
-
(_, index) => index + 1
|
|
27574
|
-
);
|
|
27575
|
-
function workerCapacity(input2, fallback = WORKER_DEFAULT_CAPACITY) {
|
|
27576
|
-
const value = typeof input2 === "number" ? input2 : Number(input2);
|
|
27577
|
-
if (!Number.isFinite(value)) return fallback;
|
|
27578
|
-
return Math.min(WORKER_MAX_CAPACITY, Math.max(WORKER_MIN_CAPACITY, Math.trunc(value)));
|
|
27579
|
-
}
|
|
27580
|
-
function workerCapacityForMode(input2, mode) {
|
|
27581
|
-
return mode === "host" ? WORKER_MIN_CAPACITY : workerCapacity(input2);
|
|
27582
|
-
}
|
|
27583
|
-
|
|
27584
27683
|
// ../../packages/shared/src/job-contract.ts
|
|
27585
27684
|
var WORKER_JOB_LOG_BATCH_BYTES = 64 * 1024;
|
|
27586
27685
|
var WORKER_JOB_LOG_BATCH_MS = 1500;
|
|
@@ -28224,7 +28323,8 @@ var PLAN_CATALOG = {
|
|
|
28224
28323
|
seats: 1,
|
|
28225
28324
|
projects: 2,
|
|
28226
28325
|
runsPerMonth: 50,
|
|
28227
|
-
storageBytes: 500 * MEGABYTE
|
|
28326
|
+
storageBytes: 500 * MEGABYTE,
|
|
28327
|
+
poolSecondsPerMonth: 18e3
|
|
28228
28328
|
}
|
|
28229
28329
|
},
|
|
28230
28330
|
team: {
|
|
@@ -28236,7 +28336,8 @@ var PLAN_CATALOG = {
|
|
|
28236
28336
|
seats: 10,
|
|
28237
28337
|
projects: 20,
|
|
28238
28338
|
runsPerMonth: 2e3,
|
|
28239
|
-
storageBytes: 20 * GIGABYTE
|
|
28339
|
+
storageBytes: 20 * GIGABYTE,
|
|
28340
|
+
poolSecondsPerMonth: 18e4
|
|
28240
28341
|
}
|
|
28241
28342
|
},
|
|
28242
28343
|
enterprise: {
|
|
@@ -28248,7 +28349,8 @@ var PLAN_CATALOG = {
|
|
|
28248
28349
|
seats: null,
|
|
28249
28350
|
projects: null,
|
|
28250
28351
|
runsPerMonth: null,
|
|
28251
|
-
storageBytes: null
|
|
28352
|
+
storageBytes: null,
|
|
28353
|
+
poolSecondsPerMonth: null
|
|
28252
28354
|
}
|
|
28253
28355
|
}
|
|
28254
28356
|
};
|
|
@@ -28277,6 +28379,124 @@ var cardOpenModeSchema = external_exports.enum(CARD_OPEN_MODES);
|
|
|
28277
28379
|
var CARD_OPEN_SIZES = ["default", "large", "fullscreen"];
|
|
28278
28380
|
var cardOpenSizeSchema = external_exports.enum(CARD_OPEN_SIZES);
|
|
28279
28381
|
|
|
28382
|
+
// src/pool-boot.ts
|
|
28383
|
+
import { createHash as createHash2, createHmac } from "node:crypto";
|
|
28384
|
+
import { cpus, totalmem } from "node:os";
|
|
28385
|
+
async function containerCredentials(fetchImpl) {
|
|
28386
|
+
const relative5 = process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI?.trim();
|
|
28387
|
+
const full = process.env.AWS_CONTAINER_CREDENTIALS_FULL_URI?.trim();
|
|
28388
|
+
if (!relative5 && !full) return null;
|
|
28389
|
+
const response = await fetchImpl(relative5 ? `http://169.254.170.2${relative5}` : full);
|
|
28390
|
+
if (!response.ok) return null;
|
|
28391
|
+
const json3 = await response.json();
|
|
28392
|
+
if (!json3.AccessKeyId || !json3.SecretAccessKey) return null;
|
|
28393
|
+
return {
|
|
28394
|
+
accessKeyId: json3.AccessKeyId,
|
|
28395
|
+
secretAccessKey: json3.SecretAccessKey,
|
|
28396
|
+
sessionToken: json3.Token ?? null
|
|
28397
|
+
};
|
|
28398
|
+
}
|
|
28399
|
+
async function resolveAwsCredentials(fetchImpl = fetch) {
|
|
28400
|
+
const key = process.env.AWS_ACCESS_KEY_ID?.trim();
|
|
28401
|
+
const secret = process.env.AWS_SECRET_ACCESS_KEY?.trim();
|
|
28402
|
+
if (key && secret) {
|
|
28403
|
+
return {
|
|
28404
|
+
accessKeyId: key,
|
|
28405
|
+
secretAccessKey: secret,
|
|
28406
|
+
sessionToken: process.env.AWS_SESSION_TOKEN?.trim() || null
|
|
28407
|
+
};
|
|
28408
|
+
}
|
|
28409
|
+
return containerCredentials(fetchImpl);
|
|
28410
|
+
}
|
|
28411
|
+
function sha256(value) {
|
|
28412
|
+
return createHash2("sha256").update(value, "utf8").digest("hex");
|
|
28413
|
+
}
|
|
28414
|
+
function hmac(key, value) {
|
|
28415
|
+
return createHmac("sha256", key).update(value, "utf8").digest();
|
|
28416
|
+
}
|
|
28417
|
+
async function ssmCall(action, name, region, credentials, fetchImpl) {
|
|
28418
|
+
const host = `ssm.${region}.amazonaws.com`;
|
|
28419
|
+
const body = JSON.stringify(
|
|
28420
|
+
action === "GetParameter" ? { Name: name, WithDecryption: true } : { Name: name }
|
|
28421
|
+
);
|
|
28422
|
+
const now = /* @__PURE__ */ new Date();
|
|
28423
|
+
const long = now.toISOString().replace(/[:-]|\.\d{3}/g, "");
|
|
28424
|
+
const short = long.slice(0, 8);
|
|
28425
|
+
const headers = {
|
|
28426
|
+
host,
|
|
28427
|
+
"content-type": "application/x-amz-json-1.1",
|
|
28428
|
+
"x-amz-date": long,
|
|
28429
|
+
"x-amz-target": `AmazonSSM.${action}`
|
|
28430
|
+
};
|
|
28431
|
+
if (credentials.sessionToken) headers["x-amz-security-token"] = credentials.sessionToken;
|
|
28432
|
+
const names = Object.keys(headers).map((h) => h.toLowerCase()).sort();
|
|
28433
|
+
const canonicalHeaders = names.map((h) => `${h}:${headers[h]}
|
|
28434
|
+
`).join("");
|
|
28435
|
+
const signedHeaders = names.join(";");
|
|
28436
|
+
const canonicalRequest = [
|
|
28437
|
+
"POST",
|
|
28438
|
+
"/",
|
|
28439
|
+
"",
|
|
28440
|
+
canonicalHeaders,
|
|
28441
|
+
signedHeaders,
|
|
28442
|
+
sha256(body)
|
|
28443
|
+
].join("\n");
|
|
28444
|
+
const scope = `${short}/${region}/ssm/aws4_request`;
|
|
28445
|
+
const stringToSign = ["AWS4-HMAC-SHA256", long, scope, sha256(canonicalRequest)].join("\n");
|
|
28446
|
+
const signingKey = hmac(
|
|
28447
|
+
hmac(hmac(hmac(`AWS4${credentials.secretAccessKey}`, short), region), "ssm"),
|
|
28448
|
+
"aws4_request"
|
|
28449
|
+
);
|
|
28450
|
+
const signature = createHmac("sha256", signingKey).update(stringToSign, "utf8").digest("hex");
|
|
28451
|
+
headers.authorization = `AWS4-HMAC-SHA256 Credential=${credentials.accessKeyId}/${scope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
|
|
28452
|
+
const response = await fetchImpl(`https://${host}/`, { method: "POST", headers, body });
|
|
28453
|
+
const text = await response.text();
|
|
28454
|
+
if (!response.ok) {
|
|
28455
|
+
throw new Error(`SSM ${action} failed ${response.status}: ${text.slice(0, 300)}`);
|
|
28456
|
+
}
|
|
28457
|
+
return text ? JSON.parse(text) : {};
|
|
28458
|
+
}
|
|
28459
|
+
async function readAndDeletePoolToken(input2) {
|
|
28460
|
+
const fetchImpl = input2.fetchImpl ?? fetch;
|
|
28461
|
+
const credentials = input2.credentials ?? await resolveAwsCredentials(fetchImpl);
|
|
28462
|
+
if (!credentials) throw new Error("Pool worker has no AWS credentials for SSM");
|
|
28463
|
+
const region = input2.region ?? process.env.AWS_REGION?.trim() ?? "us-east-1";
|
|
28464
|
+
const prefix = input2.prefix ?? process.env.AGLIO_POOL_SSM_PREFIX?.trim() ?? "/aglio/pool/workers";
|
|
28465
|
+
const name = `${prefix}/${input2.workerId}`;
|
|
28466
|
+
const response = await ssmCall("GetParameter", name, region, credentials, fetchImpl);
|
|
28467
|
+
const parameter = response.Parameter;
|
|
28468
|
+
const token = parameter?.Value;
|
|
28469
|
+
if (!token || !token.startsWith("aglio_wkr_")) {
|
|
28470
|
+
throw new Error("SSM parameter did not contain a worker token");
|
|
28471
|
+
}
|
|
28472
|
+
await ssmCall("DeleteParameter", name, region, credentials, fetchImpl).catch((error62) => {
|
|
28473
|
+
console.warn("[pool] could not delete the SSM token parameter", {
|
|
28474
|
+
error: error62 instanceof Error ? error62.message : String(error62)
|
|
28475
|
+
});
|
|
28476
|
+
});
|
|
28477
|
+
return token;
|
|
28478
|
+
}
|
|
28479
|
+
async function resolvePoolBoot(options) {
|
|
28480
|
+
const config2 = POOL_SIZE_CONFIG[options.size];
|
|
28481
|
+
const check2 = poolBootCapCheck({
|
|
28482
|
+
size: options.size,
|
|
28483
|
+
capacity: config2.capacity,
|
|
28484
|
+
hostCpu: cpus().length,
|
|
28485
|
+
hostMemoryBytes: totalmem()
|
|
28486
|
+
});
|
|
28487
|
+
if (!check2.ok) throw new Error(`Pool worker refuses to start: ${check2.reason}`);
|
|
28488
|
+
return {
|
|
28489
|
+
size: options.size,
|
|
28490
|
+
workerId: options.workerId,
|
|
28491
|
+
token: options.token,
|
|
28492
|
+
capacity: config2.capacity,
|
|
28493
|
+
labels: poolWorkerLabels(options.size),
|
|
28494
|
+
cpuLimit: config2.cpuLimit,
|
|
28495
|
+
memoryLimit: config2.memoryLimit,
|
|
28496
|
+
name: process.env.AGLIO_POOL_WORKER_NAME?.trim() || `pool-${options.size}-${options.workerId}`
|
|
28497
|
+
};
|
|
28498
|
+
}
|
|
28499
|
+
|
|
28280
28500
|
// src/remote-docker.ts
|
|
28281
28501
|
import { existsSync, readFileSync as readFileSync2 } from "node:fs";
|
|
28282
28502
|
import { PassThrough } from "node:stream";
|
|
@@ -28386,7 +28606,7 @@ async function ensureDockerImage(input2) {
|
|
|
28386
28606
|
// src/resources.ts
|
|
28387
28607
|
var GIB = 1024 * 1024 * 1024;
|
|
28388
28608
|
var AUTO_MEMORY_RATIO = 0.8;
|
|
28389
|
-
var
|
|
28609
|
+
var MEMORY_UNITS2 = {
|
|
28390
28610
|
"": 1,
|
|
28391
28611
|
b: 1,
|
|
28392
28612
|
k: 1024,
|
|
@@ -28419,7 +28639,7 @@ function parseCpuLimit(value, variable = "AGENT_CPU_LIMIT") {
|
|
|
28419
28639
|
function parseMemoryLimit(value, variable = "AGENT_MEMORY_LIMIT") {
|
|
28420
28640
|
const trimmed = value.trim().toLowerCase();
|
|
28421
28641
|
const match = /^(\d+(?:\.\d+)?)\s*([bkmgt]?b?)$/.exec(trimmed);
|
|
28422
|
-
const multiplier = match ?
|
|
28642
|
+
const multiplier = match ? MEMORY_UNITS2[match[2] ?? ""] : void 0;
|
|
28423
28643
|
const amount = match ? Number(match[1]) : Number.NaN;
|
|
28424
28644
|
if (!match || multiplier === void 0 || !Number.isFinite(amount)) {
|
|
28425
28645
|
throw invalid(variable, value, "bytes or a Docker-style size such as 512m, 8g, 1073741824");
|
|
@@ -28433,6 +28653,11 @@ function parseMemoryLimit(value, variable = "AGENT_MEMORY_LIMIT") {
|
|
|
28433
28653
|
function cpuToNanoCpus(cpu) {
|
|
28434
28654
|
return Math.round(cpu * 1e9);
|
|
28435
28655
|
}
|
|
28656
|
+
var NODE_HEAP_RATIO = 0.7;
|
|
28657
|
+
function nodeOptionsForMemory(memoryBytes) {
|
|
28658
|
+
const heapMb = Math.max(64, Math.floor(memoryBytes / 1024 ** 2 * NODE_HEAP_RATIO));
|
|
28659
|
+
return `--max-old-space-size=${heapMb}`;
|
|
28660
|
+
}
|
|
28436
28661
|
function readAgentResourceOverrides(source = process.env) {
|
|
28437
28662
|
const cpu = source.AGENT_CPU_LIMIT?.trim();
|
|
28438
28663
|
const memory = source.AGENT_MEMORY_LIMIT?.trim();
|
|
@@ -28606,6 +28831,8 @@ async function createDockerJobExecutor(input2) {
|
|
|
28606
28831
|
Cmd: ["sleep", "infinity"],
|
|
28607
28832
|
WorkingDir: CONTAINER_WORKSPACE,
|
|
28608
28833
|
Tty: false,
|
|
28834
|
+
// Heap follows the container cap, so a stale image default cannot overshoot it.
|
|
28835
|
+
Env: [`NODE_OPTIONS=${nodeOptionsForMemory(resources.memoryBytes)}`],
|
|
28609
28836
|
Labels: { "aglio.remote_job": input2.jobId },
|
|
28610
28837
|
HostConfig: hostConfig
|
|
28611
28838
|
});
|
|
@@ -28727,13 +28954,18 @@ async function createDockerLoginContainer(input2) {
|
|
|
28727
28954
|
if (error62.statusCode !== 404) throw error62;
|
|
28728
28955
|
}
|
|
28729
28956
|
const overrides = readAgentResourceOverrides();
|
|
28957
|
+
const memory = overrides.memory ?? 2 * 1024 * 1024 * 1024;
|
|
28730
28958
|
const container = await client.createContainer({
|
|
28731
28959
|
Image: input2.image,
|
|
28732
28960
|
name,
|
|
28733
28961
|
Cmd: ["sleep", "infinity"],
|
|
28734
28962
|
WorkingDir: CONTAINER_WORKSPACE,
|
|
28735
28963
|
Tty: false,
|
|
28736
|
-
Env: Object.entries(
|
|
28964
|
+
Env: Object.entries({
|
|
28965
|
+
...input2.env ?? {},
|
|
28966
|
+
// Heap follows the container cap, so a stale image default cannot overshoot it.
|
|
28967
|
+
NODE_OPTIONS: nodeOptionsForMemory(memory)
|
|
28968
|
+
}).map(([key, value]) => `${key}=${value}`),
|
|
28737
28969
|
Labels: { "aglio.remote_login": input2.jobId },
|
|
28738
28970
|
HostConfig: {
|
|
28739
28971
|
Binds: [`${hostPath}:${CONTAINER_WORKSPACE}`],
|
|
@@ -28741,7 +28973,7 @@ async function createDockerLoginContainer(input2) {
|
|
|
28741
28973
|
ExtraHosts: input2.extraHosts ?? [],
|
|
28742
28974
|
// Login keeps its small legacy default and never auto-detects, but an
|
|
28743
28975
|
// explicit AGENT_CPU_LIMIT / AGENT_MEMORY_LIMIT override wins.
|
|
28744
|
-
Memory:
|
|
28976
|
+
Memory: memory,
|
|
28745
28977
|
NanoCpus: overrides.cpu != null ? cpuToNanoCpus(overrides.cpu) : await loginCpuNanoCpus(client),
|
|
28746
28978
|
PidsLimit: 512,
|
|
28747
28979
|
AutoRemove: false,
|
|
@@ -30694,29 +30926,44 @@ async function runRemoteCardJob(deps) {
|
|
|
30694
30926
|
}
|
|
30695
30927
|
});
|
|
30696
30928
|
}
|
|
30697
|
-
async function runGit(cwd, args) {
|
|
30929
|
+
async function runGit(cwd, args, env = {}) {
|
|
30698
30930
|
let output2 = "";
|
|
30699
|
-
const code = await exec(
|
|
30700
|
-
|
|
30701
|
-
|
|
30702
|
-
|
|
30931
|
+
const code = await exec(
|
|
30932
|
+
[`git ${args.map(shellArg2).join(" ")}`],
|
|
30933
|
+
cwd,
|
|
30934
|
+
Object.keys(env).length > 0 ? env : gitAuthEnv(),
|
|
30935
|
+
{
|
|
30936
|
+
log: false,
|
|
30937
|
+
onOutput: (chunk) => {
|
|
30938
|
+
output2 += chunk;
|
|
30939
|
+
}
|
|
30703
30940
|
}
|
|
30704
|
-
|
|
30941
|
+
);
|
|
30705
30942
|
return { code, output: output2 };
|
|
30706
30943
|
}
|
|
30707
30944
|
async function repoGit(targetRun) {
|
|
30708
30945
|
return (args) => runGit(workspace, ["-C", targetRun, ...args]);
|
|
30709
30946
|
}
|
|
30710
30947
|
async function git(args, cwd, options = {}) {
|
|
30711
|
-
const { code } = await runGit(cwd, args);
|
|
30948
|
+
const { code } = await runGit(cwd, args, options.env);
|
|
30712
30949
|
if (code !== 0 && !options.allowFailure) throw new Error(`git ${args[0]} failed with code ${code}`);
|
|
30713
30950
|
return { code };
|
|
30714
30951
|
}
|
|
30715
|
-
|
|
30716
|
-
|
|
30952
|
+
function gitAuthEnv() {
|
|
30953
|
+
const env = {
|
|
30717
30954
|
GIT_TERMINAL_PROMPT: "0",
|
|
30718
|
-
GIT_CONFIG_NOSYSTEM: "1"
|
|
30719
|
-
|
|
30955
|
+
GIT_CONFIG_NOSYSTEM: "1"
|
|
30956
|
+
};
|
|
30957
|
+
if (!context.git.token) return env;
|
|
30958
|
+
const encoded = Buffer.from(
|
|
30959
|
+
`${context.git.username || "x-access-token"}:${context.git.token}`,
|
|
30960
|
+
"utf8"
|
|
30961
|
+
).toString("base64");
|
|
30962
|
+
return {
|
|
30963
|
+
...env,
|
|
30964
|
+
GIT_CONFIG_COUNT: "1",
|
|
30965
|
+
GIT_CONFIG_KEY_0: "http.extraheader",
|
|
30966
|
+
GIT_CONFIG_VALUE_0: `AUTHORIZATION: basic ${encoded}`
|
|
30720
30967
|
};
|
|
30721
30968
|
}
|
|
30722
30969
|
async function pushBranch(repo, branch) {
|
|
@@ -30781,7 +31028,7 @@ async function runRemoteCardJob(deps) {
|
|
|
30781
31028
|
const code = await deps.runCommand({
|
|
30782
31029
|
command: `git ${args.map(shellArg2).join(" ")}`,
|
|
30783
31030
|
cwd: workspace,
|
|
30784
|
-
env:
|
|
31031
|
+
env: gitAuthEnv(),
|
|
30785
31032
|
signal: deps.signal,
|
|
30786
31033
|
onOutput: (chunk) => {
|
|
30787
31034
|
output2 += chunk.toString("utf8");
|
|
@@ -31769,7 +32016,7 @@ async function runRemoteWorker(config2, deps = {}) {
|
|
|
31769
32016
|
...input2,
|
|
31770
32017
|
workspaceRoot: config2.workspaceRoot ?? input2.workspace,
|
|
31771
32018
|
hostWorkspaceRoot: config2.hostWorkspaceRoot ?? config2.workspaceRoot ?? input2.workspace,
|
|
31772
|
-
image: config2.runnerImage ?? "ghcr.io/h4kbas/aglio-runner:
|
|
32019
|
+
image: config2.runnerImage ?? "ghcr.io/h4kbas/aglio-runner:latest",
|
|
31773
32020
|
socketPath: config2.dockerSocket,
|
|
31774
32021
|
extraHosts: config2.extraHosts,
|
|
31775
32022
|
api: workerProviderLoginApi(
|
|
@@ -43936,10 +44183,23 @@ function defaultRunnerImage() {
|
|
|
43936
44183
|
}
|
|
43937
44184
|
async function main() {
|
|
43938
44185
|
const apiUrl = required2("AGLIO_API_URL");
|
|
43939
|
-
const
|
|
44186
|
+
const poolMode = process.env.AGLIO_POOL === "1" || process.env.AGLIO_POOL === "true";
|
|
44187
|
+
const poolSize = poolMode ? normalizePoolSize(process.env.AGLIO_POOL_SIZE) : null;
|
|
44188
|
+
const poolWorkerId = poolMode ? required2("AGLIO_POOL_WORKER_ID") : null;
|
|
44189
|
+
const poolBoot = poolMode ? await resolvePoolBoot({
|
|
44190
|
+
workerId: poolWorkerId,
|
|
44191
|
+
size: poolSize,
|
|
44192
|
+
// Local stub only. Production reads the one-time token from SSM.
|
|
44193
|
+
token: process.env.AGLIO_POOL_TOKEN?.trim() || await readAndDeletePoolToken({ workerId: poolWorkerId })
|
|
44194
|
+
}) : null;
|
|
44195
|
+
if (poolBoot) {
|
|
44196
|
+
process.env.AGENT_CPU_LIMIT = String(poolBoot.cpuLimit);
|
|
44197
|
+
process.env.AGENT_MEMORY_LIMIT = poolBoot.memoryLimit;
|
|
44198
|
+
}
|
|
44199
|
+
const token = poolBoot ? poolBoot.token : required2("AGLIO_WORKER_TOKEN");
|
|
43940
44200
|
const mode = normalizeWorkerMode(process.env.AGLIO_WORKER_MODE?.trim() || "docker");
|
|
43941
|
-
const os = normalizeWorkerOs(process.env.AGLIO_WORKER_OS?.trim() || defaultOs());
|
|
43942
|
-
const labels = parseWorkerLabels(process.env.AGLIO_WORKER_LABELS ?? "");
|
|
44201
|
+
const os = normalizeWorkerOs(poolBoot ? "linux" : process.env.AGLIO_WORKER_OS?.trim() || defaultOs());
|
|
44202
|
+
const labels = poolBoot ? poolBoot.labels : parseWorkerLabels(process.env.AGLIO_WORKER_LABELS ?? "");
|
|
43943
44203
|
const stateFile = process.env.AGLIO_WORKER_STATE_FILE?.trim() || join8(homedir3(), ".aglio", "worker.json");
|
|
43944
44204
|
const workspaceRoot = process.env.AGLIO_WORKER_WORKSPACE?.trim() || defaultWorkspaceRoot();
|
|
43945
44205
|
const hostWorkspaceRoot = process.env.AGLIO_WORKER_HOST_WORKSPACE?.trim() || workspaceRoot;
|
|
@@ -43973,14 +44233,14 @@ async function main() {
|
|
|
43973
44233
|
const config2 = {
|
|
43974
44234
|
apiUrl,
|
|
43975
44235
|
token,
|
|
43976
|
-
name: process.env.AGLIO_WORKER_NAME?.trim() || hostname3(),
|
|
44236
|
+
name: poolBoot?.name ?? (process.env.AGLIO_WORKER_NAME?.trim() || hostname3()),
|
|
43977
44237
|
os,
|
|
43978
44238
|
mode,
|
|
43979
44239
|
labels,
|
|
43980
44240
|
version: process.env.AGLIO_WORKER_VERSION?.trim() || WORKER_MIN_VERSION,
|
|
43981
44241
|
stateFile,
|
|
43982
44242
|
// Simulators and Xcode share one signing context on a Mac host.
|
|
43983
|
-
capacity: workerCapacityForMode(process.env.AGLIO_WORKER_CAPACITY ?? 1, mode),
|
|
44243
|
+
capacity: poolBoot?.capacity ?? workerCapacityForMode(process.env.AGLIO_WORKER_CAPACITY ?? 1, mode),
|
|
43984
44244
|
claimTimeoutSeconds,
|
|
43985
44245
|
workspaceRoot,
|
|
43986
44246
|
hostWorkspaceRoot,
|