Skip to content

Commit d83aa35

Browse files
authored
Merge pull request #1133 from daniel6yi8-gif/fix/1107-normalize-cli-exit-codes
Normalize error handling and exit codes across CLI commands
2 parents d79ac75 + 62588fd commit d83aa35

3 files changed

Lines changed: 29 additions & 9 deletions

File tree

docs/cli-commands-reference.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,9 @@ Available for all commands:
347347

348348
## Exit Codes
349349

350+
Every subcommand returns its error through `Error::exit_code()` (`src/error.rs`),
351+
so the code below is consistent across all commands rather than per-command.
352+
350353
| Code | Meaning |
351354
|------|---------|
352355
| 0 | Success |

src/error.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,24 @@ impl Error {
150150
pub fn status_message(&self) -> String {
151151
self.to_string()
152152
}
153+
154+
/// Map this error to the process exit code documented in
155+
/// `docs/cli-commands-reference.md#exit-codes`.
156+
///
157+
/// Rust's default `Termination` impl for `Result<(), Error>` always exits
158+
/// with code 1 on `Err`, regardless of the error variant, so every CLI
159+
/// command was silently ignoring the documented 2/3/4 exit codes unless it
160+
/// special-cased `std::process::exit` itself. Routing every command's
161+
/// error through this method instead keeps the exit code consistent with
162+
/// the error category everywhere.
163+
pub fn exit_code(&self) -> i32 {
164+
match self {
165+
Error::ValidationError(_) | Error::InvalidNodeType(_) | Error::MissingRequiredField { .. } => 2,
166+
Error::KubeError(_) | Error::KubeconfigError(_) | Error::FinalizerError(_) | Error::NotFound { .. } => 3,
167+
Error::ConfigError(_) | Error::MaintenanceError(_) | Error::CertificateError(_) => 4,
168+
_ => 1,
169+
}
170+
}
153171
}
154172

155173
// Implement From for kube::runtime::finalizer::Error to enable ? operator

src/main.rs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,21 @@ use crate::commands::runbook::run_generate_runbook;
1414
use crate::commands::simulator::run_simulator;
1515
use crate::commands::webhook::run_webhook;
1616
use clap::Parser;
17-
use std::process;
1817

1918
use stellar_k8s::controller::archive_prune::prune_archive;
2019
use stellar_k8s::controller::diff::diff;
2120
use stellar_k8s::version_check;
2221
use stellar_k8s::{incident, Error};
2322

2423
#[tokio::main]
25-
async fn main() -> Result<(), Error> {
26-
// rustls 0.23 requires an explicit crypto provider when aws-lc-rs/ring are
27-
// not auto-selected via default features (common with kube/reqwest stacks).
28-
rustls::crypto::ring::default_provider()
29-
.install_default()
30-
.expect("failed to install rustls ring CryptoProvider");
24+
async fn main() {
25+
if let Err(e) = run().await {
26+
eprintln!("Error: {e}");
27+
std::process::exit(e.exit_code());
28+
}
29+
}
3130

31+
async fn run() -> Result<(), Error> {
3232
let args = Args::parse();
3333

3434
let offline = args.offline;
@@ -111,8 +111,7 @@ async fn main() -> Result<(), Error> {
111111
}
112112
Commands::Run(run_args) => {
113113
if let Err(e) = run_args.validate() {
114-
eprintln!("error: {e}");
115-
process::exit(2);
114+
return Err(Error::validation_step("run args", e));
116115
}
117116
return run_operator(run_args).await;
118117
}

0 commit comments

Comments
 (0)