§ Expect для автоматизации задач.

К своему стыду узнал я об этой утилите только вчера, но уже сегодня она позволила мне достаточно быстро автоматизировать огромный пласт рутины при этом с минимальными потерями времени. Итак немного подробностей.

Так как у системного администратора часть задачь приходится выполнять на удалённых серверах доступ к которым есть преимущественно только по SSH, то автоматизация подобной рутины дело достаточно геморойное. Можно конечно писать на bash что нибудь однострочное и монструозное

$ ssh root@server "cd /srv/service/files; sed -i -e 's/old_line/new_line/g' filename.txt"

И пожалуй для простых тасков это имеет право на жизнь, но как быть если приходится запускать какой нибудь более-менее интерактивный скрипт, например создание SSL сертификата?

В голову сразу лезут мысли про Ruby и Python, и все вроде бы нормально. И тот и другой имеют вагон и маленькую тележку способов как залезть на сервер по SSH и выполнить код, причём интерактивно, но если нам потребуется выполнить капризный sudo, то все ещё раз усложнится тем что необходимо создавать TTY на удалённой системе, ибо sudo по умолчанию требует TTY для своей работы. Способов обхода этого тоже предостаточно, но количество кода в этом случае зачастую начинает рости непропорционально.

Что делать? А есть простой и почти гениальный способ - установить expect. Он есть в большинстве дистрибутивов, так что с поисками пакета даже возиться не придётся.

Что такое expect? Это такой язык интерактивного обмена данными с консолью. В своём примитивном варианте он позволяет отправлять в консоль определённый набор символов, и ожидать от консоли некоторый заранее известный ответ, после чего снова отправлять в консоль последовательность символов. Вот простой пример:

spawn root@server
expect -exact "password: "
send -- "mypassword\r"
expect -exact "# "
send -- "ls -la\r"
expect -exact "# "
send -- "logoff\r"
expect eof

Данный пример позволяет открыть ssh сессию на удалённой машине, выполнить ls -la и закрыть сессию. Достаточно просто, и в принципе не сложно.. Но что делать если нам нужно делать длинные и муторные операции, последовательность которых мы уже и не помним, и не вполне понимаем чего ожидать от вывода?

Для этого есть дополнительная утилита autoexpect которая открывает сессию с текущим shell (у меня это bash), и начинает записывать все движения пользователя в файл ./script.exp. Для завершения записи достаточно выйти из сессии Ctrl+D или exit.

[user@manjaro ~]$ autoexpect 
autoexpect started, file is script.exp
[user@manjaro ~]$ cd /etc
[user@manjaro etc]$ cd ./X11
[user@manjaro X11]$ ls -la *.conf
....
[user@manjaro X11]$ exit
exit
autoexpect done, file is script.exp

Вот теперь у нас есть скрипт script.exp который мы тут же можем запустит, и он воспроизведёт в точности все наши действия. Но я всеже рекомендую его немного подправить. expect поддерживает и задание переменных, и различные условия и циклы. Да это все не очень красиво выглядит, но тем не менее с работой своей справляется на ура. Вот пирмер моего рабочего скрипта который лезет на сервер Vyatta и забирает оттуда openvpn ключи интересующего меня пользователя

#!/usr/bin/expect -f

set client [lindex $argv 0]
set passwd $env(PASSWD)

if {$argc < 1} {
    send_user "USAGE: getkeys.exp client_name\n"
    exit 1
}

if {$env(PASSWD) == ""} {
    send_user "You have to setup your SSH password in PASSWD env. variable"
    exit 1
}

set timeout -1
spawn ssh admin@vyatta1
expect -exact "~admin@Vyatta1:~\$ "
send -- "sudo -i\r"
expect -exact "root@Vyatta1:~# "
send -- "cd /config/easy-rsa2/keys\r"
expect -exact "root@Vyatta1:/config/easy-rsa2/keys# "
send -- "tar cvJf ./$client.tar.xz ./$client.*\r"
expect -exact "root@Vyatta1:/config/easy-rsa2/keys# "
send -- "scp ./$client.tar.xz user@pivpav.ru:/home/user/$client.tar.xz\r"
expect -exact "user@pivpav.ru's password: "
send -- "$passwd\r"
expect -exact "root@Vyatta1:/config/easy-rsa2/keys# "
send -- "rm $client.tar.xz\r"
expect -exact "root@Vyatta1:/config/easy-rsa2/keys# "
send -- "logout\r"
expect -exact "~admin@Vyatta33:~\$ "
send -- "logout\r"
expect eof

Словом скудного синтаксиса expect совместно с autoexpect должно заглаза хватить для автоматизации различных задач для shell. Для дальнейшего изучения рекомендую начать с официального сайта.


comments powered by Disqus