Skip to content

Commit b01bcd3

Browse files
feat: [PRODUCT-585] implement sf nodes logs and sf nodes ssh (#208)
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
1 parent 11e5b3e commit b01bcd3

File tree

13 files changed

+589
-139
lines changed

13 files changed

+589
-139
lines changed

deno.lock

Lines changed: 39 additions & 76 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/lib/nodes/create.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,7 @@ export async function addCreate(program: Command) {
466466
create.addOption(
467467
new Option(
468468
"-i, --image <image-id>",
469-
"ID of the VM image to boot on the nodes. View available images with `sf vms images list`.",
469+
"ID of the VM image to boot on the nodes. View available images with `sf node images list`.",
470470
),
471471
);
472472
}

src/lib/vm/image/index.ts renamed to src/lib/nodes/image/index.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Command } from "@commander-js/extra-typings";
2+
import { isFeatureEnabled } from "../../posthog.ts";
23
import upload from "./upload.ts";
34
import show from "./show.tsx";
45
import list from "./list.tsx";
@@ -13,13 +14,13 @@ const image = new Command("images")
1314
`
1415
Examples:\n
1516
\x1b[2m# Upload an image file\x1b[0m
16-
$ sf vm image upload ./my-image.img
17+
$ sf node image upload ./my-image.img
1718
1819
\x1b[2m# List all images\x1b[0m
19-
$ sf vm image list
20+
$ sf node image list
2021
2122
\x1b[2m# Show image details and download URL\x1b[0m
22-
$ sf vm image show <image-id>
23+
$ sf node image show <image-id>
2324
`,
2425
)
2526
.addCommand(list)
@@ -29,4 +30,11 @@ Examples:\n
2930
image.help();
3031
});
3132

33+
export async function addImage(program: Command) {
34+
const imagesEnabled = await isFeatureEnabled("custom-vm-images");
35+
if (imagesEnabled) {
36+
program.addCommand(image);
37+
}
38+
}
39+
3240
export default image;

src/lib/vm/image/list.tsx renamed to src/lib/nodes/image/list.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@ const list = new Command("list")
2626
`
2727
Next Steps:\n
2828
\x1b[2m# List all images\x1b[0m
29-
$ sf vms images list
29+
$ sf node images list
3030
3131
\x1b[2m# Get detailed info for a specific image\x1b[0m
32-
$ sf vms images show <image-id>
32+
$ sf node images show <image-id>
3333
3434
\x1b[2m# List images in JSON format\x1b[0m
35-
$ sf vms images list --json
35+
$ sf node images list --json
3636
`,
3737
)
3838
.action(async (options) => {
@@ -56,7 +56,7 @@ Next Steps:\n
5656
if (images.length === 0) {
5757
console.log("No images found.");
5858
console.log(gray("\nUpload your first image:"));
59-
console.log(" sf vms images upload -f ./my-image.img -n my-image");
59+
console.log(" sf node images upload -f ./my-image.img -n my-image");
6060
return;
6161
}
6262

@@ -116,7 +116,7 @@ Next Steps:\n
116116
content: brightBlack(
117117
`${images.length - 5} older ${
118118
images.length - 5 === 1 ? "image" : "images"
119-
} not shown. Use sf vms images list --json to list all images.`,
119+
} not shown. Use sf node images list --json to list all images.`,
120120
),
121121
},
122122
]);
@@ -130,7 +130,7 @@ Next Steps:\n
130130
// Always show how to get info for a specific image
131131
const firstImage = sortedImages[0];
132132
if (firstImage) {
133-
console.log(` sf vms images show ${cyan(firstImage.image_id)}`);
133+
console.log(` sf node images show ${cyan(firstImage.image_id)}`);
134134
}
135135
const firstCompletedImage = sortedImages.find(
136136
(image) => image.upload_status === "completed",
File renamed without changes.

src/lib/vm/image/upload.ts renamed to src/lib/nodes/image/upload.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ const upload = new Command("upload")
375375

376376
const object = completeResponse.data;
377377
console.log(gray("\nNext steps:"));
378-
console.log(` sf vm images show ${cyan(object.image_id)}`);
378+
console.log(` sf nodes images show ${cyan(object.image_id)}`);
379379
} catch (err) {
380380
// Clean up spinner timer
381381
if (spinnerTimer) {

src/lib/nodes/index.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,11 @@ import set from "./set.ts";
99
import extend from "./extend.ts";
1010
import get from "./get.tsx";
1111
import { addRedeploy } from "./redeploy.ts";
12-
import { isFeatureEnabled } from "../posthog.ts";
12+
import ssh from "./ssh.ts";
13+
import logs from "./logs.ts";
14+
import { addImage } from "./image/index.ts";
1315

1416
export async function registerNodes(program: Command) {
15-
const isEnabled = await isFeatureEnabled("vm-provider");
16-
if (!isEnabled) return;
17-
1817
const nodes = program
1918
.command("nodes")
2019
.alias("node")
@@ -25,7 +24,9 @@ export async function registerNodes(program: Command) {
2524
.addCommand(extend)
2625
.addCommand(release)
2726
.addCommand(deleteCommand)
28-
.addCommand(set);
27+
.addCommand(set)
28+
.addCommand(ssh)
29+
.addCommand(logs);
2930

3031
const baseHelpText = nodes.helpInformation();
3132

@@ -64,6 +65,21 @@ $ sf nodes set my-node-name --max-price 12.50
6465
6566
\x1b[2m# Extend a reserved node\x1b[0m
6667
$ sf nodes extend my-node-name --duration 3600 --max-price 12.50
68+
69+
\x1b[2m# SSH into a node's current VM\x1b[0m
70+
$ sf nodes ssh my-node-name
71+
72+
\x1b[2m# View logs from a node's current VM\x1b[0m
73+
$ sf nodes logs my-node-name
74+
75+
\x1b[2m# SSH into a specific VM\x1b[0m
76+
$ sf nodes ssh user@vm_xxxxxxxxxxxxxxxxxxxxx
77+
78+
\x1b[2m# View logs from a specific VM\x1b[0m
79+
$ sf nodes logs -i vm_xxxxxxxxxxxxxxxxxxxxx
80+
81+
\x1b[2m# Manage custom VM images\x1b[0m
82+
$ sf nodes images --help
6783
`,
6884
)
6985
// Add action to display help if no subcommand is provided
@@ -87,6 +103,7 @@ $ sf nodes --help
87103
`);
88104
});
89105

106+
await addImage(nodes);
90107
await addCreate(nodes);
91108
await addRedeploy(nodes);
92109
}

src/lib/nodes/list.tsx

Lines changed: 19 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { handleNodesError, nodesClient } from "../../nodesClient.ts";
2020
import { Row } from "../Row.tsx";
2121
import {
2222
createNodesTable,
23+
getLastVM,
2324
getStatusColor,
2425
getVMStatusColor,
2526
jsonOption,
@@ -102,14 +103,12 @@ function getActionsForNode(node: SFCNodes.Node) {
102103
const nodeActions: { label: string; command: string }[] = [];
103104

104105
// Get the last VM for logs/ssh commands
105-
const lastVm = node.vms?.data?.sort((a, b) =>
106-
(b.start_at ?? b.updated_at) - (a.start_at ?? a.updated_at)
107-
).at(0);
106+
const lastVm = getLastVM(node);
108107

109108
if (lastVm?.image_id) {
110109
nodeActions.push({
111110
label: "Image",
112-
command: `sf vms image show ${brightBlack(lastVm.image_id)}`,
111+
command: `sf nodes image show ${brightBlack(lastVm.image_id)}`,
113112
});
114113
}
115114

@@ -119,12 +118,12 @@ function getActionsForNode(node: SFCNodes.Node) {
119118
if (lastVm?.id) {
120119
nodeActions.push({
121120
label: "SSH",
122-
command: `sf vms ssh root@${brightBlack(String(lastVm.id))}`,
121+
command: `sf nodes ssh root@${brightBlack(node.name)}`,
123122
});
124123
nodeActions.push(
125124
{
126125
label: "Logs",
127-
command: `sf vms logs -i ${brightBlack(String(lastVm.id))}`,
126+
command: `sf nodes logs ${brightBlack(node.name)}`,
128127
},
129128
);
130129
}
@@ -146,11 +145,11 @@ function getActionsForNode(node: SFCNodes.Node) {
146145
nodeActions.push(
147146
{
148147
label: "SSH",
149-
command: `sf vms ssh root@${brightBlack(lastVm.id)}`,
148+
command: `sf nodes ssh root@${brightBlack(node.name)}`,
150149
},
151150
{
152151
label: "Logs",
153-
command: `sf vms logs -i ${brightBlack(lastVm.id)}`,
152+
command: `sf nodes logs ${brightBlack(node.name)}`,
154153
},
155154
);
156155
}
@@ -205,7 +204,7 @@ function getActionsForNode(node: SFCNodes.Node) {
205204
nodeActions.push(
206205
{
207206
label: "Logs",
208-
command: `sf vms logs -i ${brightBlack(lastVm.id)}`,
207+
command: `sf nodes logs ${brightBlack(node.name)}`,
209208
},
210209
);
211210
}
@@ -245,11 +244,11 @@ function getActionsForNode(node: SFCNodes.Node) {
245244
nodeActions.push(
246245
{
247246
label: "SSH",
248-
command: `sf vms ssh root@${brightBlack(lastVm.id)}`,
247+
command: `sf nodes ssh root@${brightBlack(node.name)}`,
249248
},
250249
{
251250
label: "Logs",
252-
command: `sf vms logs -i ${brightBlack(lastVm.id)}`,
251+
command: `sf nodes logs ${brightBlack(node.name)}`,
253252
},
254253
);
255254
}
@@ -481,9 +480,8 @@ async function listNodesAction(options: ReturnType<typeof list.opts>) {
481480
);
482481

483482
// Get actions from all nodes, deduplicated with newest nodes taking precedence
484-
const nodesCommands: string[] = [];
485-
const vmsCommands: string[] = [];
486-
const seenNodesLabels = new Set<string>();
483+
const allCommands: string[] = [];
484+
const seenLabels = new Set<string>();
487485

488486
// Sort nodes by created_at (newest first), fallback to index for consistent ordering
489487
const sortedNodes = [...nodes].sort((a, b) => {
@@ -493,38 +491,22 @@ async function listNodesAction(options: ReturnType<typeof list.opts>) {
493491
});
494492

495493
// Collect actions from each node, with newer nodes taking precedence
496-
// Limit to 3 nodes commands and 1 vms command
494+
// Limit to 5 commands total, deduplicated by label
497495
for (const node of sortedNodes) {
498496
const nodeActions = getActionsForNode(node);
499497
for (const action of nodeActions) {
500-
const isVmsCommand = action.command.includes("sf vms");
501-
502-
if (isVmsCommand) {
503-
// Only add the first vms command we encounter (from newest node)
504-
if (vmsCommands.length === 0) {
505-
vmsCommands.push(action.command);
506-
}
507-
} else {
508-
// For nodes commands, limit to 3 and deduplicate by label
509-
if (
510-
nodesCommands.length < 3 && !seenNodesLabels.has(action.label)
511-
) {
512-
nodesCommands.push(action.command);
513-
seenNodesLabels.add(action.label);
514-
}
498+
// Add command if we haven't seen this label and haven't reached the limit
499+
if (allCommands.length < 5 && !seenLabels.has(action.label)) {
500+
allCommands.push(action.command);
501+
seenLabels.add(action.label);
515502
}
516503
}
517504
}
518505

519506
// Print Next Steps section
520-
if (nodesCommands.length > 0 || vmsCommands.length > 0) {
507+
if (allCommands.length > 0) {
521508
console.log(gray("\nNext steps:"));
522-
// Print nodes commands first
523-
for (const command of nodesCommands) {
524-
console.log(` ${command}`);
525-
}
526-
// Then print vms commands
527-
for (const command of vmsCommands) {
509+
for (const command of allCommands) {
528510
console.log(` ${command}`);
529511
}
530512
}

0 commit comments

Comments
 (0)