Skip to main content

torrust_tracker_deployer_lib/adapters/ssh/
config.rs

1//! SSH configuration and management
2//!
3//! This module provides the `SshConfig` struct which encapsulates all the information
4//! needed to establish an SSH connection to a remote host, including credentials and
5//! target host socket address.
6//!
7//! ## Key Components
8//!
9//! - Connection configuration combining credentials and target host socket address
10//! - Socket address management (IP address and port) for remote instances
11//! - Integration with SSH credentials for authentication
12//!
13//! The connection configuration is used by SSH clients to establish secure
14//! connections for remote command execution.
15
16use std::net::{IpAddr, SocketAddr};
17use std::path::PathBuf;
18
19use super::SshCredentials;
20
21/// Default SSH port number.
22pub const DEFAULT_SSH_PORT: u16 = 22;
23
24/// Default SSH connection timeout in seconds
25pub const DEFAULT_CONNECT_TIMEOUT_SECS: u32 = 5;
26
27/// Default maximum number of connection retry attempts.
28/// Set to 60 to allow up to 300 seconds total wait time (60 attempts × 5 second interval).
29/// Cloud-init provisioning (user creation, SSH key injection) can take over 3 minutes on some
30/// providers and small machines, so a 5-minute budget is used as the default.
31pub const DEFAULT_MAX_RETRY_ATTEMPTS: u32 = 60;
32
33/// Default retry interval in seconds
34pub const DEFAULT_RETRY_INTERVAL_SECS: u32 = 5;
35
36/// Default retry log frequency (log every N attempts)
37pub const DEFAULT_RETRY_LOG_FREQUENCY: u32 = 5;
38
39/// SSH connection behavior configuration
40///
41/// Groups all connection-related parameters (timeouts, retries, logging)
42/// separately from authentication credentials and target address.
43///
44/// This type encapsulates connection behavior settings that affect how
45/// SSH connections are established and retried, distinct from authentication
46/// credentials and the target host address.
47///
48/// # Examples
49///
50/// ```rust
51/// use torrust_tracker_deployer_lib::adapters::ssh::SshConnectionConfig;
52///
53/// // Use default configuration
54/// let config = SshConnectionConfig::default();
55///
56/// // Create custom configuration for fast testing
57/// let fast_config = SshConnectionConfig::new(1, 5, 1, 2);
58///
59/// // Create custom configuration for slow networks
60/// let slow_config = SshConnectionConfig::new(30, 20, 6, 3);
61/// ```
62#[derive(Clone, Debug)]
63pub struct SshConnectionConfig {
64    /// SSH connection timeout in seconds
65    pub connect_timeout_secs: u32,
66    /// Maximum number of connection retry attempts
67    pub max_retry_attempts: u32,
68    /// Seconds to wait between retry attempts
69    pub retry_interval_secs: u32,
70    /// Log progress every N retry attempts
71    pub retry_log_frequency: u32,
72}
73
74impl SshConnectionConfig {
75    /// Create a new connection configuration with custom values
76    ///
77    /// # Arguments
78    ///
79    /// * `connect_timeout_secs` - SSH connection timeout in seconds
80    /// * `max_retry_attempts` - Maximum number of connection retry attempts
81    /// * `retry_interval_secs` - Seconds to wait between retry attempts
82    /// * `retry_log_frequency` - Log progress every N retry attempts
83    ///
84    /// # Examples
85    ///
86    /// ```rust
87    /// use torrust_tracker_deployer_lib::adapters::ssh::SshConnectionConfig;
88    ///
89    /// // Fast configuration for testing
90    /// let fast = SshConnectionConfig::new(1, 5, 1, 2);
91    ///
92    /// // Slow configuration for unreliable networks
93    /// let slow = SshConnectionConfig::new(30, 20, 6, 3);
94    /// ```
95    #[must_use]
96    pub fn new(
97        connect_timeout_secs: u32,
98        max_retry_attempts: u32,
99        retry_interval_secs: u32,
100        retry_log_frequency: u32,
101    ) -> Self {
102        Self {
103            connect_timeout_secs,
104            max_retry_attempts,
105            retry_interval_secs,
106            retry_log_frequency,
107        }
108    }
109
110    /// Calculate total wait time in seconds (`max_retry_attempts` × `retry_interval_secs`)
111    ///
112    /// Returns the maximum time that will be spent waiting for SSH connectivity
113    /// if all retry attempts are exhausted.
114    ///
115    /// # Examples
116    ///
117    /// ```rust
118    /// use torrust_tracker_deployer_lib::adapters::ssh::SshConnectionConfig;
119    ///
120    /// let config = SshConnectionConfig::default();
121    /// assert_eq!(config.total_timeout_secs(), 300); // 60 attempts × 5 seconds
122    /// ```
123    #[must_use]
124    pub fn total_timeout_secs(&self) -> u32 {
125        self.max_retry_attempts * self.retry_interval_secs
126    }
127}
128
129impl Default for SshConnectionConfig {
130    /// Default connection configuration (production settings)
131    ///
132    /// Uses constants defined at module level:
133    /// - Connection timeout: `DEFAULT_CONNECT_TIMEOUT_SECS` (5 seconds)
134    /// - Max retry attempts: `DEFAULT_MAX_RETRY_ATTEMPTS` (60)
135    /// - Retry interval: `DEFAULT_RETRY_INTERVAL_SECS` (5 seconds)
136    /// - Retry log frequency: `DEFAULT_RETRY_LOG_FREQUENCY` (every 5 attempts)
137    /// - Total wait time: 60 × 5 = 300 seconds
138    fn default() -> Self {
139        Self {
140            connect_timeout_secs: DEFAULT_CONNECT_TIMEOUT_SECS,
141            max_retry_attempts: DEFAULT_MAX_RETRY_ATTEMPTS,
142            retry_interval_secs: DEFAULT_RETRY_INTERVAL_SECS,
143            retry_log_frequency: DEFAULT_RETRY_LOG_FREQUENCY,
144        }
145    }
146}
147
148/// SSH connection configuration for a specific remote instance.
149///
150/// Contains both the SSH credentials and the target host socket address,
151/// representing everything needed to establish an SSH connection.
152#[derive(Clone)]
153pub struct SshConfig {
154    /// SSH authentication credentials.
155    pub credentials: SshCredentials,
156
157    /// Socket address (IP address and port) of the target host for SSH connections.
158    ///
159    /// This contains both the IP address and port number of the remote instance
160    /// that the SSH client will connect to.
161    pub socket_addr: SocketAddr,
162
163    /// SSH connection behavior configuration (timeouts, retries, logging).
164    pub connection_config: SshConnectionConfig,
165}
166
167impl SshConfig {
168    /// Creates a new SSH connection configuration with default connection settings.
169    ///
170    /// Uses `SshConnectionConfig::default()` for connection behavior parameters.
171    ///
172    /// ```rust
173    /// # use std::net::{IpAddr, Ipv4Addr, SocketAddr};
174    /// # use std::path::PathBuf;
175    /// # use torrust_tracker_deployer_lib::shared::Username;
176    /// use torrust_tracker_deployer_lib::adapters::ssh::{SshCredentials, SshConfig};
177    /// let credentials = SshCredentials::new(
178    ///     PathBuf::from("/home/user/.ssh/deploy_key"),
179    ///     PathBuf::from("/home/user/.ssh/deploy_key.pub"),
180    ///     Username::new("ubuntu").unwrap(),
181    /// );
182    /// let socket_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 100)), 22);
183    /// let config = SshConfig::new(
184    ///     credentials,
185    ///     socket_addr,
186    /// );
187    /// ```
188    #[must_use]
189    pub fn new(credentials: SshCredentials, ssh_socket_addr: SocketAddr) -> Self {
190        Self {
191            credentials,
192            socket_addr: ssh_socket_addr,
193            connection_config: SshConnectionConfig::default(),
194        }
195    }
196
197    /// Creates a new SSH connection configuration with custom connection settings.
198    ///
199    /// Use this constructor when you need to customize connection behavior
200    /// (timeouts, retries, logging) for specific scenarios.
201    ///
202    /// ```rust
203    /// # use std::net::{IpAddr, Ipv4Addr, SocketAddr};
204    /// # use std::path::PathBuf;
205    /// # use torrust_tracker_deployer_lib::shared::Username;
206    /// use torrust_tracker_deployer_lib::adapters::ssh::{SshCredentials, SshConfig, SshConnectionConfig};
207    /// let credentials = SshCredentials::new(
208    ///     PathBuf::from("/home/user/.ssh/deploy_key"),
209    ///     PathBuf::from("/home/user/.ssh/deploy_key.pub"),
210    ///     Username::new("ubuntu").unwrap(),
211    /// );
212    /// let socket_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 100)), 22);
213    ///
214    /// // Custom configuration for fast testing
215    /// let connection_config = SshConnectionConfig::new(1, 5, 1, 2);
216    /// let config = SshConfig::with_connection_config(
217    ///     credentials,
218    ///     socket_addr,
219    ///     connection_config,
220    /// );
221    /// ```
222    #[must_use]
223    pub fn with_connection_config(
224        credentials: SshCredentials,
225        ssh_socket_addr: SocketAddr,
226        connection_config: SshConnectionConfig,
227    ) -> Self {
228        Self {
229            credentials,
230            socket_addr: ssh_socket_addr,
231            connection_config,
232        }
233    }
234
235    /// Creates a new SSH connection configuration with the default port (22).
236    ///
237    /// This is a convenience method for when you want to use the standard SSH port.
238    ///
239    /// ```rust
240    /// # use std::net::{IpAddr, Ipv4Addr, SocketAddr};
241    /// # use std::path::PathBuf;
242    /// # use torrust_tracker_deployer_lib::shared::Username;
243    /// use torrust_tracker_deployer_lib::adapters::ssh::{SshCredentials, SshConfig};
244    /// let credentials = SshCredentials::new(
245    ///     PathBuf::from("/home/user/.ssh/deploy_key"),
246    ///     PathBuf::from("/home/user/.ssh/deploy_key.pub"),
247    ///     Username::new("ubuntu").unwrap(),
248    /// );
249    /// let config = SshConfig::with_default_port(
250    ///     credentials,
251    ///     IpAddr::V4(Ipv4Addr::new(192, 168, 1, 100)),
252    /// );
253    /// ```
254    #[must_use]
255    pub fn with_default_port(credentials: SshCredentials, host_ip: IpAddr) -> Self {
256        let socket_addr = SocketAddr::new(host_ip, DEFAULT_SSH_PORT);
257        Self::new(credentials, socket_addr)
258    }
259
260    /// Access the SSH private key path.
261    #[must_use]
262    pub fn ssh_priv_key_path(&self) -> &PathBuf {
263        &self.credentials.ssh_priv_key_path
264    }
265
266    /// Access the SSH public key path.
267    #[must_use]
268    pub fn ssh_pub_key_path(&self) -> &PathBuf {
269        &self.credentials.ssh_pub_key_path
270    }
271
272    /// Access the SSH username.
273    #[must_use]
274    pub fn ssh_username(&self) -> &str {
275        self.credentials.ssh_username.as_str()
276    }
277
278    /// Access the SSH port.
279    #[must_use]
280    pub fn ssh_port(&self) -> u16 {
281        self.socket_addr.port()
282    }
283
284    /// Access the host IP address.
285    #[must_use]
286    pub fn host_ip(&self) -> IpAddr {
287        self.socket_addr.ip()
288    }
289
290    /// Access the socket address.
291    #[must_use]
292    pub fn socket_addr(&self) -> SocketAddr {
293        self.socket_addr
294    }
295
296    /// Access the connection timeout in seconds.
297    #[must_use]
298    pub fn connection_timeout_secs(&self) -> u32 {
299        self.connection_config.connect_timeout_secs
300    }
301}