use super::config::ConnectionConfig;
use super::core::SshClient;
use super::result::CommandResult;
use crate::ssh::known_hosts::StrictHostKeyChecking;
use anyhow::{Context, Result};
use std::path::Path;
use std::time::Duration;
const DEFAULT_COMMAND_TIMEOUT_SECS: u64 = 300;
impl SshClient {
pub async fn connect_and_execute(
&mut self,
command: &str,
key_path: Option<&Path>,
use_agent: bool,
) -> Result<CommandResult> {
self.connect_and_execute_with_host_check(command, key_path, None, use_agent, false, None)
.await
}
pub async fn connect_and_execute_with_host_check(
&mut self,
command: &str,
key_path: Option<&Path>,
strict_mode: Option<StrictHostKeyChecking>,
use_agent: bool,
use_password: bool,
timeout_seconds: Option<u64>,
) -> Result<CommandResult> {
let config = ConnectionConfig {
key_path,
strict_mode,
use_agent,
use_password,
timeout_seconds,
jump_hosts_spec: None, };
self.connect_and_execute_with_jump_hosts(command, &config)
.await
}
pub async fn connect_and_execute_with_jump_hosts(
&mut self,
command: &str,
config: &ConnectionConfig<'_>,
) -> Result<CommandResult> {
tracing::debug!("Connecting to {}:{}", self.host, self.port);
let auth_method = self
.determine_auth_method(config.key_path, config.use_agent, config.use_password)
.await?;
let strict_mode = config
.strict_mode
.unwrap_or(StrictHostKeyChecking::AcceptNew);
let client = self
.establish_connection(
&auth_method,
strict_mode,
config.jump_hosts_spec,
config.key_path,
config.use_agent,
config.use_password,
)
.await?;
tracing::debug!("Connected and authenticated successfully");
tracing::debug!("Executing command: {}", command);
let result = self
.execute_with_timeout(&client, command, config.timeout_seconds)
.await?;
tracing::debug!(
"Command execution completed with status: {}",
result.exit_status
);
Ok(CommandResult {
host: self.host.clone(),
output: result.stdout.into_bytes(),
stderr: result.stderr.into_bytes(),
exit_status: result.exit_status,
})
}
async fn execute_with_timeout(
&self,
client: &crate::ssh::tokio_client::Client,
command: &str,
timeout_seconds: Option<u64>,
) -> Result<crate::ssh::tokio_client::CommandExecutedResult> {
if let Some(timeout_secs) = timeout_seconds {
if timeout_secs == 0 {
tracing::debug!("Executing command with no timeout (unlimited)");
client.execute(command)
.await
.with_context(|| format!("Failed to execute command '{}' on {}:{}. The SSH connection was successful but the command could not be executed.", command, self.host, self.port))
} else {
let command_timeout = Duration::from_secs(timeout_secs);
tracing::debug!("Executing command with timeout of {} seconds", timeout_secs);
tokio::time::timeout(
command_timeout,
client.execute(command)
)
.await
.with_context(|| format!("Command execution timeout: The command '{}' did not complete within {} seconds on {}:{}", command, timeout_secs, self.host, self.port))?
.with_context(|| format!("Failed to execute command '{}' on {}:{}. The SSH connection was successful but the command could not be executed.", command, self.host, self.port))
}
} else {
let command_timeout = Duration::from_secs(DEFAULT_COMMAND_TIMEOUT_SECS);
tracing::debug!("Executing command with default timeout of 300 seconds");
tokio::time::timeout(
command_timeout,
client.execute(command)
)
.await
.with_context(|| format!("Command execution timeout: The command '{}' did not complete within 5 minutes on {}:{}", command, self.host, self.port))?
.with_context(|| format!("Failed to execute command '{}' on {}:{}. The SSH connection was successful but the command could not be executed.", command, self.host, self.port))
}
}
}