-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy patherrors.rs
More file actions
461 lines (399 loc) · 16.6 KB
/
errors.rs
File metadata and controls
461 lines (399 loc) · 16.6 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
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
//! Error types for the Run Subcommand
//!
//! This module defines error types that can occur during CLI run command execution.
//! All errors follow the project's error handling principles by providing clear,
//! contextual, and actionable error messages with `.help()` methods.
use thiserror::Error;
use crate::application::command_handlers::run::RunCommandHandlerError;
use crate::domain::environment::name::EnvironmentNameError;
use crate::domain::environment::repository::RepositoryError;
use crate::presentation::cli::views::progress::ProgressReporterError;
use crate::presentation::cli::views::ViewRenderError;
/// Run command specific errors
///
/// This enum contains all error variants specific to the run command,
/// including environment validation, state validation, and service start failures.
/// Each variant includes relevant context and actionable error messages.
#[derive(Debug, Error)]
pub enum RunSubcommandError {
// ===== Environment Validation Errors =====
/// Environment name validation failed
///
/// The provided environment name doesn't meet the validation requirements.
/// Use `.help()` for detailed troubleshooting steps.
#[error("Invalid environment name '{name}': {source}
Tip: Environment names must be 1-63 characters, start with letter/digit, contain only letters/digits/hyphens")]
InvalidEnvironmentName {
name: String,
#[source]
source: EnvironmentNameError,
},
/// Environment not found or inaccessible
///
/// The environment couldn't be loaded from persistent storage.
/// Use `.help()` for detailed troubleshooting steps.
#[error(
"Environment '{name}' not found or inaccessible from data directory '{data_dir}'
Tip: Check if environment exists: ls -la {data_dir}/"
)]
EnvironmentNotAccessible { name: String, data_dir: String },
// ===== State Validation Errors =====
/// Environment is not in the required state for run
///
/// The run command requires the environment to be in the Released state.
/// Use `.help()` for detailed troubleshooting steps.
#[error(
"Environment '{name}' is not in the required state for run (current: {current_state}, required: Released)
Tip: Run 'release' command first to deploy the application"
)]
InvalidEnvironmentState { name: String, current_state: String },
// ===== Run Operation Errors =====
/// Run operation failed
///
/// The run process encountered an error during execution.
/// Use `.help()` for detailed troubleshooting steps.
#[error(
"Failed to run application stack in environment '{name}': {reason}
Tip: Check logs and try running with --log-output file-and-stderr for more details"
)]
RunOperationFailed { name: String, reason: String },
/// Service start failed
///
/// One or more services failed to start properly.
/// Use `.help()` for detailed troubleshooting steps.
#[error(
"Failed to start services in environment '{name}': {reason}
Tip: SSH into the VM and check Docker container logs: docker compose logs"
)]
ServiceStartFailed { name: String, reason: String },
// ===== Internal Errors =====
/// Progress reporting failed
///
/// Failed to report progress to the user due to an internal error.
/// This indicates a critical internal error.
#[error(
"Failed to report progress: {source}
Tip: This is a critical bug - please report it with full logs using --log-output file-and-stderr"
)]
ProgressReportingFailed {
#[source]
source: ProgressReporterError,
},
/// Output formatting failed (JSON serialization error).
/// This indicates an internal error in data serialization.
#[error(
"Failed to format output: {reason}\nTip: This is a critical bug - please report it with full logs using --log-output file-and-stderr"
)]
OutputFormatting { reason: String },
}
// ============================================================================
// ERROR CONVERSIONS
// ============================================================================
impl From<ProgressReporterError> for RunSubcommandError {
fn from(source: ProgressReporterError) -> Self {
Self::ProgressReportingFailed { source }
}
}
impl From<ViewRenderError> for RunSubcommandError {
fn from(e: ViewRenderError) -> Self {
Self::OutputFormatting {
reason: e.to_string(),
}
}
}
impl From<RepositoryError> for RunSubcommandError {
fn from(error: RepositoryError) -> Self {
match error {
RepositoryError::NotFound => Self::EnvironmentNotAccessible {
name: "environment".to_string(),
data_dir: "data".to_string(),
},
RepositoryError::Conflict => Self::RunOperationFailed {
name: "environment".to_string(),
reason: "Another process is accessing this environment".to_string(),
},
RepositoryError::Internal(err) => Self::RunOperationFailed {
name: "environment".to_string(),
reason: format!("Repository error: {err}"),
},
}
}
}
impl From<RunCommandHandlerError> for RunSubcommandError {
fn from(error: RunCommandHandlerError) -> Self {
match error {
RunCommandHandlerError::EnvironmentNotFound { name } => {
Self::EnvironmentNotAccessible {
name,
data_dir: "data".to_string(),
}
}
RunCommandHandlerError::InvalidState(state_err) => Self::InvalidEnvironmentState {
name: "environment".to_string(),
current_state: state_err.to_string(),
},
RunCommandHandlerError::MissingInstanceIp { name } => Self::RunOperationFailed {
name,
reason: "Instance IP not available - environment may not be fully provisioned"
.to_string(),
},
RunCommandHandlerError::StartServicesFailed { message, .. } => {
Self::ServiceStartFailed {
name: "environment".to_string(),
reason: message,
}
}
RunCommandHandlerError::StatePersistence(err) => Self::RunOperationFailed {
name: "environment".to_string(),
reason: format!("Failed to persist state: {err}"),
},
RunCommandHandlerError::RunOperationFailed { name, message } => {
Self::RunOperationFailed {
name,
reason: message,
}
}
}
}
}
impl RunSubcommandError {
/// Get detailed troubleshooting guidance for this error
///
/// This method provides comprehensive troubleshooting steps that can be
/// displayed to users when they need more help resolving the error.
#[must_use]
#[allow(clippy::too_many_lines)] // Help text is comprehensive for user guidance
pub fn help(&self) -> &'static str {
match self {
Self::InvalidEnvironmentName { .. } => {
"Invalid Environment Name - Detailed Troubleshooting:
1. Check environment name format:
- Length: Must be 1-63 characters
- Start: Must begin with letter (a-z, A-Z) or digit (0-9)
- Characters: Only letters, digits, and hyphens allowed
- End: Must not end with a hyphen
2. Common valid examples:
- 'production'
- 'test-env'
- 'e2e-provision'
- 'dev123'
3. Common invalid examples:
- 'test_env' (underscores not allowed)
- '-test' (starts with hyphen)
- 'test-' (ends with hyphen)
- '' (empty string)
For more information, see the environment naming conventions in the documentation."
}
Self::EnvironmentNotAccessible { .. } => {
"Environment Not Accessible - Detailed Troubleshooting:
1. Check if environment exists:
- List environments: ls -la data/
- Look for directory with your environment name
2. Verify file permissions:
- Check directory permissions: ls -ld data/
- Ensure read/write access: chmod 755 data/
3. Check if environment was created:
- Look for environment.json file: ls -la data/{env_name}/
- Verify it's a valid deployment environment
4. Common causes:
- Environment was never created (run create first)
- Wrong data directory path
- Permission issues
- Corrupted environment state
If the environment should exist, check the logs for more details."
}
Self::InvalidEnvironmentState { .. } => {
"Invalid Environment State - Detailed Troubleshooting:
1. Check current environment state:
- The run command requires the environment to be 'Released'
- Run the workflow in order: create → provision → configure → release → run
2. Required workflow:
- First: torrust-tracker-deployer create environment -f env.json
- Then: torrust-tracker-deployer provision <env-name>
- Then: torrust-tracker-deployer configure <env-name>
- Then: torrust-tracker-deployer release <env-name>
- Finally: torrust-tracker-deployer run <env-name>
3. Check environment state:
- Look at data/<env-name>/environment.json
- Verify the 'state' field shows 'Released'
4. Common issues:
- Skipped the 'release' step
- Previous release command failed
- Environment was reset or recreated
Run 'release' command first, then retry 'run'."
}
Self::RunOperationFailed { .. } => {
"Run Operation Failed - Detailed Troubleshooting:
1. Check system resources:
- Ensure sufficient disk space on target VM
- Check network connectivity to the VM
- Verify SSH access is working
2. Review the operation logs:
- Run with verbose logging: --log-output file-and-stderr
- Check log files in data/logs/
- Look for specific error details
3. Check Docker status on VM:
- SSH into the VM
- Run: docker compose ps
- Check container health: docker ps -a
4. Common issues:
- Docker daemon not running
- Port conflicts with existing services
- Missing Docker images
- Resource constraints (memory, CPU)
5. Recovery options:
- Retry the run operation
- Run 'release' again to reset deployment
- Manually stop conflicting services on VM
For persistent issues, check the deployment documentation."
}
Self::ServiceStartFailed { .. } => {
"Service Start Failed - Detailed Troubleshooting:
1. Check service logs:
- SSH into the VM
- Run: docker compose logs -f
- Look for startup errors
2. Verify service configuration:
- Check docker-compose.yml syntax
- Verify environment variables are set
- Ensure required secrets are available
3. Check resource availability:
- Sufficient memory for all services
- Available ports (not already in use)
- Required volumes and mounts exist
4. Common service issues:
- Database connection failures
- Missing configuration files
- Network connectivity between containers
- Permission issues on mounted volumes
5. Manual debugging:
- SSH into the VM
- Run: docker compose up (without -d) to see logs
- Check individual container logs
For persistent issues, check individual service documentation."
}
Self::ProgressReportingFailed { .. } => {
"Progress Reporting Failed - Critical Internal Error:
This is a critical bug that indicates progress reporting to the user failed.
This should never happen in normal operation.
1. Immediate actions:
- Save all relevant logs and error messages
- Note what operation was being performed
- Record the environment state
2. Report the issue:
- This is a bug that needs to be reported
- Include full logs: --log-output file-and-stderr
- Provide steps to reproduce if possible
- Include system information (OS, versions)
3. Workaround:
- Restart the application
- Try the operation again
- Check for resource exhaustion (memory, threads)
This error indicates a serious bug in the application's progress reporting system.
Please report it to the development team with full details."
}
Self::OutputFormatting { .. } => {
"Output Formatting Failed - Critical Internal Error:\n\nThis error should not occur during normal operation. It indicates a bug in the output formatting system.\n\n1. Immediate actions:\n - Save full error output\n - Copy log files from data/logs/\n - Note the exact command and output format being used\n\n2. Report the issue:\n - Create GitHub issue with full details\n - Include: command, output format (--output-format), error output, logs\n - Describe steps to reproduce\n\n3. Temporary workarounds:\n - Try using different output format (text vs json)\n - Try running command again\n\nPlease report it so we can fix it."
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_should_provide_help_for_invalid_environment_name() {
let error = RunSubcommandError::InvalidEnvironmentName {
name: "invalid_name".to_string(),
source: EnvironmentNameError::InvalidFormat {
attempted_name: "invalid_name".to_string(),
reason: "contains underscore".to_string(),
valid_examples: vec!["dev".to_string(), "staging".to_string()],
},
};
let help = error.help();
assert!(help.contains("Invalid Environment Name"));
assert!(help.contains("Check environment name format"));
}
#[test]
fn it_should_provide_help_for_environment_not_accessible() {
let error = RunSubcommandError::EnvironmentNotAccessible {
name: "test-env".to_string(),
data_dir: "/tmp/data".to_string(),
};
let help = error.help();
assert!(help.contains("Environment Not Accessible"));
assert!(help.contains("Check if environment exists"));
}
#[test]
fn it_should_provide_help_for_invalid_environment_state() {
let error = RunSubcommandError::InvalidEnvironmentState {
name: "test-env".to_string(),
current_state: "Configured".to_string(),
};
let help = error.help();
assert!(help.contains("Invalid Environment State"));
assert!(help.contains("release"));
}
#[test]
fn it_should_provide_help_for_service_start_failed() {
let error = RunSubcommandError::ServiceStartFailed {
name: "test-env".to_string(),
reason: "container exited".to_string(),
};
let help = error.help();
assert!(help.contains("Service Start Failed"));
assert!(help.contains("docker compose logs"));
}
#[test]
fn it_should_have_help_for_all_error_variants() {
let errors: Vec<RunSubcommandError> = vec![
RunSubcommandError::InvalidEnvironmentName {
name: "test".to_string(),
source: EnvironmentNameError::InvalidFormat {
attempted_name: "test".to_string(),
reason: "invalid".to_string(),
valid_examples: vec!["dev".to_string()],
},
},
RunSubcommandError::EnvironmentNotAccessible {
name: "test".to_string(),
data_dir: "/tmp".to_string(),
},
RunSubcommandError::InvalidEnvironmentState {
name: "test".to_string(),
current_state: "Created".to_string(),
},
RunSubcommandError::RunOperationFailed {
name: "test".to_string(),
reason: "connection failed".to_string(),
},
RunSubcommandError::ServiceStartFailed {
name: "test".to_string(),
reason: "timeout".to_string(),
},
];
for error in errors {
let help = error.help();
assert!(!help.is_empty(), "Help text should not be empty");
assert!(
help.contains("Troubleshooting") || help.len() > 50,
"Help should contain actionable guidance"
);
}
}
#[test]
fn it_should_display_error_with_context() {
let error = RunSubcommandError::InvalidEnvironmentName {
name: "invalid_env".to_string(),
source: EnvironmentNameError::InvalidFormat {
attempted_name: "invalid_env".to_string(),
reason: "contains underscore".to_string(),
valid_examples: vec!["dev".to_string()],
},
};
let message = error.to_string();
assert!(message.contains("invalid_env"));
assert!(message.contains("Invalid environment name"));
}
}