tensorlake 0.5.17 → 0.5.19
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/dist/bin/darwin-arm64/tensorlake +0 -0
- package/dist/bin/darwin-arm64/tl +0 -0
- package/dist/bin/linux-x64/tensorlake +0 -0
- package/dist/bin/linux-x64/tl +0 -0
- package/dist/bin/win32-x64/tensorlake.exe +0 -0
- package/dist/bin/win32-x64/tl.exe +0 -0
- package/dist/index.cjs +127 -801
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +346 -3
- package/dist/index.d.ts +346 -3
- package/dist/index.js +132 -801
- package/dist/index.js.map +1 -1
- package/dist/native/darwin-arm64/tensorlake-node.node +0 -0
- package/dist/native/linux-x64/tensorlake-node.node +0 -0
- package/dist/native/win32-x64/tensorlake-node.node +0 -0
- package/dist/sandbox-image-CmX-BLMV.d.cts +117 -0
- package/dist/sandbox-image-CmX-BLMV.d.ts +117 -0
- package/dist/sandbox-image.cjs +216 -5098
- package/dist/sandbox-image.cjs.map +1 -1
- package/dist/sandbox-image.d.cts +1 -1
- package/dist/sandbox-image.d.ts +1 -1
- package/dist/sandbox-image.js +232 -5103
- package/dist/sandbox-image.js.map +1 -1
- package/lib/runtime.cjs +19 -0
- package/package.json +3 -2
- package/dist/sandbox-image-BBcx3bSx.d.cts +0 -477
- package/dist/sandbox-image-BBcx3bSx.d.ts +0 -477
package/dist/index.cjs
CHANGED
|
@@ -35,7 +35,7 @@ var SDK_VERSION, API_URL, API_KEY, NAMESPACE, SANDBOX_PROXY_URL, DEFAULT_HTTP_TI
|
|
|
35
35
|
var init_defaults = __esm({
|
|
36
36
|
"src/defaults.ts"() {
|
|
37
37
|
"use strict";
|
|
38
|
-
SDK_VERSION = "0.5.
|
|
38
|
+
SDK_VERSION = "0.5.19";
|
|
39
39
|
API_URL = process.env.TENSORLAKE_API_URL ?? "https://api.tensorlake.ai";
|
|
40
40
|
API_KEY = process.env.TENSORLAKE_API_KEY ?? void 0;
|
|
41
41
|
NAMESPACE = process.env.INDEXIFY_NAMESPACE ?? "default";
|
|
@@ -4503,11 +4503,6 @@ var init_image = __esm({
|
|
|
4503
4503
|
* and registers the snapshot as a named sandbox template.
|
|
4504
4504
|
*/
|
|
4505
4505
|
async build(options = {}) {
|
|
4506
|
-
if ((options.registeredName ?? this._name) === DEFAULT_IMAGE_NAME) {
|
|
4507
|
-
console.warn(
|
|
4508
|
-
`Building sandbox image with the default name "${DEFAULT_IMAGE_NAME}". Pass \`registeredName\` or \`Image({ name })\` to avoid collisions with other default-named images in this project.`
|
|
4509
|
-
);
|
|
4510
|
-
}
|
|
4511
4506
|
const { createSandboxImage: createSandboxImage2 } = await Promise.resolve().then(() => (init_sandbox_image(), sandbox_image_exports));
|
|
4512
4507
|
return createSandboxImage2(this, options);
|
|
4513
4508
|
}
|
|
@@ -4522,263 +4517,61 @@ var init_image = __esm({
|
|
|
4522
4517
|
// src/sandbox-image.ts
|
|
4523
4518
|
var sandbox_image_exports = {};
|
|
4524
4519
|
__export(sandbox_image_exports, {
|
|
4520
|
+
__setNativeBindingForTest: () => __setNativeBindingForTest,
|
|
4525
4521
|
createSandboxImage: () => createSandboxImage,
|
|
4526
|
-
defaultRegisteredName: () => defaultRegisteredName,
|
|
4527
|
-
loadDockerfilePlan: () => loadDockerfilePlan,
|
|
4528
|
-
loadImagePlan: () => loadImagePlan,
|
|
4529
|
-
logicalDockerfileLines: () => logicalDockerfileLines,
|
|
4530
|
-
registerImage: () => registerImage,
|
|
4531
4522
|
runCreateSandboxImageCli: () => runCreateSandboxImageCli
|
|
4532
4523
|
});
|
|
4533
|
-
function
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
const parentName = import_node_path.default.basename(import_node_path.default.dirname(dockerfilePath)).trim();
|
|
4537
|
-
return parentName || "sandbox-image";
|
|
4524
|
+
function resolveRequire() {
|
|
4525
|
+
if (typeof module !== "undefined" && module.exports != null && typeof require !== "undefined") {
|
|
4526
|
+
return require;
|
|
4538
4527
|
}
|
|
4539
|
-
return
|
|
4528
|
+
return (0, import_node_module.createRequire)(import_meta.url);
|
|
4540
4529
|
}
|
|
4541
|
-
function
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
if (startLine == null) {
|
|
4552
|
-
startLine = lineNumber;
|
|
4553
|
-
}
|
|
4554
|
-
let line = rawLine.replace(/\s+$/, "");
|
|
4555
|
-
const continued = line.endsWith("\\");
|
|
4556
|
-
if (continued) {
|
|
4557
|
-
line = line.slice(0, -1);
|
|
4558
|
-
}
|
|
4559
|
-
const normalized = line.trim();
|
|
4560
|
-
if (normalized && !normalized.startsWith("#")) {
|
|
4561
|
-
parts.push(normalized);
|
|
4562
|
-
}
|
|
4563
|
-
if (continued) {
|
|
4564
|
-
continue;
|
|
4565
|
-
}
|
|
4566
|
-
if (parts.length > 0) {
|
|
4567
|
-
logicalLines.push({
|
|
4568
|
-
lineNumber: startLine ?? lineNumber,
|
|
4569
|
-
line: parts.join(" ")
|
|
4570
|
-
});
|
|
4571
|
-
}
|
|
4572
|
-
parts = [];
|
|
4573
|
-
startLine = null;
|
|
4574
|
-
}
|
|
4575
|
-
if (parts.length > 0) {
|
|
4576
|
-
logicalLines.push({
|
|
4577
|
-
lineNumber: startLine ?? 1,
|
|
4578
|
-
line: parts.join(" ")
|
|
4579
|
-
});
|
|
4580
|
-
}
|
|
4581
|
-
return logicalLines;
|
|
4582
|
-
}
|
|
4583
|
-
function splitInstruction(line, lineNumber) {
|
|
4584
|
-
const trimmed = line.trim();
|
|
4585
|
-
if (!trimmed) {
|
|
4586
|
-
throw new Error(`line ${lineNumber}: empty Dockerfile instruction`);
|
|
4587
|
-
}
|
|
4588
|
-
const match = trimmed.match(/^(\S+)(?:\s+(.*))?$/);
|
|
4589
|
-
if (!match) {
|
|
4590
|
-
throw new Error(`line ${lineNumber}: invalid Dockerfile instruction`);
|
|
4591
|
-
}
|
|
4592
|
-
return {
|
|
4593
|
-
keyword: match[1].toUpperCase(),
|
|
4594
|
-
value: (match[2] ?? "").trim()
|
|
4595
|
-
};
|
|
4596
|
-
}
|
|
4597
|
-
function shellSplit(input) {
|
|
4598
|
-
const tokens = [];
|
|
4599
|
-
let current = "";
|
|
4600
|
-
let quote = null;
|
|
4601
|
-
let escape = false;
|
|
4602
|
-
for (let i = 0; i < input.length; i++) {
|
|
4603
|
-
const char = input[i];
|
|
4604
|
-
if (escape) {
|
|
4605
|
-
current += char;
|
|
4606
|
-
escape = false;
|
|
4607
|
-
continue;
|
|
4608
|
-
}
|
|
4609
|
-
if (quote == null) {
|
|
4610
|
-
if (/\s/.test(char)) {
|
|
4611
|
-
if (current) {
|
|
4612
|
-
tokens.push(current);
|
|
4613
|
-
current = "";
|
|
4614
|
-
}
|
|
4615
|
-
continue;
|
|
4616
|
-
}
|
|
4617
|
-
if (char === "'" || char === '"') {
|
|
4618
|
-
quote = char;
|
|
4619
|
-
continue;
|
|
4620
|
-
}
|
|
4621
|
-
if (char === "\\") {
|
|
4622
|
-
escape = true;
|
|
4623
|
-
continue;
|
|
4624
|
-
}
|
|
4625
|
-
current += char;
|
|
4626
|
-
continue;
|
|
4627
|
-
}
|
|
4628
|
-
if (quote === "'") {
|
|
4629
|
-
if (char === "'") {
|
|
4630
|
-
quote = null;
|
|
4631
|
-
} else {
|
|
4632
|
-
current += char;
|
|
4633
|
-
}
|
|
4634
|
-
continue;
|
|
4635
|
-
}
|
|
4636
|
-
if (char === '"') {
|
|
4637
|
-
quote = null;
|
|
4638
|
-
continue;
|
|
4639
|
-
}
|
|
4640
|
-
if (char === "\\") {
|
|
4641
|
-
const next = input[++i];
|
|
4642
|
-
if (next == null) {
|
|
4643
|
-
throw new Error(`unterminated escape sequence in '${input}'`);
|
|
4644
|
-
}
|
|
4645
|
-
current += next;
|
|
4646
|
-
continue;
|
|
4647
|
-
}
|
|
4648
|
-
current += char;
|
|
4649
|
-
}
|
|
4650
|
-
if (escape) {
|
|
4651
|
-
throw new Error(`unterminated escape sequence in '${input}'`);
|
|
4652
|
-
}
|
|
4653
|
-
if (quote != null) {
|
|
4654
|
-
throw new Error(`unterminated quoted string in '${input}'`);
|
|
4655
|
-
}
|
|
4656
|
-
if (current) {
|
|
4657
|
-
tokens.push(current);
|
|
4530
|
+
function loadNativeBinding() {
|
|
4531
|
+
if (cachedNativeBinding) return cachedNativeBinding;
|
|
4532
|
+
if (cachedNativeBindingError) throw cachedNativeBindingError;
|
|
4533
|
+
try {
|
|
4534
|
+
const { loadNative } = resolveRequire()("../lib/runtime.cjs");
|
|
4535
|
+
cachedNativeBinding = loadNative();
|
|
4536
|
+
return cachedNativeBinding;
|
|
4537
|
+
} catch (error) {
|
|
4538
|
+
cachedNativeBindingError = error instanceof Error ? error : new Error(String(error));
|
|
4539
|
+
throw cachedNativeBindingError;
|
|
4658
4540
|
}
|
|
4659
|
-
return tokens;
|
|
4660
4541
|
}
|
|
4661
|
-
function
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
while (remaining.startsWith("--")) {
|
|
4665
|
-
const firstSpace = remaining.indexOf(" ");
|
|
4666
|
-
if (firstSpace === -1) {
|
|
4667
|
-
throw new Error(`invalid Dockerfile flag syntax: ${value}`);
|
|
4668
|
-
}
|
|
4669
|
-
const token = remaining.slice(0, firstSpace);
|
|
4670
|
-
const rest = remaining.slice(firstSpace + 1).trimStart();
|
|
4671
|
-
const flagBody = token.slice(2);
|
|
4672
|
-
if (flagBody.includes("=")) {
|
|
4673
|
-
const [key, flagValue2] = flagBody.split(/=(.*)/s, 2);
|
|
4674
|
-
flags[key] = flagValue2;
|
|
4675
|
-
remaining = rest;
|
|
4676
|
-
continue;
|
|
4677
|
-
}
|
|
4678
|
-
const [flagValue, ...restTokens] = shellSplit(rest);
|
|
4679
|
-
if (flagValue == null) {
|
|
4680
|
-
throw new Error(`missing value for Dockerfile flag '${token}'`);
|
|
4681
|
-
}
|
|
4682
|
-
flags[flagBody] = flagValue;
|
|
4683
|
-
remaining = restTokens.join(" ");
|
|
4684
|
-
}
|
|
4685
|
-
return { flags, remaining };
|
|
4542
|
+
function __setNativeBindingForTest(binding) {
|
|
4543
|
+
cachedNativeBinding = binding;
|
|
4544
|
+
cachedNativeBindingError = void 0;
|
|
4686
4545
|
}
|
|
4687
|
-
function
|
|
4688
|
-
|
|
4689
|
-
const tokens = shellSplit(remaining);
|
|
4690
|
-
if (tokens.length === 0) {
|
|
4691
|
-
throw new Error(`line ${lineNumber}: FROM must include a base image`);
|
|
4692
|
-
}
|
|
4693
|
-
if (tokens.length > 1 && tokens[1].toLowerCase() !== "as") {
|
|
4694
|
-
throw new Error(`line ${lineNumber}: unsupported FROM syntax '${value}'`);
|
|
4695
|
-
}
|
|
4696
|
-
return tokens[0];
|
|
4546
|
+
function nonEmpty(value) {
|
|
4547
|
+
return value && value.length > 0 ? value : void 0;
|
|
4697
4548
|
}
|
|
4698
|
-
function
|
|
4699
|
-
|
|
4700
|
-
const
|
|
4701
|
-
for (const logicalLine of logicalDockerfileLines(dockerfileText)) {
|
|
4702
|
-
const { keyword, value } = splitInstruction(
|
|
4703
|
-
logicalLine.line,
|
|
4704
|
-
logicalLine.lineNumber
|
|
4705
|
-
);
|
|
4706
|
-
if (keyword === "FROM") {
|
|
4707
|
-
if (baseImage != null) {
|
|
4708
|
-
throw new Error(
|
|
4709
|
-
`line ${logicalLine.lineNumber}: multi-stage Dockerfiles are not supported for sandbox image creation`
|
|
4710
|
-
);
|
|
4711
|
-
}
|
|
4712
|
-
baseImage = parseFromValue(value, logicalLine.lineNumber);
|
|
4713
|
-
continue;
|
|
4714
|
-
}
|
|
4715
|
-
if (UNSUPPORTED_DOCKERFILE_INSTRUCTIONS.has(keyword)) {
|
|
4716
|
-
throw new Error(
|
|
4717
|
-
`line ${logicalLine.lineNumber}: Dockerfile instruction '${keyword}' is not supported for sandbox image creation`
|
|
4718
|
-
);
|
|
4719
|
-
}
|
|
4720
|
-
instructions.push({
|
|
4721
|
-
keyword,
|
|
4722
|
-
value,
|
|
4723
|
-
lineNumber: logicalLine.lineNumber
|
|
4724
|
-
});
|
|
4725
|
-
}
|
|
4726
|
-
if (!baseImage) {
|
|
4727
|
-
throw new Error("Dockerfile must contain a FROM instruction");
|
|
4728
|
-
}
|
|
4729
|
-
return {
|
|
4730
|
-
dockerfilePath,
|
|
4731
|
-
contextDir,
|
|
4732
|
-
registeredName: registeredName ?? defaultRegisteredName(dockerfilePath),
|
|
4733
|
-
dockerfileText,
|
|
4734
|
-
baseImage,
|
|
4735
|
-
instructions
|
|
4736
|
-
};
|
|
4737
|
-
}
|
|
4738
|
-
async function loadDockerfilePlan(dockerfilePath, registeredName) {
|
|
4739
|
-
const resolvedPath = import_node_path.default.resolve(dockerfilePath);
|
|
4740
|
-
const fileStats = await (0, import_promises.stat)(resolvedPath).catch(() => null);
|
|
4741
|
-
if (!fileStats?.isFile()) {
|
|
4742
|
-
throw new Error(`Dockerfile not found: ${dockerfilePath}`);
|
|
4743
|
-
}
|
|
4744
|
-
const dockerfileText = await (0, import_promises.readFile)(resolvedPath, "utf8");
|
|
4745
|
-
return buildPlanFromDockerfileText(
|
|
4746
|
-
dockerfileText,
|
|
4747
|
-
resolvedPath,
|
|
4748
|
-
import_node_path.default.dirname(resolvedPath),
|
|
4749
|
-
registeredName
|
|
4750
|
-
);
|
|
4751
|
-
}
|
|
4752
|
-
function loadImagePlan(image, options = {}) {
|
|
4753
|
-
const contextDir = import_node_path.default.resolve(options.contextDir ?? process.cwd());
|
|
4754
|
-
const dockerfileText = dockerfileContent(image);
|
|
4755
|
-
const logicalLines = logicalDockerfileLines(dockerfileText);
|
|
4756
|
-
const instructions = image.baseImage == null ? logicalLines : logicalLines.slice(1);
|
|
4549
|
+
function buildContextFromEnv() {
|
|
4550
|
+
const debugRaw = (process.env.TENSORLAKE_DEBUG ?? "").toLowerCase();
|
|
4551
|
+
const debug = ["1", "true", "yes", "on"].includes(debugRaw);
|
|
4757
4552
|
return {
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
return {
|
|
4766
|
-
keyword: parsed.keyword,
|
|
4767
|
-
value: parsed.value,
|
|
4768
|
-
lineNumber
|
|
4769
|
-
};
|
|
4770
|
-
})
|
|
4553
|
+
apiUrl: nonEmpty(process.env.TENSORLAKE_API_URL) ?? "https://api.tensorlake.ai",
|
|
4554
|
+
apiKey: nonEmpty(process.env.TENSORLAKE_API_KEY),
|
|
4555
|
+
personalAccessToken: nonEmpty(process.env.TENSORLAKE_PAT),
|
|
4556
|
+
namespace: nonEmpty(process.env.INDEXIFY_NAMESPACE) ?? "default",
|
|
4557
|
+
organizationId: nonEmpty(process.env.TENSORLAKE_ORGANIZATION_ID),
|
|
4558
|
+
projectId: nonEmpty(process.env.TENSORLAKE_PROJECT_ID),
|
|
4559
|
+
debug
|
|
4771
4560
|
};
|
|
4772
4561
|
}
|
|
4773
|
-
function
|
|
4774
|
-
|
|
4775
|
-
|
|
4562
|
+
function defaultRegisteredName(dockerfilePath) {
|
|
4563
|
+
const parsed = import_node_path.default.parse(dockerfilePath);
|
|
4564
|
+
if (parsed.name.toLowerCase() === "dockerfile") {
|
|
4565
|
+
const parentName = import_node_path.default.basename(import_node_path.default.dirname(dockerfilePath)).trim();
|
|
4566
|
+
return parentName || "sandbox-image";
|
|
4567
|
+
}
|
|
4568
|
+
return parsed.name || "sandbox-image";
|
|
4776
4569
|
}
|
|
4777
4570
|
function noopEmit(_event) {
|
|
4778
4571
|
}
|
|
4779
4572
|
function stderrEmit(event) {
|
|
4780
|
-
const type = typeof event.type === "string" ? event.type : "";
|
|
4781
4573
|
const message = typeof event.message === "string" ? event.message : "";
|
|
4574
|
+
const type = typeof event.type === "string" ? event.type : "";
|
|
4782
4575
|
if (type === "build_log") {
|
|
4783
4576
|
const stream = typeof event.stream === "string" ? event.stream : "stdout";
|
|
4784
4577
|
process.stderr.write(`[${stream}] ${message}
|
|
@@ -4788,557 +4581,103 @@ function stderrEmit(event) {
|
|
|
4788
4581
|
`);
|
|
4789
4582
|
}
|
|
4790
4583
|
}
|
|
4791
|
-
function
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
);
|
|
4584
|
+
function ndjsonStdoutEmit(event) {
|
|
4585
|
+
process.stdout.write(`${JSON.stringify(event)}
|
|
4586
|
+
`);
|
|
4795
4587
|
}
|
|
4796
|
-
function
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
personalAccessToken: process.env.TENSORLAKE_PAT,
|
|
4801
|
-
namespace: process.env.INDEXIFY_NAMESPACE ?? "default",
|
|
4802
|
-
organizationId: process.env.TENSORLAKE_ORGANIZATION_ID,
|
|
4803
|
-
projectId: process.env.TENSORLAKE_PROJECT_ID,
|
|
4804
|
-
debug: debugEnabled()
|
|
4588
|
+
function eventToEmitDict(event) {
|
|
4589
|
+
const out = {
|
|
4590
|
+
type: event.eventType,
|
|
4591
|
+
message: event.message
|
|
4805
4592
|
};
|
|
4593
|
+
if (event.stream != null) out.stream = event.stream;
|
|
4594
|
+
return out;
|
|
4806
4595
|
}
|
|
4807
|
-
function
|
|
4808
|
-
const
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4596
|
+
async function createSandboxImage(source, options = {}, deps = {}) {
|
|
4597
|
+
const emit = deps.emit ?? (options.verbose ? stderrEmit : noopEmit);
|
|
4598
|
+
const context = buildContextFromEnv();
|
|
4599
|
+
let dockerfilePath;
|
|
4600
|
+
let dockerfileText;
|
|
4601
|
+
let nativeContextDir;
|
|
4602
|
+
let effectiveName;
|
|
4603
|
+
if (source instanceof Image) {
|
|
4604
|
+
if (!source.baseImage) {
|
|
4605
|
+
throw new Error("Image must have a baseImage to build");
|
|
4606
|
+
}
|
|
4607
|
+
const resolvedContext = import_node_path.default.resolve(options.contextDir ?? process.cwd());
|
|
4608
|
+
dockerfilePath = import_node_path.default.join(resolvedContext, "Dockerfile");
|
|
4609
|
+
let text = dockerfileContent(source);
|
|
4610
|
+
if (!text.endsWith("\n")) text += "\n";
|
|
4611
|
+
dockerfileText = text;
|
|
4612
|
+
nativeContextDir = resolvedContext;
|
|
4613
|
+
effectiveName = options.registeredName ?? source.name;
|
|
4614
|
+
} else if (typeof source === "string") {
|
|
4615
|
+
const resolvedPath = import_node_path.default.resolve(source);
|
|
4616
|
+
if (!(0, import_node_fs.existsSync)(resolvedPath)) {
|
|
4617
|
+
throw new Error(`Dockerfile not found: ${source}`);
|
|
4618
|
+
}
|
|
4619
|
+
dockerfilePath = resolvedPath;
|
|
4620
|
+
dockerfileText = void 0;
|
|
4621
|
+
nativeContextDir = void 0;
|
|
4622
|
+
effectiveName = options.registeredName ?? defaultRegisteredName(resolvedPath);
|
|
4623
|
+
} else {
|
|
4624
|
+
throw new TypeError(
|
|
4625
|
+
`source must be an Image or a Dockerfile path, got ${typeof source}`
|
|
4626
|
+
);
|
|
4831
4627
|
}
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
const response = await fetch(url, init);
|
|
4836
|
-
if (!response.ok) {
|
|
4837
|
-
throw new Error(
|
|
4838
|
-
`${errorPrefix} (HTTP ${response.status}): ${await response.text()}`
|
|
4628
|
+
if (effectiveName === DEFAULT_IMAGE_NAME2) {
|
|
4629
|
+
console.warn(
|
|
4630
|
+
`Building sandbox image with the default name "${DEFAULT_IMAGE_NAME2}". Pass \`registeredName\` or \`Image({ name })\` to avoid collisions with other default-named images in this project.`
|
|
4839
4631
|
);
|
|
4840
4632
|
}
|
|
4841
|
-
const text = await response.text();
|
|
4842
|
-
return text ? JSON.parse(text) : {};
|
|
4843
|
-
}
|
|
4844
|
-
async function resolveBuildContext(context) {
|
|
4845
4633
|
const bearerToken = context.apiKey ?? context.personalAccessToken;
|
|
4846
4634
|
if (!bearerToken) {
|
|
4847
|
-
throw new Error("Missing TENSORLAKE_API_KEY or TENSORLAKE_PAT.");
|
|
4848
|
-
}
|
|
4849
|
-
if (context.apiKey) {
|
|
4850
|
-
const scope = await requestJson(
|
|
4851
|
-
`${baseApiUrl(context)}/platform/v1/keys/introspect`,
|
|
4852
|
-
{
|
|
4853
|
-
method: "POST",
|
|
4854
|
-
headers: {
|
|
4855
|
-
Authorization: `Bearer ${bearerToken}`,
|
|
4856
|
-
"Content-Type": "application/json"
|
|
4857
|
-
}
|
|
4858
|
-
},
|
|
4859
|
-
"API key introspection failed"
|
|
4860
|
-
);
|
|
4861
|
-
if (!scope.organizationId || !scope.projectId) {
|
|
4862
|
-
throw new Error("API key introspection response is missing organizationId or projectId");
|
|
4863
|
-
}
|
|
4864
|
-
return {
|
|
4865
|
-
...context,
|
|
4866
|
-
bearerToken,
|
|
4867
|
-
organizationId: scope.organizationId,
|
|
4868
|
-
projectId: scope.projectId,
|
|
4869
|
-
useScopeHeaders: false
|
|
4870
|
-
};
|
|
4871
|
-
}
|
|
4872
|
-
if (!context.organizationId || !context.projectId) {
|
|
4873
|
-
throw new Error(
|
|
4874
|
-
"Personal Access Token authentication requires TENSORLAKE_ORGANIZATION_ID and TENSORLAKE_PROJECT_ID to be set (e.g. via 'tl login && tl init'). To skip this requirement, authenticate with TENSORLAKE_API_KEY instead \u2014 API keys are bound to a single project at creation."
|
|
4875
|
-
);
|
|
4876
|
-
}
|
|
4877
|
-
return {
|
|
4878
|
-
...context,
|
|
4879
|
-
bearerToken,
|
|
4880
|
-
organizationId: context.organizationId,
|
|
4881
|
-
projectId: context.projectId,
|
|
4882
|
-
useScopeHeaders: true
|
|
4883
|
-
};
|
|
4884
|
-
}
|
|
4885
|
-
async function prepareRootfsBuild(context, plan, isPublic) {
|
|
4886
|
-
if (!plan.baseImage) {
|
|
4887
|
-
throw new Error("Sandbox image builds require a Dockerfile FROM image or Image baseImage");
|
|
4888
|
-
}
|
|
4889
|
-
const spec = await requestJson(
|
|
4890
|
-
`${baseApiUrl(context)}${scopedBuildsPath(context)}`,
|
|
4891
|
-
{
|
|
4892
|
-
method: "POST",
|
|
4893
|
-
headers: platformHeaders(context),
|
|
4894
|
-
body: JSON.stringify({
|
|
4895
|
-
name: plan.registeredName,
|
|
4896
|
-
dockerfile: plan.dockerfileText,
|
|
4897
|
-
baseImage: plan.baseImage,
|
|
4898
|
-
public: isPublic
|
|
4899
|
-
})
|
|
4900
|
-
},
|
|
4901
|
-
"failed to prepare sandbox image build"
|
|
4902
|
-
);
|
|
4903
|
-
return { prepared: parsePreparedBuild(spec), spec };
|
|
4904
|
-
}
|
|
4905
|
-
function parsePreparedBuild(raw) {
|
|
4906
|
-
const builder = raw.builder;
|
|
4907
|
-
if (!builder) {
|
|
4908
|
-
throw new Error("platform API response is missing rootfs builder configuration");
|
|
4909
|
-
}
|
|
4910
|
-
const prepared = {
|
|
4911
|
-
...raw,
|
|
4912
|
-
buildId: requiredString(raw, "buildId"),
|
|
4913
|
-
snapshotId: requiredString(raw, "snapshotId"),
|
|
4914
|
-
snapshotUri: requiredString(raw, "snapshotUri"),
|
|
4915
|
-
rootfsNodeKind: requiredString(raw, "rootfsNodeKind"),
|
|
4916
|
-
builder: {
|
|
4917
|
-
image: requiredString(builder, "image"),
|
|
4918
|
-
command: requiredString(builder, "command"),
|
|
4919
|
-
cpus: requiredNumber(builder, "cpus"),
|
|
4920
|
-
memoryMb: requiredNumber(builder, "memoryMb"),
|
|
4921
|
-
diskMb: requiredNumber(builder, "diskMb")
|
|
4922
|
-
}
|
|
4923
|
-
};
|
|
4924
|
-
const parent = raw.parent;
|
|
4925
|
-
if (parent != null) {
|
|
4926
|
-
prepared.parent = {
|
|
4927
|
-
parentManifestUri: requiredString(parent, "parentManifestUri"),
|
|
4928
|
-
rootfsDiskBytes: optionalNumber(parent, "rootfsDiskBytes")
|
|
4929
|
-
};
|
|
4635
|
+
throw new Error("Missing TENSORLAKE_API_KEY or TENSORLAKE_PAT credentials.");
|
|
4930
4636
|
}
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
`${baseApiUrl(context)}${scopedBuildsPath(context)}/${encodeURIComponent(buildId)}/complete`,
|
|
4637
|
+
emit({ type: "status", message: `Building image '${effectiveName}'...` });
|
|
4638
|
+
const binding = loadNativeBinding();
|
|
4639
|
+
let emitError;
|
|
4640
|
+
const resultJson = await binding.buildSandboxImage(
|
|
4936
4641
|
{
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4642
|
+
apiUrl: context.apiUrl,
|
|
4643
|
+
bearerToken,
|
|
4644
|
+
dockerfilePath,
|
|
4645
|
+
registeredName: effectiveName,
|
|
4646
|
+
diskMb: options.diskMb,
|
|
4647
|
+
builderDiskMb: options.builderDiskMb,
|
|
4648
|
+
cpus: options.cpus,
|
|
4649
|
+
memoryMb: options.memoryMb,
|
|
4650
|
+
isPublic: options.isPublic ?? false,
|
|
4651
|
+
organizationId: context.organizationId,
|
|
4652
|
+
projectId: context.projectId,
|
|
4653
|
+
namespace: context.namespace,
|
|
4654
|
+
useScopeHeaders: context.personalAccessToken != null && context.apiKey == null,
|
|
4655
|
+
userAgent: void 0,
|
|
4656
|
+
dockerfileText,
|
|
4657
|
+
contextDir: nativeContextDir
|
|
4940
4658
|
},
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
try {
|
|
4948
|
-
const content = await (0, import_promises.readFile)(configPath, "utf8");
|
|
4949
|
-
const parsed = JSON.parse(content);
|
|
4950
|
-
const auths = parsed.auths;
|
|
4951
|
-
if (auths != null && Object.keys(auths).length > 0) {
|
|
4952
|
-
return JSON.stringify({ auths });
|
|
4953
|
-
}
|
|
4954
|
-
} catch (error) {
|
|
4955
|
-
if (error.code === "ENOENT") {
|
|
4956
|
-
return void 0;
|
|
4957
|
-
}
|
|
4958
|
-
throw error;
|
|
4959
|
-
}
|
|
4960
|
-
return void 0;
|
|
4961
|
-
}
|
|
4962
|
-
function rootfsDiskBytes(diskMb, prepared) {
|
|
4963
|
-
if (diskMb != null) {
|
|
4964
|
-
return diskMb * 1024 * 1024;
|
|
4965
|
-
}
|
|
4966
|
-
if (prepared.parent != null) {
|
|
4967
|
-
if (prepared.parent.rootfsDiskBytes == null) {
|
|
4968
|
-
throw new Error(
|
|
4969
|
-
"platform API did not return parent rootfsDiskBytes for diff build; pass diskMb explicitly or update Platform API"
|
|
4970
|
-
);
|
|
4971
|
-
}
|
|
4972
|
-
return prepared.parent.rootfsDiskBytes;
|
|
4973
|
-
}
|
|
4974
|
-
return DEFAULT_ROOTFS_DISK_MB * 1024 * 1024;
|
|
4975
|
-
}
|
|
4976
|
-
function rootfsDiskBytesToMb(bytes) {
|
|
4977
|
-
return Math.ceil(bytes / (1024 * 1024));
|
|
4978
|
-
}
|
|
4979
|
-
async function buildRootfsSpec(preparedSpec, prepared, plan, diskMb) {
|
|
4980
|
-
const spec = {
|
|
4981
|
-
...preparedSpec,
|
|
4982
|
-
dockerfile: plan.dockerfileText,
|
|
4983
|
-
contextDir: REMOTE_CONTEXT_DIR,
|
|
4984
|
-
baseImage: plan.baseImage,
|
|
4985
|
-
rootfsDiskBytes: rootfsDiskBytes(diskMb, prepared)
|
|
4986
|
-
};
|
|
4987
|
-
const dockerConfigJson = await resolvedDockerConfigJson();
|
|
4988
|
-
if (dockerConfigJson != null) {
|
|
4989
|
-
spec.dockerConfigJson = dockerConfigJson;
|
|
4990
|
-
}
|
|
4991
|
-
return spec;
|
|
4992
|
-
}
|
|
4993
|
-
function rootfsBuilderExecutable(executable) {
|
|
4994
|
-
return executable === ROOTFS_BUILDER_COMMAND ? `${ROOTFS_BUILDER_BIN_DIR}/${ROOTFS_BUILDER_COMMAND}` : executable;
|
|
4995
|
-
}
|
|
4996
|
-
function rootfsBuilderEnv() {
|
|
4997
|
-
return { PATH: ROOTFS_BUILDER_PATH };
|
|
4998
|
-
}
|
|
4999
|
-
async function runRootfsBuilder(sandbox, command, emit, sleep3) {
|
|
5000
|
-
const parts = shellSplit(command);
|
|
5001
|
-
const [executable, ...commandArgs] = parts;
|
|
5002
|
-
if (!executable) {
|
|
5003
|
-
throw new Error("empty rootfs builder command returned by platform API");
|
|
5004
|
-
}
|
|
5005
|
-
await runStreaming(
|
|
5006
|
-
sandbox,
|
|
5007
|
-
emit,
|
|
5008
|
-
sleep3,
|
|
5009
|
-
rootfsBuilderExecutable(executable),
|
|
5010
|
-
[...commandArgs, "--spec", REMOTE_SPEC_PATH, "--metadata-out", REMOTE_METADATA_PATH],
|
|
5011
|
-
rootfsBuilderEnv(),
|
|
5012
|
-
REMOTE_BUILD_DIR
|
|
5013
|
-
);
|
|
5014
|
-
}
|
|
5015
|
-
function metadataString(metadata, snakeKey, camelKey) {
|
|
5016
|
-
const value = metadata[snakeKey] ?? metadata[camelKey];
|
|
5017
|
-
return typeof value === "string" ? value : void 0;
|
|
5018
|
-
}
|
|
5019
|
-
function metadataNumber(metadata, snakeKey, camelKey) {
|
|
5020
|
-
const value = metadata[snakeKey] ?? metadata[camelKey];
|
|
5021
|
-
if (typeof value === "number") {
|
|
5022
|
-
return value;
|
|
5023
|
-
}
|
|
5024
|
-
if (typeof value === "string" && value.trim()) {
|
|
5025
|
-
const parsed = Number(value);
|
|
5026
|
-
return Number.isFinite(parsed) ? parsed : void 0;
|
|
5027
|
-
}
|
|
5028
|
-
return void 0;
|
|
5029
|
-
}
|
|
5030
|
-
function completeRequestFromMetadata(prepared, metadata) {
|
|
5031
|
-
const rootfsNodeKind = metadataString(metadata, "rootfs_node_kind", "rootfsNodeKind") ?? prepared.rootfsNodeKind;
|
|
5032
|
-
const parentManifestUri = metadataString(metadata, "parent_manifest_uri", "parentManifestUri") ?? (rootfsNodeKind === "diff" ? prepared.parent?.parentManifestUri : void 0);
|
|
5033
|
-
if (rootfsNodeKind === "diff" && parentManifestUri == null) {
|
|
5034
|
-
throw new Error("rootfs diff build completed without parent_manifest_uri");
|
|
5035
|
-
}
|
|
5036
|
-
const snapshotFormatVersion = metadataString(
|
|
5037
|
-
metadata,
|
|
5038
|
-
"snapshot_format_version",
|
|
5039
|
-
"snapshotFormatVersion"
|
|
5040
|
-
);
|
|
5041
|
-
const snapshotSizeBytes = metadataNumber(
|
|
5042
|
-
metadata,
|
|
5043
|
-
"snapshot_size_bytes",
|
|
5044
|
-
"snapshotSizeBytes"
|
|
5045
|
-
);
|
|
5046
|
-
const rootfsDiskBytesValue = metadataNumber(
|
|
5047
|
-
metadata,
|
|
5048
|
-
"rootfs_disk_bytes",
|
|
5049
|
-
"rootfsDiskBytes"
|
|
5050
|
-
);
|
|
5051
|
-
if (!snapshotFormatVersion) {
|
|
5052
|
-
throw new Error("rootfs builder metadata is missing snapshot_format_version");
|
|
5053
|
-
}
|
|
5054
|
-
if (snapshotSizeBytes == null) {
|
|
5055
|
-
throw new Error("rootfs builder metadata is missing numeric snapshot_size_bytes");
|
|
5056
|
-
}
|
|
5057
|
-
if (rootfsDiskBytesValue == null) {
|
|
5058
|
-
throw new Error("rootfs builder metadata is missing numeric rootfs_disk_bytes");
|
|
5059
|
-
}
|
|
5060
|
-
return {
|
|
5061
|
-
snapshotId: metadataString(metadata, "snapshot_id", "snapshotId") ?? prepared.snapshotId,
|
|
5062
|
-
snapshotUri: metadataString(metadata, "snapshot_uri", "snapshotUri") ?? prepared.snapshotUri,
|
|
5063
|
-
snapshotFormatVersion,
|
|
5064
|
-
snapshotSizeBytes,
|
|
5065
|
-
rootfsDiskBytes: rootfsDiskBytesValue,
|
|
5066
|
-
rootfsNodeKind,
|
|
5067
|
-
...parentManifestUri ? { parentManifestUri } : {}
|
|
5068
|
-
};
|
|
5069
|
-
}
|
|
5070
|
-
function requiredString(object, key) {
|
|
5071
|
-
const value = object[key];
|
|
5072
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
5073
|
-
throw new Error(`expected '${key}' to be a non-empty string`);
|
|
5074
|
-
}
|
|
5075
|
-
return value;
|
|
5076
|
-
}
|
|
5077
|
-
function requiredNumber(object, key) {
|
|
5078
|
-
const value = object[key];
|
|
5079
|
-
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
5080
|
-
throw new Error(`expected '${key}' to be a finite number`);
|
|
5081
|
-
}
|
|
5082
|
-
return value;
|
|
5083
|
-
}
|
|
5084
|
-
function optionalNumber(object, key) {
|
|
5085
|
-
const value = object[key];
|
|
5086
|
-
if (value == null) {
|
|
5087
|
-
return void 0;
|
|
5088
|
-
}
|
|
5089
|
-
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
5090
|
-
throw new Error(`expected '${key}' to be a finite number`);
|
|
5091
|
-
}
|
|
5092
|
-
return value;
|
|
5093
|
-
}
|
|
5094
|
-
async function runChecked(sandbox, command, args, env, workingDir, user) {
|
|
5095
|
-
const options = {
|
|
5096
|
-
args,
|
|
5097
|
-
env,
|
|
5098
|
-
workingDir
|
|
5099
|
-
};
|
|
5100
|
-
if (user != null) {
|
|
5101
|
-
options.user = user;
|
|
5102
|
-
}
|
|
5103
|
-
const result = await sandbox.run(command, options);
|
|
5104
|
-
if (result.exitCode !== 0) {
|
|
5105
|
-
throw new Error(
|
|
5106
|
-
`Command '${command} ${args.join(" ")}' failed with exit code ${result.exitCode}`
|
|
5107
|
-
);
|
|
5108
|
-
}
|
|
5109
|
-
return result;
|
|
5110
|
-
}
|
|
5111
|
-
async function runStreaming(sandbox, emit, sleep3, command, args = [], env, workingDir) {
|
|
5112
|
-
const proc = await sandbox.startProcess(command, {
|
|
5113
|
-
args,
|
|
5114
|
-
env,
|
|
5115
|
-
workingDir,
|
|
5116
|
-
user: ROOTFS_BUILDER_PROCESS_USER
|
|
5117
|
-
});
|
|
5118
|
-
let stdoutSeen = 0;
|
|
5119
|
-
let stderrSeen = 0;
|
|
5120
|
-
let info;
|
|
5121
|
-
while (true) {
|
|
5122
|
-
const stdoutResp = await sandbox.getStdout(proc.pid);
|
|
5123
|
-
emitOutputLines(emit, "stdout", stdoutResp, stdoutSeen);
|
|
5124
|
-
stdoutSeen = stdoutResp.lines.length;
|
|
5125
|
-
const stderrResp = await sandbox.getStderr(proc.pid);
|
|
5126
|
-
emitOutputLines(emit, "stderr", stderrResp, stderrSeen);
|
|
5127
|
-
stderrSeen = stderrResp.lines.length;
|
|
5128
|
-
info = await sandbox.getProcess(proc.pid);
|
|
5129
|
-
if (info.status !== "running" /* RUNNING */) {
|
|
5130
|
-
const finalStdout = await sandbox.getStdout(proc.pid);
|
|
5131
|
-
emitOutputLines(emit, "stdout", finalStdout, stdoutSeen);
|
|
5132
|
-
stdoutSeen = finalStdout.lines.length;
|
|
5133
|
-
const finalStderr = await sandbox.getStderr(proc.pid);
|
|
5134
|
-
emitOutputLines(emit, "stderr", finalStderr, stderrSeen);
|
|
5135
|
-
break;
|
|
5136
|
-
}
|
|
5137
|
-
await sleep3(300);
|
|
5138
|
-
}
|
|
5139
|
-
for (let i = 0; i < 10; i++) {
|
|
5140
|
-
if (info.exitCode != null || info.signal != null) {
|
|
5141
|
-
break;
|
|
4659
|
+
(event) => {
|
|
4660
|
+
try {
|
|
4661
|
+
emit(eventToEmitDict(event));
|
|
4662
|
+
} catch (error) {
|
|
4663
|
+
emitError ??= error;
|
|
4664
|
+
}
|
|
5142
4665
|
}
|
|
5143
|
-
await sleep3(200);
|
|
5144
|
-
info = await sandbox.getProcess(proc.pid);
|
|
5145
|
-
}
|
|
5146
|
-
const exitCode = info.exitCode != null ? info.exitCode : info.signal != null ? -info.signal : 0;
|
|
5147
|
-
if (exitCode !== 0) {
|
|
5148
|
-
throw new Error(
|
|
5149
|
-
`Command '${command} ${args.join(" ")}' failed with exit code ${exitCode}`
|
|
5150
|
-
);
|
|
5151
|
-
}
|
|
5152
|
-
}
|
|
5153
|
-
function emitOutputLines(emit, stream, response, seen) {
|
|
5154
|
-
for (const line of response.lines.slice(seen)) {
|
|
5155
|
-
emit({ type: "build_log", stream, message: line });
|
|
5156
|
-
}
|
|
5157
|
-
}
|
|
5158
|
-
async function ensureRemoteBuildRoot(sandbox) {
|
|
5159
|
-
await runChecked(
|
|
5160
|
-
sandbox,
|
|
5161
|
-
"mkdir",
|
|
5162
|
-
["-p", REMOTE_BUILD_DIR],
|
|
5163
|
-
void 0,
|
|
5164
|
-
void 0,
|
|
5165
|
-
ROOTFS_BUILDER_PROCESS_USER
|
|
5166
4666
|
);
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
"chmod",
|
|
5170
|
-
["0777", REMOTE_BUILD_DIR],
|
|
5171
|
-
void 0,
|
|
5172
|
-
void 0,
|
|
5173
|
-
ROOTFS_BUILDER_PROCESS_USER
|
|
5174
|
-
);
|
|
5175
|
-
}
|
|
5176
|
-
async function ensureRemoteDirectory(sandbox, remotePath) {
|
|
5177
|
-
await runChecked(sandbox, "mkdir", ["-p", remotePath]);
|
|
5178
|
-
}
|
|
5179
|
-
async function ensureRemoteParentDir(sandbox, remotePath) {
|
|
5180
|
-
await ensureRemoteDirectory(sandbox, import_node_path.default.posix.dirname(remotePath));
|
|
5181
|
-
}
|
|
5182
|
-
async function copyLocalPathToSandbox(sandbox, localPath, remotePath) {
|
|
5183
|
-
const fileStats = await (0, import_promises.stat)(localPath).catch(() => null);
|
|
5184
|
-
if (!fileStats) {
|
|
5185
|
-
throw new Error(`Local path not found: ${localPath}`);
|
|
5186
|
-
}
|
|
5187
|
-
if (fileStats.isFile()) {
|
|
5188
|
-
await ensureRemoteParentDir(sandbox, remotePath);
|
|
5189
|
-
await sandbox.writeFile(remotePath, await (0, import_promises.readFile)(localPath));
|
|
5190
|
-
return;
|
|
5191
|
-
}
|
|
5192
|
-
if (!fileStats.isDirectory()) {
|
|
5193
|
-
throw new Error(`Local path not found: ${localPath}`);
|
|
5194
|
-
}
|
|
5195
|
-
const entries = await (0, import_promises.readdir)(localPath, { withFileTypes: true });
|
|
5196
|
-
for (const entry of entries) {
|
|
5197
|
-
const sourcePath = import_node_path.default.join(localPath, entry.name);
|
|
5198
|
-
const destinationPath = import_node_path.default.posix.join(remotePath, entry.name);
|
|
5199
|
-
if (entry.isDirectory()) {
|
|
5200
|
-
await ensureRemoteDirectory(sandbox, destinationPath);
|
|
5201
|
-
await copyLocalPathToSandbox(sandbox, sourcePath, destinationPath);
|
|
5202
|
-
} else if (entry.isFile()) {
|
|
5203
|
-
await ensureRemoteParentDir(sandbox, destinationPath);
|
|
5204
|
-
await sandbox.writeFile(destinationPath, await (0, import_promises.readFile)(sourcePath));
|
|
5205
|
-
}
|
|
5206
|
-
}
|
|
5207
|
-
}
|
|
5208
|
-
async function registerImage(context, name, dockerfile, snapshotId, snapshotSandboxId, snapshotUri, snapshotSizeBytes, rootfsDiskBytes2, isPublic, snapshotFormatVersion) {
|
|
5209
|
-
const bearerToken = context.apiKey ?? context.personalAccessToken;
|
|
5210
|
-
if (!bearerToken) {
|
|
5211
|
-
throw new Error("Missing TENSORLAKE_API_KEY or TENSORLAKE_PAT.");
|
|
4667
|
+
if (emitError != null) {
|
|
4668
|
+
throw emitError;
|
|
5212
4669
|
}
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
"Content-Type": "application/json"
|
|
5217
|
-
};
|
|
5218
|
-
let url;
|
|
5219
|
-
if (context.apiKey) {
|
|
5220
|
-
url = `${baseUrl}/platform/v1/sandbox-templates`;
|
|
5221
|
-
} else {
|
|
5222
|
-
if (!context.organizationId || !context.projectId) {
|
|
5223
|
-
throw new Error(
|
|
5224
|
-
"Personal Access Token authentication requires TENSORLAKE_ORGANIZATION_ID and TENSORLAKE_PROJECT_ID to be set (e.g. via 'tl login && tl init'). To skip this requirement, authenticate with TENSORLAKE_API_KEY instead \u2014 API keys are bound to a single project at creation."
|
|
5225
|
-
);
|
|
5226
|
-
}
|
|
5227
|
-
url = `${baseUrl}/platform/v1/organizations/${encodeURIComponent(context.organizationId)}/projects/${encodeURIComponent(context.projectId)}/sandbox-templates`;
|
|
5228
|
-
headers["X-Forwarded-Organization-Id"] = context.organizationId;
|
|
5229
|
-
headers["X-Forwarded-Project-Id"] = context.projectId;
|
|
5230
|
-
}
|
|
5231
|
-
const response = await fetch(url, {
|
|
5232
|
-
method: "POST",
|
|
5233
|
-
headers,
|
|
5234
|
-
body: JSON.stringify({
|
|
5235
|
-
name,
|
|
5236
|
-
dockerfile,
|
|
5237
|
-
snapshotId,
|
|
5238
|
-
snapshotSandboxId,
|
|
5239
|
-
snapshotUri,
|
|
5240
|
-
...snapshotFormatVersion ? { snapshotFormatVersion } : {},
|
|
5241
|
-
snapshotSizeBytes,
|
|
5242
|
-
rootfsDiskBytes: rootfsDiskBytes2,
|
|
5243
|
-
public: isPublic
|
|
5244
|
-
})
|
|
5245
|
-
});
|
|
5246
|
-
if (!response.ok) {
|
|
5247
|
-
throw new Error(
|
|
5248
|
-
`${response.status} ${response.statusText}: ${await response.text()}`
|
|
5249
|
-
);
|
|
4670
|
+
let result = {};
|
|
4671
|
+
if (resultJson.trim().length > 0) {
|
|
4672
|
+
result = JSON.parse(resultJson);
|
|
5250
4673
|
}
|
|
5251
|
-
const text = await response.text();
|
|
5252
|
-
return text ? JSON.parse(text) : {};
|
|
5253
|
-
}
|
|
5254
|
-
async function createSandboxImage(source, options = {}, deps = {}) {
|
|
5255
|
-
const emit = deps.emit ?? (options.verbose ? stderrEmit : noopEmit);
|
|
5256
|
-
const sleep3 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
5257
|
-
const context = buildContextFromEnv();
|
|
5258
|
-
const clientFactory = deps.createClient ?? createDefaultClient;
|
|
5259
|
-
const sourceLabel = typeof source === "string" ? source : `Image(${source.name})`;
|
|
5260
|
-
emit({ type: "status", message: `Loading ${sourceLabel}...` });
|
|
5261
|
-
const plan = typeof source === "string" ? await loadDockerfilePlan(source, options.registeredName) : loadImagePlan(source, options);
|
|
5262
4674
|
emit({
|
|
5263
|
-
type: "
|
|
5264
|
-
|
|
4675
|
+
type: "image_registered",
|
|
4676
|
+
name: effectiveName,
|
|
4677
|
+
image_id: typeof result.id === "string" && result.id || typeof result.templateId === "string" && result.templateId || ""
|
|
5265
4678
|
});
|
|
5266
|
-
emit({ type: "
|
|
5267
|
-
|
|
5268
|
-
const { prepared, spec: preparedSpec } = await prepareRootfsBuild(
|
|
5269
|
-
resolvedContext,
|
|
5270
|
-
plan,
|
|
5271
|
-
options.isPublic ?? false
|
|
5272
|
-
);
|
|
5273
|
-
emit({
|
|
5274
|
-
type: "status",
|
|
5275
|
-
message: prepared.rootfsNodeKind === "diff" ? "Build mode: RootfsDiff" : "Build mode: RootfsBase"
|
|
5276
|
-
});
|
|
5277
|
-
const client = clientFactory(context);
|
|
5278
|
-
let sandbox;
|
|
5279
|
-
try {
|
|
5280
|
-
const outputRootfsDiskBytes = rootfsDiskBytes(options.diskMb, prepared);
|
|
5281
|
-
const builderDiskMb = Math.max(
|
|
5282
|
-
rootfsDiskBytesToMb(outputRootfsDiskBytes),
|
|
5283
|
-
options.builderDiskMb ?? prepared.builder.diskMb
|
|
5284
|
-
);
|
|
5285
|
-
emit({
|
|
5286
|
-
type: "status",
|
|
5287
|
-
message: `Creating rootfs builder sandbox from ${prepared.builder.image}...`
|
|
5288
|
-
});
|
|
5289
|
-
sandbox = await client.createAndConnect({
|
|
5290
|
-
image: prepared.builder.image,
|
|
5291
|
-
cpus: options.cpus ?? prepared.builder.cpus,
|
|
5292
|
-
memoryMb: options.memoryMb ?? prepared.builder.memoryMb,
|
|
5293
|
-
diskMb: builderDiskMb
|
|
5294
|
-
});
|
|
5295
|
-
emit({
|
|
5296
|
-
type: "status",
|
|
5297
|
-
message: `Rootfs builder sandbox ${sandbox.sandboxId} is running`
|
|
5298
|
-
});
|
|
5299
|
-
emit({ type: "status", message: "Uploading build context..." });
|
|
5300
|
-
await ensureRemoteBuildRoot(sandbox);
|
|
5301
|
-
await copyLocalPathToSandbox(sandbox, plan.contextDir, REMOTE_CONTEXT_DIR);
|
|
5302
|
-
const spec = await buildRootfsSpec(
|
|
5303
|
-
preparedSpec,
|
|
5304
|
-
prepared,
|
|
5305
|
-
plan,
|
|
5306
|
-
options.diskMb
|
|
5307
|
-
);
|
|
5308
|
-
await ensureRemoteParentDir(sandbox, REMOTE_SPEC_PATH);
|
|
5309
|
-
await sandbox.writeFile(
|
|
5310
|
-
REMOTE_SPEC_PATH,
|
|
5311
|
-
new TextEncoder().encode(JSON.stringify(spec, null, 2))
|
|
5312
|
-
);
|
|
5313
|
-
emit({ type: "status", message: "Running offline rootfs builder..." });
|
|
5314
|
-
await runRootfsBuilder(sandbox, prepared.builder.command, emit, sleep3);
|
|
5315
|
-
const metadataBytes = await sandbox.readFile(REMOTE_METADATA_PATH);
|
|
5316
|
-
const metadata = JSON.parse(
|
|
5317
|
-
new TextDecoder().decode(metadataBytes)
|
|
5318
|
-
);
|
|
5319
|
-
const completeRequest = completeRequestFromMetadata(prepared, metadata);
|
|
5320
|
-
emit({ type: "status", message: "Completing image registration..." });
|
|
5321
|
-
const result = await completeRootfsBuild(
|
|
5322
|
-
resolvedContext,
|
|
5323
|
-
prepared.buildId,
|
|
5324
|
-
completeRequest
|
|
5325
|
-
);
|
|
5326
|
-
emit({
|
|
5327
|
-
type: "image_registered",
|
|
5328
|
-
name: plan.registeredName,
|
|
5329
|
-
image_id: typeof result.id === "string" && result.id || typeof result.templateId === "string" && result.templateId || ""
|
|
5330
|
-
});
|
|
5331
|
-
emit({ type: "done" });
|
|
5332
|
-
return result;
|
|
5333
|
-
} finally {
|
|
5334
|
-
if (sandbox) {
|
|
5335
|
-
try {
|
|
5336
|
-
await sandbox.terminate();
|
|
5337
|
-
} catch {
|
|
5338
|
-
}
|
|
5339
|
-
}
|
|
5340
|
-
client.close();
|
|
5341
|
-
}
|
|
4679
|
+
emit({ type: "done" });
|
|
4680
|
+
return result;
|
|
5342
4681
|
}
|
|
5343
4682
|
async function runCreateSandboxImageCli(argv = process.argv.slice(2)) {
|
|
5344
4683
|
const parsed = (0, import_node_util.parseArgs)({
|
|
@@ -5355,7 +4694,9 @@ async function runCreateSandboxImageCli(argv = process.argv.slice(2)) {
|
|
|
5355
4694
|
});
|
|
5356
4695
|
const dockerfilePath = parsed.positionals[0];
|
|
5357
4696
|
if (!dockerfilePath) {
|
|
5358
|
-
throw new Error(
|
|
4697
|
+
throw new Error(
|
|
4698
|
+
"Usage: tensorlake-create-sandbox-image <dockerfile_path> [--name NAME] [--cpus N] [--memory MB] [--disk_mb MB] [--builder_disk_mb MB] [--public]"
|
|
4699
|
+
);
|
|
5359
4700
|
}
|
|
5360
4701
|
const cpus = parsed.values.cpus != null ? Number(parsed.values.cpus) : void 0;
|
|
5361
4702
|
const memoryMb = parsed.values.memory != null ? Number(parsed.values.memory) : void 0;
|
|
@@ -5388,32 +4729,17 @@ async function runCreateSandboxImageCli(argv = process.argv.slice(2)) {
|
|
|
5388
4729
|
{ emit: ndjsonStdoutEmit }
|
|
5389
4730
|
);
|
|
5390
4731
|
}
|
|
5391
|
-
var
|
|
4732
|
+
var import_node_module, import_node_path, import_node_fs, import_node_util, import_meta, cachedNativeBinding, cachedNativeBindingError, DEFAULT_IMAGE_NAME2;
|
|
5392
4733
|
var init_sandbox_image = __esm({
|
|
5393
4734
|
"src/sandbox-image.ts"() {
|
|
5394
4735
|
"use strict";
|
|
5395
|
-
|
|
5396
|
-
import_node_os = require("os");
|
|
4736
|
+
import_node_module = require("module");
|
|
5397
4737
|
import_node_path = __toESM(require("path"), 1);
|
|
4738
|
+
import_node_fs = require("fs");
|
|
5398
4739
|
import_node_util = require("util");
|
|
5399
|
-
init_models();
|
|
5400
|
-
init_client();
|
|
5401
4740
|
init_image();
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
REMOTE_CONTEXT_DIR = "/var/lib/tensorlake/rootfs-builder/build/context";
|
|
5405
|
-
REMOTE_SPEC_PATH = "/var/lib/tensorlake/rootfs-builder/build/spec.json";
|
|
5406
|
-
REMOTE_METADATA_PATH = "/var/lib/tensorlake/rootfs-builder/build/metadata.json";
|
|
5407
|
-
ROOTFS_BUILDER_BIN_DIR = "/usr/local/bin";
|
|
5408
|
-
ROOTFS_BUILDER_PATH = "/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
|
|
5409
|
-
ROOTFS_BUILDER_COMMAND = "tl-rootfs-build";
|
|
5410
|
-
ROOTFS_BUILDER_PROCESS_USER = "root";
|
|
5411
|
-
UNSUPPORTED_DOCKERFILE_INSTRUCTIONS = /* @__PURE__ */ new Set([
|
|
5412
|
-
"ARG",
|
|
5413
|
-
"ONBUILD",
|
|
5414
|
-
"SHELL",
|
|
5415
|
-
"USER"
|
|
5416
|
-
]);
|
|
4741
|
+
import_meta = {};
|
|
4742
|
+
DEFAULT_IMAGE_NAME2 = "default";
|
|
5417
4743
|
}
|
|
5418
4744
|
});
|
|
5419
4745
|
|