diff --git a/CHANGELOG.md b/CHANGELOG.md index 84cedea..953fff7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added + +- New `ClientConnectConfig` to support connect settings, specifically for ssh + +### Fixed + +- `ssh` option to specify external binary not working on `launch` due to the + key being mis-labeled as `ssh.bind` instead of `ssh.bin` +- All ssh settings were not being applied with manager handlers due to some key + checks being incorrect (e.g. `backend` instead of `ssh.backend`). This has + now been corrected and settings now properly get applied ## [0.17.2] - 2022-08-16 ### Added diff --git a/src/cli/commands/manager/handlers.rs b/src/cli/commands/manager/handlers.rs index a4849c6..a9974b2 100644 --- a/src/cli/commands/manager/handlers.rs +++ b/src/cli/commands/manager/handlers.rs @@ -362,43 +362,54 @@ impl<'a> distant_ssh2::SshAuthHandler for AuthClientSshAuthHandler<'a> { #[cfg(any(feature = "libssh", feature = "ssh2"))] fn load_ssh(destination: &Destination, extra: &Extra) -> io::Result { + trace!("load_ssh({destination}, {extra}"); use distant_ssh2::{Ssh, SshOpts}; let host = destination.to_host_string(); let opts = SshOpts { - backend: match extra.get("backend") { + backend: match extra.get("backend").or_else(|| extra.get("ssh.backend")) { Some(s) => s.parse().map_err(|_| invalid("backend"))?, None => Default::default(), }, identity_files: extra .get("identity_files") + .or_else(|| extra.get("ssh.identity_files")) .map(|s| s.split(',').map(|s| PathBuf::from(s.trim())).collect()) .unwrap_or_default(), - identities_only: match extra.get("identities_only") { + identities_only: match extra + .get("identities_only") + .or_else(|| extra.get("ssh.identities_only")) + { Some(s) => Some(s.parse().map_err(|_| invalid("identities_only"))?), None => None, }, port: destination.port(), - proxy_command: extra.get("proxy_command").cloned(), + proxy_command: extra + .get("proxy_command") + .or_else(|| extra.get("ssh.proxy_command")) + .cloned(), user: destination.username().map(ToString::to_string), user_known_hosts_files: extra .get("user_known_hosts_files") + .or_else(|| extra.get("ssh.user_known_hosts_files")) .map(|s| s.split(',').map(|s| PathBuf::from(s.trim())).collect()) .unwrap_or_default(), - verbose: match extra.get("verbose") { + verbose: match extra.get("verbose").or_else(|| extra.get("ssh.verbose")) { Some(s) => s.parse().map_err(|_| invalid("verbose"))?, None => false, }, ..Default::default() }; + + debug!("Connecting to {host} via ssh with {opts:?}"); Ssh::connect(host, opts) } diff --git a/src/config/client.rs b/src/config/client.rs index 3b568b2..d8b5da2 100644 --- a/src/config/client.rs +++ b/src/config/client.rs @@ -2,10 +2,12 @@ use super::{CommonConfig, NetworkConfig}; use serde::{Deserialize, Serialize}; mod action; +mod connect; mod launch; mod repl; pub use action::*; +pub use connect::*; pub use launch::*; pub use repl::*; @@ -16,6 +18,7 @@ pub struct ClientConfig { pub common: CommonConfig, pub action: ClientActionConfig, + pub connect: ClientConnectConfig, pub launch: ClientLaunchConfig, pub repl: ClientReplConfig, diff --git a/src/config/client/connect.rs b/src/config/client/connect.rs new file mode 100644 index 0000000..e735731 --- /dev/null +++ b/src/config/client/connect.rs @@ -0,0 +1,80 @@ +use clap::Args; +use distant_core::Map; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; + +#[derive(Args, Debug, Default, Serialize, Deserialize)] +pub struct ClientConnectConfig { + #[cfg(any(feature = "libssh", feature = "ssh2"))] + #[clap(flatten)] + #[serde(flatten)] + pub ssh: ClientConnectSshConfig, +} + +impl From for ClientConnectConfig { + fn from(mut map: Map) -> Self { + Self { + #[cfg(any(feature = "libssh", feature = "ssh2"))] + ssh: ClientConnectSshConfig { + backend: map + .remove("ssh.backend") + .and_then(|x| x.parse::().ok()), + username: map.remove("ssh.username"), + identity_file: map + .remove("ssh.identity_file") + .and_then(|x| x.parse::().ok()), + port: map.remove("ssh.port").and_then(|x| x.parse::().ok()), + }, + } + } +} + +impl From for Map { + fn from(config: ClientConnectConfig) -> Self { + let mut this = Self::new(); + + #[cfg(any(feature = "libssh", feature = "ssh2"))] + { + if let Some(x) = config.ssh.backend { + this.insert("ssh.backend".to_string(), x.to_string()); + } + + if let Some(x) = config.ssh.username { + this.insert("ssh.username".to_string(), x); + } + + if let Some(x) = config.ssh.identity_file { + this.insert( + "ssh.identity_file".to_string(), + x.to_string_lossy().to_string(), + ); + } + + if let Some(x) = config.ssh.port { + this.insert("ssh.port".to_string(), x.to_string()); + } + } + + this + } +} + +#[cfg(any(feature = "libssh", feature = "ssh2"))] +#[derive(Args, Debug, Default, Serialize, Deserialize)] +pub struct ClientConnectSshConfig { + /// Represents the backend + #[clap(name = "ssh-backend", long)] + pub backend: Option, + + /// Username to use when sshing into remote machine + #[clap(name = "ssh-username", short = 'u', long)] + pub username: Option, + + /// Explicit identity file to use with ssh + #[clap(name = "ssh-identity-file", short = 'i', long)] + pub identity_file: Option, + + /// Port to use for sshing into the remote machine + #[clap(name = "ssh-port", short = 'p', long)] + pub port: Option, +} diff --git a/src/config/client/launch.rs b/src/config/client/launch.rs index 7c3cb83..6bcdc55 100644 --- a/src/config/client/launch.rs +++ b/src/config/client/launch.rs @@ -30,7 +30,7 @@ impl From for ClientLaunchConfig { .unwrap_or_default(), }, ssh: ClientLaunchSshConfig { - bin: map.remove("ssh.bind"), + bin: map.remove("ssh.bin"), #[cfg(any(feature = "libssh", feature = "ssh2"))] backend: map .remove("ssh.backend")