1717use anyhow:: Result ;
1818
1919use crate :: node:: Node ;
20+ use crate :: ssh:: ssh_config:: SshConfig ;
2021
21- use super :: types:: { Cluster , Config , NodeConfig } ;
22+ use super :: types:: { Cluster , Config , JumpHostConfig , NodeConfig } ;
2223use super :: utils:: { expand_env_vars, get_current_username} ;
2324
2425impl Config {
@@ -132,6 +133,9 @@ impl Config {
132133 /// 3. Global default `jump_host` (in `Defaults`)
133134 ///
134135 /// Empty string (`""`) explicitly disables jump host inheritance.
136+ ///
137+ /// Note: This method does not resolve SSH config references (`@alias`).
138+ /// Use `get_jump_host_with_key_and_ssh_config` for full resolution.
135139 pub fn get_jump_host ( & self , cluster_name : & str , node_index : usize ) -> Option < String > {
136140 self . get_jump_host_with_key ( cluster_name, node_index)
137141 . map ( |( conn_str, _) | conn_str)
@@ -146,10 +150,34 @@ impl Config {
146150 ///
147151 /// Empty string (`""`) explicitly disables jump host inheritance.
148152 /// Returns tuple of (connection_string, optional_ssh_key_path)
153+ ///
154+ /// Note: This method does not resolve SSH config references (`@alias`).
155+ /// Use `get_jump_host_with_key_and_ssh_config` for full resolution.
149156 pub fn get_jump_host_with_key (
150157 & self ,
151158 cluster_name : & str ,
152159 node_index : usize ,
160+ ) -> Option < ( String , Option < String > ) > {
161+ self . get_jump_host_with_key_and_ssh_config ( cluster_name, node_index, None )
162+ }
163+
164+ /// Get jump host with SSH key for a specific node, with SSH config reference resolution.
165+ ///
166+ /// This is the full-featured version that can resolve SSH config Host alias references
167+ /// (`@alias` or `ssh_config_host` field) using the provided SSH config.
168+ ///
169+ /// Resolution priority (highest to lowest):
170+ /// 1. Node-level `jump_host` (in `NodeConfig::Detailed`)
171+ /// 2. Cluster-level `jump_host` (in `ClusterDefaults`)
172+ /// 3. Global default `jump_host` (in `Defaults`)
173+ ///
174+ /// Empty string (`""`) explicitly disables jump host inheritance.
175+ /// Returns tuple of (connection_string, optional_ssh_key_path)
176+ pub fn get_jump_host_with_key_and_ssh_config (
177+ & self ,
178+ cluster_name : & str ,
179+ node_index : usize ,
180+ ssh_config : Option < & SshConfig > ,
153181 ) -> Option < ( String , Option < String > ) > {
154182 if let Some ( cluster) = self . get_cluster ( cluster_name) {
155183 // Check node-level first
@@ -158,31 +186,36 @@ impl Config {
158186 ..
159187 } ) = cluster. nodes . get ( node_index)
160188 {
161- return self . process_jump_host_config ( jh) ;
189+ return self . process_jump_host_config ( jh, ssh_config ) ;
162190 }
163191 // Check cluster-level
164192 if let Some ( jh) = & cluster. defaults . jump_host {
165- return self . process_jump_host_config ( jh) ;
193+ return self . process_jump_host_config ( jh, ssh_config ) ;
166194 }
167195 }
168196 // Fall back to global default
169197 self . defaults
170198 . jump_host
171199 . as_ref ( )
172- . and_then ( |jh| self . process_jump_host_config ( jh) )
200+ . and_then ( |jh| self . process_jump_host_config ( jh, ssh_config ) )
173201 }
174202
175203 /// Process a JumpHostConfig and return (connection_string, optional_ssh_key_path)
204+ ///
205+ /// If `ssh_config` is provided, SSH config references (`@alias` or `ssh_config_host`)
206+ /// will be resolved using the SSH config. Otherwise, the reference string is returned as-is.
176207 fn process_jump_host_config (
177208 & self ,
178- config : & super :: types:: JumpHostConfig ,
209+ config : & JumpHostConfig ,
210+ ssh_config : Option < & SshConfig > ,
179211 ) -> Option < ( String , Option < String > ) > {
180- use super :: types:: JumpHostConfig ;
181-
182212 match config {
183213 JumpHostConfig :: Simple ( s) => {
184214 if s. is_empty ( ) {
185215 None // Explicitly disabled
216+ } else if let Some ( alias) = s. strip_prefix ( '@' ) {
217+ // SSH config reference with @ prefix
218+ self . resolve_ssh_config_jump_host ( alias, ssh_config)
186219 } else {
187220 Some ( ( expand_env_vars ( s) , None ) )
188221 }
@@ -206,9 +239,42 @@ impl Config {
206239 let key = ssh_key. as_ref ( ) . map ( |k| expand_env_vars ( k) ) ;
207240 Some ( ( conn_str, key) )
208241 }
242+ JumpHostConfig :: SshConfigHostRef { ssh_config_host } => {
243+ self . resolve_ssh_config_jump_host ( ssh_config_host, ssh_config)
244+ }
209245 }
210246 }
211247
248+ /// Resolve an SSH config Host alias to connection string and SSH key.
249+ ///
250+ /// If `ssh_config` is provided, looks up the alias and extracts:
251+ /// - HostName (or uses the alias as hostname)
252+ /// - User
253+ /// - Port
254+ /// - IdentityFile (first one, used as SSH key)
255+ ///
256+ /// If `ssh_config` is None, returns the alias as the hostname with no SSH key.
257+ fn resolve_ssh_config_jump_host (
258+ & self ,
259+ alias : & str ,
260+ ssh_config : Option < & SshConfig > ,
261+ ) -> Option < ( String , Option < String > ) > {
262+ if let Some ( ssh_cfg) = ssh_config {
263+ // Try to resolve from SSH config
264+ if let Some ( ( conn_str, identity_file) ) = ssh_cfg. resolve_jump_host_connection ( alias) {
265+ return Some ( ( conn_str, identity_file) ) ;
266+ }
267+ }
268+
269+ // Fallback: use the alias as the hostname (SSH will resolve it)
270+ // This allows the connection to proceed even without explicit SSH config resolution
271+ tracing:: debug!(
272+ "SSH config reference '{}' could not be resolved, using as hostname" ,
273+ alias
274+ ) ;
275+ Some ( ( alias. to_string ( ) , None ) )
276+ }
277+
212278 /// Get jump host for a cluster (cluster-level default).
213279 ///
214280 /// Resolution priority (highest to lowest):
@@ -232,19 +298,35 @@ impl Config {
232298 pub fn get_cluster_jump_host_with_key (
233299 & self ,
234300 cluster_name : Option < & str > ,
301+ ) -> Option < ( String , Option < String > ) > {
302+ self . get_cluster_jump_host_with_key_and_ssh_config ( cluster_name, None )
303+ }
304+
305+ /// Get jump host with SSH key for a cluster, with SSH config reference resolution.
306+ ///
307+ /// Resolution priority (highest to lowest):
308+ /// 1. Cluster-level `jump_host` (in `ClusterDefaults`)
309+ /// 2. Global default `jump_host` (in `Defaults`)
310+ ///
311+ /// Empty string (`""`) explicitly disables jump host inheritance.
312+ /// Returns tuple of (connection_string, optional_ssh_key_path)
313+ pub fn get_cluster_jump_host_with_key_and_ssh_config (
314+ & self ,
315+ cluster_name : Option < & str > ,
316+ ssh_config : Option < & SshConfig > ,
235317 ) -> Option < ( String , Option < String > ) > {
236318 if let Some ( cluster_name) = cluster_name {
237319 if let Some ( cluster) = self . get_cluster ( cluster_name) {
238320 if let Some ( jh) = & cluster. defaults . jump_host {
239- return self . process_jump_host_config ( jh) ;
321+ return self . process_jump_host_config ( jh, ssh_config ) ;
240322 }
241323 }
242324 }
243325 // Fall back to global default
244326 self . defaults
245327 . jump_host
246328 . as_ref ( )
247- . and_then ( |jh| self . process_jump_host_config ( jh) )
329+ . and_then ( |jh| self . process_jump_host_config ( jh, ssh_config ) )
248330 }
249331
250332 /// Get SSH keepalive interval for a cluster.
0 commit comments