Refactor ssh options into a generic options map and rename extra/Extra into options/Map

pull/137/head
Chip Senkbeil 2 years ago
parent b9c00153a0
commit 1fa3a8acea
No known key found for this signature in database
GPG Key ID: 35EF1F8EC72A4131

@ -1,8 +1,9 @@
use super::data::{
ConnectionId, ConnectionInfo, ConnectionList, Destination, Extra, ManagerRequest,
ManagerResponse,
ConnectionId, ConnectionInfo, ConnectionList, Destination, ManagerRequest, ManagerResponse,
};
use crate::{
DistantChannel, DistantClient, DistantMsg, DistantRequestData, DistantResponseData, Map,
};
use crate::{DistantChannel, DistantClient, DistantMsg, DistantRequestData, DistantResponseData};
use distant_net::{
router, Auth, AuthServer, Client, IntoSplit, MpscTransport, OneshotListener, Request, Response,
ServerExt, ServerRef, UntypedTransportRead, UntypedTransportWrite,
@ -122,20 +123,23 @@ impl DistantManagerClient {
}
/// Request that the manager launches a new server at the given `destination`
/// with `extra` being passed for destination-specific details, returning the new
/// with `options` being passed for destination-specific details, returning the new
/// `destination` of the spawned server to connect to
pub async fn launch(
&mut self,
destination: impl Into<Destination>,
extra: impl Into<Extra>,
options: impl Into<Map>,
) -> io::Result<Destination> {
let destination = Box::new(destination.into());
let extra = extra.into();
trace!("launch({}, {})", destination, extra);
let options = options.into();
trace!("launch({}, {})", destination, options);
let res = self
.client
.send(ManagerRequest::Launch { destination, extra })
.send(ManagerRequest::Launch {
destination,
options,
})
.await?;
match res.payload {
ManagerResponse::Launched { destination } => Ok(destination),
@ -148,19 +152,22 @@ impl DistantManagerClient {
}
/// Request that the manager establishes a new connection at the given `destination`
/// with `extra` being passed for destination-specific details
/// with `options` being passed for destination-specific details
pub async fn connect(
&mut self,
destination: impl Into<Destination>,
extra: impl Into<Extra>,
options: impl Into<Map>,
) -> io::Result<ConnectionId> {
let destination = Box::new(destination.into());
let extra = extra.into();
trace!("connect({}, {})", destination, extra);
let options = options.into();
trace!("connect({}, {})", destination, options);
let res = self
.client
.send(ManagerRequest::Connect { destination, extra })
.send(ManagerRequest::Connect {
destination,
options,
})
.await?;
match res.payload {
ManagerResponse::Connected { id } => Ok(id),
@ -406,7 +413,7 @@ mod tests {
let err = client
.connect(
"scheme://host".parse::<Destination>().unwrap(),
"key=value".parse::<Extra>().unwrap(),
"key=value".parse::<Map>().unwrap(),
)
.await
.unwrap_err();
@ -434,7 +441,7 @@ mod tests {
let err = client
.connect(
"scheme://host".parse::<Destination>().unwrap(),
"key=value".parse::<Extra>().unwrap(),
"key=value".parse::<Map>().unwrap(),
)
.await
.unwrap_err();
@ -465,7 +472,7 @@ mod tests {
let id = client
.connect(
"scheme://host".parse::<Destination>().unwrap(),
"key=value".parse::<Extra>().unwrap(),
"key=value".parse::<Map>().unwrap(),
)
.await
.unwrap();
@ -532,7 +539,7 @@ mod tests {
let info = ConnectionInfo {
id: 123,
destination: "scheme://host".parse::<Destination>().unwrap(),
extra: "key=value".parse::<Extra>().unwrap(),
options: "key=value".parse::<Map>().unwrap(),
};
transport
@ -547,7 +554,7 @@ mod tests {
info.destination,
"scheme://host".parse::<Destination>().unwrap()
);
assert_eq!(info.extra, "key=value".parse::<Extra>().unwrap());
assert_eq!(info.options, "key=value".parse::<Map>().unwrap());
}
#[tokio::test]

@ -1,9 +1,6 @@
mod destination;
pub use destination::*;
mod extra;
pub use extra::*;
mod id;
pub use id::*;

@ -1,2 +0,0 @@
/// Represents extra data included for connections
pub type Extra = crate::data::Map;

@ -1,4 +1,5 @@
use super::{ConnectionId, Destination, Extra};
use super::{ConnectionId, Destination};
use crate::data::Map;
use serde::{Deserialize, Serialize};
/// Information about a specific connection
@ -10,6 +11,6 @@ pub struct ConnectionInfo {
/// Destination with which this connection is associated
pub destination: Destination,
/// Extra information associated with this connection
pub extra: Extra,
/// Additional options associated with this connection
pub options: Map,
}

@ -1,5 +1,5 @@
use super::{ChannelId, ConnectionId, Destination, Extra};
use crate::{DistantMsg, DistantRequestData};
use super::{ChannelId, ConnectionId, Destination};
use crate::{DistantMsg, DistantRequestData, Map};
use distant_net::Request;
use serde::{Deserialize, Serialize};
@ -12,9 +12,9 @@ pub enum ManagerRequest {
// NOTE: Boxed per clippy's large_enum_variant warning
destination: Box<Destination>,
/// Extra details specific to the connection
/// Additional options specific to the connection
#[cfg_attr(feature = "clap", clap(short, long, action = clap::ArgAction::Append))]
extra: Extra,
options: Map,
},
/// Initiate a connection through the manager
@ -22,9 +22,9 @@ pub enum ManagerRequest {
// NOTE: Boxed per clippy's large_enum_variant warning
destination: Box<Destination>,
/// Extra details specific to the connection
/// Additional options specific to the connection
#[cfg_attr(feature = "clap", clap(short, long, action = clap::ArgAction::Append))]
extra: Extra,
options: Map,
},
/// Opens a channel for communication with a server

@ -1,6 +1,6 @@
use crate::{
ChannelId, ConnectionId, ConnectionInfo, ConnectionList, Destination, Extra, ManagerRequest,
ManagerResponse,
ChannelId, ConnectionId, ConnectionInfo, ConnectionList, Destination, ManagerRequest,
ManagerResponse, Map,
};
use async_trait::async_trait;
use distant_net::{
@ -123,14 +123,14 @@ impl DistantManager {
})
}
/// Launches a new server at the specified `destination` using the given `extra` information
/// Launches a new server at the specified `destination` using the given `options` information
/// and authentication client (if needed) to retrieve additional information needed to
/// enter the destination prior to starting the server, returning the destination of the
/// launched server
async fn launch(
&self,
destination: Destination,
extra: Extra,
options: Map,
auth: Option<&mut AuthClient>,
) -> io::Result<Destination> {
let auth = auth.ok_or_else(|| {
@ -163,19 +163,19 @@ impl DistantManager {
format!("No launch handler registered for {}", scheme),
)
})?;
handler.launch(&destination, &extra, auth).await?
handler.launch(&destination, &options, auth).await?
};
Ok(credentials)
}
/// Connects to a new server at the specified `destination` using the given `extra` information
/// Connects to a new server at the specified `destination` using the given `options` information
/// and authentication client (if needed) to retrieve additional information needed to
/// establish the connection to the server
async fn connect(
&self,
destination: Destination,
extra: Extra,
options: Map,
auth: Option<&mut AuthClient>,
) -> io::Result<ConnectionId> {
let auth = auth.ok_or_else(|| {
@ -208,10 +208,10 @@ impl DistantManager {
format!("No connect handler registered for {}", scheme),
)
})?;
handler.connect(&destination, &extra, auth).await?
handler.connect(&destination, &options, auth).await?
};
let connection = DistantManagerConnection::new(destination, extra, writer, reader);
let connection = DistantManagerConnection::new(destination, options, writer, reader);
let id = connection.id;
self.connections.write().await.insert(id, connection);
Ok(id)
@ -223,7 +223,7 @@ impl DistantManager {
Some(connection) => Ok(ConnectionInfo {
id: connection.id,
destination: connection.destination.clone(),
extra: connection.extra.clone(),
options: connection.options.clone(),
}),
None => Err(io::Error::new(
io::ErrorKind::NotConnected,
@ -297,24 +297,36 @@ impl Server for DistantManager {
} = ctx;
let response = match request.payload {
ManagerRequest::Launch { destination, extra } => {
ManagerRequest::Launch {
destination,
options,
} => {
let mut auth = match local_data.auth_client.as_ref() {
Some(client) => Some(client.lock().await),
None => None,
};
match self.launch(*destination, extra, auth.as_deref_mut()).await {
match self
.launch(*destination, options, auth.as_deref_mut())
.await
{
Ok(destination) => ManagerResponse::Launched { destination },
Err(x) => ManagerResponse::Error(x.into()),
}
}
ManagerRequest::Connect { destination, extra } => {
ManagerRequest::Connect {
destination,
options,
} => {
let mut auth = match local_data.auth_client.as_ref() {
Some(client) => Some(client.lock().await),
None => None,
};
match self.connect(*destination, extra, auth.as_deref_mut()).await {
match self
.connect(*destination, options, auth.as_deref_mut())
.await
{
Ok(id) => ManagerResponse::Connected { id },
Err(x) => ManagerResponse::Error(x.into()),
}
@ -461,10 +473,10 @@ mod tests {
let server = setup();
let destination = "scheme://host".parse::<Destination>().unwrap();
let extra = "".parse::<Extra>().unwrap();
let options = "".parse::<Map>().unwrap();
let (mut auth, _auth_server) = auth_client_server();
let err = server
.launch(destination, extra, Some(&mut auth))
.launch(destination, options, Some(&mut auth))
.await
.unwrap_err();
assert_eq!(err.kind(), io::ErrorKind::InvalidInput, "{:?}", err);
@ -485,10 +497,10 @@ mod tests {
.insert("scheme".to_string(), handler);
let destination = "scheme://host".parse::<Destination>().unwrap();
let extra = "".parse::<Extra>().unwrap();
let options = "".parse::<Map>().unwrap();
let (mut auth, _auth_server) = auth_client_server();
let err = server
.launch(destination, extra, Some(&mut auth))
.launch(destination, options, Some(&mut auth))
.await
.unwrap_err();
assert_eq!(err.kind(), io::ErrorKind::Other);
@ -512,10 +524,10 @@ mod tests {
.insert("scheme".to_string(), handler);
let destination = "scheme://host".parse::<Destination>().unwrap();
let extra = "key=value".parse::<Extra>().unwrap();
let options = "key=value".parse::<Map>().unwrap();
let (mut auth, _auth_server) = auth_client_server();
let destination = server
.launch(destination, extra, Some(&mut auth))
.launch(destination, options, Some(&mut auth))
.await
.unwrap();
@ -530,10 +542,10 @@ mod tests {
let server = setup();
let destination = "scheme://host".parse::<Destination>().unwrap();
let extra = "".parse::<Extra>().unwrap();
let options = "".parse::<Map>().unwrap();
let (mut auth, _auth_server) = auth_client_server();
let err = server
.connect(destination, extra, Some(&mut auth))
.connect(destination, options, Some(&mut auth))
.await
.unwrap_err();
assert_eq!(err.kind(), io::ErrorKind::InvalidInput, "{:?}", err);
@ -554,10 +566,10 @@ mod tests {
.insert("scheme".to_string(), handler);
let destination = "scheme://host".parse::<Destination>().unwrap();
let extra = "".parse::<Extra>().unwrap();
let options = "".parse::<Map>().unwrap();
let (mut auth, _auth_server) = auth_client_server();
let err = server
.connect(destination, extra, Some(&mut auth))
.connect(destination, options, Some(&mut auth))
.await
.unwrap_err();
assert_eq!(err.kind(), io::ErrorKind::Other);
@ -578,10 +590,10 @@ mod tests {
.insert("scheme".to_string(), handler);
let destination = "scheme://host".parse::<Destination>().unwrap();
let extra = "key=value".parse::<Extra>().unwrap();
let options = "key=value".parse::<Map>().unwrap();
let (mut auth, _auth_server) = auth_client_server();
let id = server
.connect(destination, extra, Some(&mut auth))
.connect(destination, options, Some(&mut auth))
.await
.unwrap();
@ -589,7 +601,7 @@ mod tests {
let connection = lock.get(&id).unwrap();
assert_eq!(connection.id, id);
assert_eq!(connection.destination, "scheme://host");
assert_eq!(connection.extra, "key=value".parse().unwrap());
assert_eq!(connection.options, "key=value".parse().unwrap());
}
#[tokio::test]
@ -620,7 +632,7 @@ mod tests {
ConnectionInfo {
id,
destination: "scheme://host".parse().unwrap(),
extra: "key=value".parse().unwrap(),
options: "key=value".parse().unwrap(),
}
);
}

@ -1,6 +1,7 @@
use crate::{
data::Map,
manager::{
data::{ChannelId, ConnectionId, Destination, Extra},
data::{ChannelId, ConnectionId, Destination},
BoxedDistantReader, BoxedDistantWriter,
},
DistantMsg, DistantRequestData, DistantResponseData, ManagerResponse,
@ -14,7 +15,7 @@ use tokio::{sync::mpsc, task::JoinHandle};
pub struct DistantManagerConnection {
pub id: ConnectionId,
pub destination: Destination,
pub extra: Extra,
pub options: Map,
tx: mpsc::Sender<StateMachine>,
reader_task: JoinHandle<()>,
writer_task: JoinHandle<()>,
@ -84,7 +85,7 @@ enum StateMachine {
impl DistantManagerConnection {
pub fn new(
destination: Destination,
extra: Extra,
options: Map,
mut writer: BoxedDistantWriter,
mut reader: BoxedDistantReader,
) -> Self {
@ -162,7 +163,7 @@ impl DistantManagerConnection {
Self {
id: connection_id,
destination,
extra,
options,
tx,
reader_task,
writer_task,

@ -1,6 +1,5 @@
use crate::{
manager::data::{Destination, Extra},
DistantMsg, DistantRequestData, DistantResponseData,
data::Map, manager::data::Destination, DistantMsg, DistantRequestData, DistantResponseData,
};
use async_trait::async_trait;
use distant_net::{AuthClient, Request, Response, TypedAsyncRead, TypedAsyncWrite};
@ -20,7 +19,7 @@ pub trait LaunchHandler: Send + Sync {
async fn launch(
&self,
destination: &Destination,
extra: &Extra,
options: &Map,
auth_client: &mut AuthClient,
) -> io::Result<Destination>;
}
@ -28,16 +27,16 @@ pub trait LaunchHandler: Send + Sync {
#[async_trait]
impl<F, R> LaunchHandler for F
where
F: for<'a> Fn(&'a Destination, &'a Extra, &'a mut AuthClient) -> R + Send + Sync + 'static,
F: for<'a> Fn(&'a Destination, &'a Map, &'a mut AuthClient) -> R + Send + Sync + 'static,
R: Future<Output = io::Result<Destination>> + Send + 'static,
{
async fn launch(
&self,
destination: &Destination,
extra: &Extra,
options: &Map,
auth_client: &mut AuthClient,
) -> io::Result<Destination> {
self(destination, extra, auth_client).await
self(destination, options, auth_client).await
}
}
@ -47,7 +46,7 @@ pub trait ConnectHandler: Send + Sync {
async fn connect(
&self,
destination: &Destination,
extra: &Extra,
options: &Map,
auth_client: &mut AuthClient,
) -> io::Result<BoxedDistantWriterReader>;
}
@ -55,15 +54,15 @@ pub trait ConnectHandler: Send + Sync {
#[async_trait]
impl<F, R> ConnectHandler for F
where
F: for<'a> Fn(&'a Destination, &'a Extra, &'a mut AuthClient) -> R + Send + Sync + 'static,
F: for<'a> Fn(&'a Destination, &'a Map, &'a mut AuthClient) -> R + Send + Sync + 'static,
R: Future<Output = io::Result<BoxedDistantWriterReader>> + Send + 'static,
{
async fn connect(
&self,
destination: &Destination,
extra: &Extra,
options: &Map,
auth_client: &mut AuthClient,
) -> io::Result<BoxedDistantWriterReader> {
self(destination, extra, auth_client).await
self(destination, options, auth_client).await
}
}

@ -1,7 +1,7 @@
use distant_core::{
net::{FramedTransport, InmemoryTransport, IntoSplit, OneshotListener, PlainCodec},
BoxedDistantReader, BoxedDistantWriter, Destination, DistantApiServer, DistantChannelExt,
DistantManager, DistantManagerClient, DistantManagerClientConfig, DistantManagerConfig, Extra,
DistantManager, DistantManagerClient, DistantManagerClientConfig, DistantManagerConfig, Map,
};
use std::io;
@ -54,7 +54,7 @@ async fn should_be_able_to_establish_a_single_connection_and_communicate() {
let id = client
.connect(
"scheme://host".parse::<Destination>().unwrap(),
"key=value".parse::<Extra>().unwrap(),
"key=value".parse::<Map>().unwrap(),
)
.await
.expect("Failed to connect to a remote server");
@ -74,7 +74,7 @@ async fn should_be_able_to_establish_a_single_connection_and_communicate() {
.expect("Failed to get info about connection");
assert_eq!(info.id, id);
assert_eq!(info.destination.to_string(), "scheme://host");
assert_eq!(info.extra, "key=value".parse::<Extra>().unwrap());
assert_eq!(info.options, "key=value".parse::<Map>().unwrap());
// Create a new channel and request some data
let mut channel = client

@ -50,7 +50,7 @@ pub enum AuthRequest {
/// Represents a challenge comprising a series of questions to be presented
Challenge {
questions: Vec<AuthQuestion>,
extra: HashMap<String, String>,
options: HashMap<String, String>,
},
/// Represents an ask to verify some information
@ -91,17 +91,17 @@ pub struct AuthQuestion {
/// The text of the question
pub text: String,
/// Any extra information specific to a particular auth domain
/// Any options information specific to a particular auth domain
/// such as including a username and instructions for SSH authentication
pub extra: HashMap<String, String>,
pub options: HashMap<String, String>,
}
impl AuthQuestion {
/// Creates a new question without any extra data
/// Creates a new question without any options data
pub fn new(text: impl Into<String>) -> Self {
Self {
text: text.into(),
extra: HashMap::new(),
options: HashMap::new(),
}
}
}

@ -83,18 +83,18 @@ impl AuthClient {
pub async fn challenge(
&mut self,
questions: Vec<AuthQuestion>,
extra: HashMap<String, String>,
options: HashMap<String, String>,
) -> io::Result<Vec<String>> {
trace!(
"AuthClient::challenge(questions = {:?}, extra = {:?})",
"AuthClient::challenge(questions = {:?}, options = {:?})",
questions,
extra
options
);
// Perform JIT handshake if enabled
self.jit_handshake().await?;
let payload = AuthRequest::Challenge { questions, extra };
let payload = AuthRequest::Challenge { questions, options };
let encrypted_payload = self.serialize_and_encrypt(&payload)?;
let response = self.inner.send(Auth::Msg { encrypted_payload }).await?;
@ -281,7 +281,7 @@ mod tests {
AuthQuestion::new("question1".to_string()),
AuthQuestion {
text: "question2".to_string(),
extra: vec![("key2".to_string(), "value2".to_string())]
options: vec![("key2".to_string(), "value2".to_string())]
.into_iter()
.collect(),
},
@ -360,7 +360,7 @@ mod tests {
AuthQuestion::new("question1".to_string()),
AuthQuestion {
text: "question2".to_string(),
extra: vec![("key2".to_string(), "value2".to_string())]
options: vec![("key2".to_string(), "value2".to_string())]
.into_iter()
.collect(),
},
@ -398,14 +398,14 @@ mod tests {
match request.payload {
Auth::Msg { encrypted_payload } => {
match decrypt_and_deserialize(&mut codec, &encrypted_payload).unwrap() {
AuthRequest::Challenge { questions, extra } => {
AuthRequest::Challenge { questions, options } => {
assert_eq!(
questions,
vec![
AuthQuestion::new("question1".to_string()),
AuthQuestion {
text: "question2".to_string(),
extra: vec![("key2".to_string(), "value2".to_string())]
options: vec![("key2".to_string(), "value2".to_string())]
.into_iter()
.collect(),
},
@ -413,7 +413,7 @@ mod tests {
);
assert_eq!(
extra,
options,
vec![("key".to_string(), "value".to_string())]
.into_iter()
.collect(),

@ -120,12 +120,12 @@ where
let response = match request {
Ok(request) => match request {
AuthRequest::Challenge { questions, extra } => {
AuthRequest::Challenge { questions, options } => {
trace!("Received challenge request");
trace!("questions = {:?}", questions);
trace!("extra = {:?}", extra);
trace!("options = {:?}", options);
let answers = (self.on_challenge)(questions, extra);
let answers = (self.on_challenge)(questions, options);
AuthResponse::Challenge { answers }
}
AuthRequest::Verify { kind, text } => {
@ -310,8 +310,8 @@ mod tests {
let (tx, mut rx) = mpsc::channel(1);
let (mut t, _) = spawn_auth_server(
/* on_challenge */
move |questions, extra| {
tx.try_send((questions, extra)).unwrap();
move |questions, options| {
tx.try_send((questions, options)).unwrap();
vec!["answer1".to_string(), "answer2".to_string()]
},
/* on_verify */ |_, _| false,
@ -346,12 +346,12 @@ mod tests {
AuthQuestion::new("question1".to_string()),
AuthQuestion {
text: "question2".to_string(),
extra: vec![("key".to_string(), "value".to_string())]
options: vec![("key".to_string(), "value".to_string())]
.into_iter()
.collect(),
},
],
extra: vec![("hello".to_string(), "world".to_string())]
options: vec![("hello".to_string(), "world".to_string())]
.into_iter()
.collect(),
},
@ -362,21 +362,21 @@ mod tests {
.unwrap();
// Verify that the handler was triggered
let (questions, extra) = rx.recv().await.expect("Channel closed unexpectedly");
let (questions, options) = rx.recv().await.expect("Channel closed unexpectedly");
assert_eq!(
questions,
vec![
AuthQuestion::new("question1".to_string()),
AuthQuestion {
text: "question2".to_string(),
extra: vec![("key".to_string(), "value".to_string())]
options: vec![("key".to_string(), "value".to_string())]
.into_iter()
.collect(),
}
]
);
assert_eq!(
extra,
options,
vec![("hello".to_string(), "world".to_string())]
.into_iter()
.collect()

@ -23,9 +23,9 @@ fn setup() -> (AuthClient, mpsc::Receiver<AuthRequest>) {
// Make a server that echos questions back as answers and only verifies the text "yes"
let server = AuthServer {
on_challenge: move |questions, extra| {
on_challenge: move |questions, options| {
let questions_2 = questions.clone();
tx.try_send(AuthRequest::Challenge { questions, extra })
tx.try_send(AuthRequest::Challenge { questions, options })
.unwrap();
questions_2.into_iter().map(|x| x.text).collect()
},
@ -75,12 +75,12 @@ async fn client_should_be_able_to_challenge_against_server() {
// Verify that the server received the request
let request = rx.recv().await.unwrap();
match request {
AuthRequest::Challenge { questions, extra } => {
AuthRequest::Challenge { questions, options } => {
assert_eq!(questions.len(), 1);
assert_eq!(questions[0].text, "hello");
assert_eq!(questions[0].extra, HashMap::new());
assert_eq!(questions[0].options, HashMap::new());
assert_eq!(extra, HashMap::new());
assert_eq!(options, HashMap::new());
}
x => panic!("Unexpected request received by server: {:?}", x),
}

@ -42,10 +42,11 @@ impl Client {
on_challenge: {
let tx = tx.clone();
let rx = rx.clone();
Box::new(move |questions, extra| {
Box::new(move |questions, options| {
let question_cnt = questions.len();
if let Err(x) = tx.send_blocking(&AuthRequest::Challenge { questions, extra }) {
if let Err(x) = tx.send_blocking(&AuthRequest::Challenge { questions, options })
{
error!("{}", x);
return (0..question_cnt)
.into_iter()

@ -3,7 +3,10 @@ use crate::{
client::{MsgReceiver, MsgSender},
Cache, Client,
},
config::{ClientConfig, ClientLaunchConfig, NetworkConfig},
config::{
ClientActionConfig, ClientConfig, ClientConnectConfig, ClientLaunchConfig,
ClientReplConfig, NetworkConfig,
},
paths::user::CACHE_FILE_PATH_STR,
CliError, CliResult,
};
@ -14,7 +17,7 @@ use distant_core::{
data::{ChangeKindSet, Environment},
net::{IntoSplit, Request, Response, TypedAsyncRead, TypedAsyncWrite},
ConnectionId, Destination, DistantManagerClient, DistantMsg, DistantRequestData,
DistantResponseData, Extra, Host, RemoteCommand, Watcher,
DistantResponseData, Host, Map, RemoteCommand, Watcher,
};
use log::*;
use serde_json::{json, Value};
@ -50,6 +53,9 @@ pub enum ClientSubcommand {
)]
cache: PathBuf,
#[clap(flatten)]
config: ClientActionConfig,
/// Specify a connection being managed
#[clap(long)]
connection: Option<ConnectionId>,
@ -57,10 +63,6 @@ pub enum ClientSubcommand {
#[clap(flatten)]
network: NetworkConfig,
/// Represents the maximum time (in seconds) to wait for a network request before timing out
#[clap(short, long)]
timeout: Option<f32>,
#[clap(subcommand)]
request: DistantRequestData,
},
@ -76,6 +78,9 @@ pub enum ClientSubcommand {
)]
cache: PathBuf,
#[clap(flatten)]
config: ClientConnectConfig,
#[clap(flatten)]
network: NetworkConfig,
@ -149,6 +154,9 @@ pub enum ClientSubcommand {
)]
cache: PathBuf,
#[clap(flatten)]
config: ClientReplConfig,
/// Specify a connection being managed
#[clap(long)]
connection: Option<ConnectionId>,
@ -159,10 +167,6 @@ pub enum ClientSubcommand {
/// Format used for input into and output from the repl
#[clap(short, long, default_value_t, value_enum)]
format: Format,
/// Represents the maximum time (in seconds) to wait for a network request before timing out
#[clap(short, long)]
timeout: Option<f32>,
},
/// Select the active connection
@ -244,10 +248,10 @@ impl ClientSubcommand {
match self {
Self::Action {
config: action_config,
connection,
network,
request,
timeout,
..
} => {
let network = network.merge(config.network);
@ -265,6 +269,8 @@ impl ClientSubcommand {
format!("Failed to open channel to connection {connection_id}")
})?;
let timeout = action_config.timeout.or(config.action.timeout);
debug!(
"Timeout configured to be {}",
match timeout {
@ -375,6 +381,7 @@ impl ClientSubcommand {
}
}
Self::Connect {
config: connect_config,
network,
format,
destination,
@ -393,10 +400,15 @@ impl ClientSubcommand {
.context("Failed to connect to manager")?
};
// Merge our connect configs, overwriting anything in the config file with our cli
// arguments
let mut options = Map::from(config.connect);
options.extend(Map::from(connect_config).into_map());
// Trigger our manager to connect to the launched server
debug!("Connecting to server at {}", destination);
debug!("Connecting to server at {} with {}", destination, options);
let id = client
.connect(*destination, Extra::new())
.connect(*destination, options)
.await
.context("Failed to connect to server")?;
@ -418,7 +430,7 @@ impl ClientSubcommand {
}
}
Self::Launch {
config: launcher_config,
config: launch_config,
network,
format,
mut destination,
@ -439,8 +451,8 @@ impl ClientSubcommand {
// Merge our launch configs, overwriting anything in the config file
// with our cli arguments
let mut extra = Extra::from(config.launch);
extra.extend(Extra::from(launcher_config).into_map());
let mut options = Map::from(config.launch);
options.extend(Map::from(launch_config).into_map());
// Grab the host we are connecting to for later use
let host = destination.host.to_string();
@ -455,9 +467,9 @@ impl ClientSubcommand {
}
// Start the server using our manager
debug!("Launching server at {} with {}", destination, extra);
debug!("Launching server at {} with {}", destination, options);
let mut new_destination = client
.launch(*destination, extra)
.launch(*destination, options)
.await
.context("Failed to launch server")?;
@ -480,7 +492,7 @@ impl ClientSubcommand {
// Trigger our manager to connect to the launched server
debug!("Connecting to server at {}", new_destination);
let id = client
.connect(new_destination, Extra::new())
.connect(new_destination, Map::new())
.await
.context("Failed to connect to server")?;
@ -531,10 +543,10 @@ impl ClientSubcommand {
Lsp::new(channel).spawn(cmd, persist, pty).await?;
}
Self::Repl {
config: repl_config,
connection,
network,
format,
timeout,
..
} => {
let network = network.merge(config.network);
@ -548,6 +560,8 @@ impl ClientSubcommand {
let connection_id =
use_or_lookup_connection_id(&mut cache, connection, &mut client).await?;
let timeout = repl_config.timeout.or(config.repl.timeout);
debug!("Opening raw channel to connection {}", connection_id);
let channel = client
.open_raw_channel(connection_id)

@ -343,7 +343,7 @@ impl ManagerSubcommand {
scheme: String,
host: String,
port: String,
extra: String,
options: String,
}
println!(
@ -357,7 +357,7 @@ impl ManagerSubcommand {
.port
.map(|x| x.to_string())
.unwrap_or_default(),
extra: info.extra.to_string()
options: info.options.to_string()
}])
);

@ -6,7 +6,7 @@ use distant_core::{
XChaCha20Poly1305Codec,
},
BoxedDistantReader, BoxedDistantWriter, BoxedDistantWriterReader, ConnectHandler, Destination,
Extra, LaunchHandler,
LaunchHandler, Map,
};
use log::*;
use std::{
@ -49,11 +49,11 @@ impl LaunchHandler for ManagerLaunchHandler {
async fn launch(
&self,
destination: &Destination,
extra: &Extra,
options: &Map,
_auth_client: &mut AuthClient,
) -> io::Result<Destination> {
trace!("Handling launch of {destination} with extra '{extra}'");
let config = ClientLaunchConfig::from(extra.clone());
debug!("Handling launch of {destination} with options '{options}'");
let config = ClientLaunchConfig::from(options.clone());
// Get the path to the distant binary, ensuring it exists and is executable
let program = which::which(match config.distant.bin {
@ -86,13 +86,13 @@ impl LaunchHandler for ManagerLaunchHandler {
args.push(port.to_string());
}
// Add any extra arguments to the command
if let Some(extra_args) = config.distant.args {
// Add any options arguments to the command
if let Some(options_args) = config.distant.args {
// NOTE: Split arguments based on whether we are running on windows or unix
args.extend(if cfg!(windows) {
winsplit::split(&extra_args)
winsplit::split(&options_args)
} else {
shell_words::split(&extra_args)
shell_words::split(&options_args)
.map_err(|x| io::Error::new(io::ErrorKind::InvalidInput, x))?
});
}
@ -162,14 +162,14 @@ impl LaunchHandler for SshLaunchHandler {
async fn launch(
&self,
destination: &Destination,
extra: &Extra,
options: &Map,
auth_client: &mut AuthClient,
) -> io::Result<Destination> {
trace!("Handling launch of {destination} with extra '{extra}'");
let config = ClientLaunchConfig::from(extra.clone());
debug!("Handling launch of {destination} with options '{options}'");
let config = ClientLaunchConfig::from(options.clone());
use distant_ssh2::DistantLaunchOpts;
let mut ssh = load_ssh(destination, extra)?;
let mut ssh = load_ssh(destination, options)?;
let handler = AuthClientSshAuthHandler::new(auth_client);
let _ = ssh.authenticate(handler).await?;
let opts = {
@ -178,7 +178,7 @@ impl LaunchHandler for SshLaunchHandler {
binary: config.distant.bin.unwrap_or(opts.binary),
args: config.distant.args.unwrap_or(opts.args),
use_login_shell: !config.distant.no_shell,
timeout: match extra.get("timeout") {
timeout: match options.get("timeout") {
Some(s) => std::time::Duration::from_millis(
s.parse::<u64>().map_err(|_| invalid("timeout"))?,
),
@ -218,10 +218,10 @@ impl ConnectHandler for DistantConnectHandler {
async fn connect(
&self,
destination: &Destination,
extra: &Extra,
options: &Map,
auth_client: &mut AuthClient,
) -> io::Result<BoxedDistantWriterReader> {
trace!("Handling connect of {destination} with extra '{extra}'");
debug!("Handling connect of {destination} with options '{options}'");
let host = destination.host.to_string();
let port = destination.port.ok_or_else(|| missing("port"))?;
@ -246,13 +246,13 @@ impl ConnectHandler for DistantConnectHandler {
));
}
// Use provided password or extra key if available, otherwise ask for it, and produce a
// Use provided password or options key if available, otherwise ask for it, and produce a
// codec using the key
let codec = {
let key = destination
.password
.as_deref()
.or_else(|| extra.get("key").map(|s| s.as_str()));
.or_else(|| options.get("key").map(|s| s.as_str()));
let key = match key {
Some(key) => key.parse::<SecretKey32>().map_err(|_| invalid("key"))?,
@ -290,11 +290,11 @@ impl ConnectHandler for SshConnectHandler {
async fn connect(
&self,
destination: &Destination,
extra: &Extra,
options: &Map,
auth_client: &mut AuthClient,
) -> io::Result<BoxedDistantWriterReader> {
trace!("Handling connect of {destination} with extra '{extra}'");
let mut ssh = load_ssh(destination, extra)?;
debug!("Handling connect of {destination} with options '{options}'");
let mut ssh = load_ssh(destination, options)?;
let handler = AuthClientSshAuthHandler::new(auth_client);
let _ = ssh.authenticate(handler).await?;
ssh.into_distant_writer_reader().await
@ -316,22 +316,22 @@ impl<'a> AuthClientSshAuthHandler<'a> {
impl<'a> distant_ssh2::SshAuthHandler for AuthClientSshAuthHandler<'a> {
async fn on_authenticate(&self, event: distant_ssh2::SshAuthEvent) -> io::Result<Vec<String>> {
use std::collections::HashMap;
let mut extra = HashMap::new();
let mut options = HashMap::new();
let mut questions = Vec::new();
for prompt in event.prompts {
let mut extra = HashMap::new();
extra.insert("echo".to_string(), prompt.echo.to_string());
let mut options = HashMap::new();
options.insert("echo".to_string(), prompt.echo.to_string());
questions.push(AuthQuestion {
text: prompt.prompt,
extra,
options,
});
}
extra.insert("instructions".to_string(), event.instructions);
extra.insert("username".to_string(), event.username);
options.insert("instructions".to_string(), event.instructions);
options.insert("username".to_string(), event.username);
self.0.lock().await.challenge(questions, extra).await
self.0.lock().await.challenge(questions, options).await
}
async fn on_verify_host(&self, host: &str) -> io::Result<bool> {
@ -364,27 +364,30 @@ impl<'a> distant_ssh2::SshAuthHandler for AuthClientSshAuthHandler<'a> {
}
#[cfg(any(feature = "libssh", feature = "ssh2"))]
fn load_ssh(destination: &Destination, extra: &Extra) -> io::Result<distant_ssh2::Ssh> {
trace!("load_ssh({destination}, {extra}");
fn load_ssh(destination: &Destination, options: &Map) -> io::Result<distant_ssh2::Ssh> {
trace!("load_ssh({destination}, {options})");
use distant_ssh2::{Ssh, SshOpts};
let host = destination.host.to_string();
let opts = SshOpts {
backend: match extra.get("backend").or_else(|| extra.get("ssh.backend")) {
backend: match options
.get("backend")
.or_else(|| options.get("ssh.backend"))
{
Some(s) => s.parse().map_err(|_| invalid("backend"))?,
None => Default::default(),
},
identity_files: extra
identity_files: options
.get("identity_files")
.or_else(|| extra.get("ssh.identity_files"))
.or_else(|| options.get("ssh.identity_files"))
.map(|s| s.split(',').map(|s| PathBuf::from(s.trim())).collect())
.unwrap_or_default(),
identities_only: match extra
identities_only: match options
.get("identities_only")
.or_else(|| extra.get("ssh.identities_only"))
.or_else(|| options.get("ssh.identities_only"))
{
Some(s) => Some(s.parse().map_err(|_| invalid("identities_only"))?),
None => None,
@ -392,20 +395,23 @@ fn load_ssh(destination: &Destination, extra: &Extra) -> io::Result<distant_ssh2
port: destination.port,
proxy_command: extra
proxy_command: options
.get("proxy_command")
.or_else(|| extra.get("ssh.proxy_command"))
.or_else(|| options.get("ssh.proxy_command"))
.cloned(),
user: destination.username.clone(),
user_known_hosts_files: extra
user_known_hosts_files: options
.get("user_known_hosts_files")
.or_else(|| extra.get("ssh.user_known_hosts_files"))
.or_else(|| options.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").or_else(|| extra.get("ssh.verbose")) {
verbose: match options
.get("verbose")
.or_else(|| options.get("ssh.verbose"))
{
Some(s) => s.parse().map_err(|_| invalid("verbose"))?,
None => false,
},

@ -1,6 +1,7 @@
use clap::Args;
use serde::{Deserialize, Serialize};
#[derive(Debug, Default, Serialize, Deserialize)]
#[derive(Args, Debug, Default, Serialize, Deserialize)]
pub struct ClientActionConfig {
/// Represents the maximum time (in seconds) to wait for a network request before timing out
pub timeout: Option<f32>,

@ -1,62 +1,28 @@
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)]
/// Additional options to provide, typically forwarded to the handler within the manager
/// facilitating the connection. Options are key-value pairs separated by comma.
///
/// E.g. `key="value",key2="value2"`
#[clap(long, default_value_t)]
#[serde(flatten)]
pub ssh: ClientConnectSshConfig,
pub options: Map,
}
impl From<Map> 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::<distant_ssh2::SshBackend>().ok()),
identity_file: map
.remove("ssh.identity_file")
.and_then(|x| x.parse::<PathBuf>().ok()),
},
}
fn from(map: Map) -> Self {
Self { options: map }
}
}
impl From<ClientConnectConfig> 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.identity_file {
this.insert(
"ssh.identity_file".to_string(),
x.to_string_lossy().to_string(),
);
}
}
this.extend(config.options);
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<distant_ssh2::SshBackend>,
/// Explicit identity file to use with ssh
#[clap(name = "ssh-identity-file", short = 'i', long)]
pub identity_file: Option<PathBuf>,
}

@ -2,7 +2,6 @@ use crate::config::BindAddress;
use clap::Args;
use distant_core::Map;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Args, Debug, Default, Serialize, Deserialize)]
pub struct ClientLaunchConfig {
@ -10,9 +9,14 @@ pub struct ClientLaunchConfig {
#[serde(flatten)]
pub distant: ClientLaunchDistantConfig,
#[clap(flatten)]
/// Additional options to provide, typically forwarded to the handler within the manager
/// facilitating the launch of a distant server. Options are key-value pairs separated by
/// comma.
///
/// E.g. `key="value",key2="value2"`
#[clap(long, default_value_t)]
#[serde(flatten)]
pub ssh: ClientLaunchSshConfig,
pub options: Map,
}
impl From<Map> for ClientLaunchConfig {
@ -29,20 +33,7 @@ impl From<Map> for ClientLaunchConfig {
.and_then(|x| x.parse::<bool>().ok())
.unwrap_or_default(),
},
ssh: ClientLaunchSshConfig {
bin: map.remove("ssh.bin"),
#[cfg(any(feature = "libssh", feature = "ssh2"))]
backend: map
.remove("ssh.backend")
.and_then(|x| x.parse::<distant_ssh2::SshBackend>().ok()),
external: map
.remove("ssh.external")
.and_then(|x| x.parse::<bool>().ok())
.unwrap_or_default(),
identity_file: map
.remove("ssh.identity_file")
.and_then(|x| x.parse::<PathBuf>().ok()),
},
options: map,
}
}
}
@ -68,23 +59,7 @@ impl From<ClientLaunchConfig> for Map {
config.distant.no_shell.to_string(),
);
if let Some(x) = config.ssh.bin {
this.insert("ssh.bin".to_string(), x);
}
#[cfg(any(feature = "libssh", feature = "ssh2"))]
if let Some(x) = config.ssh.backend {
this.insert("ssh.backend".to_string(), x.to_string());
}
this.insert("ssh.external".to_string(), config.ssh.external.to_string());
if let Some(x) = config.ssh.identity_file {
this.insert(
"ssh.identity_file".to_string(),
x.to_string_lossy().to_string(),
);
}
this.extend(config.options);
this
}
@ -120,25 +95,3 @@ pub struct ClientLaunchDistantConfig {
#[clap(long)]
pub no_shell: bool,
}
#[derive(Args, Debug, Default, Serialize, Deserialize)]
pub struct ClientLaunchSshConfig {
/// Path to ssh program on local machine to execute when using external ssh
#[clap(name = "ssh", long)]
pub bin: Option<String>,
/// If using native ssh integration, represents the backend
#[cfg(any(feature = "libssh", feature = "ssh2"))]
#[clap(name = "ssh-backend", long)]
pub backend: Option<distant_ssh2::SshBackend>,
/// If specified, will use the external ssh program to launch the server
/// instead of the native integration; does nothing if the ssh2 feature is
/// not enabled as there is no other option than external ssh
#[clap(name = "ssh-external", long)]
pub external: bool,
/// Explicit identity file to use with ssh
#[clap(name = "ssh-identity-file", short = 'i', long)]
pub identity_file: Option<PathBuf>,
}

@ -1,6 +1,7 @@
use clap::Args;
use serde::{Deserialize, Serialize};
#[derive(Debug, Default, Serialize, Deserialize)]
#[derive(Args, Debug, Default, Serialize, Deserialize)]
pub struct ClientReplConfig {
/// Represents the maximum time (in seconds) to wait for a network request before timing out
pub timeout: Option<f32>,

Loading…
Cancel
Save