Files
dtic-DIIAA/adn/tools/cli/proceso.rb
T
Ricardo Monla a5f71df15d [P2604-S13] Consolidar core/ — lógica de dominio a submódulos CLI
Arquitectura antes:
  core/nodos_info.rb    → usado por 4 CLIs
  core/ejecutor_remoto.rb → usado por proceso.rb
  core/triggers.rb      → usado por triggers.rb
  core/eventos.rb       → usado por triggers.rb
  core/configurador.rb  → usado por backup.rb

Arquitectura después:
  cli/nodos/info.rb       ← submódulo de nodos
  cli/ssh/ejecutor.rb     ← submódulo de ssh
  cli/triggers/motor.rb   ← submódulo de triggers
  cli/triggers/eventos.rb ← submódulo de triggers
  cli/backup/configurador.rb ← submódulo de backup

core/ queda limpio con solo infraestructura genuina:
  colores, constants, error_handler, logger, help_formatter,
  conciliador (transversal), validador (obsoleto)

- 15 tests pasan sin regresiones
- Evento 1078 (17:14→17:17)
2026-03-15 17:19:14 -03:00

193 lines
7.7 KiB
Ruby

# frozen_string_literal: true
# adn/tools/cli/proceso.rb — Subcomando para ejecución de procesos automatizados
# ==============================================================================
require 'optparse'
require 'open3'
require 'date'
require_relative '../core/colores'
require_relative 'nodos/info'
require_relative 'ssh/ejecutor'
require_relative '../db/core/bitacora_db'
module ADN
class SubcomandoProceso
def initialize(args, logger)
@args = args
@logger = logger
end
def ejecutar
if @args.empty? || @args[0] == 'help'
mostrar_ayuda
return
end
proceso = @args.shift
sub_args = @args
case proceso
when 'nodos:migrar_backup'
comando_migrar_backup(sub_args)
else
@logger.error("Proceso desconocido: #{proceso}")
mostrar_ayuda
exit 1
end
end
private
def mostrar_ayuda
puts "#{Color::CYAN}⚙️ Procesos Automatizados ADN#{Color::RESET}"
puts ""
puts "#{Color::YELLOW}Uso:#{Color::RESET} ./adn/tools/run proceso <nombre_proceso> [argumentos]"
puts ""
puts "#{Color::BOLD}nodos:migrar_backup [OPC]#{Color::RESET} Migración de backups entre nodos"
puts " --desde NODO Nodo origen"
puts " --hacia NODO Nodo destino"
puts " --ruta RUTA Ruta del archivo en el origen"
puts " --ejecutar Ejecutar transferencia real"
end
def comando_migrar_backup(args)
opts = { desde: nil, hacia: nil, ruta: nil, simular: true }
OptionParser.new do |o|
o.on("--desde NODO") { |v| opts[:desde] = v }
o.on("--hacia NODO") { |v| opts[:hacia] = v }
o.on("--ruta RUTA") { |v| opts[:ruta] = v }
o.on("--ejecutar") { opts[:simular] = false }
end.parse!(args)
unless opts[:desde] && opts[:hacia] && opts[:ruta]
puts "#{Color::RED}✗ Error: Faltan argumentos (--desde, --hacia, --ruta)#{Color::RESET}"
return
end
# 1. Resolver información de nodos
info_src = NodosInfo.extraer_metadata(opts[:desde])
info_dst = NodosInfo.extraer_metadata(opts[:hacia])
unless info_src && info_dst
puts "#{Color::RED}✗ Error: No se pudo obtener información de #{opts[:desde]} o #{opts[:hacia]}#{Color::RESET}"
return
end
# Ajuste manual para srv-ns8 (detectar si estamos corriendo en él)
info_src[:ip] = 'localhost' if opts[:desde] == 'srv-ns8'
puts "#{Color::CYAN}🚀 Preparando #{opts[:simular] ? 'SIMULACIÓN' : 'MIGRACIÓN'} de backup...#{Color::RESET}"
puts " 📤 Origen : #{Color::BOLD}#{opts[:desde]}#{Color::RESET} (#{info_src[:ip]})"
puts " 📥 Destino: #{Color::BOLD}#{opts[:hacia]}#{Color::RESET} (#{info_dst[:ip]})"
# 2. Registrar en Bitácora
id_evento = registrar_inicio_bitacora(opts)
# 3. Ejecutar
if opts[:simular]
ejecutar_simulacion(id_evento, opts)
else
ejecutar_migracion_real(id_evento, opts, info_src, info_dst)
end
end
def ejecutar_migracion_real(id_evento, opts, info_src, info_dst)
puts "#{Color::BOLD}🚀 Iniciando transferencia real...#{Color::RESET}"
desc = "PROCESO: Migración REAL de backup desde #{opts[:desde]} a #{opts[:hacia]}."
# A. Verificar Origen
print " ▸ Verificando origen... "
size = nil
if info_src[:ip] == 'localhost'
size = File.exist?(opts[:ruta]) ? File.size(opts[:ruta]) : nil
else
# Usar EjecutorRemoto para verificar el archivo
res = EjecutorRemoto.ejecutar(info_src, "powershell -NoProfile -Command \"if(Test-Path '#{opts[:ruta]}'){$s=Get-Item '#{opts[:ruta]}'; $s.Length}else{exit 1}\"")
size = res[:success] ? res[:output].to_i : nil
end
if size
mb = (size.to_f / 1024**2).round(2)
puts "#{Color::GREEN}OK#{Color::RESET} (#{mb} MB)"
desc += "\n- #{Time.now.strftime('%H:%M')} > Archivo localizado: #{mb} MB."
else
puts "#{Color::RED}FALLÓ#{Color::RESET} (No se encontró el archivo)"
actualizar_bitacora(id_evento, { estado: '❌', descripcion: desc + "\n- ERROR: Archivo no encontrado." })
return
end
# B. Preparar Destino
print " ▸ Preparando directorio en destino... "
EjecutorRemoto.ejecutar(info_dst, "powershell -NoProfile -Command \"if(!(Test-Path C:\\temp)){New-Item -ItemType Directory -Path C:\\temp}\"")
puts "#{Color::GREEN}OK#{Color::RESET}"
# C. Pipeline de Streaming
filename = File.basename(opts[:ruta].gsub('\\', '/'))
dst_path = "C:\\temp\\#{filename}"
puts " 🚀 Streaming: #{opts[:desde]} -> #{opts[:hacia]} [#{dst_path}]"
# Definir comandos de stream adaptados
if info_src[:ip] == 'localhost'
src_cmd = ["cat", opts[:ruta]]
else
ps_src = "$s=[System.IO.File]::OpenRead('#{opts[:ruta].gsub('/', '\\\\')}'); $o=[Console]::OpenStandardOutput(); $b=New-Object byte[] 65536; while(($r=$s.Read($b,0,$b.Length)) -gt 0){ $o.Write($b,0,$r) }; $s.Close()"
# Inyectar SSHPass vía EjecutorRemoto.comando_candados
clave_src = (info_src[:host] == 'srv-dasu' ? 'srv-dasu:rmonla' : 'win_domain')
ssh_user_src = "#{info_src[:usuario]}@#{info_src[:ip]}"
src_cmd = EjecutorRemoto.comando_candados(clave_src, 'SSHPASS', ["sshpass", "-e", "ssh", "-p", info_src[:puerto_ssh].to_s, "-o", "StrictHostKeyChecking=no", ssh_user_src, "powershell -NoProfile -Command \"#{ps_src}\""])
end
ps_dst = "$i=[Console]::OpenStandardInput(); $s=[System.IO.File]::Create('#{dst_path.gsub('/', '\\\\')}'); $b=New-Object byte[] 65536; while(($r=$i.Read($b,0,$b.Length)) -gt 0){$s.Write($b,0,$r)}; $s.Close()"
clave_dst = (info_dst[:host] == 'srv-dasu' ? 'srv-dasu:rmonla' : 'admindasu')
ssh_user_dst = "#{info_dst[:usuario]}@#{info_dst[:ip]}"
dst_cmd = EjecutorRemoto.comando_candados(clave_dst, 'SSHPASS', ["sshpass", "-e", "ssh", "-p", info_dst[:puerto_ssh].to_s, "-o", "StrictHostKeyChecking=no", ssh_user_dst, "powershell -NoProfile -Command \"#{ps_dst}\""])
start_time = Time.now
success = EjecutorRemoto.stream_pipeline(src_cmd, dst_cmd)
end_time = Time.now
if success
duracion = (end_time - start_time).round(0)
puts "#{Color::GREEN}✅ Transferencia exitosa en #{duracion} segundos.#{Color::RESET}"
actualizar_bitacora(id_evento, {
estado: '✅',
fin: Time.now.strftime('%H:%M'),
descripcion: desc + "\n- #{Time.now.strftime('%H:%M')} > Transferencia completada en #{duracion}s."
})
else
puts "#{Color::RED}✗ Falló la transferencia.#{Color::RESET}"
actualizar_bitacora(id_evento, { estado: '❌', descripcion: desc + "\n- ERROR: Streaming fallido." })
end
end
# Auxiliares de Bitácora
def registrar_inicio_bitacora(opts)
BitacorasDB::BitacoraDB.with_connection do |db|
nodo = db.find_nodo_by_nombre(opts[:hacia])
bit = db.find_bitacora_by_fecha(Date.today.to_s) || db.create_bitacora(fecha: Date.today.to_s)
db.create_entrada(
bitacora_id: bit['id'],
nodo_id: nodo ? nodo['id'] : nil,
inicio: Time.now.strftime('%H:%M'),
estado: '⏳',
descripcion: "PROCESO: Migración de backup #{opts[:desde]} -> #{opts[:hacia]}."
)['id']
end
end
def actualizar_bitacora(id, data)
BitacorasDB::BitacoraDB.with_connection { |db| db.update_entrada(id, data) }
end
def ejecutar_simulacion(id_evento, opts)
puts " 🧪 Modo simulación activo."
sleep(2)
actualizar_bitacora(id_evento, { estado: '✅', fin: Time.now.strftime('%H:%M'), descripcion: "PROCESO: Simulación de migración completada." })
puts " #{Color::GREEN}OK#{Color::RESET}"
end
end
end