mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
chore(ENG-21504): Remove bit-rotted scripts (#24606)
### What does this PR do? Removes some scripts which haven't been tested in a while. ### How did you verify your code works? CI passes
This commit is contained in:
@@ -1,510 +0,0 @@
|
||||
import { $, getUsernameForDistro, spawnSafe, which } from "./utils.mjs";
|
||||
|
||||
export const google = {
|
||||
get cloud() {
|
||||
return "google";
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string[]} args
|
||||
* @param {import("./utils.mjs").SpawnOptions} [options]
|
||||
* @returns {Promise<unknown>}
|
||||
*/
|
||||
async spawn(args, options = {}) {
|
||||
const gcloud = which("gcloud", { required: true });
|
||||
|
||||
let env = { ...process.env };
|
||||
// if (isCI) {
|
||||
// env; // TODO: Add Google Cloud credentials
|
||||
// } else {
|
||||
// env["TERM"] = "dumb";
|
||||
// }
|
||||
|
||||
const { stdout } = await spawnSafe($`${gcloud} ${args} --format json`, {
|
||||
env,
|
||||
...options,
|
||||
});
|
||||
try {
|
||||
return JSON.parse(stdout);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Record<string, string | undefined>} [options]
|
||||
* @returns {string[]}
|
||||
*/
|
||||
getFilters(options = {}) {
|
||||
const filter = Object.entries(options)
|
||||
.filter(([, value]) => value !== undefined)
|
||||
.map(([key, value]) => [value.includes("*") ? `${key}~${value}` : `${key}=${value}`])
|
||||
.join(" AND ");
|
||||
return filter ? ["--filter", filter] : [];
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Record<string, string | boolean | undefined>} options
|
||||
* @returns {string[]}
|
||||
*/
|
||||
getFlags(options) {
|
||||
return Object.entries(options)
|
||||
.filter(([, value]) => value !== undefined)
|
||||
.flatMap(([key, value]) => {
|
||||
if (typeof value === "boolean") {
|
||||
return value ? [`--${key}`] : [];
|
||||
}
|
||||
return [`--${key}=${value}`];
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Record<string, string | boolean | undefined>} options
|
||||
* @returns {string}
|
||||
* @link https://cloud.google.com/sdk/gcloud/reference/topic/escaping
|
||||
*/
|
||||
getMetadata(options) {
|
||||
const delimiter = Math.random().toString(36).substring(2, 15);
|
||||
const entries = Object.entries(options)
|
||||
.map(([key, value]) => `${key}=${value}`)
|
||||
.join(delimiter);
|
||||
return `^${delimiter}^${entries}`;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @returns {string}
|
||||
*/
|
||||
getLabel(name) {
|
||||
return name.replace(/[^a-z0-9_-]/g, "-").toLowerCase();
|
||||
},
|
||||
|
||||
/**
|
||||
* @typedef {Object} GoogleImage
|
||||
* @property {string} id
|
||||
* @property {string} name
|
||||
* @property {string} family
|
||||
* @property {"X86_64" | "ARM64"} architecture
|
||||
* @property {string} diskSizeGb
|
||||
* @property {string} selfLink
|
||||
* @property {"READY"} status
|
||||
* @property {string} creationTimestamp
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {Partial<GoogleImage>} [options]
|
||||
* @returns {Promise<GoogleImage[]>}
|
||||
* @link https://cloud.google.com/sdk/gcloud/reference/compute/images/list
|
||||
*/
|
||||
async listImages(options) {
|
||||
const filters = google.getFilters(options);
|
||||
const images = await google.spawn($`compute images list ${filters} --preview-images --show-deprecated`);
|
||||
return images.sort((a, b) => (a.creationTimestamp < b.creationTimestamp ? 1 : -1));
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Record<string, string | boolean | undefined>} options
|
||||
* @returns {Promise<GoogleImage>}
|
||||
* @link https://cloud.google.com/sdk/gcloud/reference/compute/images/create
|
||||
*/
|
||||
async createImage(options) {
|
||||
const { name, ...otherOptions } = options;
|
||||
const flags = this.getFlags(otherOptions);
|
||||
const imageId = name || "i-" + Math.random().toString(36).substring(2, 15);
|
||||
return this.spawn($`compute images create ${imageId} ${flags}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* @typedef {Object} GoogleInstance
|
||||
* @property {string} id
|
||||
* @property {string} name
|
||||
* @property {"RUNNING"} status
|
||||
* @property {string} machineType
|
||||
* @property {string} zone
|
||||
* @property {GoogleDisk[]} disks
|
||||
* @property {GoogleNetworkInterface[]} networkInterfaces
|
||||
* @property {object} [scheduling]
|
||||
* @property {"STANDARD" | "SPOT"} [scheduling.provisioningModel]
|
||||
* @property {boolean} [scheduling.preemptible]
|
||||
* @property {Record<string, string | undefined>} [labels]
|
||||
* @property {string} selfLink
|
||||
* @property {string} creationTimestamp
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} GoogleDisk
|
||||
* @property {string} deviceName
|
||||
* @property {boolean} boot
|
||||
* @property {"X86_64" | "ARM64"} architecture
|
||||
* @property {string[]} [licenses]
|
||||
* @property {number} diskSizeGb
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} GoogleNetworkInterface
|
||||
* @property {"IPV4_ONLY" | "IPV4_IPV6" | "IPV6_ONLY"} stackType
|
||||
* @property {string} name
|
||||
* @property {string} network
|
||||
* @property {string} networkIP
|
||||
* @property {string} subnetwork
|
||||
* @property {GoogleAccessConfig[]} accessConfigs
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} GoogleAccessConfig
|
||||
* @property {string} name
|
||||
* @property {"ONE_TO_ONE_NAT" | "INTERNAL_NAT"} type
|
||||
* @property {string} [natIP]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {Record<string, string | boolean | undefined>} options
|
||||
* @returns {Promise<GoogleInstance>}
|
||||
* @link https://cloud.google.com/sdk/gcloud/reference/compute/instances/create
|
||||
*/
|
||||
async createInstance(options) {
|
||||
const { name, ...otherOptions } = options || {};
|
||||
const flags = this.getFlags(otherOptions);
|
||||
const instanceId = name || "i-" + Math.random().toString(36).substring(2, 15);
|
||||
const [instance] = await this.spawn($`compute instances create ${instanceId} ${flags}`);
|
||||
return instance;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} instanceId
|
||||
* @param {string} zoneId
|
||||
* @returns {Promise<void>}
|
||||
* @link https://cloud.google.com/sdk/gcloud/reference/compute/instances/stop
|
||||
*/
|
||||
async stopInstance(instanceId, zoneId) {
|
||||
await this.spawn($`compute instances stop ${instanceId} --zone=${zoneId}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} instanceId
|
||||
* @param {string} zoneId
|
||||
* @returns {Promise<void>}
|
||||
* @link https://cloud.google.com/sdk/gcloud/reference/compute/instances/delete
|
||||
*/
|
||||
async deleteInstance(instanceId, zoneId) {
|
||||
await this.spawn($`compute instances delete ${instanceId} --delete-disks=all --zone=${zoneId}`, {
|
||||
throwOnError: error => !/not found/i.test(inspect(error)),
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} instanceId
|
||||
* @param {string} username
|
||||
* @param {string} zoneId
|
||||
* @param {object} [options]
|
||||
* @param {boolean} [options.wait]
|
||||
* @returns {Promise<string | undefined>}
|
||||
* @link https://cloud.google.com/sdk/gcloud/reference/compute/reset-windows-password
|
||||
*/
|
||||
async resetWindowsPassword(instanceId, username, zoneId, options = {}) {
|
||||
const attempts = options.wait ? 15 : 1;
|
||||
for (let i = 0; i < attempts; i++) {
|
||||
const result = await this.spawn(
|
||||
$`compute reset-windows-password ${instanceId} --user=${username} --zone=${zoneId}`,
|
||||
{
|
||||
throwOnError: error => !/instance may not be ready for use/i.test(inspect(error)),
|
||||
},
|
||||
);
|
||||
if (result) {
|
||||
const { password } = result;
|
||||
if (password) {
|
||||
return password;
|
||||
}
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 60000 * i));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Partial<GoogleInstance>} options
|
||||
* @returns {Promise<GoogleInstance[]>}
|
||||
*/
|
||||
async listInstances(options) {
|
||||
const filters = this.getFilters(options);
|
||||
const instances = await this.spawn($`compute instances list ${filters}`);
|
||||
return instances.sort((a, b) => (a.creationTimestamp < b.creationTimestamp ? 1 : -1));
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {MachineOptions} options
|
||||
* @returns {Promise<GoogleImage>}
|
||||
*/
|
||||
async getMachineImage(options) {
|
||||
const { os, arch, distro, release } = options;
|
||||
const architecture = arch === "aarch64" ? "ARM64" : "X86_64";
|
||||
|
||||
/** @type {string | undefined} */
|
||||
let family;
|
||||
if (os === "linux") {
|
||||
if (!distro || distro === "debian") {
|
||||
family = `debian-${release || "*"}`;
|
||||
} else if (distro === "ubuntu") {
|
||||
family = `ubuntu-${release?.replace(/\./g, "") || "*"}`;
|
||||
} else if (distro === "fedora") {
|
||||
family = `fedora-coreos-${release || "*"}`;
|
||||
} else if (distro === "rhel") {
|
||||
family = `rhel-${release || "*"}`;
|
||||
}
|
||||
} else if (os === "windows" && arch === "x64") {
|
||||
if (!distro || distro === "server") {
|
||||
family = `windows-${release || "*"}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (family) {
|
||||
const images = await this.listImages({ family, architecture });
|
||||
if (images.length) {
|
||||
const [image] = images;
|
||||
return image;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Unsupported platform: ${inspect(options)}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {MachineOptions} options
|
||||
* @returns {Promise<Machine>}
|
||||
*/
|
||||
async createMachine(options) {
|
||||
const { name, os, arch, distro, instanceType, tags, preemptible, detached } = options;
|
||||
const image = await google.getMachineImage(options);
|
||||
const { selfLink: imageUrl } = image;
|
||||
|
||||
const username = getUsername(distro || os);
|
||||
const userData = getUserData({ ...options, username });
|
||||
|
||||
/** @type {Record<string, string>} */
|
||||
let metadata;
|
||||
if (os === "windows") {
|
||||
metadata = {
|
||||
"enable-windows-ssh": "TRUE",
|
||||
"sysprep-specialize-script-ps1": userData,
|
||||
};
|
||||
} else {
|
||||
metadata = {
|
||||
"user-data": userData,
|
||||
};
|
||||
}
|
||||
|
||||
const instance = await google.createInstance({
|
||||
"name": name,
|
||||
"zone": "us-central1-a",
|
||||
"image": imageUrl,
|
||||
"machine-type": instanceType || (arch === "aarch64" ? "t2a-standard-2" : "t2d-standard-2"),
|
||||
"boot-disk-auto-delete": true,
|
||||
"boot-disk-size": `${getDiskSize(options)}GB`,
|
||||
"metadata": this.getMetadata(metadata),
|
||||
"labels": Object.entries(tags || {})
|
||||
.filter(([, value]) => value !== undefined)
|
||||
.map(([key, value]) => `${this.getLabel(key)}=${value}`)
|
||||
.join(","),
|
||||
"provisioning-model": preemptible ? "SPOT" : "STANDARD",
|
||||
"instance-termination-action": preemptible || !detached ? "DELETE" : undefined,
|
||||
"no-restart-on-failure": true,
|
||||
"threads-per-core": 1,
|
||||
"max-run-duration": detached ? undefined : "6h",
|
||||
});
|
||||
|
||||
return this.toMachine(instance, options);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {GoogleInstance} instance
|
||||
* @param {MachineOptions} [options]
|
||||
* @returns {Machine}
|
||||
*/
|
||||
toMachine(instance, options = {}) {
|
||||
const { id: instanceId, name, zone: zoneUrl, machineType: machineTypeUrl, labels } = instance;
|
||||
const machineType = machineTypeUrl.split("/").pop();
|
||||
const zoneId = zoneUrl.split("/").pop();
|
||||
|
||||
let os, arch, distro, release;
|
||||
const { disks = [] } = instance;
|
||||
for (const { boot, architecture, licenses = [] } of disks) {
|
||||
if (!boot) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (architecture === "X86_64") {
|
||||
arch = "x64";
|
||||
} else if (architecture === "ARM64") {
|
||||
arch = "aarch64";
|
||||
}
|
||||
|
||||
for (const license of licenses) {
|
||||
const linuxMatch = /(debian|ubuntu|fedora|rhel)-(\d+)/i.exec(license);
|
||||
if (linuxMatch) {
|
||||
os = "linux";
|
||||
[, distro, release] = linuxMatch;
|
||||
} else {
|
||||
const windowsMatch = /windows-server-(\d+)-dc-core/i.exec(license);
|
||||
if (windowsMatch) {
|
||||
os = "windows";
|
||||
distro = "windowsserver";
|
||||
[, release] = windowsMatch;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let publicIp;
|
||||
const { networkInterfaces = [] } = instance;
|
||||
for (const { accessConfigs = [] } of networkInterfaces) {
|
||||
for (const { type, natIP } of accessConfigs) {
|
||||
if (type === "ONE_TO_ONE_NAT" && natIP) {
|
||||
publicIp = natIP;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let preemptible;
|
||||
const { scheduling } = instance;
|
||||
if (scheduling) {
|
||||
const { provisioningModel, preemptible: isPreemptible } = scheduling;
|
||||
preemptible = provisioningModel === "SPOT" || isPreemptible;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {SshOptions}
|
||||
*/
|
||||
const connect = () => {
|
||||
if (!publicIp) {
|
||||
throw new Error(`Failed to find public IP for instance: ${name}`);
|
||||
}
|
||||
|
||||
/** @type {string | undefined} */
|
||||
let username;
|
||||
|
||||
const { os, distro } = options;
|
||||
if (os || distro) {
|
||||
username = getUsernameForDistro(distro || os);
|
||||
}
|
||||
|
||||
return { hostname: publicIp, username };
|
||||
};
|
||||
|
||||
const spawn = async (command, options) => {
|
||||
const connectOptions = connect();
|
||||
return spawnSsh({ ...connectOptions, command }, options);
|
||||
};
|
||||
|
||||
const spawnSafe = async (command, options) => {
|
||||
const connectOptions = connect();
|
||||
return spawnSshSafe({ ...connectOptions, command }, options);
|
||||
};
|
||||
|
||||
const rdp = async () => {
|
||||
const { hostname, username } = connect();
|
||||
const rdpUsername = `${username}-rdp`;
|
||||
const password = await google.resetWindowsPassword(instanceId, rdpUsername, zoneId, { wait: true });
|
||||
return { hostname, username: rdpUsername, password };
|
||||
};
|
||||
|
||||
const attach = async () => {
|
||||
const connectOptions = connect();
|
||||
await spawnSshSafe({ ...connectOptions });
|
||||
};
|
||||
|
||||
const upload = async (source, destination) => {
|
||||
const connectOptions = connect();
|
||||
await spawnScp({ ...connectOptions, source, destination });
|
||||
};
|
||||
|
||||
const snapshot = async name => {
|
||||
const stopResult = await this.stopInstance(instanceId, zoneId);
|
||||
console.log(stopResult);
|
||||
const image = await this.createImage({
|
||||
["source-disk"]: instanceId,
|
||||
["zone"]: zoneId,
|
||||
["name"]: name || `${instanceId}-snapshot-${Date.now()}`,
|
||||
});
|
||||
console.log(image);
|
||||
return;
|
||||
};
|
||||
|
||||
const terminate = async () => {
|
||||
await google.deleteInstance(instanceId, zoneId);
|
||||
};
|
||||
|
||||
return {
|
||||
cloud: "google",
|
||||
os,
|
||||
arch,
|
||||
distro,
|
||||
release,
|
||||
id: instanceId,
|
||||
imageId: undefined,
|
||||
name,
|
||||
instanceType: machineType,
|
||||
region: zoneId,
|
||||
publicIp,
|
||||
preemptible,
|
||||
labels,
|
||||
spawn,
|
||||
spawnSafe,
|
||||
rdp,
|
||||
attach,
|
||||
upload,
|
||||
snapshot,
|
||||
close: terminate,
|
||||
[Symbol.asyncDispose]: terminate,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Record<string, string>} [labels]
|
||||
* @returns {Promise<Machine[]>}
|
||||
*/
|
||||
async getMachines(labels) {
|
||||
const filters = labels ? this.getFilters({ labels }) : {};
|
||||
const instances = await google.listInstances(filters);
|
||||
return instances.map(instance => this.toMachine(instance));
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {MachineOptions} options
|
||||
* @returns {Promise<MachineImage>}
|
||||
*/
|
||||
async getImage(options) {
|
||||
const { os, arch, distro, release } = options;
|
||||
const architecture = arch === "aarch64" ? "ARM64" : "X86_64";
|
||||
|
||||
let name;
|
||||
let username;
|
||||
if (os === "linux") {
|
||||
if (distro === "debian") {
|
||||
name = `debian-${release}-*`;
|
||||
username = "admin";
|
||||
} else if (distro === "ubuntu") {
|
||||
name = `ubuntu-${release.replace(/\./g, "")}-*`;
|
||||
username = "ubuntu";
|
||||
}
|
||||
} else if (os === "windows" && arch === "x64") {
|
||||
if (distro === "server") {
|
||||
name = `windows-server-${release}-dc-core-*`;
|
||||
username = "administrator";
|
||||
}
|
||||
}
|
||||
|
||||
if (name && username) {
|
||||
const images = await google.listImages({ name, architecture });
|
||||
if (images.length) {
|
||||
const [image] = images;
|
||||
const { name, selfLink } = image;
|
||||
return {
|
||||
id: selfLink,
|
||||
name,
|
||||
username,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Unsupported platform: ${inspect(platform)}`);
|
||||
},
|
||||
};
|
||||
@@ -5,8 +5,6 @@ import { basename, extname, join, relative, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { inspect, parseArgs } from "node:util";
|
||||
import { docker } from "./docker.mjs";
|
||||
import { google } from "./google.mjs";
|
||||
import { orbstack } from "./orbstack.mjs";
|
||||
import { tart } from "./tart.mjs";
|
||||
import {
|
||||
$,
|
||||
@@ -1064,14 +1062,10 @@ function getCloud(name) {
|
||||
switch (name) {
|
||||
case "docker":
|
||||
return docker;
|
||||
case "orbstack":
|
||||
return orbstack;
|
||||
case "tart":
|
||||
return tart;
|
||||
case "aws":
|
||||
return aws;
|
||||
case "google":
|
||||
return google;
|
||||
case "tart":
|
||||
return tart;
|
||||
}
|
||||
throw new Error(`Unsupported cloud: ${name}`);
|
||||
}
|
||||
|
||||
@@ -1,195 +0,0 @@
|
||||
import { inspect } from "node:util";
|
||||
import { getUserData } from "./machine.mjs";
|
||||
import { $, getUsernameForDistro, mkdtemp, rm, setupUserData, spawnSafe, spawnSshSafe, writeFile } from "./utils.mjs";
|
||||
|
||||
/**
|
||||
* @link https://docs.orbstack.dev/
|
||||
*/
|
||||
export const orbstack = {
|
||||
get name() {
|
||||
return "orbstack";
|
||||
},
|
||||
|
||||
/**
|
||||
* @typedef {Object} OrbstackImage
|
||||
* @property {string} distro
|
||||
* @property {string} version
|
||||
* @property {string} arch
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {Platform} platform
|
||||
* @returns {OrbstackImage}
|
||||
*/
|
||||
getImage(platform) {
|
||||
const { os, arch, distro, release } = platform;
|
||||
if (os !== "linux" || !/^debian|ubuntu|alpine|fedora|centos$/.test(distro)) {
|
||||
throw new Error(`Unsupported platform: ${inspect(platform)}`);
|
||||
}
|
||||
|
||||
return {
|
||||
distro,
|
||||
version: release,
|
||||
arch: arch === "aarch64" ? "arm64" : "amd64",
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* @typedef {Object} OrbstackVm
|
||||
* @property {string} id
|
||||
* @property {string} name
|
||||
* @property {"running"} state
|
||||
* @property {OrbstackImage} image
|
||||
* @property {OrbstackConfig} config
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} OrbstackConfig
|
||||
* @property {string} default_username
|
||||
* @property {boolean} isolated
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} OrbstackVmOptions
|
||||
* @property {string} [name]
|
||||
* @property {OrbstackImage} image
|
||||
* @property {string} [username]
|
||||
* @property {string} [password]
|
||||
* @property {string} [userData]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {OrbstackVmOptions} options
|
||||
* @returns {Promise<OrbstackVm>}
|
||||
*/
|
||||
async createVm(options) {
|
||||
const { name, image, username, password, userData } = options;
|
||||
const { distro, version, arch } = image;
|
||||
const uniqueId = name || `linux-${distro}-${version}-${arch}-${Math.random().toString(36).slice(2, 11)}`;
|
||||
|
||||
const args = [`--arch=${arch}`, `${distro}:${version}`, uniqueId];
|
||||
if (username) {
|
||||
args.push(`--user=${username}`);
|
||||
}
|
||||
if (password) {
|
||||
args.push(`--set-password=${password}`);
|
||||
}
|
||||
|
||||
let userDataPath;
|
||||
if (userData) {
|
||||
userDataPath = mkdtemp("orbstack-user-data-", "user-data.txt");
|
||||
console.log("User data path:", userData);
|
||||
writeFile(userDataPath, userData);
|
||||
args.push(`--user-data=${userDataPath}`);
|
||||
}
|
||||
|
||||
try {
|
||||
await spawnSafe($`orbctl create ${args}`);
|
||||
} finally {
|
||||
if (userDataPath) {
|
||||
rm(userDataPath);
|
||||
}
|
||||
}
|
||||
|
||||
return this.inspectVm(uniqueId);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
*/
|
||||
async deleteVm(name) {
|
||||
await spawnSafe($`orbctl delete ${name}`, {
|
||||
throwOnError: error => !/machine not found/i.test(inspect(error)),
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @returns {Promise<OrbstackVm | undefined>}
|
||||
*/
|
||||
async inspectVm(name) {
|
||||
const { exitCode, stdout } = await spawnSafe($`orbctl info ${name} --format=json`, {
|
||||
throwOnError: error => !/machine not found/i.test(inspect(error)),
|
||||
});
|
||||
if (exitCode === 0) {
|
||||
return JSON.parse(stdout);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns {Promise<OrbstackVm[]>}
|
||||
*/
|
||||
async listVms() {
|
||||
const { stdout } = await spawnSafe($`orbctl list --format=json`);
|
||||
return JSON.parse(stdout);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {MachineOptions} options
|
||||
* @returns {Promise<Machine>}
|
||||
*/
|
||||
async createMachine(options) {
|
||||
const { distro } = options;
|
||||
const username = getUsernameForDistro(distro);
|
||||
const userData = getUserData({ ...options, username });
|
||||
|
||||
const image = this.getImage(options);
|
||||
const vm = await this.createVm({
|
||||
image,
|
||||
username,
|
||||
userData,
|
||||
});
|
||||
|
||||
const machine = this.toMachine(vm, options);
|
||||
|
||||
await setupUserData(machine, options);
|
||||
|
||||
return machine;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {OrbstackVm} vm
|
||||
* @returns {Machine}
|
||||
*/
|
||||
toMachine(vm) {
|
||||
const { id, name, config } = vm;
|
||||
|
||||
const { default_username: username } = config;
|
||||
const connectOptions = {
|
||||
username,
|
||||
hostname: `${name}@orb`,
|
||||
};
|
||||
|
||||
const exec = async (command, options) => {
|
||||
return spawnSsh({ ...connectOptions, command }, options);
|
||||
};
|
||||
|
||||
const execSafe = async (command, options) => {
|
||||
return spawnSshSafe({ ...connectOptions, command }, options);
|
||||
};
|
||||
|
||||
const attach = async () => {
|
||||
await spawnSshSafe({ ...connectOptions });
|
||||
};
|
||||
|
||||
const upload = async (source, destination) => {
|
||||
await spawnSafe(["orbctl", "push", `--machine=${name}`, source, destination]);
|
||||
};
|
||||
|
||||
const close = async () => {
|
||||
await this.deleteVm(name);
|
||||
};
|
||||
|
||||
return {
|
||||
cloud: "orbstack",
|
||||
id,
|
||||
name,
|
||||
spawn: exec,
|
||||
spawnSafe: execSafe,
|
||||
upload,
|
||||
attach,
|
||||
close,
|
||||
[Symbol.asyncDispose]: close,
|
||||
};
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user