Capistrano Prompt For Server Selection
Problem #
At the time I was working on a project that had two application servers on AWS EC2 running behind an EC2 load balancer. For a reason I never uncovered when I would deploy to one of the application servers the application would become unavailable for a period of time even though the other application server was not interrupted. Therefore the process to deploy the project was to deploy to one instance at a time, modifying the Capistrano recipe at least once for every release. This got old pretty fast. What I wanted was to get prompted with a numbered list of servers upon deployment, enter the corresponding number and have Capistrano deploy to that server only. Then I could swap the servers out on the EC2 load balancer, and run it again this time picking the other server.
The Solution #
I looked at a few gems that appeared to provide the desired functionality by extending Capistrano, but none of them really seemed to fit well and I wasn’t interested in redesigning the project’s deployment scheme or adding a gem for such a simple task for that matter.
With no real desire to get fancy by querying AWS for the servers I decided I could accomplish my goal with not much more than a Hash for prompt and selection management. In fact I could probably reduce the complexity by using an Array. I made a compromise between automation, stability and minimal effort.
Here is what I added to the project’s config/deploy.rb
set :servers, {'1' => 'server1', '2' => 'server2'}
def get_server_choice
return fetch(:server_choice) if exists?(:server_choice)
puts "Please choose one of the following servers to deploy to..."
fetch(:servers).each {|key, name| puts "#{key}: #{name}"}
choice = Capistrano::CLI.ui.ask("Enter number (or Cancel): ")
if choice != 'Cancel' && fetch(:servers).keys.include?(choice)
set :server_choice, fetch(:servers)[choice]
return fetch(:server_choice)
else
raise StandardError.new "User cancelled deployment or entered an invalid server choice."
end
end
role(:app) do
get_server_choice
end
role(:web) do
get_server_choice
end
role(:db) do
[get_server_choice, {:primary => true}]
end
Perhaps some refactoring could take place in the get_server_choice method. I initially tried using a Capistrano callback but experienced major breakdown as a result.
This solution could be expanded to query the AWS EC2 API to present the list of servers, for small setups (1-3 servers) this works pretty well. Once the project grew beyond just a few servers I found the application would survive when deploying to say 4 of 8 application servers. Originally I intended to automate the entire deployment process including release to both servers one at a time, no prompts at all. While this is certainly possible, it carries some complications that were not worth solving for the project.
The Takeaway #
Sometimes it does make sense to roll your own solution, I felt this was one of those scenarios. Building your own WYSIWYG is probably an awful idea, no…it is an awful idea. Always spend a few minutes looking for an existing solution. Just don’t be afraid to build your own when you feel that you can in a fraction of the time to integrate another solution.
Please comment and tell me what you think about this or any suggestions for refactor.