modal 0.10.2-dev.5 → 0.10.2-dev.6
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 +1 -0
- package/dist/index.cjs +175 -147
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +175 -147
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -50,6 +50,7 @@ We also provide a number of examples:
|
|
|
50
50
|
- [Create a Sandbox using a private image from AWS ECR](https://github.com/modal-labs/modal-client/blob/main/js/examples/sandbox-private-image.ts)
|
|
51
51
|
- [Run a Sidecar and snapshot its filesystem](https://github.com/modal-labs/modal-client/blob/main/js/examples/sandbox-sidecar.ts)
|
|
52
52
|
- [Persist a workspace across Sidecars](https://github.com/modal-labs/modal-client/blob/main/js/examples/sandbox-sidecar-directory-snapshot.ts)
|
|
53
|
+
- [Mount a Volume in a Sidecar](https://github.com/modal-labs/modal-client/blob/main/js/examples/sandbox-sidecar-volume.ts)
|
|
53
54
|
- [Take a snapshot of the filesystem of a Sandbox](https://github.com/modal-labs/modal-client/blob/main/js/examples/sandbox-filesystem-snapshot.ts)
|
|
54
55
|
- [Snapshot the filesystem of a Sandbox when it exits](https://github.com/modal-labs/modal-client/blob/main/js/examples/sandbox-exit-snapshot.ts)
|
|
55
56
|
- [Snapshot a directory, and mount it in a running Sandbox](https://github.com/modal-labs/modal-client/blob/main/js/examples/sandbox-directory-snapshot.ts)
|
package/dist/index.cjs
CHANGED
|
@@ -70555,8 +70555,151 @@ var SandboxFilesystem = class {
|
|
|
70555
70555
|
};
|
|
70556
70556
|
|
|
70557
70557
|
// src/sandbox_sidecar.ts
|
|
70558
|
-
var
|
|
70558
|
+
var import_nice_grpc11 = require("nice-grpc");
|
|
70559
70559
|
var import_uuid = require("uuid");
|
|
70560
|
+
|
|
70561
|
+
// src/volume.ts
|
|
70562
|
+
var import_nice_grpc10 = require("nice-grpc");
|
|
70563
|
+
var VolumeService = class {
|
|
70564
|
+
#client;
|
|
70565
|
+
constructor(client) {
|
|
70566
|
+
this.#client = client;
|
|
70567
|
+
}
|
|
70568
|
+
/**
|
|
70569
|
+
* Reference a {@link Volume} by its name.
|
|
70570
|
+
*/
|
|
70571
|
+
async fromName(name, params) {
|
|
70572
|
+
try {
|
|
70573
|
+
const resp = await this.#client.cpClient.volumeGetOrCreate({
|
|
70574
|
+
deploymentName: name,
|
|
70575
|
+
environmentName: this.#client.environmentName(params?.environment),
|
|
70576
|
+
objectCreationType: params?.createIfMissing ? 1 /* OBJECT_CREATION_TYPE_CREATE_IF_MISSING */ : 0 /* OBJECT_CREATION_TYPE_UNSPECIFIED */
|
|
70577
|
+
});
|
|
70578
|
+
this.#client.logger.debug(
|
|
70579
|
+
"Retrieved Volume",
|
|
70580
|
+
"volume_id",
|
|
70581
|
+
resp.volumeId,
|
|
70582
|
+
"volume_name",
|
|
70583
|
+
name
|
|
70584
|
+
);
|
|
70585
|
+
return new Volume(resp.volumeId, name);
|
|
70586
|
+
} catch (err) {
|
|
70587
|
+
if (err instanceof import_nice_grpc10.ClientError && err.code === import_nice_grpc10.Status.NOT_FOUND)
|
|
70588
|
+
throw new NotFoundError(err.details);
|
|
70589
|
+
throw err;
|
|
70590
|
+
}
|
|
70591
|
+
}
|
|
70592
|
+
/**
|
|
70593
|
+
* Create a nameless, temporary {@link Volume}.
|
|
70594
|
+
* It persists until closeEphemeral() is called, or the process exits.
|
|
70595
|
+
*/
|
|
70596
|
+
async ephemeral(params = {}) {
|
|
70597
|
+
const resp = await this.#client.cpClient.volumeGetOrCreate({
|
|
70598
|
+
objectCreationType: 5 /* OBJECT_CREATION_TYPE_EPHEMERAL */,
|
|
70599
|
+
environmentName: this.#client.environmentName(params.environment)
|
|
70600
|
+
});
|
|
70601
|
+
this.#client.logger.debug(
|
|
70602
|
+
"Created ephemeral Volume",
|
|
70603
|
+
"volume_id",
|
|
70604
|
+
resp.volumeId
|
|
70605
|
+
);
|
|
70606
|
+
const ephemeralHbManager = new EphemeralHeartbeatManager(
|
|
70607
|
+
() => this.#client.cpClient.volumeHeartbeat({ volumeId: resp.volumeId })
|
|
70608
|
+
);
|
|
70609
|
+
return new Volume(resp.volumeId, void 0, void 0, ephemeralHbManager);
|
|
70610
|
+
}
|
|
70611
|
+
/**
|
|
70612
|
+
* Delete a named {@link Volume}.
|
|
70613
|
+
*
|
|
70614
|
+
* Warning: Deletion is irreversible and will affect any Apps currently using the Volume.
|
|
70615
|
+
*/
|
|
70616
|
+
async delete(name, params) {
|
|
70617
|
+
try {
|
|
70618
|
+
const volume = await this.fromName(name, {
|
|
70619
|
+
environment: params?.environment,
|
|
70620
|
+
createIfMissing: false
|
|
70621
|
+
});
|
|
70622
|
+
await this.#client.cpClient.volumeDelete({
|
|
70623
|
+
volumeId: volume.volumeId
|
|
70624
|
+
});
|
|
70625
|
+
this.#client.logger.debug(
|
|
70626
|
+
"Deleted Volume",
|
|
70627
|
+
"volume_name",
|
|
70628
|
+
name,
|
|
70629
|
+
"volume_id",
|
|
70630
|
+
volume.volumeId
|
|
70631
|
+
);
|
|
70632
|
+
} catch (err) {
|
|
70633
|
+
const isNotFound = err instanceof NotFoundError || err instanceof import_nice_grpc10.ClientError && err.code === import_nice_grpc10.Status.NOT_FOUND;
|
|
70634
|
+
if (isNotFound && params?.allowMissing) {
|
|
70635
|
+
return;
|
|
70636
|
+
}
|
|
70637
|
+
throw err;
|
|
70638
|
+
}
|
|
70639
|
+
}
|
|
70640
|
+
};
|
|
70641
|
+
var DEFAULT_MOUNT_OPTIONS = {
|
|
70642
|
+
readOnly: false,
|
|
70643
|
+
subPath: void 0
|
|
70644
|
+
};
|
|
70645
|
+
var Volume = class _Volume {
|
|
70646
|
+
volumeId;
|
|
70647
|
+
name;
|
|
70648
|
+
/**
|
|
70649
|
+
* @hidden
|
|
70650
|
+
* @internal
|
|
70651
|
+
*/
|
|
70652
|
+
_mountOptions = DEFAULT_MOUNT_OPTIONS;
|
|
70653
|
+
#ephemeralHbManager;
|
|
70654
|
+
/** @ignore */
|
|
70655
|
+
constructor(volumeId, name, mountOptions, ephemeralHbManager) {
|
|
70656
|
+
this.volumeId = volumeId;
|
|
70657
|
+
this.name = name;
|
|
70658
|
+
if (mountOptions) {
|
|
70659
|
+
this._mountOptions = mountOptions;
|
|
70660
|
+
}
|
|
70661
|
+
this.#ephemeralHbManager = ephemeralHbManager;
|
|
70662
|
+
}
|
|
70663
|
+
/**
|
|
70664
|
+
* Configure options used when mounting this Volume. Fields left undefined preserve their value
|
|
70665
|
+
* from any previous withMountOptions call on the same Volume (stacking).
|
|
70666
|
+
*/
|
|
70667
|
+
withMountOptions(params = {}) {
|
|
70668
|
+
let subPath = this._mountOptions.subPath;
|
|
70669
|
+
if (params.subPath !== void 0) {
|
|
70670
|
+
subPath = params.subPath === "/" ? void 0 : params.subPath;
|
|
70671
|
+
}
|
|
70672
|
+
return new _Volume(
|
|
70673
|
+
this.volumeId,
|
|
70674
|
+
this.name,
|
|
70675
|
+
{
|
|
70676
|
+
readOnly: params.readOnly ?? this._mountOptions.readOnly,
|
|
70677
|
+
subPath
|
|
70678
|
+
},
|
|
70679
|
+
this.#ephemeralHbManager
|
|
70680
|
+
);
|
|
70681
|
+
}
|
|
70682
|
+
/** Delete the ephemeral Volume. Only usable with emphemeral Volumes. */
|
|
70683
|
+
closeEphemeral() {
|
|
70684
|
+
if (this.#ephemeralHbManager) {
|
|
70685
|
+
this.#ephemeralHbManager.stop();
|
|
70686
|
+
} else {
|
|
70687
|
+
throw new InvalidError("Volume is not ephemeral.");
|
|
70688
|
+
}
|
|
70689
|
+
}
|
|
70690
|
+
};
|
|
70691
|
+
function volumeToMountProto(mountPath, volume) {
|
|
70692
|
+
const opts = volume._mountOptions ?? DEFAULT_MOUNT_OPTIONS;
|
|
70693
|
+
return {
|
|
70694
|
+
volumeId: volume.volumeId,
|
|
70695
|
+
mountPath,
|
|
70696
|
+
allowBackgroundCommits: true,
|
|
70697
|
+
readOnly: opts.readOnly,
|
|
70698
|
+
subPath: opts.subPath
|
|
70699
|
+
};
|
|
70700
|
+
}
|
|
70701
|
+
|
|
70702
|
+
// src/sandbox_sidecar.ts
|
|
70560
70703
|
var MAIN_CONTAINER_NAME = "main";
|
|
70561
70704
|
var CONTAINER_WAIT_POLL_TIMEOUT_SECONDS = 10;
|
|
70562
70705
|
var CONTROL_PLANE_SIDECAR_CREATE_ENV_VAR = "MODAL_USE_CONTROL_PLANE_SIDECAR_CREATE";
|
|
@@ -70573,6 +70716,29 @@ function validateSidecarName(name) {
|
|
|
70573
70716
|
);
|
|
70574
70717
|
}
|
|
70575
70718
|
}
|
|
70719
|
+
function buildSidecarVolumeMounts(volumes) {
|
|
70720
|
+
const mountPathsByVolumeId = /* @__PURE__ */ new Map();
|
|
70721
|
+
const volumeMounts = [];
|
|
70722
|
+
for (const [mountPath, volume] of Object.entries(volumes ?? {})) {
|
|
70723
|
+
if (!volume) {
|
|
70724
|
+
throw new InvalidError(
|
|
70725
|
+
`Volume mounted at "${mountPath}" must not be null or undefined`
|
|
70726
|
+
);
|
|
70727
|
+
}
|
|
70728
|
+
const mountPaths = mountPathsByVolumeId.get(volume.volumeId) ?? [];
|
|
70729
|
+
mountPaths.push(mountPath);
|
|
70730
|
+
mountPathsByVolumeId.set(volume.volumeId, mountPaths);
|
|
70731
|
+
volumeMounts.push(volumeToMountProto(mountPath, volume));
|
|
70732
|
+
}
|
|
70733
|
+
for (const mountPaths of mountPathsByVolumeId.values()) {
|
|
70734
|
+
if (mountPaths.length > 1) {
|
|
70735
|
+
throw new InvalidError(
|
|
70736
|
+
"The same Volume cannot be mounted at multiple paths in a sidecar: " + mountPaths.sort().join(", ")
|
|
70737
|
+
);
|
|
70738
|
+
}
|
|
70739
|
+
}
|
|
70740
|
+
return volumeMounts;
|
|
70741
|
+
}
|
|
70576
70742
|
function sidecarContainerFromProto(access, info) {
|
|
70577
70743
|
return new SidecarContainer(
|
|
70578
70744
|
access,
|
|
@@ -70610,6 +70776,7 @@ var SidecarService = class {
|
|
|
70610
70776
|
Object.assign(envDict, params?.env ?? {});
|
|
70611
70777
|
await hydrateSecrets(this.#access.client, resolvableSecrets);
|
|
70612
70778
|
const secretIds = collectSecretIds(resolvableSecrets);
|
|
70779
|
+
const volumeMounts = buildSidecarVolumeMounts(params?.volumes);
|
|
70613
70780
|
const ptyInfo = params?.pty ? defaultSandboxPTYInfo() : void 0;
|
|
70614
70781
|
const networkAccess = buildOutboundNetworkAccess(
|
|
70615
70782
|
false,
|
|
@@ -70629,6 +70796,7 @@ var SidecarService = class {
|
|
|
70629
70796
|
entrypointArgs: command,
|
|
70630
70797
|
workdir: params?.workdir ?? void 0,
|
|
70631
70798
|
secretIds,
|
|
70799
|
+
volumeMounts,
|
|
70632
70800
|
networkAccess,
|
|
70633
70801
|
ptyInfo
|
|
70634
70802
|
}),
|
|
@@ -70646,17 +70814,18 @@ var SidecarService = class {
|
|
|
70646
70814
|
env: envDict,
|
|
70647
70815
|
workdir: params?.workdir ?? "",
|
|
70648
70816
|
secretIds,
|
|
70817
|
+
volumeMounts,
|
|
70649
70818
|
networkAccess,
|
|
70650
70819
|
ptyInfo
|
|
70651
70820
|
})
|
|
70652
70821
|
);
|
|
70653
70822
|
}
|
|
70654
70823
|
} catch (err) {
|
|
70655
|
-
if (err instanceof
|
|
70656
|
-
if (err.code ===
|
|
70824
|
+
if (err instanceof import_nice_grpc11.ClientError) {
|
|
70825
|
+
if (err.code === import_nice_grpc11.Status.ALREADY_EXISTS) {
|
|
70657
70826
|
throw new AlreadyExistsError(err.details || err.message);
|
|
70658
70827
|
}
|
|
70659
|
-
if (err.code ===
|
|
70828
|
+
if (err.code === import_nice_grpc11.Status.INVALID_ARGUMENT) {
|
|
70660
70829
|
throw new InvalidError(err.details || err.message);
|
|
70661
70830
|
}
|
|
70662
70831
|
}
|
|
@@ -70682,7 +70851,7 @@ var SidecarService = class {
|
|
|
70682
70851
|
})
|
|
70683
70852
|
);
|
|
70684
70853
|
} catch (err) {
|
|
70685
|
-
if (err instanceof
|
|
70854
|
+
if (err instanceof import_nice_grpc11.ClientError && err.code === import_nice_grpc11.Status.NOT_FOUND) {
|
|
70686
70855
|
throw new NotFoundError(`Sidecar container "${name}" not found`);
|
|
70687
70856
|
}
|
|
70688
70857
|
throw err;
|
|
@@ -71053,147 +71222,6 @@ var SandboxSnapshotService = class {
|
|
|
71053
71222
|
}
|
|
71054
71223
|
};
|
|
71055
71224
|
|
|
71056
|
-
// src/volume.ts
|
|
71057
|
-
var import_nice_grpc11 = require("nice-grpc");
|
|
71058
|
-
var VolumeService = class {
|
|
71059
|
-
#client;
|
|
71060
|
-
constructor(client) {
|
|
71061
|
-
this.#client = client;
|
|
71062
|
-
}
|
|
71063
|
-
/**
|
|
71064
|
-
* Reference a {@link Volume} by its name.
|
|
71065
|
-
*/
|
|
71066
|
-
async fromName(name, params) {
|
|
71067
|
-
try {
|
|
71068
|
-
const resp = await this.#client.cpClient.volumeGetOrCreate({
|
|
71069
|
-
deploymentName: name,
|
|
71070
|
-
environmentName: this.#client.environmentName(params?.environment),
|
|
71071
|
-
objectCreationType: params?.createIfMissing ? 1 /* OBJECT_CREATION_TYPE_CREATE_IF_MISSING */ : 0 /* OBJECT_CREATION_TYPE_UNSPECIFIED */
|
|
71072
|
-
});
|
|
71073
|
-
this.#client.logger.debug(
|
|
71074
|
-
"Retrieved Volume",
|
|
71075
|
-
"volume_id",
|
|
71076
|
-
resp.volumeId,
|
|
71077
|
-
"volume_name",
|
|
71078
|
-
name
|
|
71079
|
-
);
|
|
71080
|
-
return new Volume(resp.volumeId, name);
|
|
71081
|
-
} catch (err) {
|
|
71082
|
-
if (err instanceof import_nice_grpc11.ClientError && err.code === import_nice_grpc11.Status.NOT_FOUND)
|
|
71083
|
-
throw new NotFoundError(err.details);
|
|
71084
|
-
throw err;
|
|
71085
|
-
}
|
|
71086
|
-
}
|
|
71087
|
-
/**
|
|
71088
|
-
* Create a nameless, temporary {@link Volume}.
|
|
71089
|
-
* It persists until closeEphemeral() is called, or the process exits.
|
|
71090
|
-
*/
|
|
71091
|
-
async ephemeral(params = {}) {
|
|
71092
|
-
const resp = await this.#client.cpClient.volumeGetOrCreate({
|
|
71093
|
-
objectCreationType: 5 /* OBJECT_CREATION_TYPE_EPHEMERAL */,
|
|
71094
|
-
environmentName: this.#client.environmentName(params.environment)
|
|
71095
|
-
});
|
|
71096
|
-
this.#client.logger.debug(
|
|
71097
|
-
"Created ephemeral Volume",
|
|
71098
|
-
"volume_id",
|
|
71099
|
-
resp.volumeId
|
|
71100
|
-
);
|
|
71101
|
-
const ephemeralHbManager = new EphemeralHeartbeatManager(
|
|
71102
|
-
() => this.#client.cpClient.volumeHeartbeat({ volumeId: resp.volumeId })
|
|
71103
|
-
);
|
|
71104
|
-
return new Volume(resp.volumeId, void 0, void 0, ephemeralHbManager);
|
|
71105
|
-
}
|
|
71106
|
-
/**
|
|
71107
|
-
* Delete a named {@link Volume}.
|
|
71108
|
-
*
|
|
71109
|
-
* Warning: Deletion is irreversible and will affect any Apps currently using the Volume.
|
|
71110
|
-
*/
|
|
71111
|
-
async delete(name, params) {
|
|
71112
|
-
try {
|
|
71113
|
-
const volume = await this.fromName(name, {
|
|
71114
|
-
environment: params?.environment,
|
|
71115
|
-
createIfMissing: false
|
|
71116
|
-
});
|
|
71117
|
-
await this.#client.cpClient.volumeDelete({
|
|
71118
|
-
volumeId: volume.volumeId
|
|
71119
|
-
});
|
|
71120
|
-
this.#client.logger.debug(
|
|
71121
|
-
"Deleted Volume",
|
|
71122
|
-
"volume_name",
|
|
71123
|
-
name,
|
|
71124
|
-
"volume_id",
|
|
71125
|
-
volume.volumeId
|
|
71126
|
-
);
|
|
71127
|
-
} catch (err) {
|
|
71128
|
-
const isNotFound = err instanceof NotFoundError || err instanceof import_nice_grpc11.ClientError && err.code === import_nice_grpc11.Status.NOT_FOUND;
|
|
71129
|
-
if (isNotFound && params?.allowMissing) {
|
|
71130
|
-
return;
|
|
71131
|
-
}
|
|
71132
|
-
throw err;
|
|
71133
|
-
}
|
|
71134
|
-
}
|
|
71135
|
-
};
|
|
71136
|
-
var DEFAULT_MOUNT_OPTIONS = {
|
|
71137
|
-
readOnly: false,
|
|
71138
|
-
subPath: void 0
|
|
71139
|
-
};
|
|
71140
|
-
var Volume = class _Volume {
|
|
71141
|
-
volumeId;
|
|
71142
|
-
name;
|
|
71143
|
-
/**
|
|
71144
|
-
* @hidden
|
|
71145
|
-
* @internal
|
|
71146
|
-
*/
|
|
71147
|
-
_mountOptions = DEFAULT_MOUNT_OPTIONS;
|
|
71148
|
-
#ephemeralHbManager;
|
|
71149
|
-
/** @ignore */
|
|
71150
|
-
constructor(volumeId, name, mountOptions, ephemeralHbManager) {
|
|
71151
|
-
this.volumeId = volumeId;
|
|
71152
|
-
this.name = name;
|
|
71153
|
-
if (mountOptions) {
|
|
71154
|
-
this._mountOptions = mountOptions;
|
|
71155
|
-
}
|
|
71156
|
-
this.#ephemeralHbManager = ephemeralHbManager;
|
|
71157
|
-
}
|
|
71158
|
-
/**
|
|
71159
|
-
* Configure options used when mounting this Volume. Fields left undefined preserve their value
|
|
71160
|
-
* from any previous withMountOptions call on the same Volume (stacking).
|
|
71161
|
-
*/
|
|
71162
|
-
withMountOptions(params = {}) {
|
|
71163
|
-
let subPath = this._mountOptions.subPath;
|
|
71164
|
-
if (params.subPath !== void 0) {
|
|
71165
|
-
subPath = params.subPath === "/" ? void 0 : params.subPath;
|
|
71166
|
-
}
|
|
71167
|
-
return new _Volume(
|
|
71168
|
-
this.volumeId,
|
|
71169
|
-
this.name,
|
|
71170
|
-
{
|
|
71171
|
-
readOnly: params.readOnly ?? this._mountOptions.readOnly,
|
|
71172
|
-
subPath
|
|
71173
|
-
},
|
|
71174
|
-
this.#ephemeralHbManager
|
|
71175
|
-
);
|
|
71176
|
-
}
|
|
71177
|
-
/** Delete the ephemeral Volume. Only usable with emphemeral Volumes. */
|
|
71178
|
-
closeEphemeral() {
|
|
71179
|
-
if (this.#ephemeralHbManager) {
|
|
71180
|
-
this.#ephemeralHbManager.stop();
|
|
71181
|
-
} else {
|
|
71182
|
-
throw new InvalidError("Volume is not ephemeral.");
|
|
71183
|
-
}
|
|
71184
|
-
}
|
|
71185
|
-
};
|
|
71186
|
-
function volumeToMountProto(mountPath, volume) {
|
|
71187
|
-
const opts = volume._mountOptions ?? DEFAULT_MOUNT_OPTIONS;
|
|
71188
|
-
return {
|
|
71189
|
-
volumeId: volume.volumeId,
|
|
71190
|
-
mountPath,
|
|
71191
|
-
allowBackgroundCommits: true,
|
|
71192
|
-
readOnly: opts.readOnly,
|
|
71193
|
-
subPath: opts.subPath
|
|
71194
|
-
};
|
|
71195
|
-
}
|
|
71196
|
-
|
|
71197
71225
|
// src/sandbox.ts
|
|
71198
71226
|
var SB_LOGS_INITIAL_DELAY_MS = 10;
|
|
71199
71227
|
var SB_LOGS_DELAY_FACTOR = 2;
|
|
@@ -73372,7 +73400,7 @@ var AuthTokenManager = class {
|
|
|
73372
73400
|
};
|
|
73373
73401
|
|
|
73374
73402
|
// src/version.ts
|
|
73375
|
-
var SDK_VERSION = "0.10.2-dev.
|
|
73403
|
+
var SDK_VERSION = "0.10.2-dev.6";
|
|
73376
73404
|
function getSDKVersion() {
|
|
73377
73405
|
return SDK_VERSION;
|
|
73378
73406
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -8893,6 +8893,8 @@ type SidecarCreateParams = {
|
|
|
8893
8893
|
secrets?: Secret[];
|
|
8894
8894
|
/** Working directory of the sidecar container. */
|
|
8895
8895
|
workdir?: string;
|
|
8896
|
+
/** Mount points for Modal {@link Volume}s. */
|
|
8897
|
+
volumes?: Record<string, Volume>;
|
|
8896
8898
|
/**
|
|
8897
8899
|
* List of CIDRs the sidecar is allowed to access. Independent of the main
|
|
8898
8900
|
* container; if not set, all CIDRs are allowed. An empty list blocks all
|
package/dist/index.d.ts
CHANGED
|
@@ -8893,6 +8893,8 @@ type SidecarCreateParams = {
|
|
|
8893
8893
|
secrets?: Secret[];
|
|
8894
8894
|
/** Working directory of the sidecar container. */
|
|
8895
8895
|
workdir?: string;
|
|
8896
|
+
/** Mount points for Modal {@link Volume}s. */
|
|
8897
|
+
volumes?: Record<string, Volume>;
|
|
8896
8898
|
/**
|
|
8897
8899
|
* List of CIDRs the sidecar is allowed to access. Independent of the main
|
|
8898
8900
|
* container; if not set, all CIDRs are allowed. An empty list blocks all
|
package/dist/index.js
CHANGED
|
@@ -70475,8 +70475,151 @@ var SandboxFilesystem = class {
|
|
|
70475
70475
|
};
|
|
70476
70476
|
|
|
70477
70477
|
// src/sandbox_sidecar.ts
|
|
70478
|
-
import { ClientError as
|
|
70478
|
+
import { ClientError as ClientError10, Status as Status10 } from "nice-grpc";
|
|
70479
70479
|
import { v4 as uuidv4 } from "uuid";
|
|
70480
|
+
|
|
70481
|
+
// src/volume.ts
|
|
70482
|
+
import { ClientError as ClientError9, Status as Status9 } from "nice-grpc";
|
|
70483
|
+
var VolumeService = class {
|
|
70484
|
+
#client;
|
|
70485
|
+
constructor(client) {
|
|
70486
|
+
this.#client = client;
|
|
70487
|
+
}
|
|
70488
|
+
/**
|
|
70489
|
+
* Reference a {@link Volume} by its name.
|
|
70490
|
+
*/
|
|
70491
|
+
async fromName(name, params) {
|
|
70492
|
+
try {
|
|
70493
|
+
const resp = await this.#client.cpClient.volumeGetOrCreate({
|
|
70494
|
+
deploymentName: name,
|
|
70495
|
+
environmentName: this.#client.environmentName(params?.environment),
|
|
70496
|
+
objectCreationType: params?.createIfMissing ? 1 /* OBJECT_CREATION_TYPE_CREATE_IF_MISSING */ : 0 /* OBJECT_CREATION_TYPE_UNSPECIFIED */
|
|
70497
|
+
});
|
|
70498
|
+
this.#client.logger.debug(
|
|
70499
|
+
"Retrieved Volume",
|
|
70500
|
+
"volume_id",
|
|
70501
|
+
resp.volumeId,
|
|
70502
|
+
"volume_name",
|
|
70503
|
+
name
|
|
70504
|
+
);
|
|
70505
|
+
return new Volume(resp.volumeId, name);
|
|
70506
|
+
} catch (err) {
|
|
70507
|
+
if (err instanceof ClientError9 && err.code === Status9.NOT_FOUND)
|
|
70508
|
+
throw new NotFoundError(err.details);
|
|
70509
|
+
throw err;
|
|
70510
|
+
}
|
|
70511
|
+
}
|
|
70512
|
+
/**
|
|
70513
|
+
* Create a nameless, temporary {@link Volume}.
|
|
70514
|
+
* It persists until closeEphemeral() is called, or the process exits.
|
|
70515
|
+
*/
|
|
70516
|
+
async ephemeral(params = {}) {
|
|
70517
|
+
const resp = await this.#client.cpClient.volumeGetOrCreate({
|
|
70518
|
+
objectCreationType: 5 /* OBJECT_CREATION_TYPE_EPHEMERAL */,
|
|
70519
|
+
environmentName: this.#client.environmentName(params.environment)
|
|
70520
|
+
});
|
|
70521
|
+
this.#client.logger.debug(
|
|
70522
|
+
"Created ephemeral Volume",
|
|
70523
|
+
"volume_id",
|
|
70524
|
+
resp.volumeId
|
|
70525
|
+
);
|
|
70526
|
+
const ephemeralHbManager = new EphemeralHeartbeatManager(
|
|
70527
|
+
() => this.#client.cpClient.volumeHeartbeat({ volumeId: resp.volumeId })
|
|
70528
|
+
);
|
|
70529
|
+
return new Volume(resp.volumeId, void 0, void 0, ephemeralHbManager);
|
|
70530
|
+
}
|
|
70531
|
+
/**
|
|
70532
|
+
* Delete a named {@link Volume}.
|
|
70533
|
+
*
|
|
70534
|
+
* Warning: Deletion is irreversible and will affect any Apps currently using the Volume.
|
|
70535
|
+
*/
|
|
70536
|
+
async delete(name, params) {
|
|
70537
|
+
try {
|
|
70538
|
+
const volume = await this.fromName(name, {
|
|
70539
|
+
environment: params?.environment,
|
|
70540
|
+
createIfMissing: false
|
|
70541
|
+
});
|
|
70542
|
+
await this.#client.cpClient.volumeDelete({
|
|
70543
|
+
volumeId: volume.volumeId
|
|
70544
|
+
});
|
|
70545
|
+
this.#client.logger.debug(
|
|
70546
|
+
"Deleted Volume",
|
|
70547
|
+
"volume_name",
|
|
70548
|
+
name,
|
|
70549
|
+
"volume_id",
|
|
70550
|
+
volume.volumeId
|
|
70551
|
+
);
|
|
70552
|
+
} catch (err) {
|
|
70553
|
+
const isNotFound = err instanceof NotFoundError || err instanceof ClientError9 && err.code === Status9.NOT_FOUND;
|
|
70554
|
+
if (isNotFound && params?.allowMissing) {
|
|
70555
|
+
return;
|
|
70556
|
+
}
|
|
70557
|
+
throw err;
|
|
70558
|
+
}
|
|
70559
|
+
}
|
|
70560
|
+
};
|
|
70561
|
+
var DEFAULT_MOUNT_OPTIONS = {
|
|
70562
|
+
readOnly: false,
|
|
70563
|
+
subPath: void 0
|
|
70564
|
+
};
|
|
70565
|
+
var Volume = class _Volume {
|
|
70566
|
+
volumeId;
|
|
70567
|
+
name;
|
|
70568
|
+
/**
|
|
70569
|
+
* @hidden
|
|
70570
|
+
* @internal
|
|
70571
|
+
*/
|
|
70572
|
+
_mountOptions = DEFAULT_MOUNT_OPTIONS;
|
|
70573
|
+
#ephemeralHbManager;
|
|
70574
|
+
/** @ignore */
|
|
70575
|
+
constructor(volumeId, name, mountOptions, ephemeralHbManager) {
|
|
70576
|
+
this.volumeId = volumeId;
|
|
70577
|
+
this.name = name;
|
|
70578
|
+
if (mountOptions) {
|
|
70579
|
+
this._mountOptions = mountOptions;
|
|
70580
|
+
}
|
|
70581
|
+
this.#ephemeralHbManager = ephemeralHbManager;
|
|
70582
|
+
}
|
|
70583
|
+
/**
|
|
70584
|
+
* Configure options used when mounting this Volume. Fields left undefined preserve their value
|
|
70585
|
+
* from any previous withMountOptions call on the same Volume (stacking).
|
|
70586
|
+
*/
|
|
70587
|
+
withMountOptions(params = {}) {
|
|
70588
|
+
let subPath = this._mountOptions.subPath;
|
|
70589
|
+
if (params.subPath !== void 0) {
|
|
70590
|
+
subPath = params.subPath === "/" ? void 0 : params.subPath;
|
|
70591
|
+
}
|
|
70592
|
+
return new _Volume(
|
|
70593
|
+
this.volumeId,
|
|
70594
|
+
this.name,
|
|
70595
|
+
{
|
|
70596
|
+
readOnly: params.readOnly ?? this._mountOptions.readOnly,
|
|
70597
|
+
subPath
|
|
70598
|
+
},
|
|
70599
|
+
this.#ephemeralHbManager
|
|
70600
|
+
);
|
|
70601
|
+
}
|
|
70602
|
+
/** Delete the ephemeral Volume. Only usable with emphemeral Volumes. */
|
|
70603
|
+
closeEphemeral() {
|
|
70604
|
+
if (this.#ephemeralHbManager) {
|
|
70605
|
+
this.#ephemeralHbManager.stop();
|
|
70606
|
+
} else {
|
|
70607
|
+
throw new InvalidError("Volume is not ephemeral.");
|
|
70608
|
+
}
|
|
70609
|
+
}
|
|
70610
|
+
};
|
|
70611
|
+
function volumeToMountProto(mountPath, volume) {
|
|
70612
|
+
const opts = volume._mountOptions ?? DEFAULT_MOUNT_OPTIONS;
|
|
70613
|
+
return {
|
|
70614
|
+
volumeId: volume.volumeId,
|
|
70615
|
+
mountPath,
|
|
70616
|
+
allowBackgroundCommits: true,
|
|
70617
|
+
readOnly: opts.readOnly,
|
|
70618
|
+
subPath: opts.subPath
|
|
70619
|
+
};
|
|
70620
|
+
}
|
|
70621
|
+
|
|
70622
|
+
// src/sandbox_sidecar.ts
|
|
70480
70623
|
var MAIN_CONTAINER_NAME = "main";
|
|
70481
70624
|
var CONTAINER_WAIT_POLL_TIMEOUT_SECONDS = 10;
|
|
70482
70625
|
var CONTROL_PLANE_SIDECAR_CREATE_ENV_VAR = "MODAL_USE_CONTROL_PLANE_SIDECAR_CREATE";
|
|
@@ -70493,6 +70636,29 @@ function validateSidecarName(name) {
|
|
|
70493
70636
|
);
|
|
70494
70637
|
}
|
|
70495
70638
|
}
|
|
70639
|
+
function buildSidecarVolumeMounts(volumes) {
|
|
70640
|
+
const mountPathsByVolumeId = /* @__PURE__ */ new Map();
|
|
70641
|
+
const volumeMounts = [];
|
|
70642
|
+
for (const [mountPath, volume] of Object.entries(volumes ?? {})) {
|
|
70643
|
+
if (!volume) {
|
|
70644
|
+
throw new InvalidError(
|
|
70645
|
+
`Volume mounted at "${mountPath}" must not be null or undefined`
|
|
70646
|
+
);
|
|
70647
|
+
}
|
|
70648
|
+
const mountPaths = mountPathsByVolumeId.get(volume.volumeId) ?? [];
|
|
70649
|
+
mountPaths.push(mountPath);
|
|
70650
|
+
mountPathsByVolumeId.set(volume.volumeId, mountPaths);
|
|
70651
|
+
volumeMounts.push(volumeToMountProto(mountPath, volume));
|
|
70652
|
+
}
|
|
70653
|
+
for (const mountPaths of mountPathsByVolumeId.values()) {
|
|
70654
|
+
if (mountPaths.length > 1) {
|
|
70655
|
+
throw new InvalidError(
|
|
70656
|
+
"The same Volume cannot be mounted at multiple paths in a sidecar: " + mountPaths.sort().join(", ")
|
|
70657
|
+
);
|
|
70658
|
+
}
|
|
70659
|
+
}
|
|
70660
|
+
return volumeMounts;
|
|
70661
|
+
}
|
|
70496
70662
|
function sidecarContainerFromProto(access, info) {
|
|
70497
70663
|
return new SidecarContainer(
|
|
70498
70664
|
access,
|
|
@@ -70530,6 +70696,7 @@ var SidecarService = class {
|
|
|
70530
70696
|
Object.assign(envDict, params?.env ?? {});
|
|
70531
70697
|
await hydrateSecrets(this.#access.client, resolvableSecrets);
|
|
70532
70698
|
const secretIds = collectSecretIds(resolvableSecrets);
|
|
70699
|
+
const volumeMounts = buildSidecarVolumeMounts(params?.volumes);
|
|
70533
70700
|
const ptyInfo = params?.pty ? defaultSandboxPTYInfo() : void 0;
|
|
70534
70701
|
const networkAccess = buildOutboundNetworkAccess(
|
|
70535
70702
|
false,
|
|
@@ -70549,6 +70716,7 @@ var SidecarService = class {
|
|
|
70549
70716
|
entrypointArgs: command,
|
|
70550
70717
|
workdir: params?.workdir ?? void 0,
|
|
70551
70718
|
secretIds,
|
|
70719
|
+
volumeMounts,
|
|
70552
70720
|
networkAccess,
|
|
70553
70721
|
ptyInfo
|
|
70554
70722
|
}),
|
|
@@ -70566,17 +70734,18 @@ var SidecarService = class {
|
|
|
70566
70734
|
env: envDict,
|
|
70567
70735
|
workdir: params?.workdir ?? "",
|
|
70568
70736
|
secretIds,
|
|
70737
|
+
volumeMounts,
|
|
70569
70738
|
networkAccess,
|
|
70570
70739
|
ptyInfo
|
|
70571
70740
|
})
|
|
70572
70741
|
);
|
|
70573
70742
|
}
|
|
70574
70743
|
} catch (err) {
|
|
70575
|
-
if (err instanceof
|
|
70576
|
-
if (err.code ===
|
|
70744
|
+
if (err instanceof ClientError10) {
|
|
70745
|
+
if (err.code === Status10.ALREADY_EXISTS) {
|
|
70577
70746
|
throw new AlreadyExistsError(err.details || err.message);
|
|
70578
70747
|
}
|
|
70579
|
-
if (err.code ===
|
|
70748
|
+
if (err.code === Status10.INVALID_ARGUMENT) {
|
|
70580
70749
|
throw new InvalidError(err.details || err.message);
|
|
70581
70750
|
}
|
|
70582
70751
|
}
|
|
@@ -70602,7 +70771,7 @@ var SidecarService = class {
|
|
|
70602
70771
|
})
|
|
70603
70772
|
);
|
|
70604
70773
|
} catch (err) {
|
|
70605
|
-
if (err instanceof
|
|
70774
|
+
if (err instanceof ClientError10 && err.code === Status10.NOT_FOUND) {
|
|
70606
70775
|
throw new NotFoundError(`Sidecar container "${name}" not found`);
|
|
70607
70776
|
}
|
|
70608
70777
|
throw err;
|
|
@@ -70973,147 +71142,6 @@ var SandboxSnapshotService = class {
|
|
|
70973
71142
|
}
|
|
70974
71143
|
};
|
|
70975
71144
|
|
|
70976
|
-
// src/volume.ts
|
|
70977
|
-
import { ClientError as ClientError10, Status as Status10 } from "nice-grpc";
|
|
70978
|
-
var VolumeService = class {
|
|
70979
|
-
#client;
|
|
70980
|
-
constructor(client) {
|
|
70981
|
-
this.#client = client;
|
|
70982
|
-
}
|
|
70983
|
-
/**
|
|
70984
|
-
* Reference a {@link Volume} by its name.
|
|
70985
|
-
*/
|
|
70986
|
-
async fromName(name, params) {
|
|
70987
|
-
try {
|
|
70988
|
-
const resp = await this.#client.cpClient.volumeGetOrCreate({
|
|
70989
|
-
deploymentName: name,
|
|
70990
|
-
environmentName: this.#client.environmentName(params?.environment),
|
|
70991
|
-
objectCreationType: params?.createIfMissing ? 1 /* OBJECT_CREATION_TYPE_CREATE_IF_MISSING */ : 0 /* OBJECT_CREATION_TYPE_UNSPECIFIED */
|
|
70992
|
-
});
|
|
70993
|
-
this.#client.logger.debug(
|
|
70994
|
-
"Retrieved Volume",
|
|
70995
|
-
"volume_id",
|
|
70996
|
-
resp.volumeId,
|
|
70997
|
-
"volume_name",
|
|
70998
|
-
name
|
|
70999
|
-
);
|
|
71000
|
-
return new Volume(resp.volumeId, name);
|
|
71001
|
-
} catch (err) {
|
|
71002
|
-
if (err instanceof ClientError10 && err.code === Status10.NOT_FOUND)
|
|
71003
|
-
throw new NotFoundError(err.details);
|
|
71004
|
-
throw err;
|
|
71005
|
-
}
|
|
71006
|
-
}
|
|
71007
|
-
/**
|
|
71008
|
-
* Create a nameless, temporary {@link Volume}.
|
|
71009
|
-
* It persists until closeEphemeral() is called, or the process exits.
|
|
71010
|
-
*/
|
|
71011
|
-
async ephemeral(params = {}) {
|
|
71012
|
-
const resp = await this.#client.cpClient.volumeGetOrCreate({
|
|
71013
|
-
objectCreationType: 5 /* OBJECT_CREATION_TYPE_EPHEMERAL */,
|
|
71014
|
-
environmentName: this.#client.environmentName(params.environment)
|
|
71015
|
-
});
|
|
71016
|
-
this.#client.logger.debug(
|
|
71017
|
-
"Created ephemeral Volume",
|
|
71018
|
-
"volume_id",
|
|
71019
|
-
resp.volumeId
|
|
71020
|
-
);
|
|
71021
|
-
const ephemeralHbManager = new EphemeralHeartbeatManager(
|
|
71022
|
-
() => this.#client.cpClient.volumeHeartbeat({ volumeId: resp.volumeId })
|
|
71023
|
-
);
|
|
71024
|
-
return new Volume(resp.volumeId, void 0, void 0, ephemeralHbManager);
|
|
71025
|
-
}
|
|
71026
|
-
/**
|
|
71027
|
-
* Delete a named {@link Volume}.
|
|
71028
|
-
*
|
|
71029
|
-
* Warning: Deletion is irreversible and will affect any Apps currently using the Volume.
|
|
71030
|
-
*/
|
|
71031
|
-
async delete(name, params) {
|
|
71032
|
-
try {
|
|
71033
|
-
const volume = await this.fromName(name, {
|
|
71034
|
-
environment: params?.environment,
|
|
71035
|
-
createIfMissing: false
|
|
71036
|
-
});
|
|
71037
|
-
await this.#client.cpClient.volumeDelete({
|
|
71038
|
-
volumeId: volume.volumeId
|
|
71039
|
-
});
|
|
71040
|
-
this.#client.logger.debug(
|
|
71041
|
-
"Deleted Volume",
|
|
71042
|
-
"volume_name",
|
|
71043
|
-
name,
|
|
71044
|
-
"volume_id",
|
|
71045
|
-
volume.volumeId
|
|
71046
|
-
);
|
|
71047
|
-
} catch (err) {
|
|
71048
|
-
const isNotFound = err instanceof NotFoundError || err instanceof ClientError10 && err.code === Status10.NOT_FOUND;
|
|
71049
|
-
if (isNotFound && params?.allowMissing) {
|
|
71050
|
-
return;
|
|
71051
|
-
}
|
|
71052
|
-
throw err;
|
|
71053
|
-
}
|
|
71054
|
-
}
|
|
71055
|
-
};
|
|
71056
|
-
var DEFAULT_MOUNT_OPTIONS = {
|
|
71057
|
-
readOnly: false,
|
|
71058
|
-
subPath: void 0
|
|
71059
|
-
};
|
|
71060
|
-
var Volume = class _Volume {
|
|
71061
|
-
volumeId;
|
|
71062
|
-
name;
|
|
71063
|
-
/**
|
|
71064
|
-
* @hidden
|
|
71065
|
-
* @internal
|
|
71066
|
-
*/
|
|
71067
|
-
_mountOptions = DEFAULT_MOUNT_OPTIONS;
|
|
71068
|
-
#ephemeralHbManager;
|
|
71069
|
-
/** @ignore */
|
|
71070
|
-
constructor(volumeId, name, mountOptions, ephemeralHbManager) {
|
|
71071
|
-
this.volumeId = volumeId;
|
|
71072
|
-
this.name = name;
|
|
71073
|
-
if (mountOptions) {
|
|
71074
|
-
this._mountOptions = mountOptions;
|
|
71075
|
-
}
|
|
71076
|
-
this.#ephemeralHbManager = ephemeralHbManager;
|
|
71077
|
-
}
|
|
71078
|
-
/**
|
|
71079
|
-
* Configure options used when mounting this Volume. Fields left undefined preserve their value
|
|
71080
|
-
* from any previous withMountOptions call on the same Volume (stacking).
|
|
71081
|
-
*/
|
|
71082
|
-
withMountOptions(params = {}) {
|
|
71083
|
-
let subPath = this._mountOptions.subPath;
|
|
71084
|
-
if (params.subPath !== void 0) {
|
|
71085
|
-
subPath = params.subPath === "/" ? void 0 : params.subPath;
|
|
71086
|
-
}
|
|
71087
|
-
return new _Volume(
|
|
71088
|
-
this.volumeId,
|
|
71089
|
-
this.name,
|
|
71090
|
-
{
|
|
71091
|
-
readOnly: params.readOnly ?? this._mountOptions.readOnly,
|
|
71092
|
-
subPath
|
|
71093
|
-
},
|
|
71094
|
-
this.#ephemeralHbManager
|
|
71095
|
-
);
|
|
71096
|
-
}
|
|
71097
|
-
/** Delete the ephemeral Volume. Only usable with emphemeral Volumes. */
|
|
71098
|
-
closeEphemeral() {
|
|
71099
|
-
if (this.#ephemeralHbManager) {
|
|
71100
|
-
this.#ephemeralHbManager.stop();
|
|
71101
|
-
} else {
|
|
71102
|
-
throw new InvalidError("Volume is not ephemeral.");
|
|
71103
|
-
}
|
|
71104
|
-
}
|
|
71105
|
-
};
|
|
71106
|
-
function volumeToMountProto(mountPath, volume) {
|
|
71107
|
-
const opts = volume._mountOptions ?? DEFAULT_MOUNT_OPTIONS;
|
|
71108
|
-
return {
|
|
71109
|
-
volumeId: volume.volumeId,
|
|
71110
|
-
mountPath,
|
|
71111
|
-
allowBackgroundCommits: true,
|
|
71112
|
-
readOnly: opts.readOnly,
|
|
71113
|
-
subPath: opts.subPath
|
|
71114
|
-
};
|
|
71115
|
-
}
|
|
71116
|
-
|
|
71117
71145
|
// src/sandbox.ts
|
|
71118
71146
|
var SB_LOGS_INITIAL_DELAY_MS = 10;
|
|
71119
71147
|
var SB_LOGS_DELAY_FACTOR = 2;
|
|
@@ -73292,7 +73320,7 @@ var AuthTokenManager = class {
|
|
|
73292
73320
|
};
|
|
73293
73321
|
|
|
73294
73322
|
// src/version.ts
|
|
73295
|
-
var SDK_VERSION = "0.10.2-dev.
|
|
73323
|
+
var SDK_VERSION = "0.10.2-dev.6";
|
|
73296
73324
|
function getSDKVersion() {
|
|
73297
73325
|
return SDK_VERSION;
|
|
73298
73326
|
}
|