Skip to main content

inferlab_runtime/
ssh.rs

1//! The one authority for the SSH client invocation shape, shared by every
2//! module that reaches a remote machine.
3
4use crate::shell::shell_quote;
5use std::process::{Command, Output};
6use thiserror::Error;
7
8const SSH_OPTIONS: &[&str] = &[
9    "-o",
10    "BatchMode=yes",
11    "-o",
12    "ConnectTimeout=10",
13    "-o",
14    "ServerAliveInterval=5",
15    "-o",
16    "ServerAliveCountMax=2",
17    "--",
18];
19
20/// The full SSH argv for bounded execution through an owning operation or
21/// cleanup deadline.
22pub fn ssh_argv(target: &str, script: &str) -> Vec<String> {
23    let mut argv: Vec<String> = ["ssh"]
24        .into_iter()
25        .map(str::to_owned)
26        .chain(SSH_OPTIONS.iter().map(|option| (*option).to_owned()))
27        .collect();
28    argv.extend([
29        target.to_owned(),
30        "bash".to_owned(),
31        "-lic".to_owned(),
32        shell_quote(script),
33    ]);
34    argv
35}
36
37#[derive(Debug, Error)]
38pub enum SshError {
39    #[error("failed to launch SSH for {target:?}: {source}")]
40    Launch {
41        target: String,
42        #[source]
43        source: std::io::Error,
44    },
45}
46
47pub fn ssh_output(target: &str, script: &str) -> Result<Output, SshError> {
48    let argv = ssh_argv(target, script);
49    Command::new(&argv[0])
50        .args(&argv[1..])
51        .output()
52        .map_err(|source| SshError::Launch {
53            target: target.to_owned(),
54            source,
55        })
56}