[A04.P005] Extraído módulo común DasuExecutor para eliminar código repetido
Problema: 6 drones repetían el mismo patrón de ejecución remota: - Base64 encoding de scripts PowerShell - SSH vía srv-dasu con sshpass - Lectura de JSON output desde dasu-sql4 - Verificación de archivos en fenix vía xp_cmdshell Solución: adn/tools/cli/drones/lib/dasu_executor.rb Funciones del módulo: - execute_ps_on_dasu(ps_script) → ejecuta PowerShell en dasu-sql4 - read_json_from_dasu(path) → lee JSON output - execute_ps_with_json_output(ps_script) → combina ambas - execute_ps_on_fenix(client, ps_script) → PowerShell en fenix - file_exists_on_fenix?(client, path) → verifica existencia - get_file_info_on_fenix(client, path) → info completa de archivo Drones refactorizados (líneas ahorradas): - dasuten_restaurar_dasu-sql4.rb: 200 → 95 líneas (-52%) - dasuten_restaurar_diferencial_dasu-sql4.rb: 207 → 100 líneas (-52%) - dasuten_download_drive-dasu-sql4.rb: 269 → 130 líneas (-52%) - dasuten_verificar-integridad_dasu-sql4.rb: 182 → 85 líneas (-53%) - dasuten_exportar_srvv-fenix.rb: usa módulo para verificación - dasuten_exportar_diferencial_srvv-fenix.rb: usa módulo para verificación Beneficio: Si se modifica el patrón SSH/PowerShell, se cambia en 1 solo lugar.
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# ==========================================================
|
||||
# adn/tools/cli/drones/lib/dasu_executor.rb
|
||||
# ==========================================================
|
||||
# Módulo helper para ejecutar PowerShell en dasu-sql4 vía srv-dasu
|
||||
# Centraliza: SSH, sshpass, base64 encoding, lectura de JSON
|
||||
# ==========================================================
|
||||
|
||||
require 'base64'
|
||||
require 'tempfile'
|
||||
|
||||
module DasuExecutor
|
||||
# Configuración del target dasu-sql4
|
||||
TARGET_IP = '192.168.1.11'
|
||||
TARGET_USER = 'Administrador'
|
||||
TARGET_PASS = 'UTNlarioja00DASU'
|
||||
TARGET_PORT = '7022'
|
||||
|
||||
# Path donde se guardan los JSON output en dasu-sql4
|
||||
DEFAULT_JSON_PATH = 'C:\temp\output.json'
|
||||
|
||||
module_function
|
||||
|
||||
# Ejecuta un script PowerShell en dasu-sql4 vía srv-dasu
|
||||
#
|
||||
# @param ps_script [String] Script PowerShell a ejecutar
|
||||
# @param output_path [String] Path donde PowerShell guardará el JSON (default: C:\temp\output.json)
|
||||
# @return [Hash] JSON parseado del output
|
||||
#
|
||||
# Ejemplo:
|
||||
# result = DasuExecutor.execute_ps_on_dasu(<<~PS)
|
||||
# @{ status = "ok" } | ConvertTo-Json | Out-File C:\temp\out.json
|
||||
# Get-Content C:\temp\out.json -Raw
|
||||
# PS
|
||||
def execute_ps_on_dasu(ps_script, output_path: DEFAULT_JSON_PATH)
|
||||
ps_encoded = Base64.strict_encode64(ps_script)
|
||||
|
||||
bash_script = <<~BASHHEREDOC
|
||||
#!/bin/bash
|
||||
export SSHPASS="#{TARGET_PASS}"
|
||||
sshpass -e ssh -o StrictHostKeyChecking=no -p #{TARGET_PORT} #{TARGET_USER}@#{TARGET_IP} \
|
||||
"powershell -Command \\\"Invoke-Expression ([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('#{ps_encoded}')))\\\""
|
||||
BASHHEREDOC
|
||||
|
||||
execute_bash_temporary(bash_script)
|
||||
end
|
||||
|
||||
# Ejecuta un script PowerShell y lee el JSON resultante
|
||||
# Asume que el script escribe un JSON en output_path
|
||||
#
|
||||
# @param ps_script [String] Script PowerShell a ejecutar
|
||||
# @param output_path [String] Path del JSON en dasu-sql4
|
||||
# @return [Hash] JSON parseado
|
||||
def execute_ps_with_json_output(ps_script, output_path: DEFAULT_JSON_PATH)
|
||||
# Ejecutar el script principal
|
||||
execute_ps_on_dasu(ps_script, output_path: output_path)
|
||||
|
||||
# Leer el JSON resultante
|
||||
read_json_from_dasu(output_path)
|
||||
end
|
||||
|
||||
# Lee un JSON desde dasu-sql4
|
||||
#
|
||||
# @param output_path [String] Path del JSON en dasu-sql4 (ej: C:\temp\output.json)
|
||||
# @return [Hash] JSON parseado
|
||||
def read_json_from_dasu(output_path)
|
||||
json_ps = "Get-Content #{output_path} -Raw"
|
||||
json_encoded = Base64.strict_encode64(json_ps)
|
||||
|
||||
json_script = <<~BASHHEREDOC.strip
|
||||
#!/bin/bash
|
||||
export SSHPASS="#{TARGET_PASS}"
|
||||
sshpass -e ssh -o StrictHostKeyChecking=no -p #{TARGET_PORT} #{TARGET_USER}@#{TARGET_IP} \
|
||||
"powershell -Command \\\"Invoke-Expression ([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('#{json_encoded}')))\\\""
|
||||
BASHHEREDOC
|
||||
|
||||
resultado = execute_bash_temporary(json_script)
|
||||
resultado = resultado.strip
|
||||
|
||||
# Extraer JSON del output (puede tener ruido)
|
||||
json_match = resultado.match(/\{.*\}/m)
|
||||
resultado = json_match ? json_match[0] : resultado
|
||||
|
||||
require 'json'
|
||||
JSON.parse(resultado)
|
||||
end
|
||||
|
||||
# Ejecuta un script bash temporal y lo limpia después
|
||||
#
|
||||
# @param bash_script [String] Script bash a ejecutar
|
||||
# @param remote_base [String] Directorio remoto base (default: /tmp)
|
||||
# @return [String] Output del comando
|
||||
def execute_bash_temporary(bash_script, remote_base: '/tmp')
|
||||
bash_file = Tempfile.new(['dasu', '.sh'])
|
||||
bash_file.write(bash_script)
|
||||
bash_file.chmod(0755)
|
||||
bash_file.close
|
||||
|
||||
remote_path = "#{remote_base}/dron_#{Time.now.to_i}.sh"
|
||||
|
||||
# Copiar script a srv-dasu
|
||||
scp_cmd = "scp -o StrictHostKeyChecking=no #{bash_file.path} srv-dasu:#{remote_path}"
|
||||
unless system(scp_cmd)
|
||||
bash_file.unlink
|
||||
raise "Error al copiar script a srv-dasu"
|
||||
end
|
||||
|
||||
# Ejecutar script
|
||||
ssh_cmd = "ssh srv-dasu 'bash #{remote_path}'"
|
||||
output = `#{ssh_cmd}`
|
||||
output = output.encode('UTF-8', invalid: :replace, undef: :replace, replace: '?') if output
|
||||
|
||||
# Limpieza
|
||||
bash_file.unlink
|
||||
system("ssh srv-dasu 'rm -f #{remote_path}' 2>/dev/null")
|
||||
|
||||
output
|
||||
end
|
||||
|
||||
# Helper para ejecutar PowerShell en fenix vía xp_cmdshell
|
||||
# Útil para verificar archivos en shares mapeados
|
||||
#
|
||||
# @param client [TinyTds::Client] Conexión SQL Server
|
||||
# @param ps_script [String] Script PowerShell a ejecutar
|
||||
# @return [Array] Resultado de la ejecución
|
||||
def execute_ps_on_fenix(client, ps_script)
|
||||
escaped = ps_script.gsub('"', '\"')
|
||||
client.execute("EXEC xp_cmdshell 'powershell -Command \"#{escaped}\"'")
|
||||
end
|
||||
|
||||
# Verifica si un archivo existe en un path Windows/fenix
|
||||
#
|
||||
# @param client [TinyTds::Client] Conexión SQL Server
|
||||
# @param path [String] Path del archivo (ej: X:\backup.bak)
|
||||
# @return [Boolean] true si existe
|
||||
def file_exists_on_fenix?(client, path)
|
||||
result = client.execute("EXEC master.sys.xp_fileexist '#{path}'")
|
||||
result.to_a.any? { |f| f['File Exists'] == 1 }
|
||||
end
|
||||
|
||||
# Obtiene información de tamaño de un archivo en fenix
|
||||
#
|
||||
# @param client [TinyTds::Client] Conexión SQL Server
|
||||
# @param path [String] Path del archivo
|
||||
# @return [Hash] Con :exists, :size_bytes, :size_mb
|
||||
def get_file_info_on_fenix(client, path)
|
||||
ps_script = <<~PSCMD.strip
|
||||
if (Test-Path "#{path}") {
|
||||
$item = Get-Item "#{path}"
|
||||
Write-Host "EXISTS:1"
|
||||
Write-Host "SIZE:$($item.Length)"
|
||||
} else {
|
||||
Write-Host "EXISTS:0"
|
||||
}
|
||||
PSCMD
|
||||
|
||||
result = execute_ps_on_fenix(client, ps_script)
|
||||
output = result.map { |r| r['output'] }.compact.join("\n")
|
||||
|
||||
exists = output.include?('EXISTS:1')
|
||||
size = output.match(/SIZE:(\d+)/) ? $1.to_i : 0
|
||||
|
||||
{
|
||||
exists: exists,
|
||||
size_bytes: size,
|
||||
size_mb: (size / 1024.0 / 1024.0).round(2)
|
||||
}
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user