Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
"imports": {
"@cfa/gitignore-parser": "jsr:@cfa/gitignore-parser@^0.1.4",
"@cliffy/command": "jsr:@cliffy/command@^1.0.0-rc.8",
"@deno/sandbox": "jsr:@deno/sandbox@^0.4.3",
"@deno/sandbox": "jsr:@deno/sandbox@^0.4.4",
"@std/async": "jsr:@std/async@^1.0.15",
"@std/cli": "jsr:@std/cli@1.0.22",
"@std/dotenv": "jsr:@std/dotenv@^0.225.5",
"@std/encoding": "jsr:@std/encoding@^1.0.10",
Expand Down
10 changes: 6 additions & 4 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

126 changes: 83 additions & 43 deletions sandbox.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { Command } from "@cliffy/command";
import { Sandbox } from "@deno/sandbox";
import { green, magenta, red } from "@std/fmt/colors";
import { pooledMap } from "@std/async";
import { expandGlob } from "@std/fs";
import { join } from "@std/path";

import { getAppFromConfig, readConfig, writeConfig } from "./config.ts";
import { error, renderTemporalTimestamp, withApp } from "./util.ts";
Expand Down Expand Up @@ -36,8 +39,7 @@ export const sandboxCreateCommand = new Command<SandboxContext>()
debug: options.debug,
token,
org,
// deno-lint-ignore no-explicit-any
lifetime: options.lifetime as any,
lifetime: options.lifetime as `${number}s` | `${number}m` | "session",
});

if (options.copy) {
Expand All @@ -47,6 +49,8 @@ export const sandboxCreateCommand = new Command<SandboxContext>()
}

if (options.lifetime === "session") {
console.log(`Created sandbox with id '${sandbox.id}'`);

const success = await sshIntoSandbox(sandbox);
const stopMessage = "Stopping the sandbox...";
if (success) {
Expand Down Expand Up @@ -174,18 +178,18 @@ export const sandboxSshCommand = new Command<SandboxContext>()

export const sandboxCopyCommand = new Command<SandboxContext>()
.description("Copy files from or to a running sandbox")
/*.example(
.example(
"Copy a file from a sandbox to the local machine",
"copy someSandboxId:/app/remote-file.txt ./local-file.txt",
)*/
)
.example(
"Copy a file from the local machine to a sandbox",
"copy ./local-file.txt someSandboxId:/app/remote-file.txt",
)
/*.example(
.example(
"Copy multiple files from a sandbox to the local machine",
"copy someSandboxId:/app/remote-file.txt someSandboxId:/app/another-remote-file.txt ./",
)*/
)
.example(
"Copy multiple files from the local machine to a sandbox",
"copy ./local-file.txt ./another-local-file.txt someSandboxId:/app/",
Expand All @@ -194,10 +198,14 @@ export const sandboxCopyCommand = new Command<SandboxContext>()
"Copy a directory from the local machine to a sandbox",
"copy ./ ./another-local-file.txt someSandboxId:/app/",
)
/*.example(
.example(
"Copy files from a sandbox to another sandbox",
"copy someSandboxId:/app/remote-file.txt anotherSandboxId:/app/remote-file.txt",
)*/
)
.example(
"Copy all files from a directory in a sandbox to the local machine",
"copy someSandboxId:/app/* ./",
)
.arguments("<paths...:string>")
.action(async (options, ...paths) => {
if (paths.length < 2) {
Expand All @@ -207,56 +215,75 @@ export const sandboxCopyCommand = new Command<SandboxContext>()
const target = paths.pop()!;

if (target.includes(":")) {
const [sandboxId, sandboxPath] = target.split(":");
await using sandbox = await connectToSandbox(options, sandboxId);
const separatorIndex = target.indexOf(":");
const sandboxId = target.slice(0, separatorIndex);
const targetSandboxPath = target.slice(separatorIndex + 1);

await using targetSandbox = await connectToSandbox(options, sandboxId);

//const sourceSandboxPaths = [];
const sourceSandboxPaths = [];
const localPaths = [];

for (const path of paths) {
if (path.includes(":")) {
error(
options.debug,
"Copying between sandboxes is currently not supported",
);

//sourceSandboxPaths.push(path);
sourceSandboxPaths.push(path);
} else {
localPaths.push(path);
}
}

/*const sourceSandboxGroups = groupPathsBySandbox(sourceSandboxPaths);
const sourceSandboxGroups = groupPathsBySandbox(sourceSandboxPaths);
const sourceSandboxes: Record<string, Sandbox> = {};

for (const sandboxId of Object.keys(sourceSandboxGroups)) {
sourceSandboxes[sandboxId] = await connectToSandbox(options, sandboxId);
}*/
}

await Promise.all([
...localPaths.map((path) => {
return sandbox.upload(path, sandboxPath);
return targetSandbox.upload(path, targetSandboxPath);
}),
/*...Object.entries(sourceSandboxGroups).map(
...Object.entries(sourceSandboxGroups).map(
async ([sandboxId, sourceSandboxPaths]) => {
const sourceSandbox = sourceSandboxes[sandboxId];

await Promise.all(
sourceSandboxPaths.map(async (sourceSandboxPath) => {
const tempDir = await Deno.makeTempDir();
await sourceSandbox.download(sourceSandboxPath, tempDir);
await sandbox.upload(tempDir, target);

await Array.fromAsync(pooledMap(
Infinity,
sourceSandbox.expandGlob(sourceSandboxPath),
async (sandboxEntry) => {
Comment thread
crowlKats marked this conversation as resolved.
const tempPath = join(tempDir, sandboxEntry.path);
await Deno.mkdir(tempPath, { recursive: true });
await sourceSandbox.download(sandboxEntry.path, tempPath);
Comment thread
crowlKats marked this conversation as resolved.

await Array.fromAsync(pooledMap(
Infinity,
expandGlob(`${tempPath}/*`),
(localEntry) =>
targetSandbox.upload(
localEntry.path,
join(
targetSandboxPath,
localEntry.isDirectory
? "./"
: `./${localEntry.name}`,
),
),
));
},
));
Comment thread
crowlKats marked this conversation as resolved.
}),
);

await sandbox.close();
await sourceSandbox.close();
},
),*/
),
]);
} else {
error(options.debug, "Copying from sandboxes is currently not supported");

/*for (const path of paths) {
for (const path of paths) {
if (!path.includes(":")) {
error(
options.debug,
Expand All @@ -276,13 +303,17 @@ export const sandboxCopyCommand = new Command<SandboxContext>()
Object.entries(groups).map(async ([sandboxId, sandboxPaths]) => {
const sandbox = sandboxes[sandboxId];

await Promise.all(sandboxPaths.map((sandboxPath) => {
return sandbox.download(sandboxPath, target);
await Promise.all(sandboxPaths.map(async (sandboxPath) => {
await Array.fromAsync(pooledMap(
Infinity,
sandbox.expandGlob(sandboxPath),
(entry) => sandbox.download(entry.path, target),
));
}));

await sandbox.close();
}),
);*/
);
}
});

Expand All @@ -305,11 +336,14 @@ export const sandboxExecCommand = new Command<SandboxContext>()
const child = await sandbox.spawn("bash", {
cwd: options.cwd,
args: ["-c", command.join(" ")],
stdin: "piped",
stdout: options.quiet ? "null" : "inherit",
stderr: options.quiet ? "null" : "inherit",
});

const status = await child.status;
const write = Deno.stdin.readable.pipeTo(child.stdin!);

const [status] = await Promise.all([child.status, write]);
Deno.exit(status.code);
});

Expand Down Expand Up @@ -341,8 +375,7 @@ export const sandboxRunCommand = new Command<SandboxContext>()
debug: options.debug,
token,
org,
// deno-lint-ignore no-explicit-any
lifetime: options.lifetime as any,
lifetime: options.lifetime as `${number}s` | `${number}m` | "session",
});

console.log(`Created sandbox with id '${sandbox.id}'`);
Expand Down Expand Up @@ -371,39 +404,45 @@ export const sandboxRunCommand = new Command<SandboxContext>()
const child = await sandbox.spawn("bash", {
cwd: options.cwd,
args: ["-c", command.join(" ")],
stdin: "piped",
stdout: options.quiet ? "null" : "inherit",
stderr: options.quiet ? "null" : "inherit",
});

const status = await child.status;
const write = Deno.stdin.readable.pipeTo(child.stdin!);

const [status] = await Promise.all([child.status, write]);
Deno.exit(status.code);
});

/*
export const sandboxExtendCommand = new Command<SandboxContext>()
.description("Extend the lifetime of a running sandbox")
.arguments("<sandbox-id:string> <lifetime:string>")
.action(async (options, sandboxId, lifetime) => {
await using sandbox = await connectToSandbox(options, sandboxId);

console.log(await sandbox.extendLifetime(lifetime));
console.log(
await sandbox.extendLifetime(lifetime as `${number}s` | `${number}m`),
);
});
*/

/*
function groupPathsBySandbox(paths: string[]): Record<string, string[]> {
const groups = {};
const groups: Record<string, string[]> = {};

for (const path of paths) {
const [sandboxId, sandboxPath] = path.split(":");
const separatorIndex = path.indexOf(":");
const sandboxId = path.slice(0, separatorIndex);
const sandboxPath = path.slice(separatorIndex + 1);

if (!groups[sandboxId]) {
groups[sandboxId] = [];
}

groups[sandboxId].push(sandboxPath);
}

return groups;
}*/
}

async function ensureOrg(options: SandboxContext, quiet: boolean = true) {
const config = await readConfig(Deno.cwd(), options.config);
Expand Down Expand Up @@ -440,6 +479,7 @@ async function connectToSandbox(

return await Sandbox.connect({
id: sandboxId,
apiEndpoint: options.endpoint,
region: cluster.region,
debug: options.debug,
token,
Expand Down Expand Up @@ -597,5 +637,5 @@ export const sandboxCommand = new Command<GlobalOptions>()
.alias("cp")
.command("exec", sandboxExecCommand)
.command("run", sandboxRunCommand)
//.command("extend", sandboxExtendCommand)
.command("extend", sandboxExtendCommand)
.command("ssh", sandboxSshCommand);