Fun with SwitchTower
With SwitchTower out of the gate and feedback coming in, I thought I’d take a moment to post a few custom tasks I created while working on Backpack and Tada Lists.
Firstly, the default SwitchTower tasks assume you are using Apache to host your apps. (Mostly this is an assumption of the enable_web
and disable_web
tasks, because they require some conditional rewrite rules in order to do their thing.) However, Tada Lists is being run via lighttpd, which doesn’t have (to my knowledge) any way to say “use this rewrite rule only if this file exists”.
So, what we’re doing instead is managing two lighttpd configurations—one for “live” mode, and one for “maintenance” mode. When in maintenance mode, all requests are rewritten so that a “system/maintenance.html” file is displayed.
Then, I create two new tasks, after_disable_web
and before_enable_web
. I use these tasks to kill the running lighttpd instance on the web servers, and then start up a new lighttpd instance using the correct configuration:
(Note that these are “hook” tasks that are called automatically by SwitchTower when the disable_web
and enable_web
tasks are called—you don’t need to call these explicitly, they just extend the existing tasks.)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
desc "Restart lighttpd with the $HOST-maint.conf file" task :after_disable_web, :roles => :web do pids = "ps wwwax | grep lighttpd | grep -v grep " + "| grep -v assets | cut -b 1-5" sudo "kill `#{pids}`" sudo "lighttpd -f #{current_path}/config/lighttpd/" + "${HOSTNAME%.37signals.com}-maint.conf" end desc "Restart lighttpd with the $HOST.conf file" task :before_enable_web, :roles => :web do pids = "ps wwwax | grep lighttpd | grep maint " + "| grep -v grep | cut -b 1-5" sudo "kill `#{pids}`" sudo "lighttpd -f #{current_path}/config/lighttpd/" + "${HOSTNAME%.37signals.com}.conf" end |
(Note that the above tasks will need a little more work if you host multiple apps on the same web server—you’ll probably want to find a way to only kill the lighttpd instances pertaining to the app in question.)
The other tasks I created were for examing the status of the master and slave database servers. To do this, I needed the root MySQL password so that I could run the SHOW MASTER STATUS
query, but I didn’t want to put the password in the recipe… not exactly a secure solution. So, instead, I set the password variable to a Proc
instance. When SwitchTower sees a variable that is a Proc
, it will execute the Proc
and cache the result. So, the variable is defined like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
set :root_db_password, Proc.new { sync = STDOUT.sync begin echo false STDOUT.sync = true print "Enter DB password for 'root': " gets.chomp ensure STDOUT.sync = sync echo true puts end } |
This means that the first time the root_db_password
is queried, the Proc
will be invoked, the user will be prompted for the root DB password, and the result cached so that subsequent uses of the variable reuse the result.
The tasks themselves look something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
desc "Show the master DB status" task :show_master_status, :roles => :db, :only => { :primary => true } do require 'yaml' db = YAML.load(File.read(File.dirname(__FILE__) + "/database.yml")) prod = db['production'] cmd = 'echo "SHOW MASTER STATUS\G" ' + "| mysql -u root -p #{prod['database']}" run cmd do |ch, stream, data| logger.info data, "[#{stream} :: #{ch[:host]}]" ch.send_data "#{root_db_password}\n" if data =~ /^Enter password:/ end end desc "Show the slave DB status" task :show_slave_status, :roles => :db, :only => { :slave => true } do require 'yaml' db = YAML.load(File.read(File.dirname(__FILE__) + "/database.yml")) prod = db['production'] cmd = 'echo "SHOW SLAVE STATUS\G" ' + "| mysql -u root -p #{prod['database']}" run cmd do |ch, stream, data| logger.info data, "[#{stream} :: #{ch[:host]}]" ch.send_data "#{root_db_password}\n" if data =~ /^Enter password:/ end end |
To use them, then, I just do:
1 |
rake remote_exec ACTION=show_master_status,show_slave_status |
And voila!