16.03.2009Автоматизация процесса публикации
Когда я впервые прочитал про, например, Capistrano, мне, конечно же, сразу захотелось тоже начать применять эту клёвую штуку. Но я, конечно же, не преодолел барьер входа. На тот момент у меня было полтора приложения на ruby on rails, которые я довольно редко обновлял. Позже, когда я начал регулярно обновлять несколько приложений, использовать средства автоматизации оказалось очень просто и очень естественно. Для этого достаточно было вручную обновить приложение раз двадцать. :)
Задача
Допустим, речь идёт не о веб-приложении, а о библиотеке, которая используется на сервере несколькими веб-приложениями и другими программами, которые так же исполняются на сервере. Вполне логичным представляется сделать её в виде rubygem. И тогда встает вопрос обновления этой библиотеки на сервере.
Если делать это вручную достаточно долго, то со временем, после упрощений и оптимизаций, становится понятно, что для обновления нужно зайти на сервер по ssh и выполнить простую комманду:
cd somedir && do_some_stuff && sudo do_some_sudo_stuff
Использовать для этого любую готовую библиотеку публикации веб-приложений кажется слишком громостким. Так почему бы не написать задачу для rake, которая бы делала именно то, что нужно.
Ресурсы
Нам понадобится две библиотеки: одна для использования ssh, и другая для защищенного от заглядывания через плечо ввода sudo-пароля. (Оказалось, что сделать на руби такой ввод не так просто, поэтому я просто взял готовую библиотеку, которую и так использует, например, Capistrano и ряд других приложений).
sudo gem i net-ssh highline
Решение
Первым делом я, конечно, попробовал:
Net::SSH.start("myserver", "sudouser") do |ssh|
result = ssh.exec!("cd somedir && do_some_stuff && sudo do_some_sudo_stuff")
puts result
end
Но никакого вывода просто не дождался. Потому что дойдя до sudo-команды, процесс просто оставался в вечном ожидании.
Чтобы сделать ввод пароля, нужно создавать канал. А так же неплохо было бы проверить возможность интерактивного взаимодействия:
Net::SSH.start("myserver", "sudouser") do |ssh|
channel = ssh.open_channel do |ch|
ch.request_pty do |c, success|
raise "Cannot obtain pty" unless success
end
...
end
end
Теперь нужно отправить пароль в нужный момент. Чтобы узнать, когда наступил нужный момент, нужно использовать ключ -p (prompt) при вызове sudo, чтобы сказать ему, каким запросом спрашивать у нас пароль.
sudo -p 'sudo password: ' do_some_sudo_stuff
Когда нужно будет запросить пароль, воспользуемся библиотекой highline:
pwd = HighLine.new.ask("Input remote host sudo password: ") { |q| q.echo = false }
Это позволит нам получить пароль, не светя его на экране. Как это обычно и делает sudo.
Теперь посмотрим на всё решение целиком. В папке библиотеки создаем файл Rakefile. Записываем в него нашу задачу. В моём случае команда для сервера состояла примерно из следующего набора: «Перейти в папку, обновить исходники из scm, собрать джем, sudo установить джем, sudo удалить установленные старые версии джема».
Rakefile
require 'rubygems'
require 'rake'
require "net/ssh"
require 'highline'
...
desc "Update gem on the server by current version on remote origin"
task :deploy do
Net::SSH.start("myserver", "sudouser") do |ssh|
channel = ssh.open_channel do |ch|
ch.request_pty do |c, success|
# Если pseudo-tty недоступен, то невозможно никакого интерактива
raise "Cannot obtain pty" unless success
end
ch.exec("cd somedir && do_some_stuff && sudo -p 'sudo password: ' do_some_sudo_stuff") do |c, success|
abort "Could not execute command" unless success
c.on_data do |c, data|
if data =~ /sudo password: /
pwd = HighLine.new.ask("Input remote host sudo password: ") { |q| q.echo = false }
c.send_data "#{pwd}\n"
else
c[:result] ||= ""
c[:result] << data # Можно, конечно, и в процессе выводить
end
end
c.on_extended_data do |c, type, data|
puts "STDERR : #{data}"
end
end
end
ssh.loop # Ожидаем, пока закончится сеанс
puts channel[:result] # Выводим результат сеанса (можно было и в процессе)
end
end
...
Теперь вместо всей той последовательности действий достаточно написать запустить rake deploy и ввести пароль.