use crate::ui::Colorize;
use anyhow::Result;
use std::sync::Arc;
use crate::commands::error_format::format_connection_error;
use crate::diagnosticln as eprintln;
use crate::pty::PtyManager;
use super::super::interactive_signal::{
TerminalGuard, reset_interrupt, setup_async_signal_handlers, setup_signal_handlers,
};
use super::types::{InteractiveCommand, InteractiveResult};
impl InteractiveCommand {
pub async fn execute(self) -> Result<InteractiveResult> {
let use_raw_session = self.should_use_raw_session()?;
if use_raw_session {
self.execute_with_pty().await
} else {
self.execute_traditional().await
}
}
pub(super) async fn execute_with_pty(mut self) -> Result<InteractiveResult> {
let start_time = std::time::Instant::now();
let ssh_compatible = self.session_policy.is_some();
if !ssh_compatible {
println!("Starting interactive SSH byte-stream session...");
}
let nodes_to_connect = self.select_nodes_to_connect()?;
let mut channels = Vec::new();
let mut clients = Vec::new();
let mut agent_forwarding_leases = Vec::new();
let mut connected_nodes = Vec::new();
for node in nodes_to_connect {
match self.connect_to_node_pty(node.clone()).await {
Ok((client, channel, agent_forwarding_lease)) => {
if !ssh_compatible {
println!("✓ Connected to {}", node.to_string().green());
}
channels.push(channel);
clients.push(client);
agent_forwarding_leases.extend(agent_forwarding_lease);
connected_nodes.push(node);
}
Err(e) => {
eprintln!(
"✗ Failed to connect to {}: {}",
node.to_string().red_stderr(),
format_connection_error(&e)
);
}
}
}
if channels.is_empty() {
anyhow::bail!("Failed to connect to any nodes");
}
let nodes_connected = channels.len();
let mut session_config = self.pty_config.clone();
if let Some(policy) = self.session_policy.take() {
session_config.environment = policy.environment;
}
let requested_remote_pty = !session_config.disable_pty;
let mut pty_manager = PtyManager::new();
let session_result: Result<()> = async {
if self.single_node && channels.len() == 1 {
let session_id = pty_manager
.create_single_session(
channels.into_iter().next().unwrap(),
session_config.clone(),
)
.await?;
pty_manager.run_single_session(session_id).await?;
} else {
let session_ids = pty_manager
.create_multiplex_sessions(channels, session_config)
.await?;
pty_manager.run_multiplex_sessions(session_ids).await?;
}
Ok(())
}
.await;
drop(agent_forwarding_leases);
let disconnect_result = Self::disconnect_clients(&clients).await;
if requested_remote_pty {
crate::pty::terminal::force_terminal_cleanup();
let _ = std::io::Write::flush(&mut std::io::stdout());
}
match (session_result, disconnect_result) {
(Err(session_error), Err(disconnect_error)) => {
tracing::warn!(
"Interactive session failed and SSH teardown also failed: {disconnect_error:#}"
);
return Err(session_error);
}
(Err(session_error), Ok(())) => return Err(session_error),
(Ok(()), Err(disconnect_error)) => return Err(disconnect_error),
(Ok(()), Ok(())) => {}
}
Ok(InteractiveResult {
duration: start_time.elapsed(),
commands_executed: 0, nodes_connected,
})
}
async fn disconnect_clients(clients: &[crate::ssh::tokio_client::Client]) -> Result<()> {
let mut first_error = None;
for client in clients {
if let Err(error) = client.disconnect().await {
let error = anyhow::Error::new(error).context("Failed to disconnect SSH session");
if first_error.is_none() {
first_error = Some(error);
} else {
tracing::warn!("Additional SSH teardown failure: {error:#}");
}
}
}
first_error.map_or(Ok(()), Err)
}
pub(super) async fn execute_traditional(self) -> Result<InteractiveResult> {
let start_time = std::time::Instant::now();
let _terminal_guard = TerminalGuard::new();
let shutdown = setup_signal_handlers()?;
setup_async_signal_handlers(Arc::clone(&shutdown)).await;
reset_interrupt();
let nodes_to_connect = self.select_nodes_to_connect()?;
println!("Connecting to {} node(s)...", nodes_to_connect.len());
let mut sessions = Vec::new();
for node in nodes_to_connect {
match self.connect_to_node(node.clone()).await {
Ok(session) => {
println!("✓ Connected to {}", session.node.to_string().green());
sessions.push(session);
}
Err(e) => {
eprintln!(
"✗ Failed to connect to {}: {}",
node.to_string().red_stderr(),
format_connection_error(&e)
);
}
}
}
if sessions.is_empty() {
anyhow::bail!("Failed to connect to any nodes");
}
let nodes_connected = sessions.len();
let commands_executed = if self.single_node {
self.run_single_node_mode(sessions.into_iter().next().unwrap())
.await?
} else {
self.run_multiplex_mode(sessions).await?
};
Ok(InteractiveResult {
duration: start_time.elapsed(),
commands_executed,
nodes_connected,
})
}
}