luisico

Una historia de capistrano, crontabs y pipes

15 Apr 2009

Al montar el deploy de una nueva máquina con capistrano he querido afinar un poco la carga de crontabs. No me gusta poner las tareas de crontab en el /etc/crontab. Creo que es una muy mala práctica. En vez de eso prefiero que cada usuario tenga su propia tabla de crontabs y para ello hago uso del comando crontab. En muchas ocasiones hace falta definir crontabs para más de un usuario. Para no andar añadiendo una linea en el script de deploy por cada fichero de crontab que se tenga doy por hecho que los nombres de todos los archivos con las tablas de crontab siguen el mismo formato, que es “crontab .. El código pues para capistrano es:

task :app_deploy, :roles => [:app] do
  Dir["./appserver/etc/crontab.*"].each { |crontab|
    sudo "sh -c 'cat #{release_path}/#{crontab} | \
    crontab -u  #{File.extname(File.basename(crontab)).delete('.')} -"
  }
end

En este código hay varias cosas que explicar. Primero se presupone pues que si quiero, por ejemplo, añadir el crontab para el usuario www-data simplemente lo crearé y lo guardaré en appserver/etc/crontab.www-data En cuanto al código, por un lado el bloque lo que hace es cargar en un array los ficheros con un nombre que coincida con el patrón comentado anteriormente. Hay que tener en cuenta que el código en ruby se ejecuta en la máquina desde la que se lanza el deploy y lo que se le pasa al comando run o sudo es un comando unix que se va a ejecutar en la máquina en la que se quiera hacer el deploy.

Por otro lado está el hecho de que cuando se ejecutan con sudo dos comandos unidos por una tubería, el sudo se va a aplicar únicamente al primero. Si se hace:

$ sudo echo '* * * * *  date > /tmp/date' | crontab -u root -

el sudo se va a aplicar únicamente al comando echo y no al comando crontab por lo que eso no funcionará ya que no tenemos privilegios suficientes.

Por lo tanto para no tener que repetir el comando sudo a los dos lados de la tubería y también para no complicar el comando en capistrano, lo que se puede hacer es englobar toda la sentencia en una subshell de la siguiente forma:

$ sudo sh -c 'echo "* * * * *  date > /tmp/date" | crontab -u root -'