use crate::command::{Executable, default_tracing};
use crate::common::CoordinatorOptions;
use crate::common::connect_to_coordinator;
use crate::common::{expect_reply, send_control_request};
use crate::formatting::OutputFormat;
use crate::ws_client::WsSession;
use dora_core::descriptor::Descriptor;
use dora_core::descriptor::DescriptorExt;
use dora_message::{cli_to_coordinator::ControlRequest, coordinator_to_cli::DataflowStatus};
use eyre::{Context, bail};
use serde::Serialize;
use std::path::PathBuf;
use std::{
io::{IsTerminal, Write},
net::SocketAddr,
};
use termcolor::{Color, ColorChoice, ColorSpec, WriteColor};
#[derive(Debug, Clone, Serialize)]
pub struct StatusOutput {
pub coordinator: ComponentStatus,
pub daemon: ComponentStatus,
pub active_dataflows: Option<usize>,
}
#[derive(Debug, Clone, Serialize)]
pub struct ComponentStatus {
pub status: &'static str,
#[serde(skip_serializing_if = "Option::is_none")]
pub address: Option<String>,
}
fn collect_status(coordinator_addr: SocketAddr) -> (StatusOutput, bool) {
let mut error_occurred = false;
let (coordinator, session) = match connect_to_coordinator(coordinator_addr) {
Ok(session) => (
ComponentStatus {
status: "running",
address: Some(coordinator_addr.to_string()),
},
Some(session),
),
Err(_) => {
error_occurred = true;
(
ComponentStatus {
status: "not running",
address: None,
},
None,
)
}
};
let daemon_running = session
.as_ref()
.map(daemon_running)
.transpose()
.ok()
.flatten();
let daemon = if daemon_running == Some(true) {
ComponentStatus {
status: "running",
address: None,
}
} else if session.is_some() {
error_occurred = true;
ComponentStatus {
status: "not running",
address: None,
}
} else {
error_occurred = true;
ComponentStatus {
status: "unknown",
address: None,
}
};
let active_dataflows = session
.as_ref()
.and_then(|s| query_running_dataflow_count(s).ok());
let output = StatusOutput {
coordinator,
daemon,
active_dataflows,
};
(output, error_occurred)
}
fn print_status_table(output: &StatusOutput) -> eyre::Result<()> {
let color_choice = if std::io::stdout().is_terminal() {
ColorChoice::Auto
} else {
ColorChoice::Never
};
let mut stdout = termcolor::StandardStream::stdout(color_choice);
if output.coordinator.status == "running" {
let _ = stdout.set_color(ColorSpec::new().set_fg(Some(Color::Green)));
write!(stdout, "✓ ")?;
let _ = stdout.reset();
writeln!(stdout, "Coordinator: Running")?;
if let Some(addr) = &output.coordinator.address {
writeln!(stdout, " Address: {addr}")?;
}
} else {
let _ = stdout.set_color(ColorSpec::new().set_fg(Some(Color::Red)));
write!(stdout, "✗ ")?;
let _ = stdout.reset();
writeln!(stdout, "Coordinator: Not running")?;
}
match output.daemon.status {
"running" => {
let _ = stdout.set_color(ColorSpec::new().set_fg(Some(Color::Green)));
write!(stdout, "✓ ")?;
let _ = stdout.reset();
writeln!(stdout, "Daemon: Running")?;
}
"not running" => {
let _ = stdout.set_color(ColorSpec::new().set_fg(Some(Color::Red)));
write!(stdout, "✗ ")?;
let _ = stdout.reset();
writeln!(stdout, "Daemon: Not running")?;
}
_ => {
let _ = stdout.set_color(ColorSpec::new().set_fg(Some(Color::Red)));
write!(stdout, "✗ ")?;
let _ = stdout.reset();
writeln!(stdout, "Daemon: Unknown (coordinator not available)")?;
}
}
if let Some(count) = output.active_dataflows {
writeln!(stdout, "Active dataflows: {count}")?;
}
Ok(())
}
pub fn check_environment(coordinator_addr: SocketAddr, format: OutputFormat) -> eyre::Result<()> {
let (output, error_occurred) = collect_status(coordinator_addr);
match format {
OutputFormat::Table => print_status_table(&output)?,
OutputFormat::Json => {
println!("{}", serde_json::to_string_pretty(&output)?);
}
}
if error_occurred {
bail!("System check failed.");
}
Ok(())
}
pub fn daemon_running(session: &WsSession) -> Result<bool, eyre::ErrReport> {
let reply = send_control_request(session, &ControlRequest::DaemonConnected)?;
Ok(expect_reply!(reply, DaemonConnected(running))?)
}
fn query_running_dataflow_count(session: &WsSession) -> Result<usize, eyre::ErrReport> {
let reply = send_control_request(session, &ControlRequest::List)?;
let list = expect_reply!(reply, DataflowList(list))?;
Ok(list
.0
.iter()
.filter(|d| d.status == DataflowStatus::Running)
.count())
}
#[derive(Debug, clap::Args)]
pub struct Status {
#[clap(long, value_name = "PATH", value_hint = clap::ValueHint::FilePath)]
dataflow: Option<PathBuf>,
#[clap(long, short = 'f', value_name = "FORMAT", default_value_t = OutputFormat::Table)]
format: OutputFormat,
#[clap(flatten)]
coordinator: CoordinatorOptions,
}
impl Executable for Status {
fn execute(self) -> eyre::Result<()> {
default_tracing()?;
let addr = self.coordinator.socket_addr();
if let Some(dataflow) = self.dataflow {
let working_dir = dataflow
.canonicalize()
.context("failed to canonicalize dataflow path")?
.parent()
.ok_or_else(|| eyre::eyre!("dataflow path has no parent dir"))?
.to_owned();
Descriptor::blocking_read(&dataflow)?.check(&working_dir)?;
}
check_environment(addr, self.format)
}
}