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}