@hyperbrowser/sdk 0.93.0 → 1.0.1
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/README.md +190 -0
- package/dist/client.d.ts +2 -20
- package/dist/client.js +5 -17
- package/dist/error.d.ts +27 -0
- package/dist/error.js +32 -0
- package/dist/image-builds.d.ts +6 -0
- package/dist/image-builds.js +12 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +12 -1
- package/dist/retry.d.ts +14 -0
- package/dist/retry.js +49 -0
- package/dist/sandbox/base.d.ts +27 -0
- package/dist/sandbox/base.js +298 -99
- package/dist/sandbox/files.d.ts +24 -25
- package/dist/sandbox/files.js +146 -37
- package/dist/sandbox/image-build/artifacts.d.ts +16 -0
- package/dist/sandbox/image-build/artifacts.js +16 -0
- package/dist/sandbox/image-build/blake2b.d.ts +2 -0
- package/dist/sandbox/image-build/blake2b.js +89 -0
- package/dist/sandbox/image-build/common.d.ts +19 -0
- package/dist/sandbox/image-build/common.js +109 -0
- package/dist/sandbox/image-build/context.d.ts +43 -0
- package/dist/sandbox/image-build/context.js +599 -0
- package/dist/sandbox/image-build/docker-image.d.ts +34 -0
- package/dist/sandbox/image-build/docker-image.js +472 -0
- package/dist/sandbox/image-build/dockerfile-analysis.d.ts +17 -0
- package/dist/sandbox/image-build/dockerfile-analysis.js +409 -0
- package/dist/sandbox/image-build/dockerignore.d.ts +18 -0
- package/dist/sandbox/image-build/dockerignore.js +275 -0
- package/dist/sandbox/image-build/gzip.d.ts +11 -0
- package/dist/sandbox/image-build/gzip.js +91 -0
- package/dist/sandbox/image-build/image-init.d.ts +5 -0
- package/dist/sandbox/image-build/image-init.js +97 -0
- package/dist/sandbox/image-build/index.d.ts +13 -0
- package/dist/sandbox/image-build/index.js +36 -0
- package/dist/sandbox/image-build/local-docker.d.ts +11 -0
- package/dist/sandbox/image-build/local-docker.js +72 -0
- package/dist/sandbox/image-build/resolution.d.ts +27 -0
- package/dist/sandbox/image-build/resolution.js +137 -0
- package/dist/sandbox/image-build/tar.d.ts +41 -0
- package/dist/sandbox/image-build/tar.js +378 -0
- package/dist/sandbox/image-build/upload.d.ts +10 -0
- package/dist/sandbox/image-build/upload.js +143 -0
- package/dist/sandbox/index.d.ts +2 -1
- package/dist/sandbox/index.js +4 -1
- package/dist/sandbox/process-output.d.ts +36 -0
- package/dist/sandbox/process-output.js +117 -0
- package/dist/sandbox/process.d.ts +25 -1
- package/dist/sandbox/process.js +197 -29
- package/dist/sandbox/terminal.js +9 -5
- package/dist/sandbox/ws.d.ts +4 -1
- package/dist/sandbox/ws.js +33 -7
- package/dist/services/base.d.ts +2 -0
- package/dist/services/base.js +46 -30
- package/dist/services/normalize.d.ts +3 -0
- package/dist/services/normalize.js +21 -0
- package/dist/services/sandboxes.d.ts +40 -3
- package/dist/services/sandboxes.js +569 -53
- package/dist/services/volumes.js +29 -14
- package/dist/types/constants.d.ts +1 -1
- package/dist/types/index.d.ts +2 -1
- package/dist/types/sandbox.d.ts +246 -30
- package/dist/types/volume.d.ts +7 -7
- package/package.json +24 -5
|
@@ -1,29 +1,41 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.SandboxesService = exports.SandboxHandle = void 0;
|
|
4
|
-
const
|
|
4
|
+
const error_1 = require("../error");
|
|
5
5
|
const files_1 = require("../sandbox/files");
|
|
6
6
|
const base_1 = require("../sandbox/base");
|
|
7
7
|
const runtime_path_1 = require("../sandbox/runtime-path");
|
|
8
8
|
const process_1 = require("../sandbox/process");
|
|
9
9
|
const terminal_1 = require("../sandbox/terminal");
|
|
10
|
+
const image_build_1 = require("../sandbox/image-build");
|
|
11
|
+
const retry_1 = require("../retry");
|
|
12
|
+
const normalize_1 = require("./normalize");
|
|
10
13
|
const base_2 = require("./base");
|
|
11
14
|
const RUNTIME_SESSION_REFRESH_BUFFER_MS = 60000;
|
|
12
15
|
const normalizeSandbox = (sandbox) => {
|
|
13
16
|
const { vcpus, memMiB, diskSizeMiB, ...rest } = sandbox;
|
|
14
17
|
return {
|
|
15
18
|
...rest,
|
|
16
|
-
cpu: vcpus,
|
|
17
|
-
memoryMiB: memMiB,
|
|
18
|
-
diskMiB: diskSizeMiB,
|
|
19
|
+
cpu: (0, normalize_1.optionalInteger)(vcpus),
|
|
20
|
+
memoryMiB: (0, normalize_1.optionalInteger)(memMiB),
|
|
21
|
+
diskMiB: (0, normalize_1.optionalInteger)(diskSizeMiB),
|
|
22
|
+
endTime: (0, normalize_1.optionalInteger)(rest.endTime),
|
|
23
|
+
startTime: (0, normalize_1.optionalInteger)(rest.startTime),
|
|
24
|
+
dataConsumed: (0, normalize_1.optionalInteger)(rest.dataConsumed),
|
|
25
|
+
proxyDataConsumed: (0, normalize_1.optionalInteger)(rest.proxyDataConsumed),
|
|
26
|
+
proxyBytesUsed: (0, normalize_1.optionalInteger)(rest.proxyBytesUsed),
|
|
27
|
+
timeoutMinutes: (0, normalize_1.optionalInteger)(rest.timeoutMinutes),
|
|
28
|
+
creditsUsed: (0, normalize_1.optionalNumber)(rest.creditsUsed) ?? null,
|
|
29
|
+
exposedPorts: rest.exposedPorts ?? [],
|
|
30
|
+
network: rest.network ? { ...rest.network, allowOut: rest.network.allowOut ?? [], denyOut: rest.network.denyOut ?? [] } : rest.network,
|
|
19
31
|
};
|
|
20
32
|
};
|
|
21
33
|
const normalizeSandboxDetail = (detail) => {
|
|
22
34
|
const { token, tokenExpiresAt, ...sandbox } = detail;
|
|
23
35
|
return {
|
|
24
36
|
...normalizeSandbox(sandbox),
|
|
25
|
-
token,
|
|
26
|
-
tokenExpiresAt,
|
|
37
|
+
token: token || null,
|
|
38
|
+
tokenExpiresAt: tokenExpiresAt || null,
|
|
27
39
|
};
|
|
28
40
|
};
|
|
29
41
|
const normalizeSandboxListResponse = (response) => ({
|
|
@@ -31,8 +43,31 @@ const normalizeSandboxListResponse = (response) => ({
|
|
|
31
43
|
sandboxes: response.sandboxes.map(normalizeSandbox),
|
|
32
44
|
});
|
|
33
45
|
const serializeCreateSandboxParams = (params) => {
|
|
46
|
+
if (!params || typeof params !== "object")
|
|
47
|
+
throw new error_1.HyperbrowserError("Sandbox launch parameters are required");
|
|
48
|
+
for (const name of ["imageName", "snapshotName", "imageId", "snapshotId"]) {
|
|
49
|
+
const value = params[name];
|
|
50
|
+
if (value !== undefined && (typeof value !== "string" || !value.trim()))
|
|
51
|
+
throw new error_1.HyperbrowserError(`${name} must be a nonempty string`);
|
|
52
|
+
}
|
|
53
|
+
if (params.imageId !== undefined && !params.imageName)
|
|
54
|
+
throw new error_1.HyperbrowserError("imageId requires imageName");
|
|
55
|
+
if (params.snapshotId !== undefined && !params.snapshotName)
|
|
56
|
+
throw new error_1.HyperbrowserError("snapshotId requires snapshotName");
|
|
57
|
+
if (Boolean(params.imageName) === Boolean(params.snapshotName))
|
|
58
|
+
throw new error_1.HyperbrowserError("Provide exactly one start source: snapshotName or imageName");
|
|
59
|
+
for (const name of ["cpu", "memoryMiB", "diskMiB"]) {
|
|
60
|
+
const value = params[name];
|
|
61
|
+
if (value !== undefined && (!Number.isSafeInteger(value) || value < 1))
|
|
62
|
+
throw new error_1.HyperbrowserError(`${name} must be a positive integer`);
|
|
63
|
+
}
|
|
64
|
+
if (params.runtimeClass !== undefined &&
|
|
65
|
+
params.runtimeClass !== "firecracker" &&
|
|
66
|
+
params.runtimeClass !== "gvisor-cpu")
|
|
67
|
+
throw new error_1.HyperbrowserError("Unsupported sandbox runtimeClass");
|
|
34
68
|
if (typeof params.imageName === "string") {
|
|
35
69
|
return {
|
|
70
|
+
runtimeClass: params.runtimeClass,
|
|
36
71
|
imageName: params.imageName,
|
|
37
72
|
imageId: params.imageId,
|
|
38
73
|
region: params.region,
|
|
@@ -52,9 +87,10 @@ const serializeCreateSandboxParams = (params) => {
|
|
|
52
87
|
if (snapshotParams.cpu !== undefined ||
|
|
53
88
|
snapshotParams.memoryMiB !== undefined ||
|
|
54
89
|
snapshotParams.diskMiB !== undefined) {
|
|
55
|
-
throw new
|
|
90
|
+
throw new error_1.HyperbrowserError("cpu, memoryMiB, and diskMiB are only supported for image launches", undefined);
|
|
56
91
|
}
|
|
57
92
|
return {
|
|
93
|
+
runtimeClass: snapshotParams.runtimeClass,
|
|
58
94
|
snapshotName: snapshotParams.snapshotName,
|
|
59
95
|
snapshotId: snapshotParams.snapshotId,
|
|
60
96
|
region: snapshotParams.region,
|
|
@@ -67,6 +103,59 @@ const serializeCreateSandboxParams = (params) => {
|
|
|
67
103
|
denyOut: snapshotParams.denyOut,
|
|
68
104
|
};
|
|
69
105
|
};
|
|
106
|
+
const IMAGE_BUILD_DEFAULT_POLL_INTERVAL_SECONDS = 3;
|
|
107
|
+
const IMAGE_BUILD_DEFAULT_WAIT_TIMEOUT_SECONDS = 35 * 60;
|
|
108
|
+
const IMAGE_BUILD_COMPLETE_RETRY_DELAY_MS = 2000;
|
|
109
|
+
const READY_IMAGE_PAGE_SIZE = 100;
|
|
110
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
111
|
+
const validatePagination = (params, maxLimit) => {
|
|
112
|
+
for (const key of ["page", "limit"]) {
|
|
113
|
+
const value = params[key];
|
|
114
|
+
if (value !== undefined && (!Number.isSafeInteger(value) || value < 1))
|
|
115
|
+
throw new error_1.HyperbrowserError(`${key} must be a positive integer`);
|
|
116
|
+
}
|
|
117
|
+
if (maxLimit !== undefined && params.limit !== undefined && params.limit > maxLimit)
|
|
118
|
+
throw new error_1.HyperbrowserError(`limit must be at most ${maxLimit}`);
|
|
119
|
+
};
|
|
120
|
+
const validateImageBuildFormat = (params) => {
|
|
121
|
+
if (params.sourcePlatform !== undefined && params.sourcePlatform !== "linux/amd64")
|
|
122
|
+
throw new error_1.HyperbrowserError("sourcePlatform must be linux/amd64");
|
|
123
|
+
if (params.inputFormat !== undefined && ![
|
|
124
|
+
"rootfs_export_tar_gz", "dockerfile_context_tar_gz",
|
|
125
|
+
"dockerfile_context_manifest_v1", "docker_image_manifest_v1",
|
|
126
|
+
].includes(params.inputFormat))
|
|
127
|
+
throw new error_1.HyperbrowserError("Unsupported image build inputFormat");
|
|
128
|
+
};
|
|
129
|
+
const serializeCreateImageBuildParams = (params) => {
|
|
130
|
+
validateImageBuildFormat(params);
|
|
131
|
+
for (const key of ["builderCpus", "builderMemoryMiB", "builderScratchMiB"]) {
|
|
132
|
+
const value = params[key];
|
|
133
|
+
if (value !== undefined && (!Number.isSafeInteger(value) || value < 1))
|
|
134
|
+
throw new error_1.HyperbrowserError(`${key} must be a positive integer`);
|
|
135
|
+
}
|
|
136
|
+
return ({
|
|
137
|
+
imageName: params.imageName,
|
|
138
|
+
inputSha256: params.inputSha256,
|
|
139
|
+
inputSizeBytes: params.inputSizeBytes,
|
|
140
|
+
vcpus: params.builderCpus,
|
|
141
|
+
memMiB: params.builderMemoryMiB,
|
|
142
|
+
scratchMiB: params.builderScratchMiB,
|
|
143
|
+
inputFormat: params.inputFormat,
|
|
144
|
+
sourcePlatform: params.sourcePlatform,
|
|
145
|
+
imageConfigUser: params.imageConfigUser,
|
|
146
|
+
imageInit: params.imageInit,
|
|
147
|
+
dockerfilePath: params.dockerfilePath,
|
|
148
|
+
contextManifest: params.contextManifest,
|
|
149
|
+
dockerImageManifest: params.dockerImageManifest,
|
|
150
|
+
});
|
|
151
|
+
};
|
|
152
|
+
const normalizeImageBuildPlatform = (platform) => {
|
|
153
|
+
const normalized = (platform ?? image_build_1.IMAGE_BUILD_SOURCE_PLATFORM).trim().toLowerCase();
|
|
154
|
+
if (normalized !== image_build_1.IMAGE_BUILD_SOURCE_PLATFORM) {
|
|
155
|
+
throw new error_1.HyperbrowserError(`Image builds require platform '${image_build_1.IMAGE_BUILD_SOURCE_PLATFORM}'`);
|
|
156
|
+
}
|
|
157
|
+
return normalized;
|
|
158
|
+
};
|
|
70
159
|
const resolveSandboxRuntimeSessionHost = (runtime, baseUrl) => {
|
|
71
160
|
const sessionIdFromBasePath = (0, runtime_path_1.runtimeSessionIdFromPath)(baseUrl.pathname);
|
|
72
161
|
if (sessionIdFromBasePath && baseUrl.hostname) {
|
|
@@ -168,7 +257,7 @@ class SandboxHandle {
|
|
|
168
257
|
return this.service.createMemorySnapshot(this.id, params);
|
|
169
258
|
}
|
|
170
259
|
async expose(params) {
|
|
171
|
-
const exposure = await this.service.expose(this.id, params);
|
|
260
|
+
const exposure = await this.service.expose(this.id, params, this.runtime);
|
|
172
261
|
this.detail = {
|
|
173
262
|
...this.detail,
|
|
174
263
|
exposedPorts: upsertExposedPort(this.detail.exposedPorts ?? [], exposure),
|
|
@@ -243,6 +332,10 @@ class SandboxHandle {
|
|
|
243
332
|
}
|
|
244
333
|
return expiresAt - Date.now() <= RUNTIME_SESSION_REFRESH_BUFFER_MS;
|
|
245
334
|
}
|
|
335
|
+
async createRuntimeSession(options = {}) {
|
|
336
|
+
const session = await this.ensureRuntimeSession(options.forceRefresh);
|
|
337
|
+
return { ...session, runtime: { ...session.runtime } };
|
|
338
|
+
}
|
|
246
339
|
async ensureRuntimeSession(forceRefresh = false) {
|
|
247
340
|
this.assertRuntimeAvailable();
|
|
248
341
|
if (!forceRefresh && this.runtimeSession && !this.isRuntimeSessionExpiring()) {
|
|
@@ -251,7 +344,7 @@ class SandboxHandle {
|
|
|
251
344
|
const detail = await this.service.getDetail(this.id);
|
|
252
345
|
this.hydrate(detail);
|
|
253
346
|
if (!this.runtimeSession) {
|
|
254
|
-
throw new
|
|
347
|
+
throw new error_1.HyperbrowserError(`Sandbox ${this.id} is not running`, {
|
|
255
348
|
statusCode: 409,
|
|
256
349
|
code: "sandbox_not_running",
|
|
257
350
|
retryable: false,
|
|
@@ -284,7 +377,7 @@ class SandboxHandle {
|
|
|
284
377
|
if (this.detail.status === "closed" ||
|
|
285
378
|
this.detail.status === "close-error" ||
|
|
286
379
|
this.detail.status === "error") {
|
|
287
|
-
throw new
|
|
380
|
+
throw new error_1.HyperbrowserError(`Sandbox ${this.id} is not running`, {
|
|
288
381
|
statusCode: 409,
|
|
289
382
|
code: "sandbox_not_running",
|
|
290
383
|
retryable: false,
|
|
@@ -317,6 +410,29 @@ class SandboxesService extends base_2.BaseService {
|
|
|
317
410
|
const detail = await this.createDetail(params);
|
|
318
411
|
return this.attach(detail);
|
|
319
412
|
}
|
|
413
|
+
async startFromSnapshot(params) {
|
|
414
|
+
if (!params?.snapshotName)
|
|
415
|
+
throw new error_1.HyperbrowserError("snapshotName is required");
|
|
416
|
+
return this.create(params);
|
|
417
|
+
}
|
|
418
|
+
async getRuntimeSession(id) {
|
|
419
|
+
const detail = await this.getDetail(id);
|
|
420
|
+
if (!detail.token || ["closed", "close-error", "error"].includes(detail.status)) {
|
|
421
|
+
throw new error_1.HyperbrowserError(`Sandbox ${id} is not running`, {
|
|
422
|
+
statusCode: 409,
|
|
423
|
+
code: "sandbox_not_running",
|
|
424
|
+
service: "runtime",
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
return {
|
|
428
|
+
sandboxId: id,
|
|
429
|
+
status: detail.status,
|
|
430
|
+
region: detail.region,
|
|
431
|
+
token: detail.token,
|
|
432
|
+
tokenExpiresAt: detail.tokenExpiresAt,
|
|
433
|
+
runtime: { ...detail.runtime },
|
|
434
|
+
};
|
|
435
|
+
}
|
|
320
436
|
async get(id) {
|
|
321
437
|
const detail = await this.getDetail(id);
|
|
322
438
|
return this.attach(detail);
|
|
@@ -327,22 +443,23 @@ class SandboxesService extends base_2.BaseService {
|
|
|
327
443
|
return handle;
|
|
328
444
|
}
|
|
329
445
|
async list(params = {}) {
|
|
446
|
+
validatePagination(params);
|
|
330
447
|
try {
|
|
331
448
|
const response = await this.request("/sandboxes", undefined, {
|
|
332
449
|
status: params.status,
|
|
333
450
|
start: params.start,
|
|
334
451
|
end: params.end,
|
|
335
452
|
search: params.search,
|
|
336
|
-
page: params.page,
|
|
337
|
-
limit: params.limit,
|
|
453
|
+
page: params.page ?? 1,
|
|
454
|
+
limit: params.limit ?? 10,
|
|
338
455
|
});
|
|
339
456
|
return normalizeSandboxListResponse(response);
|
|
340
457
|
}
|
|
341
458
|
catch (error) {
|
|
342
|
-
if (error instanceof
|
|
459
|
+
if (error instanceof error_1.HyperbrowserError) {
|
|
343
460
|
throw error;
|
|
344
461
|
}
|
|
345
|
-
throw new
|
|
462
|
+
throw new error_1.HyperbrowserError("Failed to list sandboxes", undefined);
|
|
346
463
|
}
|
|
347
464
|
}
|
|
348
465
|
async deleteImage(image) {
|
|
@@ -352,13 +469,14 @@ class SandboxesService extends base_2.BaseService {
|
|
|
352
469
|
});
|
|
353
470
|
}
|
|
354
471
|
catch (error) {
|
|
355
|
-
if (error instanceof
|
|
472
|
+
if (error instanceof error_1.HyperbrowserError) {
|
|
356
473
|
throw error;
|
|
357
474
|
}
|
|
358
|
-
throw new
|
|
475
|
+
throw new error_1.HyperbrowserError(`Failed to delete image ${image}`);
|
|
359
476
|
}
|
|
360
477
|
}
|
|
361
478
|
async listImages(params = {}) {
|
|
479
|
+
validatePagination(params, 100);
|
|
362
480
|
try {
|
|
363
481
|
return await this.request("/images", undefined, {
|
|
364
482
|
source: params.source,
|
|
@@ -368,13 +486,14 @@ class SandboxesService extends base_2.BaseService {
|
|
|
368
486
|
});
|
|
369
487
|
}
|
|
370
488
|
catch (error) {
|
|
371
|
-
if (error instanceof
|
|
489
|
+
if (error instanceof error_1.HyperbrowserError) {
|
|
372
490
|
throw error;
|
|
373
491
|
}
|
|
374
|
-
throw new
|
|
492
|
+
throw new error_1.HyperbrowserError("Failed to list sandbox images", undefined);
|
|
375
493
|
}
|
|
376
494
|
}
|
|
377
495
|
async listSnapshots(params = {}) {
|
|
496
|
+
validatePagination(params, 100);
|
|
378
497
|
try {
|
|
379
498
|
return await this.request("/snapshots", undefined, {
|
|
380
499
|
status: params.status,
|
|
@@ -385,10 +504,10 @@ class SandboxesService extends base_2.BaseService {
|
|
|
385
504
|
});
|
|
386
505
|
}
|
|
387
506
|
catch (error) {
|
|
388
|
-
if (error instanceof
|
|
507
|
+
if (error instanceof error_1.HyperbrowserError) {
|
|
389
508
|
throw error;
|
|
390
509
|
}
|
|
391
|
-
throw new
|
|
510
|
+
throw new error_1.HyperbrowserError("Failed to list sandbox snapshots", undefined);
|
|
392
511
|
}
|
|
393
512
|
}
|
|
394
513
|
async stop(id) {
|
|
@@ -398,10 +517,10 @@ class SandboxesService extends base_2.BaseService {
|
|
|
398
517
|
});
|
|
399
518
|
}
|
|
400
519
|
catch (error) {
|
|
401
|
-
if (error instanceof
|
|
520
|
+
if (error instanceof error_1.HyperbrowserError) {
|
|
402
521
|
throw error;
|
|
403
522
|
}
|
|
404
|
-
throw new
|
|
523
|
+
throw new error_1.HyperbrowserError(`Failed to stop sandbox ${id}`, undefined);
|
|
405
524
|
}
|
|
406
525
|
}
|
|
407
526
|
async getDetail(id) {
|
|
@@ -410,10 +529,10 @@ class SandboxesService extends base_2.BaseService {
|
|
|
410
529
|
return normalizeSandboxDetail(detail);
|
|
411
530
|
}
|
|
412
531
|
catch (error) {
|
|
413
|
-
if (error instanceof
|
|
532
|
+
if (error instanceof error_1.HyperbrowserError) {
|
|
414
533
|
throw error;
|
|
415
534
|
}
|
|
416
|
-
throw new
|
|
535
|
+
throw new error_1.HyperbrowserError(`Failed to get sandbox ${id}`, undefined);
|
|
417
536
|
}
|
|
418
537
|
}
|
|
419
538
|
attach(detail) {
|
|
@@ -427,24 +546,27 @@ class SandboxesService extends base_2.BaseService {
|
|
|
427
546
|
});
|
|
428
547
|
}
|
|
429
548
|
catch (error) {
|
|
430
|
-
if (error instanceof
|
|
549
|
+
if (error instanceof error_1.HyperbrowserError) {
|
|
431
550
|
throw error;
|
|
432
551
|
}
|
|
433
|
-
throw new
|
|
552
|
+
throw new error_1.HyperbrowserError(`Failed to create memory snapshot for sandbox ${id}`, undefined);
|
|
434
553
|
}
|
|
435
554
|
}
|
|
436
|
-
async expose(id, params) {
|
|
555
|
+
async expose(id, params, runtime) {
|
|
437
556
|
try {
|
|
438
|
-
|
|
557
|
+
const exposure = await this.request(`/sandbox/${id}/expose`, {
|
|
439
558
|
method: "POST",
|
|
440
559
|
body: JSON.stringify(params),
|
|
441
560
|
});
|
|
561
|
+
if (!exposure.url)
|
|
562
|
+
exposure.url = buildSandboxExposedUrl(runtime ?? (await this.getDetail(id)).runtime, exposure.port);
|
|
563
|
+
return exposure;
|
|
442
564
|
}
|
|
443
565
|
catch (error) {
|
|
444
|
-
if (error instanceof
|
|
566
|
+
if (error instanceof error_1.HyperbrowserError) {
|
|
445
567
|
throw error;
|
|
446
568
|
}
|
|
447
|
-
throw new
|
|
569
|
+
throw new error_1.HyperbrowserError(`Failed to expose port ${params.port} for sandbox ${id}`);
|
|
448
570
|
}
|
|
449
571
|
}
|
|
450
572
|
async getSnapshot(snapshot) {
|
|
@@ -453,10 +575,10 @@ class SandboxesService extends base_2.BaseService {
|
|
|
453
575
|
return response.snapshot;
|
|
454
576
|
}
|
|
455
577
|
catch (error) {
|
|
456
|
-
if (error instanceof
|
|
578
|
+
if (error instanceof error_1.HyperbrowserError) {
|
|
457
579
|
throw error;
|
|
458
580
|
}
|
|
459
|
-
throw new
|
|
581
|
+
throw new error_1.HyperbrowserError(`Failed to get snapshot ${snapshot}`);
|
|
460
582
|
}
|
|
461
583
|
}
|
|
462
584
|
async deleteSnapshot(snapshot) {
|
|
@@ -464,36 +586,36 @@ class SandboxesService extends base_2.BaseService {
|
|
|
464
586
|
return await this.request(`/snapshots/${encodeURIComponent(snapshot)}`, { method: "DELETE" });
|
|
465
587
|
}
|
|
466
588
|
catch (error) {
|
|
467
|
-
if (error instanceof
|
|
589
|
+
if (error instanceof error_1.HyperbrowserError) {
|
|
468
590
|
throw error;
|
|
469
591
|
}
|
|
470
|
-
throw new
|
|
592
|
+
throw new error_1.HyperbrowserError(`Failed to delete snapshot ${snapshot}`);
|
|
471
593
|
}
|
|
472
594
|
}
|
|
473
595
|
async createImageBuild(params) {
|
|
474
596
|
try {
|
|
475
597
|
return await this.request("/images/builds", {
|
|
476
598
|
method: "POST",
|
|
477
|
-
body: JSON.stringify(params),
|
|
599
|
+
body: JSON.stringify(serializeCreateImageBuildParams(params)),
|
|
478
600
|
});
|
|
479
601
|
}
|
|
480
602
|
catch (error) {
|
|
481
|
-
if (error instanceof
|
|
603
|
+
if (error instanceof error_1.HyperbrowserError) {
|
|
482
604
|
throw error;
|
|
483
605
|
}
|
|
484
|
-
throw new
|
|
606
|
+
throw new error_1.HyperbrowserError("Failed to create image build");
|
|
485
607
|
}
|
|
486
608
|
}
|
|
487
|
-
async getImageBuild(buildId) {
|
|
609
|
+
async getImageBuild(buildId, options = {}) {
|
|
488
610
|
try {
|
|
489
|
-
const response = await this.request(`/images/builds/${encodeURIComponent(buildId)}
|
|
611
|
+
const response = await this.request(`/images/builds/${encodeURIComponent(buildId)}`, { signal: options.signal });
|
|
490
612
|
return response.build;
|
|
491
613
|
}
|
|
492
614
|
catch (error) {
|
|
493
|
-
if (error instanceof
|
|
615
|
+
if (error instanceof error_1.HyperbrowserError) {
|
|
494
616
|
throw error;
|
|
495
617
|
}
|
|
496
|
-
throw new
|
|
618
|
+
throw new error_1.HyperbrowserError(`Failed to get image build ${buildId}`);
|
|
497
619
|
}
|
|
498
620
|
}
|
|
499
621
|
async listImageBuilds(params = {}) {
|
|
@@ -504,13 +626,14 @@ class SandboxesService extends base_2.BaseService {
|
|
|
504
626
|
});
|
|
505
627
|
}
|
|
506
628
|
catch (error) {
|
|
507
|
-
if (error instanceof
|
|
629
|
+
if (error instanceof error_1.HyperbrowserError) {
|
|
508
630
|
throw error;
|
|
509
631
|
}
|
|
510
|
-
throw new
|
|
632
|
+
throw new error_1.HyperbrowserError("Failed to list image builds");
|
|
511
633
|
}
|
|
512
634
|
}
|
|
513
635
|
async completeImageBuild(buildId, params) {
|
|
636
|
+
validateImageBuildFormat(params);
|
|
514
637
|
try {
|
|
515
638
|
const response = await this.request(`/images/builds/${encodeURIComponent(buildId)}/complete`, {
|
|
516
639
|
method: "POST",
|
|
@@ -519,10 +642,10 @@ class SandboxesService extends base_2.BaseService {
|
|
|
519
642
|
return response.build;
|
|
520
643
|
}
|
|
521
644
|
catch (error) {
|
|
522
|
-
if (error instanceof
|
|
645
|
+
if (error instanceof error_1.HyperbrowserError) {
|
|
523
646
|
throw error;
|
|
524
647
|
}
|
|
525
|
-
throw new
|
|
648
|
+
throw new error_1.HyperbrowserError(`Failed to complete image build ${buildId}`);
|
|
526
649
|
}
|
|
527
650
|
}
|
|
528
651
|
async cancelImageBuild(buildId) {
|
|
@@ -531,10 +654,403 @@ class SandboxesService extends base_2.BaseService {
|
|
|
531
654
|
return response.build;
|
|
532
655
|
}
|
|
533
656
|
catch (error) {
|
|
534
|
-
if (error instanceof
|
|
657
|
+
if (error instanceof error_1.HyperbrowserError) {
|
|
658
|
+
throw error;
|
|
659
|
+
}
|
|
660
|
+
throw new error_1.HyperbrowserError(`Failed to cancel image build ${buildId}`);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
async reuseDockerImage(params) {
|
|
664
|
+
validateImageBuildFormat(params);
|
|
665
|
+
try {
|
|
666
|
+
return await this.request("/images/builds/reuse", {
|
|
667
|
+
method: "POST",
|
|
668
|
+
body: JSON.stringify(params),
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
catch (error) {
|
|
672
|
+
if (error instanceof error_1.HyperbrowserError) {
|
|
673
|
+
throw error;
|
|
674
|
+
}
|
|
675
|
+
throw new error_1.HyperbrowserError("Failed to reuse Docker image");
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
/** Find an exact ready team image, including revisions awaiting backup. */
|
|
679
|
+
async findReadyImage(imageName) {
|
|
680
|
+
let page = 1;
|
|
681
|
+
for (;;) {
|
|
682
|
+
const response = await this.listImages({
|
|
683
|
+
search: imageName,
|
|
684
|
+
source: ["team"],
|
|
685
|
+
page,
|
|
686
|
+
limit: READY_IMAGE_PAGE_SIZE,
|
|
687
|
+
});
|
|
688
|
+
for (const image of response.images) {
|
|
689
|
+
if (image.imageName === imageName && (image.uploaded || image.ready === true)) {
|
|
690
|
+
return image;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
if (response.images.length < READY_IMAGE_PAGE_SIZE) {
|
|
694
|
+
return null;
|
|
695
|
+
}
|
|
696
|
+
if (typeof response.totalCount === "number" &&
|
|
697
|
+
page * READY_IMAGE_PAGE_SIZE >= response.totalCount) {
|
|
698
|
+
return null;
|
|
699
|
+
}
|
|
700
|
+
page += 1;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
/** Poll an image build until it completes, rejecting on failure or timeout. */
|
|
704
|
+
async waitForImageBuild(buildId, options = {}) {
|
|
705
|
+
const pollInterval = options.pollInterval ?? IMAGE_BUILD_DEFAULT_POLL_INTERVAL_SECONDS;
|
|
706
|
+
const timeout = options.timeout === undefined ? IMAGE_BUILD_DEFAULT_WAIT_TIMEOUT_SECONDS : options.timeout;
|
|
707
|
+
if (!Number.isFinite(pollInterval) ||
|
|
708
|
+
pollInterval < 0 ||
|
|
709
|
+
(timeout !== null && (!Number.isFinite(timeout) || timeout < 0))) {
|
|
710
|
+
throw new error_1.HyperbrowserError("Image build wait timeout and interval must be nonnegative finite seconds");
|
|
711
|
+
}
|
|
712
|
+
const controller = new AbortController();
|
|
713
|
+
let timedOut = false;
|
|
714
|
+
const deadlineTimer = timeout === null
|
|
715
|
+
? undefined
|
|
716
|
+
: setTimeout(() => {
|
|
717
|
+
timedOut = true;
|
|
718
|
+
controller.abort();
|
|
719
|
+
}, timeout * 1000);
|
|
720
|
+
const cancel = () => controller.abort();
|
|
721
|
+
options.signal?.addEventListener("abort", cancel, { once: true });
|
|
722
|
+
if (options.signal?.aborted)
|
|
723
|
+
cancel();
|
|
724
|
+
const waitFailure = (error) => {
|
|
725
|
+
if (timedOut)
|
|
726
|
+
throw new error_1.HyperbrowserError(`Timed out waiting for image build ${buildId}`, {
|
|
727
|
+
code: "wait_timeout",
|
|
728
|
+
service: "control",
|
|
729
|
+
cause: error,
|
|
730
|
+
});
|
|
731
|
+
throw error;
|
|
732
|
+
};
|
|
733
|
+
try {
|
|
734
|
+
for (;;) {
|
|
735
|
+
const build = await this.getImageBuild(buildId, { signal: controller.signal }).catch(waitFailure);
|
|
736
|
+
if (build.status === "completed")
|
|
737
|
+
return build;
|
|
738
|
+
if (build.status === "failed" || build.status === "canceled") {
|
|
739
|
+
throw new error_1.HyperbrowserError(build.errorMessage || `Image build ${buildId} ${build.status}`, { code: build.errorCode || "image_build_failed", service: "control", details: build });
|
|
740
|
+
}
|
|
741
|
+
await (0, retry_1.retryDelay)(pollInterval * 1000, controller.signal).catch(waitFailure);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
finally {
|
|
745
|
+
clearTimeout(deadlineTimer);
|
|
746
|
+
options.signal?.removeEventListener("abort", cancel);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
/** Import a local Docker image as reusable layers, reusing exact cached imports. */
|
|
750
|
+
async buildImageFromDockerImage(options) {
|
|
751
|
+
const platform = normalizeImageBuildPlatform(options.platform);
|
|
752
|
+
const source = await (0, image_build_1.prepareDockerImageManifestSource)(options.dockerImage, { platform });
|
|
753
|
+
try {
|
|
754
|
+
if (options.expectedImageDigest !== undefined &&
|
|
755
|
+
source.imageDigest !== options.expectedImageDigest.toLowerCase()) {
|
|
756
|
+
throw new error_1.HyperbrowserError("Docker image changed after its cache identity was computed. " +
|
|
757
|
+
"Retry with a fresh image digest.");
|
|
758
|
+
}
|
|
759
|
+
const imageInit = (0, image_build_1.mergeImageInit)(source.imageInit, options.imageInit);
|
|
760
|
+
const imageConfigUser = options.imageConfigUser ?? source.imageConfigUser;
|
|
761
|
+
let reused = null;
|
|
762
|
+
try {
|
|
763
|
+
reused = await this.reuseDockerImage({
|
|
764
|
+
imageName: options.imageName,
|
|
765
|
+
sourceImageDigest: source.imageDigest,
|
|
766
|
+
sourcePlatform: "linux/amd64",
|
|
767
|
+
imageConfigUser,
|
|
768
|
+
imageInit,
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
catch (error) {
|
|
772
|
+
if (!(error instanceof error_1.HyperbrowserError) || error.statusCode !== 404) {
|
|
773
|
+
throw error;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
if (reused?.hit) {
|
|
777
|
+
if (!reused.build) {
|
|
778
|
+
throw new error_1.HyperbrowserError("exact image cache response is missing its completed build");
|
|
779
|
+
}
|
|
780
|
+
return reused.build;
|
|
781
|
+
}
|
|
782
|
+
const packaged = await (0, image_build_1.packageDockerImageManifest)(options.dockerImage, source.imageDigest, source.config, { platform, tempDir: options.tempDir });
|
|
783
|
+
try {
|
|
784
|
+
return await this.submitImageBuild({
|
|
785
|
+
imageName: options.imageName,
|
|
786
|
+
inputSha256: packaged.artifact.sha256Hex,
|
|
787
|
+
inputSizeBytes: packaged.artifact.sizeBytes,
|
|
788
|
+
inputFormat: packaged.artifact.inputFormat,
|
|
789
|
+
sourcePlatform: "linux/amd64",
|
|
790
|
+
imageConfigUser,
|
|
791
|
+
imageInit,
|
|
792
|
+
dockerImageManifest: packaged.manifest,
|
|
793
|
+
builderCpus: options.builderCpus,
|
|
794
|
+
builderMemoryMiB: options.builderMemoryMiB,
|
|
795
|
+
builderScratchMiB: options.builderScratchMiB,
|
|
796
|
+
}, packaged.artifact, packaged.layers, "Docker image layer", options);
|
|
797
|
+
}
|
|
798
|
+
finally {
|
|
799
|
+
packaged.cleanup();
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
finally {
|
|
803
|
+
await source.cleanup();
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Build a Dockerfile into a sandbox image.
|
|
808
|
+
*
|
|
809
|
+
* Remote builds (default) upload only the effective build context and build
|
|
810
|
+
* on Hyperbrowser. `remote: false` builds with local Docker and imports the
|
|
811
|
+
* resulting image instead.
|
|
812
|
+
*/
|
|
813
|
+
async buildImageFromDockerfile(options) {
|
|
814
|
+
options = { ...options, platform: normalizeImageBuildPlatform(options.platform) };
|
|
815
|
+
const remote = options.remote ?? true;
|
|
816
|
+
if (remote) {
|
|
817
|
+
if (options.dockerTag !== undefined ||
|
|
818
|
+
(options.buildArgs && Object.keys(options.buildArgs).length > 0)) {
|
|
819
|
+
throw new error_1.HyperbrowserError("dockerTag and buildArgs require remote: false; remote Dockerfile builds " +
|
|
820
|
+
"send the build context to Hyperbrowser");
|
|
821
|
+
}
|
|
822
|
+
return this.buildImageFromRemoteDockerfile(options);
|
|
823
|
+
}
|
|
824
|
+
if (options.expectedContextFingerprint !== undefined) {
|
|
825
|
+
throw new error_1.HyperbrowserError("expectedContextFingerprint requires remote: true");
|
|
826
|
+
}
|
|
827
|
+
const tag = options.dockerTag ?? (0, image_build_1.makeTempDockerTag)();
|
|
828
|
+
try {
|
|
829
|
+
await (0, image_build_1.buildDockerImageFromDockerfile)({
|
|
830
|
+
contextPath: options.contextPath,
|
|
831
|
+
dockerfile: options.dockerfile,
|
|
832
|
+
tag,
|
|
833
|
+
platform: options.platform,
|
|
834
|
+
buildArgs: options.buildArgs,
|
|
835
|
+
});
|
|
836
|
+
return await this.buildImageFromDockerImage({
|
|
837
|
+
dockerImage: tag,
|
|
838
|
+
imageName: options.imageName,
|
|
839
|
+
platform: options.platform,
|
|
840
|
+
imageInit: options.imageInit,
|
|
841
|
+
imageConfigUser: options.imageConfigUser,
|
|
842
|
+
builderCpus: options.builderCpus,
|
|
843
|
+
builderMemoryMiB: options.builderMemoryMiB,
|
|
844
|
+
builderScratchMiB: options.builderScratchMiB,
|
|
845
|
+
wait: options.wait,
|
|
846
|
+
pollInterval: options.pollInterval,
|
|
847
|
+
waitTimeout: options.waitTimeout,
|
|
848
|
+
signal: options.signal,
|
|
849
|
+
tempDir: options.tempDir,
|
|
850
|
+
uploadTimeout: options.uploadTimeout,
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
finally {
|
|
854
|
+
if (options.dockerTag === undefined) {
|
|
855
|
+
await (0, image_build_1.removeDockerImage)(tag);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* Reuse, join, or build content-derived remote Dockerfile/image inputs.
|
|
861
|
+
*
|
|
862
|
+
* Supply exactly one of `contextPath` or `dockerImage`. Names include source
|
|
863
|
+
* contents, platform and image initialization overrides. `forceBuild` skips
|
|
864
|
+
* ready-image lookup, but joins matching active builds and retains builder
|
|
865
|
+
* layer/artifact caches. Canceling polling never cancels the backend build.
|
|
866
|
+
* `waitTimeout` applies to this caller's polling, independently of uploads.
|
|
867
|
+
* This composes existing APIs; lookup plus creation is not server-atomic.
|
|
868
|
+
*/
|
|
869
|
+
async getOrBuildImage(options) {
|
|
870
|
+
const platform = normalizeImageBuildPlatform(options.platform);
|
|
871
|
+
const dockerfile = options.dockerfile ?? "Dockerfile";
|
|
872
|
+
const remoteFullContext = options.remoteFullContext ?? false;
|
|
873
|
+
const { contextPath, dockerImage } = options;
|
|
874
|
+
if ((contextPath === undefined) === (dockerImage === undefined)) {
|
|
875
|
+
throw new error_1.HyperbrowserError("Supply exactly one of contextPath or dockerImage");
|
|
876
|
+
}
|
|
877
|
+
let fingerprint;
|
|
878
|
+
let source;
|
|
879
|
+
let inputFormat;
|
|
880
|
+
if (contextPath !== undefined) {
|
|
881
|
+
if (options.expectedImageDigest !== undefined) {
|
|
882
|
+
throw new error_1.HyperbrowserError("expectedImageDigest requires dockerImage");
|
|
883
|
+
}
|
|
884
|
+
fingerprint =
|
|
885
|
+
options.expectedContextFingerprint ??
|
|
886
|
+
(await (0, image_build_1.dockerBuildContextFingerprint)(contextPath, {
|
|
887
|
+
dockerfile,
|
|
888
|
+
forceFullContext: remoteFullContext,
|
|
889
|
+
}));
|
|
890
|
+
source = "dockerfile";
|
|
891
|
+
inputFormat = "dockerfile_context_manifest_v1";
|
|
892
|
+
}
|
|
893
|
+
else {
|
|
894
|
+
if (options.expectedContextFingerprint !== undefined ||
|
|
895
|
+
remoteFullContext ||
|
|
896
|
+
dockerfile !== "Dockerfile") {
|
|
897
|
+
throw new error_1.HyperbrowserError("Dockerfile context options require contextPath");
|
|
898
|
+
}
|
|
899
|
+
fingerprint =
|
|
900
|
+
options.expectedImageDigest ??
|
|
901
|
+
(await (0, image_build_1.dockerImageDigest)(dockerImage, { platform }));
|
|
902
|
+
source = "prebuilt";
|
|
903
|
+
inputFormat = "docker_image_manifest_v1";
|
|
904
|
+
}
|
|
905
|
+
const imageName = (0, image_build_1.imageBuildName)({
|
|
906
|
+
source,
|
|
907
|
+
fingerprint,
|
|
908
|
+
namePrefix: options.imageNamePrefix,
|
|
909
|
+
platform,
|
|
910
|
+
imageInit: options.imageInit,
|
|
911
|
+
imageConfigUser: options.imageConfigUser,
|
|
912
|
+
});
|
|
913
|
+
if (!options.forceBuild) {
|
|
914
|
+
const image = await this.findReadyImage(imageName);
|
|
915
|
+
if (image !== null) {
|
|
916
|
+
return { outcome: "reused", imageName, imageId: image.id };
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
const common = {
|
|
920
|
+
imageName,
|
|
921
|
+
platform,
|
|
922
|
+
imageInit: options.imageInit,
|
|
923
|
+
imageConfigUser: options.imageConfigUser,
|
|
924
|
+
builderCpus: options.builderCpus,
|
|
925
|
+
builderMemoryMiB: options.builderMemoryMiB,
|
|
926
|
+
builderScratchMiB: options.builderScratchMiB,
|
|
927
|
+
wait: false,
|
|
928
|
+
uploadTimeout: options.uploadTimeout === undefined ? 600 : options.uploadTimeout,
|
|
929
|
+
tempDir: options.tempDir,
|
|
930
|
+
};
|
|
931
|
+
let outcome = "created";
|
|
932
|
+
let build;
|
|
933
|
+
try {
|
|
934
|
+
build =
|
|
935
|
+
contextPath !== undefined
|
|
936
|
+
? await this.buildImageFromDockerfile({
|
|
937
|
+
...common,
|
|
938
|
+
contextPath,
|
|
939
|
+
dockerfile,
|
|
940
|
+
remote: true,
|
|
941
|
+
remoteFullContext,
|
|
942
|
+
expectedContextFingerprint: fingerprint,
|
|
943
|
+
})
|
|
944
|
+
: await this.buildImageFromDockerImage({
|
|
945
|
+
...common,
|
|
946
|
+
dockerImage: dockerImage,
|
|
947
|
+
expectedImageDigest: fingerprint,
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
catch (error) {
|
|
951
|
+
if (!(error instanceof error_1.HyperbrowserError)) {
|
|
952
|
+
throw error;
|
|
953
|
+
}
|
|
954
|
+
const existing = (0, image_build_1.matchingImageBuild)(error, imageName, inputFormat);
|
|
955
|
+
if (existing === null) {
|
|
956
|
+
throw error;
|
|
957
|
+
}
|
|
958
|
+
build = existing;
|
|
959
|
+
outcome = "joined";
|
|
960
|
+
}
|
|
961
|
+
const wait = options.wait ?? true;
|
|
962
|
+
if (wait && build.status !== "completed") {
|
|
963
|
+
build = await this.waitForImageBuild(build.id, {
|
|
964
|
+
pollInterval: options.pollInterval,
|
|
965
|
+
timeout: options.waitTimeout,
|
|
966
|
+
signal: options.signal,
|
|
967
|
+
});
|
|
968
|
+
}
|
|
969
|
+
return { outcome, imageName, imageId: (0, image_build_1.completedImageId)(build), build };
|
|
970
|
+
}
|
|
971
|
+
async buildImageFromRemoteDockerfile(options) {
|
|
972
|
+
const packaged = await (0, image_build_1.packageDockerBuildContextManifest)(options.contextPath, {
|
|
973
|
+
dockerfile: options.dockerfile,
|
|
974
|
+
forceFullContext: options.remoteFullContext,
|
|
975
|
+
expectedContextFingerprint: options.expectedContextFingerprint,
|
|
976
|
+
tempDir: options.tempDir,
|
|
977
|
+
});
|
|
978
|
+
try {
|
|
979
|
+
return await this.submitImageBuild({
|
|
980
|
+
imageName: options.imageName,
|
|
981
|
+
inputSha256: packaged.artifact.sha256Hex,
|
|
982
|
+
inputSizeBytes: packaged.artifact.sizeBytes,
|
|
983
|
+
inputFormat: packaged.artifact.inputFormat,
|
|
984
|
+
sourcePlatform: "linux/amd64",
|
|
985
|
+
dockerfilePath: packaged.manifest.dockerfilePath,
|
|
986
|
+
imageConfigUser: options.imageConfigUser,
|
|
987
|
+
imageInit: options.imageInit,
|
|
988
|
+
contextManifest: packaged.manifest,
|
|
989
|
+
builderCpus: options.builderCpus,
|
|
990
|
+
builderMemoryMiB: options.builderMemoryMiB,
|
|
991
|
+
builderScratchMiB: options.builderScratchMiB,
|
|
992
|
+
}, packaged.artifact, packaged.bundles, "build context bundle", options);
|
|
993
|
+
}
|
|
994
|
+
finally {
|
|
995
|
+
packaged.cleanup();
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
/** Create a build, upload requested artifacts, complete it, and optionally wait. */
|
|
999
|
+
async submitImageBuild(params, artifact, artifacts, label, options) {
|
|
1000
|
+
let buildId = null;
|
|
1001
|
+
let buildStarted = false;
|
|
1002
|
+
try {
|
|
1003
|
+
const createResult = await this.createImageBuild(params);
|
|
1004
|
+
buildId = createResult.build.id;
|
|
1005
|
+
await (0, image_build_1.uploadMissingImageBuildArtifacts)(createResult.uploads, artifacts, {
|
|
1006
|
+
label,
|
|
1007
|
+
timeout: options.uploadTimeout,
|
|
1008
|
+
});
|
|
1009
|
+
const build = await this.completeImageBuildResilient(buildId, artifact);
|
|
1010
|
+
buildStarted = true;
|
|
1011
|
+
if (options.wait ?? true) {
|
|
1012
|
+
return await this.waitForImageBuild(build.id, {
|
|
1013
|
+
pollInterval: options.pollInterval,
|
|
1014
|
+
timeout: options.waitTimeout,
|
|
1015
|
+
signal: options.signal,
|
|
1016
|
+
});
|
|
1017
|
+
}
|
|
1018
|
+
return build;
|
|
1019
|
+
}
|
|
1020
|
+
catch (error) {
|
|
1021
|
+
if (buildId !== null && !buildStarted) {
|
|
1022
|
+
try {
|
|
1023
|
+
await this.cancelImageBuild(buildId);
|
|
1024
|
+
}
|
|
1025
|
+
catch {
|
|
1026
|
+
// Best-effort cancellation; surface the original error.
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
throw error;
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
async completeImageBuildResilient(buildId, artifact) {
|
|
1033
|
+
const params = {
|
|
1034
|
+
inputSha256: artifact.sha256Hex,
|
|
1035
|
+
inputSizeBytes: artifact.sizeBytes,
|
|
1036
|
+
inputFormat: artifact.inputFormat,
|
|
1037
|
+
};
|
|
1038
|
+
try {
|
|
1039
|
+
return await this.completeImageBuild(buildId, params);
|
|
1040
|
+
}
|
|
1041
|
+
catch (error) {
|
|
1042
|
+
if (!(error instanceof error_1.HyperbrowserError) || error.statusCode !== 409) {
|
|
1043
|
+
throw error;
|
|
1044
|
+
}
|
|
1045
|
+
if (error.message.toLowerCase().includes("already in progress")) {
|
|
1046
|
+
return this.getImageBuild(buildId);
|
|
1047
|
+
}
|
|
1048
|
+
const current = await this.getImageBuild(buildId);
|
|
1049
|
+
if (current.status !== "awaiting_upload" && current.status !== "upload_verified") {
|
|
535
1050
|
throw error;
|
|
536
1051
|
}
|
|
537
|
-
|
|
1052
|
+
await sleep(IMAGE_BUILD_COMPLETE_RETRY_DELAY_MS);
|
|
1053
|
+
return this.completeImageBuild(buildId, params);
|
|
538
1054
|
}
|
|
539
1055
|
}
|
|
540
1056
|
async updateNetwork(id, policy) {
|
|
@@ -545,10 +1061,10 @@ class SandboxesService extends base_2.BaseService {
|
|
|
545
1061
|
});
|
|
546
1062
|
}
|
|
547
1063
|
catch (error) {
|
|
548
|
-
if (error instanceof
|
|
1064
|
+
if (error instanceof error_1.HyperbrowserError) {
|
|
549
1065
|
throw error;
|
|
550
1066
|
}
|
|
551
|
-
throw new
|
|
1067
|
+
throw new error_1.HyperbrowserError(`Failed to update network policy for sandbox ${id}`);
|
|
552
1068
|
}
|
|
553
1069
|
}
|
|
554
1070
|
async unexpose(id, port) {
|
|
@@ -559,10 +1075,10 @@ class SandboxesService extends base_2.BaseService {
|
|
|
559
1075
|
});
|
|
560
1076
|
}
|
|
561
1077
|
catch (error) {
|
|
562
|
-
if (error instanceof
|
|
1078
|
+
if (error instanceof error_1.HyperbrowserError) {
|
|
563
1079
|
throw error;
|
|
564
1080
|
}
|
|
565
|
-
throw new
|
|
1081
|
+
throw new error_1.HyperbrowserError(`Failed to unexpose port ${port} for sandbox ${id}`);
|
|
566
1082
|
}
|
|
567
1083
|
}
|
|
568
1084
|
async createDetail(params) {
|
|
@@ -574,10 +1090,10 @@ class SandboxesService extends base_2.BaseService {
|
|
|
574
1090
|
return normalizeSandboxDetail(detail);
|
|
575
1091
|
}
|
|
576
1092
|
catch (error) {
|
|
577
|
-
if (error instanceof
|
|
1093
|
+
if (error instanceof error_1.HyperbrowserError) {
|
|
578
1094
|
throw error;
|
|
579
1095
|
}
|
|
580
|
-
throw new
|
|
1096
|
+
throw new error_1.HyperbrowserError("Failed to create sandbox", undefined);
|
|
581
1097
|
}
|
|
582
1098
|
}
|
|
583
1099
|
}
|