martes, 11 de septiembre de 2012

Automatizando con Expect

Existen algunas tareas muy tediosas que los administradores de sistemas desearían automatizar para no perder el tiempo en cosas rutinarias. Por fortuna, para los sistemas Unix, nuestro trabajo dispone de varias herramientas que nos facilitan la vida.

Una de estas herramientas es Expect, pero antes de entrar en detalles sobre ella, pintemos el siguiente escenario. Estamos dentro de una organización que dispone de muchos edificios repartidos por toda la geografía del mundo mundial. En cada edificio existen múltiples equipos dedicados a dar soporte de la red (switches, puntos de acceso y routers), que a su vez son de varias fabricantes (HP, cisco, 3com, Dlink). ¿Como hacer que regularmente dispongamos de una copia de seguridad de la configuración de cada uno de estos aparatos? Es en este momento donde Expect levanta la mano y nos da la solución.

Por sí sólo, Expect es una aplicación que sirve para leer la salida de la ejecución de un comando interactivo (por ejemplo telnet/ssh/ftp), la analiza buscando patrones y en función de lo que va encontrando, va mandando mensajes como si delante del telnet/ssh/ftp estuviéramos.

Esto, en algunos casos es posible hacerlo con redirecciones de tuberías desde la shell (<|>) pero en general es insatisfactorio al no poder controlar las excepciones ni hacer tratamientos generales.

La primera diferencia que vemos es que expect cuenta con su propia shell, que tiene un lenguaje de script. Así los scripts de expect tiene como primera línea:
#!/usr/bin/expect
Dentro de las instrucciones del lenguaje, tenemos lo siguiente:
  • Sentencias para la asignación de variables (set).
  • Leer variables desde la línea de comandos (variable $argv).
  • Iniciar la ejecución de un comando como un proceso en (spawn).
  • Esperar la recepción de una determinada cadena desde un proceso (expect).
  • Envío de mensajes al proceso ejecutado con spawn (send).
En el apartado de control de flujo tenemos tenemos:
  • Condicionales (if).
  • Bucles (for, while).
  • Evaluación de expresiones (expr).
También se pueden hacer otras cosas como:
  • Controlar el timeout de la respuesta del comando lanzado con spawn (timeout).
  • Acceder el buffer de la respuesta ($expect_out).
Lo que sigue es un ejemplo para sacar la configuración de los puntos de acceso Cisco Aironet (cisco_aironet.expect). Este script se puede mejorar, en lugar de sacar la configuración en modo texto, podemos obtenerla por tftp, con lo que el tratamiento posterior es más simple, pero como demostración del concepto, nos servirá.
#!/usr/bin/expect -f
set force_conservative 1
if {$force_conservative} {
        set send_slow {1 .1}
        proc send {ignore arg} {
                sleep .1
                exp_send -s -- $arg
        }
}
set timeout 2
puts "\n"
# Asignacion de variables
if $argc<3 {
send_user "$argv0: Faltan variables\n"
exit
}
set host [lindex $argv 0]
set user [lindex $argv 1]
set password [lindex $argv 2]
# Abrimos conexion
spawn ssh $user@$host
# Se le pasa la clave
expect {
"*password:" {send -- "$password\r"; }
}
expect {
"\#" {exp_continue}
{put "Error\n"; exit}
}
send -- "show config\r"
expect {
-exac "More" {
send -- " ";
exp_continue
}
}
send -- "quit\r"
Armados con este script de expect podemos pasar a la siguiente parte del hechizo, un script al que llamaremos backup_config.sh que lee de un fichero los datos de los elementos que queremos sacar la configuración y luego lanza el script de expect correspondiente.
#!/bin/bash
PREFIX=path_config
LISTADO=$PREFIX/hosts
while read line
do
        HOST=$(echo $line | cut -d ";" -f 1)
        FICHERO=$(echo $line | cut -d ";" -f 2)
        CLAVE1=$(echo $line | cut -d ";" -f 3)
        CLAVE2=$(echo $line | cut -d ";" -f 4)
        $($FICHERO $HOST $CLAVE1 $CLAVE2)
done < $LISTADO
Este otro script, lee los datos de un fichero llamado hosts.list cuyo contenido puede ser el siguiente. Hay que fijarse que tiene cuatro parámetros por línea y utiliza como separador de parámetros el carácter ";".
catalyst1;expects/cisco_catalyst.expect;param1_1;param1_2
catalyst2;expects/cisco_catalyst.expect;param2_1;param2_2
aironet3;expects/cisco_aironet.expect;param3_1;param3_2
aironet4;expects/cisco_aironet.expect;param4_1;param4_1
El fichero expects para los Switches Cisco Catalyst es trivial sacarlo. Añadir un nuevo elemento del que queremos sacar su configuración es tan simple como crear la entrada correspondiente, indicando su fichero de expect y pasando sus parámetros.

A partir de aquí, lo podemos complicar todo lo queremos (invocarlo desde la crontab, guardar la salida de expect en un log que se mande a un subversión, etc), es asunto nuestro, pero el trabajo importante ya ha sido realizado.