-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathdebug.rs
More file actions
345 lines (304 loc) · 12.2 KB
/
debug.rs
File metadata and controls
345 lines (304 loc) · 12.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
//! Debug utilities for Docker container troubleshooting
use std::sync::Arc;
use crate::adapters::docker::DockerClient;
use crate::testing::network::PortUsageChecker;
// Import constants only for the convenience function
use super::constants::{SSH_SERVER_IMAGE_NAME, SSH_SERVER_IMAGE_TAG};
// ============================================================================
// PUBLIC API - Structured Debug Data
// ============================================================================
/// Debug information collected about Docker containers
///
/// This struct holds structured information about Docker state for troubleshooting
/// SSH server container issues. Each field contains either successfully collected
/// data or an error message explaining what went wrong.
#[derive(Debug)]
pub struct DockerDebugInfo {
/// Docker client for executing commands
docker: Arc<DockerClient>,
/// Output from `docker ps -a` listing all containers
pub all_containers: Result<String, String>,
/// Docker images matching the SSH server image name
pub ssh_images: Result<String, String>,
/// Information about containers using the SSH server image
pub ssh_containers: Result<Vec<ContainerInfo>, String>,
/// Port usage information for the SSH port
pub port_usage: Result<Vec<String>, String>,
/// Docker image name that was searched for
image_name: String,
/// Docker image tag that was searched for
image_tag: String,
}
/// Information about a specific Docker container
#[derive(Debug, Clone)]
pub struct ContainerInfo {
/// Container ID
pub id: String,
/// Full status line from docker ps
pub status: String,
/// Container logs (last 20 lines)
pub logs: Result<String, String>,
}
// ============================================================================
// PUBLIC API - Debug Info Collection
// ============================================================================
impl DockerDebugInfo {
/// Create a new `DockerDebugInfo` and collect all diagnostic information
///
/// This constructor runs various Docker commands to gather diagnostic information
/// when SSH connectivity tests fail. It collects container status, logs,
/// and port usage information.
///
/// # Arguments
///
/// * `docker` - Docker client for executing commands
/// * `container_port` - The host port that the SSH container is mapped to
/// * `image_name` - Image name to filter by (e.g., "torrust-ssh-server")
/// * `image_tag` - Image tag to filter by (e.g., "latest")
///
/// # Returns
///
/// A `DockerDebugInfo` struct containing all collected information. Each field
/// is a `Result` that either contains the successfully collected data or an
/// error message explaining what went wrong.
///
/// # Example
///
/// ```rust,no_run
/// use std::sync::Arc;
/// use torrust_tracker_deployer_lib::adapters::docker::DockerClient;
/// use torrust_tracker_deployer_lib::testing::integration::ssh_server::DockerDebugInfo;
///
/// let docker = Arc::new(DockerClient::new());
/// let debug_info = DockerDebugInfo::new(docker, 2222, "torrust-ssh-server", "latest");
/// debug_info.print();
/// ```
#[must_use]
pub fn new(
docker: Arc<DockerClient>,
container_port: u16,
image_name: &str,
image_tag: &str,
) -> Self {
let mut instance = Self {
docker,
all_containers: Ok(String::new()),
ssh_images: Ok(String::new()),
ssh_containers: Ok(Vec::new()),
port_usage: Ok(Vec::new()),
image_name: image_name.to_string(),
image_tag: image_tag.to_string(),
};
// Collect debug information using instance methods
instance.all_containers = instance.list_all_containers();
instance.ssh_images = instance.list_ssh_images(&instance.image_name.clone());
instance.ssh_containers = instance.find_ssh_containers();
instance.port_usage =
PortUsageChecker::check_port(container_port).map_err(|e| e.to_string());
instance
}
/// List all Docker containers
///
/// Uses `DockerClient::list_containers(true)` to list all containers (running and stopped).
fn list_all_containers(&self) -> Result<String, String> {
self.docker
.list_containers(true)
.map(|containers| containers.join("\n"))
.map_err(|e| format!("Failed to list containers: {e}"))
}
/// List SSH server Docker images
///
/// Uses `DockerClient::list_images` filtered by SSH server image name.
fn list_ssh_images(&self, image_name: &str) -> Result<String, String> {
self.docker
.list_images(Some(image_name))
.map(|images| images.join("\n"))
.map_err(|e| format!("Failed to list images: {e}"))
}
/// Find containers using the SSH server image
///
/// Uses `DockerClient::list_containers` and filters by image.
/// Also fetches logs for each matching container.
fn find_ssh_containers(&self) -> Result<Vec<ContainerInfo>, String> {
// TODO: Filter by image when DockerClient supports image info in list_containers
// For now, we list all containers
let all_containers = self
.docker
.list_containers(true)
.map_err(|e| format!("Failed to list containers: {e}"))?;
let mut containers = Vec::new();
// Filter containers by image and collect their info
for container_line in all_containers {
// Container format from DockerClient: "id|name|status"
if let Some(container_id) = container_line.split('|').next() {
// For now, we include all containers
// TODO: Filter by image when DockerClient supports image info
containers.push(ContainerInfo {
id: container_id.to_string(),
status: container_line.clone(),
logs: self.get_container_logs(container_id),
});
}
}
Ok(containers)
}
/// Get logs for a specific container
///
/// Uses `DockerClient::get_container_logs` to retrieve logs.
/// Note: `DockerClient` doesn't support --tail yet, so we get all logs.
fn get_container_logs(&self, container_id: &str) -> Result<String, String> {
self.docker
.get_container_logs(container_id)
.map_err(|e| format!("Failed to get container logs: {e}"))
}
/// Get a reference to the Docker client
///
/// This allows access to the underlying Docker client for additional operations
/// if needed after debug info has been collected.
#[must_use]
pub fn docker(&self) -> &Arc<DockerClient> {
&self.docker
}
/// Print the debug information in a formatted way
///
/// Prints all collected debug information to stdout in a human-readable format.
pub fn print(&self) {
println!("\n=== Docker Debug Information ===");
self.print_all_containers();
self.print_ssh_images();
self.print_ssh_containers_and_logs();
self.print_port_usage();
println!("=== End Docker Debug Information ===\n");
}
/// Print all Docker containers
fn print_all_containers(&self) {
match &self.all_containers {
Ok(containers) => {
println!("Docker containers (docker ps -a):");
println!("{containers}");
}
Err(e) => {
println!("Failed to list containers: {e}");
}
}
}
/// Print SSH server images
fn print_ssh_images(&self) {
match &self.ssh_images {
Ok(images) => {
println!("\nDocker images for {}:", self.image_name);
println!("{images}");
}
Err(e) => {
println!("Failed to list images: {e}");
}
}
}
/// Print SSH containers and their logs
fn print_ssh_containers_and_logs(&self) {
match &self.ssh_containers {
Ok(containers) => {
let image_tag = format!("{}:{}", self.image_name, self.image_tag);
println!("\nContainers using {image_tag}:");
if containers.is_empty() {
println!("No containers found");
} else {
for container in containers {
println!("Container {}: {}", container.id, container.status);
match &container.logs {
Ok(logs) => {
println!("\nContainer logs for {}:", container.id);
println!("{logs}");
}
Err(e) => {
println!("Failed to get logs for {}: {e}", container.id);
}
}
}
}
}
Err(e) => {
println!("Failed to filter containers: {e}");
}
}
}
/// Print port usage information
fn print_port_usage(&self) {
println!("\nPort information:");
match &self.port_usage {
Ok(lines) => {
for line in lines {
println!("{line}");
}
}
Err(e) => {
println!("Failed to check port usage: {e}");
}
}
}
}
// ============================================================================
// PUBLIC API - Convenience Function
// ============================================================================
/// Debug helper function to collect and print Docker container information
///
/// This is a convenience function that collects all Docker debug information
/// and prints it to stdout. For programmatic access to the structured data,
/// use [`DockerDebugInfo::new`] instead.
///
/// This function runs various Docker commands to help diagnose issues when SSH
/// connectivity tests fail in CI environments. It prints container status, logs,
/// and other useful debugging information.
///
/// # Arguments
///
/// * `container_port` - The host port that the SSH container is mapped to
///
/// # Usage
///
/// This function is typically called when SSH connectivity tests fail to help
/// diagnose what's happening with the Docker containers in CI environments.
///
/// ```rust
/// use torrust_tracker_deployer_lib::testing::integration::ssh_server::print_docker_debug_info;
///
/// // In a test when SSH connectivity fails:
/// print_docker_debug_info(2222);
/// ```
pub fn print_docker_debug_info(container_port: u16) {
let docker = Arc::new(DockerClient::new());
let debug_info = DockerDebugInfo::new(
docker,
container_port,
SSH_SERVER_IMAGE_NAME,
SSH_SERVER_IMAGE_TAG,
);
debug_info.print();
}
// ============================================================================
// TESTS
// ============================================================================
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_should_collect_docker_debug_info() {
// This test verifies that the new method doesn't panic
// Actual Docker commands may fail in test environment, which is expected
let docker = Arc::new(DockerClient::new());
let debug_info = DockerDebugInfo::new(docker, 2222, "test-image", "latest");
// Verify structure exists (even if commands failed)
assert!(debug_info.all_containers.is_ok() || debug_info.all_containers.is_err());
assert!(debug_info.ssh_images.is_ok() || debug_info.ssh_images.is_err());
assert!(debug_info.ssh_containers.is_ok() || debug_info.ssh_containers.is_err());
assert!(debug_info.port_usage.is_ok() || debug_info.port_usage.is_err());
}
#[test]
fn it_should_print_without_panicking() {
// This test verifies that printing doesn't panic
let docker = Arc::new(DockerClient::new());
let debug_info = DockerDebugInfo::new(docker, 2222, "test-image", "latest");
debug_info.print();
// If we get here without panicking, test passes
}
}