Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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.

125 changes: 82 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,74 @@ 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 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 +302,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 +335,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 +374,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 +403,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 +478,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 +636,5 @@ export const sandboxCommand = new Command<GlobalOptions>()
.alias("cp")
.command("exec", sandboxExecCommand)
.command("run", sandboxRunCommand)
//.command("extend", sandboxExtendCommand)
.command("extend", sandboxExtendCommand)
.command("ssh", sandboxSshCommand);