Skip to main content

cellos_ctl/cmd/
rollout.rs

1//! `cellctl rollout status <formation>` — poll formation state until terminal.
2//!
3//! Terminal states from the Session 16 state table: COMPLETED (cyan exit 0)
4//! and FAILED (red, exit 2). RUNNING + DEGRADED + LAUNCHING + PENDING are
5//! non-terminal and keep polling. Hitting Ctrl-C exits cleanly with code 1.
6
7use std::time::Duration;
8
9use indicatif::{ProgressBar, ProgressStyle};
10
11use crate::client::{formation_path, CellosClient};
12use crate::exit::{CtlError, CtlResult};
13use crate::model::Formation;
14
15const POLL_INTERVAL: Duration = Duration::from_millis(1500);
16
17pub async fn status(client: &CellosClient, name: &str, timeout_secs: Option<u64>) -> CtlResult<()> {
18    let bar = ProgressBar::new_spinner();
19    bar.set_style(
20        ProgressStyle::with_template("{spinner} {wide_msg}")
21            .unwrap_or_else(|_| ProgressStyle::default_spinner()),
22    );
23    bar.enable_steady_tick(Duration::from_millis(120));
24
25    let deadline = timeout_secs.map(|s| std::time::Instant::now() + Duration::from_secs(s));
26    // CTL-002: UUID vs name routing — see `client::formation_path`.
27    let path = formation_path(name);
28
29    loop {
30        if let Some(d) = deadline {
31            if std::time::Instant::now() >= d {
32                bar.finish_and_clear();
33                return Err(CtlError::api(format!(
34                    "timed out waiting for formation/{name}"
35                )));
36            }
37        }
38
39        let f: Formation = match client.get_json::<Formation>(&path).await {
40            Ok(f) => f,
41            Err(e) => {
42                bar.finish_and_clear();
43                return Err(e);
44            }
45        };
46
47        let state = f.state.to_ascii_uppercase();
48        let cells = f.cells.len();
49        bar.set_message(format!(
50            "formation/{name}: {state} ({cells} cell{s})",
51            s = if cells == 1 { "" } else { "s" }
52        ));
53
54        match state.as_str() {
55            "COMPLETED" => {
56                bar.finish_and_clear();
57                println!("formation/{name} COMPLETED");
58                return Ok(());
59            }
60            "FAILED" => {
61                bar.finish_and_clear();
62                return Err(CtlError::api(format!("formation/{name} FAILED")));
63            }
64            _ => {
65                tokio::select! {
66                    _ = tokio::signal::ctrl_c() => {
67                        bar.finish_and_clear();
68                        return Err(CtlError::usage("rollout watch cancelled"));
69                    }
70                    _ = tokio::time::sleep(POLL_INTERVAL) => {}
71                }
72            }
73        }
74    }
75}