Capistrano used to have a pair of tasks called deploy:web:disable and deploy:web:enable that I really liked. All they basically did was to upload a file to the webserver that would be displayed while a new version was deploying. I thought this was really helpful because if anyone visited the site during the deployment, they’d see the message that the site was under maintenance. Without this file, visitors to the site would see a cryptic rails rack error that made no sense. Sometime last year, after a capistrano update, I realized that these tasks were no longer there. By that time, I had understood that it was just uploading a file, so I could duplicate the tasks manually by editing a file to add the current time and then uploading it to the webserver. (In either case, I had to modify my httpd.conf file to reflect the location of the file.) Then, after the update, I’d remove the file. This wasn’t difficult, but it was WAY easier to do this:

cap deploy:web:disable
cap deploy
cap deploy:web:enable

Since I’ve been making huge leaps in understanding capistrano the past two days, I thought that I could write something that would do this. My first idea was to just make a template file with a set string (I used TIMENOW) that I’d replace with the current time. I put the template file (maintenance_template.html) on the server in the system directory and then just wrote a capistrano task to run a sed command like this:

run "sed \"s/TIMENOW/`date`/\" #{shared_path}/system/maintenance_template.html > #{shared_path}/system/maintenance.html"

The enable task just had to remove the maintenance.html file, which was pretty easy.

desc 'removes the maintenance.html file'
  task :enable, :roles => :app do
  run "/bin/rm #{shared_path}/system/maintenance.html"
end

I was pretty happy with these, but thought I could do better. I didn’t want to have to use a template file. I thought I should be able to just write the entire file at once. In looking for this, I came across the ERB templating system. I’d been using erb forever, but just didn’t realize it. All the views in my apps are named file.html.erb.

From ruby-doc, with ERB “actual Ruby code can be added to any plain text document.” That’s exactly what I want to do, have a plain html file, but run Time.now in it.

My capistrano task is pretty long, due to the css and html stuff at the top of the file, but the task basically looks like this:

namespace :web do
desc <<-DESC 
  This task makes a maintenance.html file in system to use before a deployment.

  The task works by simply running a sed command to switch the text TIMENOW with the current time.
  The file that it is looking for as a template is system/maintenance_template.html.
DESC
task :disable, :roles => :app do
template = ERB.new <<-EOF
The system is down for maintenance as of</p>
<%= Time.now.strftime "%d %b %Y at %H:%M %p %Z" %>
It'll be back shortly.
EOF

put template.result, "#{shared_path}/system/maintenance.html"
end

This task shows just about everything I learned about capistrano tasks.

  1. desc «-DESC is a way to quote a lot of text. 2. The first line in desc is shown when cap -T is run. The rest is shown when cap -e taskname is run. 3. namespace :web is how you break up task areas. I don’t show it, but this entire task is inside another namespace called deploy. So to call this task you run cap deploy:web:enable. 4. The string is all stored in the variable template. However, the Time.now ruby code is not run until result is called on it. Interestingly, I could use instance variables in erb as well. But, if you want the values for the instance variables put in the template, you’d have to run template.result(binding). Since I don’t have any instance variables, I only need to use result to run the ruby code. 5. Capistrano provides the put method that just lets me write the template.result to the maintenance.html file. It works exactly how I hoped. The only thing that I would change is to move these tasks to another file, since the disable task is very long. However, my first attempt at using another file in capistrano didn’t work out so well. So that’s something to work on another time.