Skip to content

Commit 902970c

Browse files
committed
kit: extract CommonSshOptions into ssh_options.rs
Extract CommonSshOptions and SshConnectionOptions into a dedicated cross-platform module. These types were embedded in ssh.rs alongside Linux-specific container SSH logic, making them unavailable to other backends. Assisted-by: Antigravity (Claude Opus 4.6) Signed-off-by: Shion Tanaka <shtanaka@redhat.com>
1 parent 22a4223 commit 902970c

4 files changed

Lines changed: 175 additions & 133 deletions

File tree

crates/kit/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
pub mod cpio;
44
pub mod qemu_img;
5+
pub mod ssh_options;
56
pub mod xml_utils;
67

78
// Linux-only modules

crates/kit/src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ mod cpio;
1111
mod install_options;
1212
mod instancetypes;
1313
mod qemu_img;
14+
mod ssh_options;
1415
mod xml_utils;
1516

1617
// Linux-only modules

crates/kit/src/ssh.rs

Lines changed: 2 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ use tracing::debug;
99

1010
use crate::CONTAINER_STATEDIR;
1111

12+
pub use crate::ssh_options::{CommonSshOptions, SshConnectionOptions};
13+
1214
/// Combine multiple command arguments into a properly escaped shell command string
1315
///
1416
/// This is necessary because SSH protocol sends commands as strings, not argument arrays.
@@ -227,101 +229,6 @@ pub fn connect_via_container(container_name: &str, args: Vec<String>) -> Result<
227229
Ok(())
228230
}
229231

230-
/// SSH connection configuration options
231-
#[derive(Debug, Clone)]
232-
pub struct SshConnectionOptions {
233-
/// Common SSH options shared across implementations
234-
pub common: CommonSshOptions,
235-
/// Enable/disable TTY allocation (default: true)
236-
pub allocate_tty: bool,
237-
/// Suppress output to stdout/stderr (default: false)
238-
pub suppress_output: bool,
239-
}
240-
241-
/// Common SSH options that can be shared between different SSH implementations
242-
#[derive(Debug, Clone)]
243-
pub struct CommonSshOptions {
244-
/// Use strict host key checking
245-
pub strict_host_keys: bool,
246-
/// SSH connection timeout in seconds
247-
pub connect_timeout: u32,
248-
/// Server alive interval in seconds
249-
pub server_alive_interval: u32,
250-
/// SSH log level
251-
pub log_level: String,
252-
/// Additional SSH options as key-value pairs
253-
pub extra_options: Vec<(String, String)>,
254-
}
255-
256-
impl Default for CommonSshOptions {
257-
fn default() -> Self {
258-
Self {
259-
strict_host_keys: false,
260-
connect_timeout: 1,
261-
server_alive_interval: 60,
262-
log_level: "ERROR".to_string(),
263-
extra_options: vec![],
264-
}
265-
}
266-
}
267-
268-
impl CommonSshOptions {
269-
/// Apply these options to an SSH command
270-
pub fn apply_to_command(&self, cmd: &mut std::process::Command) {
271-
// Basic security options
272-
cmd.args(["-o", "IdentitiesOnly=yes"]);
273-
cmd.args(["-o", "PasswordAuthentication=no"]);
274-
cmd.args(["-o", "KbdInteractiveAuthentication=no"]);
275-
cmd.args(["-o", "GSSAPIAuthentication=no"]);
276-
277-
// Connection options
278-
cmd.args(["-o", &format!("ConnectTimeout={}", self.connect_timeout)]);
279-
cmd.args([
280-
"-o",
281-
&format!("ServerAliveInterval={}", self.server_alive_interval),
282-
]);
283-
cmd.args(["-o", &format!("LogLevel={}", self.log_level)]);
284-
285-
// Host key checking
286-
if !self.strict_host_keys {
287-
cmd.args(["-o", "StrictHostKeyChecking=no"]);
288-
cmd.args(["-o", "UserKnownHostsFile=/dev/null"]);
289-
}
290-
291-
// Add extra SSH options
292-
for (key, value) in &self.extra_options {
293-
cmd.args(["-o", &format!("{}={}", key, value)]);
294-
}
295-
}
296-
}
297-
298-
impl Default for SshConnectionOptions {
299-
fn default() -> Self {
300-
Self {
301-
common: CommonSshOptions::default(),
302-
allocate_tty: true,
303-
suppress_output: false,
304-
}
305-
}
306-
}
307-
308-
impl SshConnectionOptions {
309-
/// Create options suitable for quick connectivity tests (short timeout, no TTY)
310-
pub fn for_connectivity_test() -> Self {
311-
Self {
312-
common: CommonSshOptions {
313-
strict_host_keys: false,
314-
connect_timeout: 2,
315-
server_alive_interval: 60,
316-
log_level: "ERROR".to_string(),
317-
extra_options: vec![],
318-
},
319-
allocate_tty: false,
320-
suppress_output: true,
321-
}
322-
}
323-
}
324-
325232
/// Verify that a container exists and is running
326233
fn verify_container_running(container_name: &str) -> Result<()> {
327234
let status = Command::new("podman")
@@ -402,44 +309,6 @@ mod tests {
402309
assert_eq!(permissions.mode() & 0o777, 0o600);
403310
}
404311

405-
#[test]
406-
fn test_ssh_connection_options() {
407-
// Test default options
408-
let default_opts = SshConnectionOptions::default();
409-
assert_eq!(default_opts.common.connect_timeout, 1);
410-
assert!(default_opts.allocate_tty);
411-
assert_eq!(default_opts.common.log_level, "ERROR");
412-
assert!(default_opts.common.extra_options.is_empty());
413-
assert!(!default_opts.suppress_output);
414-
415-
// Test connectivity test options
416-
let test_opts = SshConnectionOptions::for_connectivity_test();
417-
assert_eq!(test_opts.common.connect_timeout, 2);
418-
assert!(!test_opts.allocate_tty);
419-
assert_eq!(test_opts.common.log_level, "ERROR");
420-
assert!(test_opts.common.extra_options.is_empty());
421-
assert!(test_opts.suppress_output);
422-
423-
// Test custom options
424-
let mut custom_opts = SshConnectionOptions::default();
425-
custom_opts.common.connect_timeout = 10;
426-
custom_opts.allocate_tty = false;
427-
custom_opts.common.log_level = "DEBUG".to_string();
428-
custom_opts
429-
.common
430-
.extra_options
431-
.push(("ServerAliveInterval".to_string(), "30".to_string()));
432-
433-
assert_eq!(custom_opts.common.connect_timeout, 10);
434-
assert!(!custom_opts.allocate_tty);
435-
assert_eq!(custom_opts.common.log_level, "DEBUG");
436-
assert_eq!(custom_opts.common.extra_options.len(), 1);
437-
assert_eq!(
438-
custom_opts.common.extra_options[0],
439-
("ServerAliveInterval".to_string(), "30".to_string())
440-
);
441-
}
442-
443312
#[test]
444313
fn test_shell_escape_command() {
445314
// Single argument

crates/kit/src/ssh_options.rs

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
//! Cross-platform SSH option types shared between different backends.
2+
//!
3+
//! Extracted from ssh.rs to allow macOS and Windows backends to share
4+
//! SSH option types without pulling in Linux-specific dependencies.
5+
6+
/// Common SSH options that can be shared between different SSH implementations
7+
#[derive(Debug, Clone)]
8+
#[allow(dead_code)]
9+
pub struct CommonSshOptions {
10+
/// Use strict host key checking
11+
pub strict_host_keys: bool,
12+
/// SSH connection timeout in seconds
13+
pub connect_timeout: u32,
14+
/// Server alive interval in seconds
15+
pub server_alive_interval: u32,
16+
/// SSH log level
17+
pub log_level: String,
18+
/// Additional SSH options as key-value pairs
19+
pub extra_options: Vec<(String, String)>,
20+
}
21+
22+
impl Default for CommonSshOptions {
23+
fn default() -> Self {
24+
Self {
25+
strict_host_keys: false,
26+
connect_timeout: 1,
27+
server_alive_interval: 60,
28+
log_level: "ERROR".to_string(),
29+
extra_options: vec![],
30+
}
31+
}
32+
}
33+
34+
impl CommonSshOptions {
35+
/// Apply these options to an SSH command
36+
#[allow(dead_code)]
37+
pub fn apply_to_command(&self, cmd: &mut std::process::Command) {
38+
// Basic security options
39+
cmd.args(["-o", "IdentitiesOnly=yes"]);
40+
cmd.args(["-o", "PasswordAuthentication=no"]);
41+
cmd.args(["-o", "KbdInteractiveAuthentication=no"]);
42+
cmd.args(["-o", "GSSAPIAuthentication=no"]);
43+
44+
// Connection options
45+
cmd.args(["-o", &format!("ConnectTimeout={}", self.connect_timeout)]);
46+
cmd.args([
47+
"-o",
48+
&format!("ServerAliveInterval={}", self.server_alive_interval),
49+
]);
50+
cmd.args(["-o", &format!("LogLevel={}", self.log_level)]);
51+
52+
// Host key checking
53+
if !self.strict_host_keys {
54+
cmd.args(["-o", "StrictHostKeyChecking=no"]);
55+
cmd.args(["-o", "UserKnownHostsFile=/dev/null"]);
56+
}
57+
58+
// Add extra SSH options
59+
for (key, value) in &self.extra_options {
60+
cmd.args(["-o", &format!("{}={}", key, value)]);
61+
}
62+
}
63+
}
64+
65+
/// SSH connection configuration options
66+
#[derive(Debug, Clone)]
67+
#[allow(dead_code)]
68+
pub struct SshConnectionOptions {
69+
/// Common SSH options shared across implementations
70+
pub common: CommonSshOptions,
71+
/// Enable/disable TTY allocation (default: true)
72+
pub allocate_tty: bool,
73+
/// Suppress output to stdout/stderr (default: false)
74+
pub suppress_output: bool,
75+
}
76+
77+
impl Default for SshConnectionOptions {
78+
fn default() -> Self {
79+
Self {
80+
common: CommonSshOptions::default(),
81+
allocate_tty: true,
82+
suppress_output: false,
83+
}
84+
}
85+
}
86+
87+
impl SshConnectionOptions {
88+
/// Create options suitable for quick connectivity tests (short timeout, no TTY)
89+
#[allow(dead_code)]
90+
pub fn for_connectivity_test() -> Self {
91+
Self {
92+
common: CommonSshOptions {
93+
strict_host_keys: false,
94+
connect_timeout: 2,
95+
server_alive_interval: 60,
96+
log_level: "ERROR".to_string(),
97+
extra_options: vec![],
98+
},
99+
allocate_tty: false,
100+
suppress_output: true,
101+
}
102+
}
103+
}
104+
105+
#[cfg(test)]
106+
mod tests {
107+
use super::*;
108+
109+
#[test]
110+
fn test_common_ssh_options_default() {
111+
let opts = CommonSshOptions::default();
112+
assert!(!opts.strict_host_keys);
113+
assert_eq!(opts.connect_timeout, 1);
114+
assert_eq!(opts.server_alive_interval, 60);
115+
assert_eq!(opts.log_level, "ERROR");
116+
assert!(opts.extra_options.is_empty());
117+
}
118+
119+
#[test]
120+
fn test_ssh_connection_options() {
121+
// Test default options
122+
let default_opts = SshConnectionOptions::default();
123+
assert_eq!(default_opts.common.connect_timeout, 1);
124+
assert!(default_opts.allocate_tty);
125+
assert_eq!(default_opts.common.log_level, "ERROR");
126+
assert!(default_opts.common.extra_options.is_empty());
127+
assert!(!default_opts.suppress_output);
128+
129+
// Test connectivity test options
130+
let test_opts = SshConnectionOptions::for_connectivity_test();
131+
assert_eq!(test_opts.common.connect_timeout, 2);
132+
assert!(!test_opts.allocate_tty);
133+
assert_eq!(test_opts.common.log_level, "ERROR");
134+
assert!(test_opts.common.extra_options.is_empty());
135+
assert!(test_opts.suppress_output);
136+
137+
// Test custom options
138+
let mut custom_opts = SshConnectionOptions::default();
139+
custom_opts.common.connect_timeout = 10;
140+
custom_opts.allocate_tty = false;
141+
custom_opts.common.log_level = "DEBUG".to_string();
142+
custom_opts
143+
.common
144+
.extra_options
145+
.push(("ServerAliveInterval".to_string(), "30".to_string()));
146+
147+
assert_eq!(custom_opts.common.connect_timeout, 10);
148+
assert!(!custom_opts.allocate_tty);
149+
assert_eq!(custom_opts.common.log_level, "DEBUG");
150+
assert_eq!(custom_opts.common.extra_options.len(), 1);
151+
assert_eq!(
152+
custom_opts.common.extra_options[0],
153+
("ServerAliveInterval".to_string(), "30".to_string())
154+
);
155+
}
156+
157+
#[test]
158+
fn test_apply_to_command() {
159+
let opts = CommonSshOptions::default();
160+
let mut cmd = std::process::Command::new("ssh");
161+
opts.apply_to_command(&mut cmd);
162+
let args: Vec<_> = cmd
163+
.get_args()
164+
.map(|a| a.to_string_lossy().to_string())
165+
.collect();
166+
assert!(args.contains(&"IdentitiesOnly=yes".to_string()));
167+
assert!(args.contains(&"PasswordAuthentication=no".to_string()));
168+
assert!(args.contains(&"StrictHostKeyChecking=no".to_string()));
169+
assert!(args.contains(&"ConnectTimeout=1".to_string()));
170+
}
171+
}

0 commit comments

Comments
 (0)