The maze book for programmers!
mazesforprogrammers.com

Algorithms, circle mazes, hex grids, masking, weaving, braiding, 3D and 4D grids, spheres, and more!

DRM-Free Ebook

The Buckblog

assorted ramblings by Jamis Buck

Fun with SwitchTower

7 August 2005 — 3-minute read

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!