284 lines
8.6 KiB
Ruby
284 lines
8.6 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
# Dron::Dispatcher — Dispatcher de subcomandos para AtomicDrones
|
|
#
|
|
# Responsabilidad única: Despachar comandos a los módulos atómicos correctos
|
|
# - Parsea argumentos de línea de comandos
|
|
# - Invoca el módulo atómico correspondiente
|
|
# - Muestra ayuda formateada
|
|
#
|
|
# Uso:
|
|
# require_relative 'dron/dispatcher'
|
|
# Dron::Dispatcher.ejecutar(ARGV)
|
|
|
|
require_relative '../../core/help_formatter'
|
|
require_relative '../../core/logger'
|
|
require_relative '../../core/colores'
|
|
|
|
module Dron
|
|
class Dispatcher
|
|
def self.ejecutar(args)
|
|
@logger ||= ADN::Logger.new
|
|
subcomando = args.shift
|
|
|
|
case subcomando
|
|
when 'lanzar', 'run', nil
|
|
EjecutorComando.ejecutar(args)
|
|
when 'flota', 'list'
|
|
VigilanteComando.flota(args)
|
|
when 'salud', 'health'
|
|
VigilanteComando.salud(args)
|
|
when 'sanear', 'clean'
|
|
SanadorComando.sanear(args)
|
|
when 'limpiar', 'cleanup'
|
|
SanadorComando.limpiar(args)
|
|
when 'reintentar', 'retry'
|
|
SanadorComando.reintentar(args)
|
|
when 'estado', 'status'
|
|
VigilanteComando.estado(args)
|
|
when 'ayuda', 'help', '--help', '-h'
|
|
mostrar_ayuda
|
|
else
|
|
@logger.error("❌ Subcomando desconocido: #{subcomando}")
|
|
mostrar_ayuda
|
|
exit 1
|
|
end
|
|
end
|
|
|
|
def self.mostrar_ayuda
|
|
lines = []
|
|
lines << "#{Color::BOLD}🛸 Sistema de Drones ADN (AtomicDrones)#{Color::RESET}"
|
|
lines << "#{Color::DIM}#{'=' * 50}#{Color::RESET}"
|
|
lines << ""
|
|
lines << "#{Color::CYAN}Gestión de drones atómicos especializados para automatización de tareas#{Color::RESET}"
|
|
lines << ""
|
|
lines << "Uso: #{Color::YELLOW}./adn/tools/run dron <subcomando> [opciones]#{Color::RESET}"
|
|
lines << ""
|
|
lines << "\n#{Color::BOLD}Subcomandos:#{Color::RESET}"
|
|
lines << " #{Color::GREEN}lanzar [OPTIONS]#{Color::RESET} Lanzar tarea como dron ejecutor"
|
|
lines << " #{Color::GREEN}flota#{Color::RESET} Dashboard compacto de la flota"
|
|
lines << " #{Color::GREEN}salud#{Color::RESET} Health check activo (detecta zombies)"
|
|
lines << " #{Color::GREEN}estado <ID>#{Color::RESET} Estado detallado de un dron"
|
|
lines << " #{Color::GREEN}sanear#{Color::RESET} Sanear flota (reintentar + limpiar)"
|
|
lines << " #{Color::GREEN}limpiar#{Color::RESET} Limpiar drones antiguos"
|
|
lines << " #{Color::GREEN}reintentar#{Color::RESET} Re-intentar drones fallidos"
|
|
lines << ""
|
|
lines << "\n#{Color::BOLD}Opciones:#{Color::RESET}"
|
|
lines << " #{Color::GREEN}--nota \"texto\"#{Color::RESET} Nota descriptiva para la bitácora"
|
|
lines << " #{Color::GREEN}--evento ID#{Color::RESET} Vincular a evento existente"
|
|
lines << " #{Color::GREEN}--nodo NOMBRE#{Color::RESET} Nodo para bitácora (auto-detect si no se especifica)"
|
|
lines << " #{Color::GREEN}--timeout N#{Color::RESET} Timeout en segundos (default: 3600)"
|
|
lines << " #{Color::GREEN}--dry-run#{Color::RESET} Simulación sin ejecutar"
|
|
lines << " #{Color::GREEN}--help#{Color::RESET} Mostrar esta ayuda"
|
|
lines << ""
|
|
lines << "\n#{Color::BOLD}Ejemplos:#{Color::RESET}"
|
|
lines << " #{Color::DIM}$ ./adn/tools/run dron lanzar --nota 'Backup SQL' -- vzdump 103#{Color::RESET}"
|
|
lines << " #{Color::CYAN}Lanzar backup como dron#{Color::RESET}"
|
|
lines << " #{Color::DIM}$ ./adn/tools/run dron flota#{Color::RESET}"
|
|
lines << " #{Color::CYAN}Ver estado de la flota#{Color::RESET}"
|
|
lines << " #{Color::DIM}$ ./adn/tools/run dron salud#{Color::RESET}"
|
|
lines << " #{Color::CYAN}Health check de la flota#{Color::RESET}"
|
|
lines << ""
|
|
|
|
puts lines.join("\n")
|
|
end
|
|
end
|
|
|
|
# ============================================================================
|
|
# Comandos encapsulados para cada módulo atómico
|
|
# ============================================================================
|
|
|
|
class EjecutorComando
|
|
def self.ejecutar(args)
|
|
require_relative 'ejecutor'
|
|
|
|
# Parsear argumentos
|
|
opciones = parsear_opciones(args)
|
|
|
|
if opciones[:cmd].nil?
|
|
puts "❌ Error: Debes especificar un comando a ejecutar"
|
|
puts ""
|
|
puts "Uso: ./adn/tools/run dron lanzar --nota \"Nota\" -- <comando>"
|
|
exit 1
|
|
end
|
|
|
|
# Lanzar dron ejecutor
|
|
resultado = Dron::Ejecutor.lanzar(
|
|
cmd: opciones[:cmd],
|
|
nota: opciones[:nota],
|
|
evento_id: opciones[:evento_id],
|
|
timeout: opciones[:timeout],
|
|
flujo_id: opciones[:flujo_id],
|
|
nodo: opciones[:nodo]
|
|
)
|
|
|
|
# Mostrar resultado
|
|
puts "\n🛸 Resultado del Dron Ejecutor"
|
|
puts "═" * 60
|
|
puts " Dron ID: #{resultado[:dron_id]}"
|
|
puts " Estado: #{resultado[:exit_code] == 0 ? '✅ Completado' : '❌ Fallido'}"
|
|
puts " Exit Code: #{resultado[:exit_code]}"
|
|
puts " Duración: #{resultado[:duration]}s"
|
|
puts "═" * 60
|
|
|
|
exit resultado[:exit_code]
|
|
end
|
|
|
|
def self.parsear_opciones(args)
|
|
opciones = {
|
|
nota: nil,
|
|
evento_id: nil,
|
|
timeout: 3600,
|
|
flujo_id: nil,
|
|
nodo: nil,
|
|
cmd: nil
|
|
}
|
|
|
|
# Parsear opciones con nombre
|
|
while args.first&.start_with?('--')
|
|
arg = args.shift
|
|
case arg
|
|
when '--nota'
|
|
opciones[:nota] = args.shift
|
|
when '--evento'
|
|
opciones[:evento_id] = args.shift.to_i
|
|
when '--timeout'
|
|
opciones[:timeout] = args.shift.to_i
|
|
when '--flujo'
|
|
opciones[:flujo_id] = args.shift
|
|
when '--nodo'
|
|
opciones[:nodo] = args.shift
|
|
when '--help', '-h'
|
|
Dispatcher.mostrar_ayuda
|
|
exit 0
|
|
end
|
|
end
|
|
|
|
# El resto es el comando a ejecutar
|
|
opciones[:cmd] = args.join(' ') unless args.empty?
|
|
|
|
opciones
|
|
end
|
|
end
|
|
|
|
class VigilanteComando
|
|
def self.flota(args)
|
|
require_relative 'vigilante'
|
|
|
|
output = Dron::Vigilante.dashboard
|
|
puts output
|
|
end
|
|
|
|
def self.salud(args)
|
|
require_relative 'vigilante'
|
|
|
|
diagnosticos = Dron::Vigilante.salud
|
|
exit(diagnosticos[:saludable] ? 0 : 1)
|
|
end
|
|
|
|
def self.estado(args)
|
|
require_relative 'vigilante'
|
|
require_relative '../../db/core/dron_db'
|
|
|
|
dron_id = args.first
|
|
|
|
if dron_id.nil?
|
|
puts "❌ Error: Debes especificar un dron_id"
|
|
puts "Uso: ./adn/tools/run dron estado <dron_id>"
|
|
exit 1
|
|
end
|
|
|
|
dron = ADN::DB::DronDB.obtener_dron(dron_id)
|
|
|
|
if dron.nil?
|
|
puts "❌ Dron no encontrado: #{dron_id}"
|
|
exit 1
|
|
end
|
|
|
|
puts "\n📊 Estado del Dron"
|
|
puts "═" * 60
|
|
puts " ID: #{dron['dron_id']}"
|
|
puts " Tipo: #{dron['tipo']}"
|
|
puts " Estado: #{dron['estado']}"
|
|
puts " Comando: #{dron['cmd']}"
|
|
puts " Inicio: #{dron['started_at']}"
|
|
puts " Fin: #{dron['completed_at'] || '—'}"
|
|
puts " Heartbeat: #{dron['heartbeat']}"
|
|
puts " Reintentos: #{dron['reintentos']}"
|
|
puts " Exit Code: #{dron['exit_code'] || '—'}"
|
|
puts "═" * 60
|
|
|
|
# Mostrar avances si existen
|
|
require_relative '../../db/core/bitacora_db'
|
|
sql = 'SELECT * FROM bitacoras.dron_avances WHERE dron_id = $1 ORDER BY paso ASC'
|
|
avances = BitacorasDB::BitacoraDB.ejecutar(sql, [dron_id])
|
|
|
|
unless avances.empty?
|
|
puts "\n📝 Avances:"
|
|
avances.each do |avance|
|
|
puts " [#{avance['estado']}] Paso #{avance['paso']}: #{avance['descripcion']}"
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
class SanadorComando
|
|
def self.sanear(args)
|
|
require_relative 'sanador'
|
|
|
|
opciones = parsear_opciones(args)
|
|
|
|
resultado = Dron::Sanador.sanear(
|
|
dias_limpieza: opciones[:dias],
|
|
dry_run: opciones[:dry_run]
|
|
)
|
|
|
|
exit(0)
|
|
end
|
|
|
|
def self.limpiar(args)
|
|
require_relative 'sanador'
|
|
|
|
opciones = parsear_opciones(args)
|
|
|
|
resultado = Dron::Sanador.limpiar_antiguos(
|
|
dias: opciones[:dias],
|
|
dry_run: opciones[:dry_run]
|
|
)
|
|
|
|
exit(0)
|
|
end
|
|
|
|
def self.reintentar(args)
|
|
require_relative 'sanador'
|
|
|
|
opciones = parsear_opciones(args)
|
|
|
|
resultado = Dron::Sanador.reintentar_fallidos(
|
|
dry_run: opciones[:dry_run]
|
|
)
|
|
|
|
exit(0)
|
|
end
|
|
|
|
def self.parsear_opciones(args)
|
|
opciones = {
|
|
dias: 7,
|
|
dry_run: false
|
|
}
|
|
|
|
args.each do |arg|
|
|
case arg
|
|
when '--dry-run'
|
|
opciones[:dry_run] = true
|
|
when '--dias'
|
|
idx = args.index(arg)
|
|
opciones[:dias] = args[idx + 1].to_i if args[idx + 1]
|
|
end
|
|
end
|
|
|
|
opciones
|
|
end
|
|
end
|
|
end
|