Files
dtic-DIIAA/adn/tools/cli/proceso.rb
T

200 lines
8.1 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'
require_relative '../core/help_formatter'
module ADN
class SubcomandoProceso
def initialize(args, logger)
@args = args
@logger = logger
end
def ejecutar
# Handle help flags
if @args.empty? || @args[0] == 'help' || @args[0] == '-h' || @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
ayuda = ADN::HelpFormatter.generar_help(
titulo: "Procesos Automatizados ADN",
descripcion: "Ejecuta procesos técnicos automatizados sobre la infraestructura",
uso: "./adn/tools/run proceso <nombre_proceso> [argumentos]",
opciones: [
{ names: ['nodos:migrar_backup'], desc: "Migración de backups entre nodos" }
],
ejemplos: [
{ cmd: "./adn/tools/run proceso nodos:migrar_backup --desde srv-ns8 --hacia srvv-dns --ruta /backups/backup.sql", desc: "Ejecutar migración de backup (modo simulación)" },
{ cmd: "./adn/tools/run proceso nodos:migrar_backup --desde srv-ns8 --hacia srvv-dns --ruta /backups/backup.sql --ejecutar", desc: "Ejecutar migración real de backup" }
]
)
puts ayuda
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