gigadrive 2.13.0 → 2.15.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.mjs CHANGED
@@ -9,7 +9,7 @@ import { Effect as Effect29, Layer, LogLevel, Logger } from "effect";
9
9
  // package.json
10
10
  var package_default = {
11
11
  name: "gigadrive",
12
- version: "2.13.0",
12
+ version: "2.15.0",
13
13
  repository: {
14
14
  type: "git",
15
15
  url: "https://github.com/Gigadrive/sdk.git",
@@ -3089,6 +3089,37 @@ var formatImageTable = (images) => formatTable([
3089
3089
  ["NAME", "STATUS", "SOURCE", "ID"],
3090
3090
  ...images.map((image) => [image.name, image.status, imageSource(image), image.id])
3091
3091
  ]);
3092
+ var formatWebhookTable = (webhooks) => formatTable([
3093
+ ["URL", "EVENTS", "STATUS", "ID"],
3094
+ ...webhooks.map((webhook) => [
3095
+ webhook.url,
3096
+ webhook.events.join(","),
3097
+ webhook.enabled ? "enabled" : "disabled",
3098
+ webhook.id
3099
+ ])
3100
+ ]);
3101
+ var formatVolumeTable = (volumes) => formatTable([
3102
+ ["NAME", "SIZE", "STORED", "STATUS", "LAST SAVED", "ID"],
3103
+ ...volumes.map((volume) => [
3104
+ volume.name,
3105
+ `${String(volume.sizeMb)} MiB`,
3106
+ volume.storedBytes === null ? "-" : formatBytes(volume.storedBytes),
3107
+ volume.attachedMicrovmId ? `${volume.status} (${volume.attachedMicrovmId})` : volume.status,
3108
+ volume.lastSaveError ? `failed: ${volume.lastSaveError}` : volume.lastSavedAt ?? "never",
3109
+ volume.id
3110
+ ])
3111
+ ]);
3112
+ var formatDeliveryTable = (deliveries) => formatTable([
3113
+ ["CREATED", "EVENT", "STATUS", "ATTEMPTS", "RESPONSE", "MICROVM"],
3114
+ ...deliveries.map((delivery) => [
3115
+ delivery.createdAt,
3116
+ delivery.eventType,
3117
+ delivery.status,
3118
+ String(delivery.attempts),
3119
+ delivery.lastStatusCode === null ? delivery.lastError ?? "-" : String(delivery.lastStatusCode),
3120
+ delivery.microvmId
3121
+ ])
3122
+ ]);
3092
3123
  var formatBytes = (bytes) => {
3093
3124
  if (bytes < 1024) return `${bytes} B`;
3094
3125
  const units = ["KiB", "MiB", "GiB", "TiB"];
@@ -3323,6 +3354,25 @@ var withClient = (run) => Effect22.gen(function* () {
3323
3354
  return yield* Effect22.tryPromise({ try: () => run(client), catch: toApiRequestError });
3324
3355
  });
3325
3356
  var signalOnInterrupt = (signal) => (command, exit) => Exit.isInterrupted(exit) ? Effect22.tryPromise(() => command.kill(signal)).pipe(Effect22.timeout("2 seconds"), Effect22.ignore) : Effect22.void;
3357
+ var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
3358
+ var parseVolumeFlags = (values) => Effect22.gen(function* () {
3359
+ if (values.length > 4) {
3360
+ return yield* new InvalidMicroVmOptionsError({ message: "--volume takes at most 4 volumes." });
3361
+ }
3362
+ const volumes = [];
3363
+ for (const value of values) {
3364
+ const separator = value.indexOf(":");
3365
+ const volumeId = separator > 0 ? value.slice(0, separator) : "";
3366
+ const mountPath = separator > 0 ? value.slice(separator + 1) : "";
3367
+ if (volumeId === "" || !mountPath.startsWith("/")) {
3368
+ return yield* new InvalidMicroVmOptionsError({
3369
+ message: `--volume must be VOLUME:PATH with an absolute path, such as cache:/cache (got ${value}).`
3370
+ });
3371
+ }
3372
+ volumes.push({ volumeId, mountPath });
3373
+ }
3374
+ return volumes;
3375
+ });
3326
3376
  var checkEgressFlags = (denyEgress, allowEgress) => Effect22.gen(function* () {
3327
3377
  if (denyEgress && allowEgress.length > 0) {
3328
3378
  return yield* new InvalidMicroVmOptionsError({
@@ -3353,6 +3403,10 @@ var createInput = (flags) => Effect22.gen(function* () {
3353
3403
  if (badPort !== void 0) {
3354
3404
  return yield* new InvalidMicroVmOptionsError({ message: `--port must be between 1 and 65535 (got ${badPort}).` });
3355
3405
  }
3406
+ const volumes = yield* parseVolumeFlags(flags.volumes);
3407
+ if (volumes.length > 0 && flags.pauseOnTimeout) {
3408
+ return yield* new InvalidMicroVmOptionsError({ message: "--volume cannot be combined with --pause-on-timeout." });
3409
+ }
3356
3410
  const applicationId = yield* resolveApplicationId(flags.app);
3357
3411
  const input = {
3358
3412
  ...applicationId ? { applicationId } : {},
@@ -3369,6 +3423,7 @@ var createInput = (flags) => Effect22.gen(function* () {
3369
3423
  ...flags.ports.length > 0 ? { ports: [...flags.ports] } : {},
3370
3424
  ...env ? { env } : {},
3371
3425
  ...metadata ? { metadata } : {},
3426
+ ...volumes.length > 0 ? { volumes } : {},
3372
3427
  ...flags.denyEgress || flags.privatePreviews || flags.allowEgress.length > 0 ? {
3373
3428
  network: {
3374
3429
  egress: flags.denyEgress ? "deny" : "allow",
@@ -3382,7 +3437,26 @@ var createInput = (flags) => Effect22.gen(function* () {
3382
3437
  });
3383
3438
  var createMicroVm = (flags) => Effect22.gen(function* () {
3384
3439
  const apiClient = yield* ApiClientService;
3385
- const input = yield* createInput(flags);
3440
+ const parsed = yield* createInput(flags);
3441
+ const named = (parsed.volumes ?? []).filter((volume) => !UUID.test(volume.volumeId));
3442
+ const known = named.length > 0 ? yield* apiClient.request(
3443
+ (client) => client.microvms.volumes.list({ ...parsed.applicationId ? { applicationId: parsed.applicationId } : {} })
3444
+ ) : [];
3445
+ const volumes = [];
3446
+ for (const volume of parsed.volumes ?? []) {
3447
+ if (UUID.test(volume.volumeId)) {
3448
+ volumes.push(volume);
3449
+ continue;
3450
+ }
3451
+ const match = known.find((entry) => entry.name === volume.volumeId);
3452
+ if (match === void 0) {
3453
+ return yield* new InvalidMicroVmOptionsError({
3454
+ message: `No volume is named ${volume.volumeId}. Create it with "gigadrive microvms volumes create ${volume.volumeId}".`
3455
+ });
3456
+ }
3457
+ volumes.push({ volumeId: match.id, mountPath: volume.mountPath });
3458
+ }
3459
+ const input = volumes.length > 0 ? { ...parsed, volumes } : parsed;
3386
3460
  if (!flags.json) yield* Console12.error(`Creating a microVM from ${input.image ?? "node22"}...`);
3387
3461
  const vm = yield* apiClient.request((client) => client.microvms.create(input));
3388
3462
  if (flags.json) {
@@ -3453,6 +3527,13 @@ var createCommand = Command12.make(
3453
3527
  "Resume the paused microVM when a command, file call or public preview request reaches it"
3454
3528
  )
3455
3529
  ),
3530
+ volumes: Options7.text("volume").pipe(
3531
+ Options7.withAlias("v"),
3532
+ Options7.withDescription(
3533
+ "Attach a volume as VOLUME:PATH, by ID or name, such as cache:/cache (repeatable, up to 4; saved when the microVM stops)"
3534
+ ),
3535
+ Options7.repeated
3536
+ ),
3456
3537
  region: Options7.text("region").pipe(Options7.withDescription("Region to place the microVM in"), Options7.optional),
3457
3538
  json: jsonOption2
3458
3539
  },
@@ -3935,6 +4016,205 @@ var imagesCommand = Command12.make("images", {}, () => Effect22.void).pipe(
3935
4016
  Command12.withDescription("Built-in and custom microVM images"),
3936
4017
  Command12.withSubcommands([imagesListCommand, imagesCreateCommand, imagesRemoveCommand])
3937
4018
  );
4019
+ var volumeIdArg = Args5.text({ name: "volume-id" }).pipe(Args5.withDescription("The volume ID"));
4020
+ var volumesListCommand = Command12.make(
4021
+ "ls",
4022
+ { app: appOption6, json: jsonOption2 },
4023
+ ({ app, json }) => Effect22.gen(function* () {
4024
+ const apiClient = yield* ApiClientService;
4025
+ const applicationId = yield* resolveApplicationId(app);
4026
+ const volumes = yield* apiClient.request((client) => client.microvms.volumes.list({ applicationId }));
4027
+ if (json) return yield* Console12.log(JSON.stringify(volumes, null, 2));
4028
+ if (volumes.length === 0)
4029
+ return yield* Console12.log('No volumes yet. Create one with "gigadrive microvms volumes create <name>".');
4030
+ for (const line of formatVolumeTable(volumes)) yield* Console12.log(line);
4031
+ }).pipe(reportErrors2("list volumes"))
4032
+ ).pipe(Command12.withDescription("List the application's volumes"));
4033
+ var createVolume = (flags) => Effect22.gen(function* () {
4034
+ const apiClient = yield* ApiClientService;
4035
+ const applicationId = yield* resolveApplicationId(flags.app);
4036
+ const volume = yield* apiClient.request(
4037
+ (client) => client.microvms.volumes.create({
4038
+ name: flags.name,
4039
+ ...Option14.match(flags.size, { onNone: () => ({}), onSome: (sizeMb) => ({ sizeMb }) }),
4040
+ ...applicationId ? { applicationId } : {}
4041
+ })
4042
+ );
4043
+ if (flags.json) {
4044
+ yield* Console12.log(JSON.stringify(volume, null, 2));
4045
+ } else {
4046
+ yield* Console12.log(`Created volume ${volume.name} (${String(volume.sizeMb)} MiB, ${volume.id}).`);
4047
+ yield* Console12.log(`Attach it: gigadrive microvms create --volume ${volume.name}:/data`);
4048
+ }
4049
+ return volume;
4050
+ });
4051
+ var volumesCreateCommand = Command12.make(
4052
+ "create",
4053
+ {
4054
+ name: Args5.text({ name: "name" }).pipe(
4055
+ Args5.withDescription("Lowercase letters, digits and -, unique per application")
4056
+ ),
4057
+ size: Options7.integer("size").pipe(
4058
+ Options7.withDescription("Size in MiB, 1024 to 51200 (default: 10240)"),
4059
+ Options7.optional
4060
+ ),
4061
+ app: appOption6,
4062
+ json: jsonOption2
4063
+ },
4064
+ (flags) => createVolume(flags).pipe(Effect22.asVoid, reportErrors2("create the volume"))
4065
+ ).pipe(Command12.withDescription("Create an empty volume"));
4066
+ var volumesRenameCommand = Command12.make(
4067
+ "rename",
4068
+ { volumeId: volumeIdArg, name: Args5.text({ name: "name" }).pipe(Args5.withDescription("The new name")) },
4069
+ ({ volumeId, name }) => Effect22.gen(function* () {
4070
+ const apiClient = yield* ApiClientService;
4071
+ const volume = yield* apiClient.request((client) => client.microvms.volumes.rename(volumeId, name));
4072
+ yield* Console12.log(`Renamed the volume to ${volume.name}.`);
4073
+ }).pipe(reportErrors2("rename the volume"))
4074
+ ).pipe(Command12.withDescription("Rename a volume"));
4075
+ var volumesSaveCommand = Command12.make(
4076
+ "save",
4077
+ { microvmId: Args5.text({ name: "microvm-id" }).pipe(Args5.withDescription("A running microVM with volumes")) },
4078
+ ({ microvmId }) => Effect22.gen(function* () {
4079
+ const apiClient = yield* ApiClientService;
4080
+ yield* apiClient.request((client) => client.microvms.saveVolumes(microvmId));
4081
+ yield* Console12.log(`Saved the volumes of ${microvmId}.`);
4082
+ }).pipe(reportErrors2("save the volumes"))
4083
+ ).pipe(Command12.withDescription("Save a running microVM's volumes without stopping it"));
4084
+ var volumesRemoveCommand = Command12.make(
4085
+ "rm",
4086
+ { volumeId: volumeIdArg },
4087
+ ({ volumeId }) => Effect22.gen(function* () {
4088
+ const apiClient = yield* ApiClientService;
4089
+ yield* apiClient.request((client) => client.microvms.volumes.delete(volumeId));
4090
+ yield* Console12.log(`Deleted volume ${volumeId}.`);
4091
+ }).pipe(reportErrors2("delete the volume"))
4092
+ ).pipe(Command12.withDescription("Delete a volume and its contents (it must not be attached)"));
4093
+ var volumesCommand = Command12.make("volumes", {}, () => Effect22.void).pipe(
4094
+ Command12.withDescription("Persistent filesystems that outlive the microVMs they are attached to"),
4095
+ Command12.withSubcommands([
4096
+ volumesListCommand,
4097
+ volumesCreateCommand,
4098
+ volumesRenameCommand,
4099
+ volumesSaveCommand,
4100
+ volumesRemoveCommand
4101
+ ])
4102
+ );
4103
+ var WEBHOOK_EVENTS = [
4104
+ "microvm.created",
4105
+ "microvm.running",
4106
+ "microvm.paused",
4107
+ "microvm.stopped",
4108
+ "microvm.failed"
4109
+ ];
4110
+ var webhookIdArg = Args5.text({ name: "webhook-id" }).pipe(Args5.withDescription("The webhook ID"));
4111
+ var webhooksListCommand = Command12.make(
4112
+ "ls",
4113
+ { app: appOption6, json: jsonOption2 },
4114
+ ({ app, json }) => Effect22.gen(function* () {
4115
+ const apiClient = yield* ApiClientService;
4116
+ const applicationId = yield* resolveApplicationId(app);
4117
+ const webhooks = yield* apiClient.request((client) => client.microvms.webhooks.list({ applicationId }));
4118
+ if (json) return yield* Console12.log(JSON.stringify(webhooks, null, 2));
4119
+ if (webhooks.length === 0)
4120
+ return yield* Console12.log('No webhooks yet. Add one with "gigadrive microvms webhooks add".');
4121
+ for (const line of formatWebhookTable(webhooks)) yield* Console12.log(line);
4122
+ }).pipe(reportErrors2("list webhooks"))
4123
+ ).pipe(Command12.withDescription("List the application's microVM webhooks"));
4124
+ var addWebhook = (flags) => Effect22.gen(function* () {
4125
+ const apiClient = yield* ApiClientService;
4126
+ const unknown = flags.events.find((event) => !WEBHOOK_EVENTS.includes(event));
4127
+ if (unknown !== void 0) {
4128
+ return yield* new InvalidMicroVmOptionsError({
4129
+ message: `${unknown} is not a microVM event. Use ${WEBHOOK_EVENTS.join(", ")}.`
4130
+ });
4131
+ }
4132
+ const events = flags.events.length > 0 ? [...new Set(flags.events)] : WEBHOOK_EVENTS;
4133
+ const applicationId = yield* resolveApplicationId(flags.app);
4134
+ const webhook = yield* apiClient.request(
4135
+ (client) => client.microvms.webhooks.create({
4136
+ url: flags.url,
4137
+ events,
4138
+ ...Option14.isSome(flags.description) ? { description: flags.description.value } : {},
4139
+ ...applicationId ? { applicationId } : {}
4140
+ })
4141
+ );
4142
+ if (flags.json) {
4143
+ yield* Console12.log(JSON.stringify(webhook, null, 2));
4144
+ } else {
4145
+ yield* Console12.log(`Added webhook ${webhook.id} for ${webhook.events.join(", ")}.`);
4146
+ yield* Console12.log(`Signing secret (shown only now): ${webhook.secret}`);
4147
+ }
4148
+ return webhook;
4149
+ });
4150
+ var webhooksAddCommand = Command12.make(
4151
+ "add",
4152
+ {
4153
+ url: Args5.text({ name: "url" }).pipe(Args5.withDescription("HTTPS URL on a public host")),
4154
+ events: Options7.text("event").pipe(
4155
+ Options7.withDescription(`Event to send (repeatable; default: all of ${WEBHOOK_EVENTS.join(", ")})`),
4156
+ Options7.repeated
4157
+ ),
4158
+ description: Options7.text("description").pipe(Options7.withDescription("A note for yourself"), Options7.optional),
4159
+ app: appOption6,
4160
+ json: jsonOption2
4161
+ },
4162
+ (flags) => addWebhook(flags).pipe(Effect22.asVoid, reportErrors2("add the webhook"))
4163
+ ).pipe(Command12.withDescription("Add an endpoint for microVM lifecycle events and print its signing secret"));
4164
+ var webhooksEnableCommand = (enabled) => Command12.make(
4165
+ enabled ? "enable" : "disable",
4166
+ { webhookId: webhookIdArg },
4167
+ ({ webhookId }) => Effect22.gen(function* () {
4168
+ const apiClient = yield* ApiClientService;
4169
+ const webhook = yield* apiClient.request((client) => client.microvms.webhooks.update(webhookId, { enabled }));
4170
+ yield* Console12.log(`${webhook.url} is ${webhook.enabled ? "enabled" : "disabled"}.`);
4171
+ }).pipe(reportErrors2(enabled ? "enable the webhook" : "disable the webhook"))
4172
+ ).pipe(
4173
+ Command12.withDescription(
4174
+ enabled ? "Send events to a webhook again" : "Stop sending events to a webhook; waiting retries are held"
4175
+ )
4176
+ );
4177
+ var webhooksSecretCommand = Command12.make(
4178
+ "rotate-secret",
4179
+ { webhookId: webhookIdArg },
4180
+ ({ webhookId }) => Effect22.gen(function* () {
4181
+ const apiClient = yield* ApiClientService;
4182
+ const webhook = yield* apiClient.request((client) => client.microvms.webhooks.rotateSecret(webhookId));
4183
+ yield* Console12.log(`New signing secret (shown only now): ${webhook.secret}`);
4184
+ }).pipe(reportErrors2("replace the signing secret"))
4185
+ ).pipe(Command12.withDescription("Replace the signing secret; the old one stops signing at once"));
4186
+ var webhooksDeliveriesCommand = Command12.make(
4187
+ "deliveries",
4188
+ { webhookId: webhookIdArg, json: jsonOption2 },
4189
+ ({ webhookId, json }) => Effect22.gen(function* () {
4190
+ const apiClient = yield* ApiClientService;
4191
+ const deliveries = yield* apiClient.request((client) => client.microvms.webhooks.listDeliveries(webhookId));
4192
+ if (json) return yield* Console12.log(JSON.stringify(deliveries, null, 2));
4193
+ if (deliveries.length === 0) return yield* Console12.log("Nothing has been sent yet.");
4194
+ for (const line of formatDeliveryTable(deliveries)) yield* Console12.log(line);
4195
+ }).pipe(reportErrors2("list the deliveries"))
4196
+ ).pipe(Command12.withDescription("Show a webhook's 50 most recent deliveries"));
4197
+ var webhooksRemoveCommand = Command12.make(
4198
+ "rm",
4199
+ { webhookId: webhookIdArg },
4200
+ ({ webhookId }) => Effect22.gen(function* () {
4201
+ const apiClient = yield* ApiClientService;
4202
+ yield* apiClient.request((client) => client.microvms.webhooks.delete(webhookId));
4203
+ yield* Console12.log(`Deleted webhook ${webhookId}.`);
4204
+ }).pipe(reportErrors2("delete the webhook"))
4205
+ ).pipe(Command12.withDescription("Delete a webhook"));
4206
+ var webhooksCommand = Command12.make("webhooks", {}, () => Effect22.void).pipe(
4207
+ Command12.withDescription("Signed HTTPS requests when a microVM is created, starts, pauses, stops or fails"),
4208
+ Command12.withSubcommands([
4209
+ webhooksListCommand,
4210
+ webhooksAddCommand,
4211
+ webhooksEnableCommand(true),
4212
+ webhooksEnableCommand(false),
4213
+ webhooksSecretCommand,
4214
+ webhooksDeliveriesCommand,
4215
+ webhooksRemoveCommand
4216
+ ])
4217
+ );
3938
4218
  var SUBCOMMANDS = [
3939
4219
  createCommand,
3940
4220
  listCommand2,
@@ -3947,7 +4227,9 @@ var SUBCOMMANDS = [
3947
4227
  removeCommand2,
3948
4228
  extendCommand,
3949
4229
  networkCommand,
3950
- imagesCommand
4230
+ imagesCommand,
4231
+ volumesCommand,
4232
+ webhooksCommand
3951
4233
  ];
3952
4234
  var microvmsCommand = Command12.make("microvms", {}, () => Effect22.void).pipe(
3953
4235
  Command12.withDescription("Isolated Linux machines for running code: create, exec, shell, cp, stop"),