@@ -6,15 +6,18 @@ import chalk from "chalk";
66import ora from "ora" ;
77import { Shescape } from "shescape" ;
88
9- import { getAuthToken } from "../../helpers/config.ts" ;
9+ import { apiClient } from "../../apiClient.ts" ;
10+ import { getAuthToken , loadConfig } from "../../helpers/config.ts" ;
1011import {
1112 logAndQuit ,
1213 logSessionTokenExpiredAndQuit ,
1314} from "../../helpers/errors.ts" ;
14- import { getApiUrl } from "../../helpers/urls.ts" ;
1515import { handleNodesError , nodesClient } from "../../nodesClient.ts" ;
16+ import type { components } from "../../schema.ts" ;
1617import { jsonOption } from "./utils.ts" ;
1718
19+ type SshInfo = components [ "schemas" ] [ "vmorch_GetSshResponse" ] ;
20+
1821const ssh = new Command ( "ssh" )
1922 . description ( `SSH into a VM on a node.
2023
@@ -46,7 +49,7 @@ Examples:
4649
4750 \x1b[2m# SSH with a specific username\x1b[0m
4851 $ sf nodes ssh jenson@my-node
49-
52+
5053 \x1b[2m# SSH directly to a VM ID\x1b[0m
5154 $ sf nodes ssh root@vm_xxxxxxxxxxxxxxxxxxxxx
5255` ,
@@ -67,75 +70,76 @@ Examples:
6770 logAndQuit ( `Invalid SSH destination string: ${ destination } ` ) ;
6871 }
6972
70- let vmId : string ;
73+ const sshSpinner = ora ( "Fetching SSH information..." ) . start ( ) ;
74+ const config = await loadConfig ( ) ;
75+ const token = await getAuthToken ( ) ;
76+
77+ let hostKeyAlias = "" ;
78+ let data : SshInfo | undefined ;
7179
72- // If the ID doesn't start with vm_, assume it's a node name/ID
80+ // Try v2 endpoint for non- vm_ IDs
7381 if ( ! nodeOrVmId . startsWith ( "vm_" ) ) {
74- const client = await nodesClient ( ) ;
75- const spinner = ora ( "Fetching node information..." ) . start ( ) ;
82+ const v2Response = await fetch (
83+ `${ config . api_url } /v2/nodes/${ nodeOrVmId } /ssh` ,
84+ {
85+ method : "GET" ,
86+ headers : { Authorization : `Bearer ${ token } ` } ,
87+ } ,
88+ ) ;
7689
77- try {
78- const node = await client . nodes . get ( nodeOrVmId ) ;
79- spinner . succeed ( `Node found for name ${ chalk . cyan ( nodeOrVmId ) } .` ) ;
80-
81- if ( ! node ?. current_vm ) {
82- spinner . fail (
83- `Node ${ chalk . cyan (
84- nodeOrVmId ,
85- ) } does not have a current VM. VMs can take up to 5-10 minutes to spin up.`,
86- ) ;
87- process . exit ( 1 ) ;
88- }
90+ if ( v2Response . ok ) {
91+ data = await v2Response . json ( ) ;
92+ hostKeyAlias = `${ nodeOrVmId } .v2.nodes.sfcompute.dev` ;
93+ }
94+ }
8995
90- vmId = node . current_vm . id ;
91- } catch {
92- spinner . info (
93- `No node found for name ${ chalk . cyan (
94- nodeOrVmId ,
95- ) } . Interpreting as VM ID...`,
96- ) ;
96+ // Fall back to v0 flow if v2 didn't resolve
97+ if ( ! data ) {
98+ let vmId : string ;
99+
100+ if ( ! nodeOrVmId . startsWith ( "vm_" ) ) {
101+ const client = await nodesClient ( ) ;
102+ try {
103+ const node = await client . nodes . get ( nodeOrVmId ) ;
104+ if ( ! node ?. current_vm ) {
105+ sshSpinner . fail (
106+ `Node ${ chalk . cyan (
107+ nodeOrVmId ,
108+ ) } does not have a current VM. VMs can take up to 5-10 minutes to spin up.`,
109+ ) ;
110+ process . exit ( 1 ) ;
111+ }
112+ vmId = node . current_vm . id ;
113+ } catch {
114+ vmId = nodeOrVmId ;
115+ }
116+ } else {
97117 vmId = nodeOrVmId ;
98118 }
99- } else {
100- vmId = nodeOrVmId ;
101- }
102119
103- const sshSpinner = ora ( "Fetching SSH information..." ) . start ( ) ;
104- const baseUrl = await getApiUrl ( "vms_ssh_get" ) ;
105- const params = new URLSearchParams ( ) ;
106- params . append ( "vm_id" , vmId ) ;
107- const url = `${ baseUrl } ?${ params . toString ( ) } ` ;
108- const response = await fetch ( url , {
109- method : "GET" ,
110- headers : {
111- Authorization : `Bearer ${ await getAuthToken ( ) } ` ,
112- } ,
113- } ) ;
120+ const client = await apiClient ( token ) ;
121+ const { response, data : sshData } = await client . GET ( "/v0/vms/ssh" , {
122+ params : { query : { vm_id : vmId } } ,
123+ } ) ;
114124
115- if ( ! response . ok ) {
116125 if ( response . status === 401 ) {
117126 sshSpinner . stop ( ) ;
118127 logSessionTokenExpiredAndQuit ( ) ;
119128 }
120129
121- sshSpinner . fail (
122- `Failed to retrieve SSH information for ${ chalk . cyan (
123- vmId ,
124- ) } : ${ response . statusText } `,
125- ) ;
126- process . exit ( 1 ) ;
130+ if ( ! response . ok || ! sshData ) {
131+ sshSpinner . fail (
132+ `Failed to retrieve SSH information for ${ chalk . cyan (
133+ vmId ,
134+ ) } : ${ response . statusText } `,
135+ ) ;
136+ process . exit ( 1 ) ;
137+ }
138+
139+ data = sshData ;
140+ hostKeyAlias = `${ vmId } .vms.sfcompute.dev` ;
127141 }
128142
129- const data = ( await response . json ( ) ) as {
130- ssh_hostname : string ;
131- ssh_port : number ;
132- ssh_host_keys :
133- | {
134- key_type : string ;
135- base64_encoded_key : string ;
136- } [ ]
137- | undefined ;
138- } ;
139143 sshSpinner . succeed ( "SSH information fetched successfully." ) ;
140144
141145 if ( options . json ) {
@@ -162,7 +166,7 @@ Examples:
162166 let knownHostsCommand = [ "/usr/bin/env" , "printf" , "%s %s %s\\n" ] ;
163167 for ( const sshHostKey of sshHostKeys ) {
164168 knownHostsCommand = knownHostsCommand . concat ( [
165- ` ${ vmId } .vms.sfcompute.dev` ,
169+ hostKeyAlias ,
166170 sshHostKey . key_type ,
167171 sshHostKey . base64_encoded_key ,
168172 ] ) ;
@@ -177,7 +181,7 @@ Examples:
177181 cmd = cmd . concat ( [ "-o" , `KnownHostsCommand=${ knownHostsCommand_str } ` ] ) ;
178182 }
179183
180- cmd = cmd . concat ( [ "-o" , `HostKeyAlias=${ vmId } .vms.sfcompute.dev ` ] ) ;
184+ cmd = cmd . concat ( [ "-o" , `HostKeyAlias=${ hostKeyAlias } ` ] ) ;
181185 cmd = cmd . concat ( [ sshDestination ] ) ;
182186
183187 let shescape : undefined | Shescape ;
0 commit comments