@decocms/sandbox-controller 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2421 -5
- package/dist/index.js +191 -29
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -842,6 +842,16 @@ function refSlugSource(projectRef) {
|
|
|
842
842
|
}
|
|
843
843
|
|
|
844
844
|
// ../sandbox/server/provider/types.ts
|
|
845
|
+
var SANDBOX_PLACEMENT_REASONS = [
|
|
846
|
+
"existing",
|
|
847
|
+
"requested",
|
|
848
|
+
"split",
|
|
849
|
+
"capacity",
|
|
850
|
+
"fallback",
|
|
851
|
+
"image",
|
|
852
|
+
"warm-pool",
|
|
853
|
+
"freestyle-unavailable"
|
|
854
|
+
];
|
|
845
855
|
function sandboxIdKey(id2) {
|
|
846
856
|
return `${id2.userId}:${id2.projectRef}`;
|
|
847
857
|
}
|
|
@@ -6749,7 +6759,7 @@ var Freestyle = class {
|
|
|
6749
6759
|
// ../sandbox/package.json
|
|
6750
6760
|
var package_default = {
|
|
6751
6761
|
name: "@decocms/sandbox",
|
|
6752
|
-
version: "1.
|
|
6762
|
+
version: "1.80.0",
|
|
6753
6763
|
private: true,
|
|
6754
6764
|
type: "module",
|
|
6755
6765
|
description: "Sandbox runtime for isolated Studio tool execution",
|
|
@@ -6827,6 +6837,17 @@ var DAEMON_PORT = 9e3;
|
|
|
6827
6837
|
var WORKDIR = "/app";
|
|
6828
6838
|
var DEFAULT_DEV_PORT2 = 3e3;
|
|
6829
6839
|
var CONTAINER = "sandbox";
|
|
6840
|
+
var SIDECAR = "orgfs";
|
|
6841
|
+
var DEFAULT_SIDECAR_IMAGE = "ghcr.io/decocms/studio/orgfs-sidecar:0.1.0";
|
|
6842
|
+
var HOST_ORG_DIR = "/srv/sandbox/org";
|
|
6843
|
+
var HOST_CTL_DIR = "/srv/sandbox/orgfs-ctl";
|
|
6844
|
+
var CTL_DIR = "/run/orgfs";
|
|
6845
|
+
var HOST_SETUP = [
|
|
6846
|
+
`mkdir -p ${HOST_ORG_DIR} ${HOST_CTL_DIR}`,
|
|
6847
|
+
`grep -q ' ${HOST_ORG_DIR} ' /etc/fstab || printf '%s\\n' '${HOST_ORG_DIR} ${HOST_ORG_DIR} none bind,rshared 0 0' 'tmpfs ${HOST_CTL_DIR} tmpfs uid=1000,gid=1000,mode=0755,size=1m 0 0' >> /etc/fstab`,
|
|
6848
|
+
`mountpoint -q ${HOST_ORG_DIR} || mount ${HOST_ORG_DIR}`,
|
|
6849
|
+
`mountpoint -q ${HOST_CTL_DIR} || mount ${HOST_CTL_DIR}`
|
|
6850
|
+
].join(" && ");
|
|
6830
6851
|
var TOKEN_KEY = "studio-daemon-token";
|
|
6831
6852
|
var ORG_KEY = "studio-org-id";
|
|
6832
6853
|
var USER_KEY = "studio-user-id";
|
|
@@ -6838,10 +6859,14 @@ var LOOKUP_TTL_MS = 5 * 6e4;
|
|
|
6838
6859
|
var MISS_TTL_MS = 1e4;
|
|
6839
6860
|
var LOOKUP_MAX = 5e3;
|
|
6840
6861
|
var WATCH_TIMEOUT_MS = 5 * 6e4;
|
|
6862
|
+
var LOOKUP_TIMEOUT_MS = 3e3;
|
|
6841
6863
|
var FreestyleSandboxProvider = class {
|
|
6842
6864
|
freestyle;
|
|
6865
|
+
lookupClient;
|
|
6843
6866
|
image;
|
|
6867
|
+
sidecarImage;
|
|
6844
6868
|
idleTimeoutSeconds;
|
|
6869
|
+
autoDeleteSeconds;
|
|
6845
6870
|
domainSuffix;
|
|
6846
6871
|
timeDaemonRequest;
|
|
6847
6872
|
inflight = new Inflight();
|
|
@@ -6849,9 +6874,24 @@ var FreestyleSandboxProvider = class {
|
|
|
6849
6874
|
lookups = /* @__PURE__ */ new Map();
|
|
6850
6875
|
releaseTimers = /* @__PURE__ */ new Map();
|
|
6851
6876
|
constructor(opts) {
|
|
6852
|
-
this.freestyle = new Freestyle({ apiKey: opts.apiKey });
|
|
6877
|
+
this.freestyle = opts.client ?? new Freestyle({ apiKey: opts.apiKey });
|
|
6878
|
+
this.lookupClient = opts.client ?? new Freestyle({
|
|
6879
|
+
apiKey: opts.apiKey,
|
|
6880
|
+
fetch: Object.assign(
|
|
6881
|
+
(input, init) => fetch(input, {
|
|
6882
|
+
...init,
|
|
6883
|
+
signal: init?.signal ? AbortSignal.any([
|
|
6884
|
+
init.signal,
|
|
6885
|
+
AbortSignal.timeout(LOOKUP_TIMEOUT_MS)
|
|
6886
|
+
]) : AbortSignal.timeout(LOOKUP_TIMEOUT_MS)
|
|
6887
|
+
}),
|
|
6888
|
+
{ preconnect: fetch.preconnect }
|
|
6889
|
+
)
|
|
6890
|
+
});
|
|
6853
6891
|
this.image = opts.image ?? `ghcr.io/decocms/studio/studio-sandbox-go:${package_default.version}`;
|
|
6892
|
+
this.sidecarImage = opts.orgFsSidecarImage ?? DEFAULT_SIDECAR_IMAGE;
|
|
6854
6893
|
this.idleTimeoutSeconds = opts.idleTimeoutSeconds ?? 15 * 60;
|
|
6894
|
+
this.autoDeleteSeconds = opts.autoDeleteSeconds ?? 3 * 24 * 60 * 60;
|
|
6855
6895
|
this.domainSuffix = opts.domainSuffix ?? "style.dev";
|
|
6856
6896
|
this.timeDaemonRequest = daemonProxyTimer(opts.meter);
|
|
6857
6897
|
}
|
|
@@ -6870,7 +6910,7 @@ var FreestyleSandboxProvider = class {
|
|
|
6870
6910
|
}
|
|
6871
6911
|
let found = null;
|
|
6872
6912
|
try {
|
|
6873
|
-
const data = await this.
|
|
6913
|
+
const data = await this.lookupClient.vms.get(handle);
|
|
6874
6914
|
const token = data.metadata[TOKEN_KEY];
|
|
6875
6915
|
if (token) {
|
|
6876
6916
|
found = {
|
|
@@ -6894,12 +6934,13 @@ var FreestyleSandboxProvider = class {
|
|
|
6894
6934
|
}
|
|
6895
6935
|
// ---- Base snapshot --------------------------------------------------------
|
|
6896
6936
|
/**
|
|
6897
|
-
* A snapshot of a VM with
|
|
6898
|
-
* seconds instead of pulling ~2GB. One per image; built on first use.
|
|
6937
|
+
* A snapshot of a VM with both images already pulled, so a sandbox boots in
|
|
6938
|
+
* seconds instead of pulling ~2GB. One per image pair; built on first use.
|
|
6899
6939
|
*/
|
|
6900
6940
|
baseSnapshot() {
|
|
6901
|
-
const image = this
|
|
6902
|
-
const slug = `studio-sandbox-${createHash3("sha256").update(image
|
|
6941
|
+
const { image, sidecarImage } = this;
|
|
6942
|
+
const slug = `studio-sandbox-${createHash3("sha256").update(`${image}
|
|
6943
|
+
${sidecarImage}`).digest("hex").slice(0, 16)}`;
|
|
6903
6944
|
return this.snapshotInflight.run(slug, async () => {
|
|
6904
6945
|
if (await this.snapshotExists(slug)) return slug;
|
|
6905
6946
|
console.log(`[${LOG_LABEL2}] building base snapshot ${slug} for ${image}`);
|
|
@@ -6912,9 +6953,11 @@ var FreestyleSandboxProvider = class {
|
|
|
6912
6953
|
}
|
|
6913
6954
|
});
|
|
6914
6955
|
try {
|
|
6915
|
-
await this.run(
|
|
6916
|
-
|
|
6917
|
-
|
|
6956
|
+
await this.run(
|
|
6957
|
+
vm,
|
|
6958
|
+
`docker pull -q ${shellQuote(image)} && docker pull -q ${shellQuote(sidecarImage)}`,
|
|
6959
|
+
{ timeoutMs: IMAGE_PULL_TIMEOUT_MS }
|
|
6960
|
+
);
|
|
6918
6961
|
await vm.snapshot({ slug });
|
|
6919
6962
|
} catch (err) {
|
|
6920
6963
|
if (!await this.snapshotExists(slug)) throw err;
|
|
@@ -6972,6 +7015,13 @@ var FreestyleSandboxProvider = class {
|
|
|
6972
7015
|
const { state } = await found.vm.data();
|
|
6973
7016
|
if (state !== "running") await found.vm.start();
|
|
6974
7017
|
await this.waitForDaemon(found.daemon.url);
|
|
7018
|
+
if (opts.orgFsConfigJson) {
|
|
7019
|
+
await postOrgFsConfig(
|
|
7020
|
+
found.daemon.url,
|
|
7021
|
+
found.daemon.token,
|
|
7022
|
+
opts.orgFsConfigJson
|
|
7023
|
+
).catch((err) => console.warn(`[${LOG_LABEL2}] org-fs relay failed`, err));
|
|
7024
|
+
}
|
|
6975
7025
|
const patch = gitCredentialRefreshPatch(opts);
|
|
6976
7026
|
if (patch) {
|
|
6977
7027
|
await postConfig(found.daemon.url, found.daemon.token, patch).catch(
|
|
@@ -6984,7 +7034,7 @@ var FreestyleSandboxProvider = class {
|
|
|
6984
7034
|
async create(handle, opts) {
|
|
6985
7035
|
const snapshotId = await this.baseSnapshot();
|
|
6986
7036
|
const token = randomBytes2(24).toString("hex");
|
|
6987
|
-
const { vm } = await this.
|
|
7037
|
+
const { vm } = await this.createVm({
|
|
6988
7038
|
snapshotId,
|
|
6989
7039
|
slug: handle,
|
|
6990
7040
|
idleTimeoutSeconds: this.idleTimeoutSeconds,
|
|
@@ -7013,12 +7063,19 @@ var FreestyleSandboxProvider = class {
|
|
|
7013
7063
|
DAEMON_TOKEN: token,
|
|
7014
7064
|
DAEMON_BOOT_ID: crypto.randomUUID(),
|
|
7015
7065
|
APP_ROOT: WORKDIR,
|
|
7016
|
-
PROXY_PORT: String(DAEMON_PORT)
|
|
7066
|
+
PROXY_PORT: String(DAEMON_PORT),
|
|
7067
|
+
ORGFS_SIDECAR_CONFIG_PATH: `${CTL_DIR}/config.json`,
|
|
7068
|
+
ORGFS_SIDECAR_STATUS_PATH: `${CTL_DIR}/status.json`
|
|
7017
7069
|
};
|
|
7018
7070
|
const envFlags = Object.keys(env).map((k) => `-e ${shellQuote(k)}`).join(" ");
|
|
7019
7071
|
await this.run(
|
|
7020
7072
|
vm,
|
|
7021
|
-
`docker run -d --name ${
|
|
7073
|
+
`sudo sh -c ${shellQuote(HOST_SETUP)} && docker run -d --name ${SIDECAR} --restart=always --privileged --device /dev/fuse -e APP_ROOT=${WORKDIR} -v ${HOST_ORG_DIR}:${WORKDIR}/org:rshared -v ${HOST_CTL_DIR}:${CTL_DIR} ${shellQuote(this.sidecarImage)}`,
|
|
7074
|
+
{ timeoutMs: IMAGE_PULL_TIMEOUT_MS }
|
|
7075
|
+
);
|
|
7076
|
+
await this.run(
|
|
7077
|
+
vm,
|
|
7078
|
+
`docker run -d --name ${CONTAINER} --restart=always -p ${DAEMON_PORT}:${DAEMON_PORT} -v ${HOST_ORG_DIR}:${WORKDIR}/org:rslave -v ${HOST_CTL_DIR}:${CTL_DIR} ${envFlags} ${shellQuote(this.image)}`,
|
|
7022
7079
|
{ env, timeoutMs: IMAGE_PULL_TIMEOUT_MS }
|
|
7023
7080
|
);
|
|
7024
7081
|
await this.waitForDaemon(daemon.url);
|
|
@@ -7036,6 +7093,21 @@ var FreestyleSandboxProvider = class {
|
|
|
7036
7093
|
}
|
|
7037
7094
|
this.remember(handle, { vm, daemon });
|
|
7038
7095
|
}
|
|
7096
|
+
/** A plan that caps autoDelete lower answers 400; omitting it gets the cap. */
|
|
7097
|
+
async createVm(opts) {
|
|
7098
|
+
try {
|
|
7099
|
+
return await this.freestyle.vms.create({
|
|
7100
|
+
...opts,
|
|
7101
|
+
autoDeleteSeconds: this.autoDeleteSeconds
|
|
7102
|
+
});
|
|
7103
|
+
} catch (err) {
|
|
7104
|
+
if (!(err instanceof FreestyleApiError && err.status === 400)) throw err;
|
|
7105
|
+
console.warn(
|
|
7106
|
+
`[${LOG_LABEL2}] autoDeleteSeconds=${this.autoDeleteSeconds} refused (${err.message}); creating with the plan's cap`
|
|
7107
|
+
);
|
|
7108
|
+
return this.freestyle.vms.create(opts);
|
|
7109
|
+
}
|
|
7110
|
+
}
|
|
7039
7111
|
configPayload(opts) {
|
|
7040
7112
|
return buildConfigPayload({
|
|
7041
7113
|
runtime: opts.workload?.runtime ?? "node",
|
|
@@ -7065,7 +7137,10 @@ var FreestyleSandboxProvider = class {
|
|
|
7065
7137
|
const found = await this.find(handle, { refresh: true });
|
|
7066
7138
|
this.lookups.delete(handle);
|
|
7067
7139
|
if (!found) return;
|
|
7068
|
-
await found.vm.exec({
|
|
7140
|
+
await found.vm.exec({
|
|
7141
|
+
command: `docker stop -t 30 ${CONTAINER}; docker stop -t 10 ${SIDECAR}`,
|
|
7142
|
+
timeoutMs: 6e4
|
|
7143
|
+
}).catch(() => {
|
|
7069
7144
|
});
|
|
7070
7145
|
await found.vm.delete();
|
|
7071
7146
|
}
|
|
@@ -7228,6 +7303,10 @@ function handleShare(handle) {
|
|
|
7228
7303
|
return Number.parseInt(hex, 16) / 2 ** 32;
|
|
7229
7304
|
}
|
|
7230
7305
|
var PICKED_MAX = 5e3;
|
|
7306
|
+
var other = (kind) => kind === "kubernetes" ? "freestyle" : "kubernetes";
|
|
7307
|
+
function repeatsElsewhere(err) {
|
|
7308
|
+
return err instanceof ConfigRequestError;
|
|
7309
|
+
}
|
|
7231
7310
|
var SandboxProviderRouter = class {
|
|
7232
7311
|
constructor(providers, opts = {}) {
|
|
7233
7312
|
this.providers = providers;
|
|
@@ -7238,6 +7317,14 @@ var SandboxProviderRouter = class {
|
|
|
7238
7317
|
}
|
|
7239
7318
|
providers;
|
|
7240
7319
|
picked = /* @__PURE__ */ new Map();
|
|
7320
|
+
/**
|
|
7321
|
+
* Handles whose sandbox on this provider outlived a fallback because its
|
|
7322
|
+
* delete failed. Retried when `place` next sees the handle.
|
|
7323
|
+
*
|
|
7324
|
+
* ponytail: in-process, lost on restart; Freestyle's autoDeleteSeconds
|
|
7325
|
+
* reclaims a stray paused VM anyway, only later.
|
|
7326
|
+
*/
|
|
7327
|
+
strays = /* @__PURE__ */ new Map();
|
|
7241
7328
|
freestyleShare;
|
|
7242
7329
|
get(kind) {
|
|
7243
7330
|
const provider = this.providers[kind];
|
|
@@ -7260,12 +7347,13 @@ var SandboxProviderRouter = class {
|
|
|
7260
7347
|
if (!kubernetes) return "freestyle";
|
|
7261
7348
|
const picked = this.picked.get(handle);
|
|
7262
7349
|
if (picked) return picked;
|
|
7263
|
-
|
|
7350
|
+
const owned = await freestyle.owns(handle).catch(() => false);
|
|
7351
|
+
return owned ? "freestyle" : "kubernetes";
|
|
7264
7352
|
}
|
|
7265
7353
|
async owner(handle) {
|
|
7266
7354
|
return this.get(await this.providerOf(handle));
|
|
7267
7355
|
}
|
|
7268
|
-
/** Where `ensure` puts this sandbox. */
|
|
7356
|
+
/** Where `ensure` puts this sandbox, and why. */
|
|
7269
7357
|
async place(handle, opts) {
|
|
7270
7358
|
const { kubernetes, freestyle } = this.providers;
|
|
7271
7359
|
const image = opts.sandboxImage ?? "default";
|
|
@@ -7273,30 +7361,98 @@ var SandboxProviderRouter = class {
|
|
|
7273
7361
|
if (!kubernetes) {
|
|
7274
7362
|
throw new Error(`sandbox image ${image} needs the kubernetes provider`);
|
|
7275
7363
|
}
|
|
7276
|
-
return "kubernetes";
|
|
7364
|
+
return { kind: "kubernetes", reason: freestyle ? "image" : void 0 };
|
|
7365
|
+
}
|
|
7366
|
+
if (!kubernetes) return { kind: "freestyle" };
|
|
7367
|
+
if (!freestyle) return { kind: "kubernetes" };
|
|
7368
|
+
const stray = this.strays.get(handle);
|
|
7369
|
+
if (stray) this.retire(stray, handle);
|
|
7370
|
+
if (stray !== "kubernetes" && await kubernetes.alive(handle).catch(() => false)) {
|
|
7371
|
+
return { kind: "kubernetes", reason: "existing" };
|
|
7372
|
+
}
|
|
7373
|
+
const owned = stray === "freestyle" ? false : await freestyle.owns(handle).catch(() => null);
|
|
7374
|
+
if (owned) return { kind: "freestyle", reason: "existing" };
|
|
7375
|
+
if (owned === null) {
|
|
7376
|
+
return { kind: "kubernetes", reason: "freestyle-unavailable" };
|
|
7377
|
+
}
|
|
7378
|
+
if (opts.provider) return { kind: opts.provider, reason: "requested" };
|
|
7379
|
+
if (this.hasWarmPool(opts, image)) {
|
|
7380
|
+
return { kind: "kubernetes", reason: "warm-pool" };
|
|
7381
|
+
}
|
|
7382
|
+
if (handleShare(handle) < this.freestyleShare) {
|
|
7383
|
+
return { kind: "freestyle", reason: "split" };
|
|
7277
7384
|
}
|
|
7278
|
-
if (!kubernetes) return "freestyle";
|
|
7279
|
-
if (!freestyle) return "kubernetes";
|
|
7280
|
-
if (await freestyle.owns(handle)) return "freestyle";
|
|
7281
|
-
if (await kubernetes.alive(handle).catch(() => false)) return "kubernetes";
|
|
7282
|
-
if (opts.provider) return opts.provider;
|
|
7283
|
-
if (handleShare(handle) < this.freestyleShare) return "freestyle";
|
|
7284
7385
|
const roomy = await kubernetes.hasSchedulableCapacity().catch(() => false);
|
|
7285
|
-
return roomy ? "kubernetes" : "freestyle";
|
|
7386
|
+
return roomy ? { kind: "kubernetes", reason: "split" } : { kind: "freestyle", reason: "capacity" };
|
|
7387
|
+
}
|
|
7388
|
+
/** The org has pods already cloned and running for this image. */
|
|
7389
|
+
hasWarmPool(opts, image) {
|
|
7390
|
+
const tenant = claimTenantKey(opts);
|
|
7391
|
+
if (!tenant) return false;
|
|
7392
|
+
const pools = this.providers.kubernetes?.listTenantPools?.() ?? [];
|
|
7393
|
+
return pools.some((p) => p.tenant === tenant && p.image === image);
|
|
7286
7394
|
}
|
|
7287
7395
|
async ensure(id2, opts = {}) {
|
|
7288
7396
|
const { provider: _requested, ...rest } = opts;
|
|
7289
7397
|
const handle = computeHandle(id2);
|
|
7290
|
-
const
|
|
7291
|
-
this.remember(handle, kind);
|
|
7398
|
+
const placed = await this.place(handle, opts);
|
|
7399
|
+
this.remember(handle, placed.kind);
|
|
7400
|
+
if (this.strays.get(handle) === placed.kind) this.strays.delete(handle);
|
|
7401
|
+
let failure;
|
|
7402
|
+
try {
|
|
7403
|
+
const sandbox2 = await this.get(placed.kind).ensure(id2, rest);
|
|
7404
|
+
return placed.reason ? { ...sandbox2, placement: { reason: placed.reason } } : sandbox2;
|
|
7405
|
+
} catch (err) {
|
|
7406
|
+
failure = tagSandboxProvider(err, placed.kind);
|
|
7407
|
+
}
|
|
7408
|
+
const fallback = other(placed.kind);
|
|
7409
|
+
if (!placed.reason || placed.reason === "image" || repeatsElsewhere(failure)) {
|
|
7410
|
+
throw failure;
|
|
7411
|
+
}
|
|
7412
|
+
console.warn(
|
|
7413
|
+
`[SandboxProviderRouter] ensure ${handle} failed on ${placed.kind}, trying ${fallback}:`,
|
|
7414
|
+
failure instanceof Error ? failure.message : String(failure)
|
|
7415
|
+
);
|
|
7416
|
+
this.remember(handle, fallback);
|
|
7417
|
+
let sandbox;
|
|
7292
7418
|
try {
|
|
7293
|
-
|
|
7419
|
+
sandbox = await this.get(fallback).ensure(id2, rest);
|
|
7294
7420
|
} catch (err) {
|
|
7295
|
-
|
|
7421
|
+
this.picked.delete(handle);
|
|
7422
|
+
throw tagSandboxProvider(err, fallback);
|
|
7296
7423
|
}
|
|
7424
|
+
if (placed.reason === "existing") this.retire(placed.kind, handle);
|
|
7425
|
+
return {
|
|
7426
|
+
...sandbox,
|
|
7427
|
+
placement: { reason: "fallback", fallbackFrom: placed.kind }
|
|
7428
|
+
};
|
|
7429
|
+
}
|
|
7430
|
+
/** Delete the sandbox a fallback left behind on `kind`, in the background. */
|
|
7431
|
+
retire(kind, handle) {
|
|
7432
|
+
this.strays.set(handle, kind);
|
|
7433
|
+
void retry(
|
|
7434
|
+
async () => {
|
|
7435
|
+
if (this.strays.get(handle) === kind)
|
|
7436
|
+
await this.get(kind).delete(handle);
|
|
7437
|
+
},
|
|
7438
|
+
{ maxAttempts: 3 }
|
|
7439
|
+
).then(() => {
|
|
7440
|
+
if (this.strays.get(handle) === kind) this.strays.delete(handle);
|
|
7441
|
+
}).catch(
|
|
7442
|
+
(err) => console.warn(
|
|
7443
|
+
`[SandboxProviderRouter] delete of ${kind} sandbox ${handle} after fallback failed:`,
|
|
7444
|
+
err instanceof Error ? err.message : String(err)
|
|
7445
|
+
)
|
|
7446
|
+
);
|
|
7297
7447
|
}
|
|
7298
7448
|
async delete(handle) {
|
|
7299
|
-
|
|
7449
|
+
const stray = this.strays.get(handle);
|
|
7450
|
+
this.strays.delete(handle);
|
|
7451
|
+
await Promise.all([
|
|
7452
|
+
(await this.owner(handle)).delete(handle),
|
|
7453
|
+
stray && this.get(stray).delete(handle).catch(() => {
|
|
7454
|
+
})
|
|
7455
|
+
]);
|
|
7300
7456
|
this.picked.delete(handle);
|
|
7301
7457
|
}
|
|
7302
7458
|
async lastTermination(handle) {
|
|
@@ -7356,6 +7512,7 @@ var SandboxProviderRouter = class {
|
|
|
7356
7512
|
this.providers.kubernetes?.close();
|
|
7357
7513
|
this.providers.freestyle?.close();
|
|
7358
7514
|
this.picked.clear();
|
|
7515
|
+
this.strays.clear();
|
|
7359
7516
|
}
|
|
7360
7517
|
};
|
|
7361
7518
|
|
|
@@ -7440,6 +7597,11 @@ var ensureOutputSchema = z4.object({
|
|
|
7440
7597
|
previewUrl: z4.string().nullable(),
|
|
7441
7598
|
warmPoolAdopted: z4.boolean(),
|
|
7442
7599
|
provider: providerKindSchema.default("kubernetes"),
|
|
7600
|
+
// A reason this build doesn't know drops the label, not the ensure.
|
|
7601
|
+
placement: z4.object({
|
|
7602
|
+
reason: z4.enum(SANDBOX_PLACEMENT_REASONS),
|
|
7603
|
+
fallbackFrom: providerKindSchema.optional()
|
|
7604
|
+
}).optional().catch(void 0),
|
|
7443
7605
|
daemon: daemonSchema
|
|
7444
7606
|
});
|
|
7445
7607
|
var handleInputSchema = z4.object({ handle: z4.string().min(1) });
|