feat(matilda): matilda-discover — estado actual del servidor
Observa qué contenedores y vhosts existen (docker ps + sitios de nginx) y reconstruye un Inventory "actual" que matilda-plan diferencia contra el deseado: detecta correctamente qué crear y qué eliminar (huérfanos). Parseo puro y testeable; sólo discover_local toca el sistema. 6 tests. La CLI gana el flag --discover en plan/script/apply: reconcilia contra el estado real de la máquina en vez de partir de vacío. matilda: 7 crates + CLI, ~48 tests. Pendiente: matilda-app (GPUI) y la inspección detallada (docker inspect) para detectar drift de configuración, no sólo presencia. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,7 @@ matilda-plan = { path = "../../modules/matilda/matilda-plan" }
|
||||
matilda-apply = { path = "../../modules/matilda/matilda-apply" }
|
||||
matilda-ghost = { path = "../../modules/matilda/matilda-ghost" }
|
||||
matilda-linker = { path = "../../modules/matilda/matilda-linker" }
|
||||
matilda-discover = { path = "../../modules/matilda/matilda-discover" }
|
||||
clap = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
|
||||
@@ -40,18 +40,26 @@ enum Cmd {
|
||||
/// Estado actual del servidor (por defecto: vacío).
|
||||
#[arg(long)]
|
||||
current: Option<PathBuf>,
|
||||
/// Descubre el estado actual de esta máquina (docker + nginx).
|
||||
#[arg(long)]
|
||||
discover: bool,
|
||||
},
|
||||
/// Emite el script de shell que aplicaría el plan.
|
||||
Script {
|
||||
inventory: PathBuf,
|
||||
#[arg(long)]
|
||||
current: Option<PathBuf>,
|
||||
#[arg(long)]
|
||||
discover: bool,
|
||||
},
|
||||
/// Aplica el plan: local, en seco, o remoto por SSH.
|
||||
Apply {
|
||||
inventory: PathBuf,
|
||||
#[arg(long)]
|
||||
current: Option<PathBuf>,
|
||||
/// Descubre el estado actual de esta máquina antes de reconciliar.
|
||||
#[arg(long)]
|
||||
discover: bool,
|
||||
/// Simula sin tocar nada.
|
||||
#[arg(long)]
|
||||
dry_run: bool,
|
||||
@@ -71,11 +79,22 @@ fn load(path: &PathBuf) -> Result<Inventory, String> {
|
||||
serde_json::from_str(&text).map_err(|e| format!("JSON inválido en {}: {e}", path.display()))
|
||||
}
|
||||
|
||||
/// Carga el inventario actual, o uno vacío si no se especificó.
|
||||
fn load_current(current: &Option<PathBuf>) -> Result<Inventory, String> {
|
||||
match current {
|
||||
Some(p) => load(p),
|
||||
None => Ok(Inventory::new()),
|
||||
/// Resuelve el inventario "actual" contra el que reconciliar:
|
||||
/// `--discover` observa esta máquina; `--current` lee un archivo; si no,
|
||||
/// se parte de un inventario vacío (todo es creación).
|
||||
fn current_inventory(
|
||||
discover: bool,
|
||||
current: &Option<PathBuf>,
|
||||
desired: &Inventory,
|
||||
) -> Result<Inventory, String> {
|
||||
if discover {
|
||||
let state = matilda_discover::discover_local();
|
||||
Ok(matilda_discover::observed_inventory(&state, desired))
|
||||
} else {
|
||||
match current {
|
||||
Some(p) => load(p),
|
||||
None => Ok(Inventory::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,9 +174,9 @@ fn run() -> Result<(), String> {
|
||||
println!("{json}");
|
||||
}
|
||||
|
||||
Cmd::Plan { inventory, current } => {
|
||||
Cmd::Plan { inventory, current, discover } => {
|
||||
let desired = load(&inventory)?;
|
||||
let p = plan(&load_current(¤t)?, &desired);
|
||||
let p = plan(¤t_inventory(discover, ¤t, &desired)?, &desired);
|
||||
if p.is_empty() {
|
||||
println!("Sin cambios: el servidor ya está al día.");
|
||||
} else {
|
||||
@@ -174,15 +193,15 @@ fn run() -> Result<(), String> {
|
||||
}
|
||||
}
|
||||
|
||||
Cmd::Script { inventory, current } => {
|
||||
Cmd::Script { inventory, current, discover } => {
|
||||
let desired = load(&inventory)?;
|
||||
let p = plan(&load_current(¤t)?, &desired);
|
||||
let p = plan(¤t_inventory(discover, ¤t, &desired)?, &desired);
|
||||
print!("{}", steps_to_script(&plan_to_steps(&p, &desired)));
|
||||
}
|
||||
|
||||
Cmd::Apply { inventory, current, dry_run, host, password } => {
|
||||
Cmd::Apply { inventory, current, discover, dry_run, host, password } => {
|
||||
let desired = load(&inventory)?;
|
||||
let p = plan(&load_current(¤t)?, &desired);
|
||||
let p = plan(¤t_inventory(discover, ¤t, &desired)?, &desired);
|
||||
let steps = plan_to_steps(&p, &desired);
|
||||
if steps.is_empty() {
|
||||
println!("Sin cambios: nada que aplicar.");
|
||||
|
||||
Reference in New Issue
Block a user