Skip to main content

cellos_ctl/cmd/
version.rs

1//! `cellctl version` — print client version, then attempt the server version.
2//!
3//! The client version is printed unconditionally on its own line so an operator
4//! always sees what they have locally even when the server is unreachable.
5//! After that, we attempt the server query and:
6//!
7//! - on success, print `server: cellos-server <ver> (<url>)` and exit 0;
8//! - on transport OR HTTP error, print `error: unable to connect to the server: <reason>`
9//!   to stderr and return a [`CtlError::api`] so the process exits non-zero.
10//!
11//! This matches `kubectl version`, which also exits non-zero when it cannot
12//! reach the API server. See SMOKE-TEST report Finding #2.
13
14use serde_json::Value;
15
16use crate::client::CellosClient;
17use crate::exit::{CtlError, CtlResult};
18
19pub async fn run(client: &CellosClient) -> CtlResult<()> {
20    println!("client: cellctl {}", env!("CARGO_PKG_VERSION"));
21
22    match client.get_json::<Value>("/v1/version").await {
23        Ok(v) => {
24            let svr = v
25                .get("version")
26                .and_then(|x| x.as_str())
27                .unwrap_or("unknown");
28            println!("server: cellos-server {svr} ({})", client.base_url());
29            Ok(())
30        }
31        Err(e) => {
32            // Propagate as an API error so the process exits with code 2 —
33            // scripts and CI gates that branch on $? get an honest signal
34            // instead of a false-success. `CtlError::exit` formats this as
35            // `cellctl: api: unable to connect to the server: <reason>` on
36            // stderr, matching the existing error-output contract.
37            Err(CtlError::api(format!(
38                "unable to connect to the server: {e}"
39            )))
40        }
41    }
42}