⎇ 94ea0eaa539cf93d58167ea1687b5a242b196573
crates/apps/sandokan/src/main.rs
History
Who touched what
raw
feat(sandokan): CLI de prueba + fix wire serialization
1//! `sandokan` — CLI de prueba del orquestador.
2//!
3//! Uso típico (dos terminales):
4//! terminal 1: sandokan daemon
5//! terminal 2: sandokan run /bin/sleep 300
6//! sandokan list
7//! sandokan status <card-id>
8//! sandokan stop <card-id>
9//!
10//! Sin daemon, `run` igual encarna el proceso, pero el registro vive en
11//! el proceso del CLI y se pierde al salir — `list` no lo verá. Para
12//! probar el lifecycle completo, corré el daemon primero.
13
14use std::path::PathBuf;
15use std::sync::Arc;
16use std::time::Duration;
17
18use brahman_card::{Card, Payload};
19use clap::{Parser, Subcommand};
20use sandokan::{auto, default_socket_path, serve, Intent, LocalEngine};
21use ulid::Ulid;
22
23#[derive(Parser)]
24#[command(name = "sandokan", about = "Orquestador brahman — CLI de prueba")]
25struct Cli {
26 /// Socket del daemon (default: $XDG_RUNTIME_DIR/sandokan.sock).
27 #[arg(long, global = true)]
28 socket: Option<PathBuf>,
29 #[command(subcommand)]
30 cmd: Cmd,
31}
32
33#[derive(Subcommand)]
34enum Cmd {
35 /// Corre el daemon: sirve un LocalEngine sobre el socket.
36 Daemon,
37 /// Encarna un ejecutable como Card y lo orquesta.
38 Run {
39 /// Ruta del ejecutable.
40 exec: String,
41 /// Argumentos del ejecutable.
42 #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
43 args: Vec<String>,
44 },
45 /// Lista las entidades activas.
46 List,
47 /// Estado de una entidad.
48 Status { card_id: String },
49 /// Telemetría puntual de una entidad.
50 Telemetry { card_id: String },
51 /// Detiene una entidad (SIGTERM + gracia + SIGKILL).
52 Stop {
53 card_id: String,
54 #[arg(long, default_value = "1000")]
55 grace_ms: u64,
56 },
57}
58
59#[tokio::main]
60async fn main() -> anyhow::Result<()> {
61 let cli = Cli::parse();
62 let socket = cli.socket.unwrap_or_else(default_socket_path);
63
64 match cli.cmd {
65 Cmd::Daemon => {
66 if let Some(parent) = socket.parent() {
67 let _ = std::fs::create_dir_all(parent);
68 }
69 println!("sandokan-daemon escuchando en {}", socket.display());
70 println!("(Ctrl-C para salir)");
71 let engine = Arc::new(LocalEngine::new());
72 serve(engine, &socket).await?;
73 }
74 Cmd::Run { exec, args } => {
75 let mut card = Card::new(format!("run:{exec}"));
76 card.payload = Payload::Native {
77 exec,
78 argv: args,
79 envp: vec![],
80 };
81 let engine = auto(&socket).await;
82 let handle = engine.run(Intent::new(card)).await?;
83 println!("encarnado:");
84 println!(" card_id : {}", handle.card_id);
85 println!(" label : {}", handle.label);
86 }
87 Cmd::List => {
88 let engine = auto(&socket).await;
89 let list = engine.list().await?;
90 if list.is_empty() {
91 println!("(sin entidades activas)");
92 }
93 for h in list {
94 println!("{} {}", h.card_id, h.label);
95 }
96 }
97 Cmd::Status { card_id } => {
98 let engine = auto(&socket).await;
99 let state = engine.status(parse_id(&card_id)?).await?;
100 println!("{state:?}");
101 }
102 Cmd::Telemetry { card_id } => {
103 let engine = auto(&socket).await;
104 let t = engine.telemetry(parse_id(&card_id)?).await?;
105 println!(
106 "mem={} KiB nproc={} cpu={:.1}%",
107 t.mem_bytes / 1024,
108 t.nproc,
109 t.cpu_pct
110 );
111 }
112 Cmd::Stop { card_id, grace_ms } => {
113 let engine = auto(&socket).await;
114 engine
115 .stop(parse_id(&card_id)?, Duration::from_millis(grace_ms))
116 .await?;
117 println!("detenido");
118 }
119 }
120 Ok(())
121}
122
123fn parse_id(s: &str) -> anyhow::Result<Ulid> {
124 Ulid::from_string(s).map_err(|e| anyhow::anyhow!("card-id inválido `{s}`: {e}"))
125}