use std::path::PathBuf;
use clap::{Args, Subcommand};
use net_sdk::deck::{MeshOsSnapshot, StatusSummary};
use serde::Serialize;
use crate::context::{resolve_profile, CliContext};
use crate::error::{generic, invalid_args, CliError};
use crate::prelude::{emit_value, OutputFormat};
#[derive(Subcommand, Debug)]
pub enum SnapshotCommand {
Get(GetArgs),
Status(StatusArgs),
}
#[derive(Args, Debug)]
pub struct GetArgs {
#[arg(long)]
pub identity: Option<PathBuf>,
#[arg(long, default_value_t = crate::prelude::DEFAULT_SUPERVISOR_NODE)]
pub node: u64,
#[arg(long)]
pub local: bool,
}
#[derive(Args, Debug)]
pub struct StatusArgs {
#[arg(long)]
pub identity: Option<PathBuf>,
#[arg(long, default_value_t = crate::prelude::DEFAULT_SUPERVISOR_NODE)]
pub node: u64,
#[arg(long)]
pub local: bool,
}
fn require_explicit_local(local: bool, verb: &str) -> Result<(), CliError> {
if local {
tracing::warn!(
"--local: reporting the in-process supervisor started by this \
command. It has no peers, daemons or replicas because it was \
created a moment ago; this is not a view of a running deployment."
);
return Ok(());
}
Err(invalid_args(format!(
"`snapshot {verb}` cannot read a running deployment. The Deck client \
is built from an in-process supervisor started by this command, so \
the only snapshot it can produce is of a runtime created moments \
ago — empty by construction, and indistinguishable from a healthy \
idle cluster. Pass --local to inspect that fresh runtime on purpose \
(output shape, smoke tests). To observe a real node, use a surface \
that attaches to one: `net-mesh aggregator`, `net-mesh peer`, or \
net-deck."
)))
}
pub async fn run(
cmd: SnapshotCommand,
output: Option<OutputFormat>,
config_path: Option<&std::path::Path>,
profile_name: &str,
) -> Result<(), CliError> {
match cmd {
SnapshotCommand::Get(args) => {
require_explicit_local(args.local, "get")?;
let profile = resolve_profile(config_path, profile_name).await?;
let ctx =
CliContext::build(&profile, args.identity.as_deref(), args.node, false).await?;
let snapshot: MeshOsSnapshot = ctx.deck().status();
emit_value(OutputFormat::resolve_oneshot(output), &snapshot)
.map_err(|e| generic(format!("write snapshot: {e}")))?;
}
SnapshotCommand::Status(args) => {
require_explicit_local(args.local, "status")?;
let profile = resolve_profile(config_path, profile_name).await?;
let ctx =
CliContext::build(&profile, args.identity.as_deref(), args.node, false).await?;
let summary: StatusSummary = ctx.deck().status_summary();
let mirror = StatusSummaryMirror::from(&summary);
emit_value(OutputFormat::resolve_oneshot(output), &mirror)
.map_err(|e| generic(format!("write status: {e}")))?;
}
}
Ok(())
}
#[derive(Serialize)]
struct StatusSummaryMirror {
peers: PeerCountsMirror,
daemons: DaemonCountsMirror,
replica_chains: u64,
avoid_list_entries: u64,
recently_emitted_count: u64,
recent_failure_count: u64,
admin_audit_ring_depth: u64,
freeze_remaining_ms: Option<u64>,
local_maintenance_active: bool,
}
#[derive(Serialize)]
struct PeerCountsMirror {
healthy: u64,
degraded: u64,
unreachable: u64,
unknown: u64,
}
#[derive(Serialize)]
struct DaemonCountsMirror {
running: u64,
starting: u64,
stopping: u64,
stopped: u64,
backing_off: u64,
crash_looping: u64,
}
impl From<&StatusSummary> for StatusSummaryMirror {
fn from(s: &StatusSummary) -> Self {
Self {
peers: PeerCountsMirror {
healthy: s.peers.healthy as u64,
degraded: s.peers.degraded as u64,
unreachable: s.peers.unreachable as u64,
unknown: s.peers.unknown as u64,
},
daemons: DaemonCountsMirror {
running: s.daemons.running as u64,
starting: s.daemons.starting as u64,
stopping: s.daemons.stopping as u64,
stopped: s.daemons.stopped as u64,
backing_off: s.daemons.backing_off as u64,
crash_looping: s.daemons.crash_looping as u64,
},
replica_chains: s.replica_chains as u64,
avoid_list_entries: s.avoid_list_entries as u64,
recently_emitted_count: s.recently_emitted_count as u64,
recent_failure_count: s.recent_failure_count as u64,
admin_audit_ring_depth: s.admin_audit_ring_depth as u64,
freeze_remaining_ms: s.freeze_remaining_ms,
local_maintenance_active: s.local_maintenance_active,
}
}
}