# This file is part of EC2 on Rails.
# http://rubyforge.org/projects/ec2onrails/
#
# Copyright 2007 Paul Dowman, http://pauldowman.com/
#
# EC2 on Rails is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# EC2 on Rails is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
require 'fileutils'
include FileUtils
require 'ec2onrails/version'
require 'ec2onrails/capistrano_utils'
include Ec2onrails::CapistranoUtils
Capistrano::Configuration.instance.load do
unless ec2onrails_config
raise "ec2onrails_config variable not set. (It should be a hash.)"
end
cfg = ec2onrails_config
set :ec2onrails_version, Ec2onrails::VERSION::STRING
set :image_id_32_bit, Ec2onrails::VERSION::AMI_ID_32_BIT
set :image_id_64_bit, Ec2onrails::VERSION::AMI_ID_64_BIT
set :deploy_to, "/mnt/app"
set :use_sudo, false
set :user, "app"
make_admin_role_for :web
make_admin_role_for :app
make_admin_role_for :db
roles[:web_admin].to_s
roles[:app_admin].to_s
roles[:db_admin].to_s
# override default start/stop/restart tasks
namespace :deploy do
desc <<-DESC
Overrides the default Capistrano deploy:start, directly calls \
/etc/init.d/mongrel
DESC
task :start, :roles => :app do
run "/etc/init.d/mongrel start"
end
desc <<-DESC
Overrides the default Capistrano deploy:stop, directly calls \
/etc/init.d/mongrel
DESC
task :stop, :roles => :app do
run "/etc/init.d/mongrel stop"
end
desc <<-DESC
Overrides the default Capistrano deploy:restart, directly calls \
/etc/init.d/mongrel
DESC
task :restart, :roles => :app do
run "/etc/init.d/mongrel restart"
end
end
namespace :ec2onrails do
desc <<-DESC
Show the AMI id's of the current images for this version of \
EC2 on Rails.
DESC
task :ami_ids, :roles => [:web, :db, :app] do
puts "32-bit server image for EC2 on Rails #{ec2onrails_version}: #{image_id_32_bit}"
puts "64-bit server image for EC2 on Rails #{ec2onrails_version}: #{image_id_64_bit}"
end
desc <<-DESC
Start a new server instance and prepare it for a cold deploy.
DESC
task :setup, :roles => [:web, :db, :app] do
ec2.start_instance
server.set_timezone
server.install_packages
server.install_gems
server.deploy_files
server.restart_services
deploy.setup
server.set_roles
db.create
end
desc <<-DESC
Deploy and restore database from S3
DESC
task :restore_db_and_deploy, :roles => [:web, :db, :app] do
db.recreate
deploy.update_code
deploy.symlink
# don't need to migrate because we're restoring the db
db.restore
deploy.restart
end
namespace :ec2 do
desc <<-DESC
Start an instance, using the AMI of the correct version to match this gem.
DESC
task :start_instance, :roles => [:web, :db, :app] do
# TODO
end
desc <<-DESC
Set default firewall rules.
DESC
task :configure_firewall do
# TODO
end
end
namespace :db do
desc <<-DESC
[internal] Load configuration info for the production database from \
config/database.yml.
DESC
task :load_config, :roles => :db do
db_config = YAML::load(ERB.new(File.read("config/database.yml")).result)['production']
cfg[:production_db_name] = db_config['database']
cfg[:production_db_user] = db_config['username']
cfg[:production_db_password] = db_config['password']
cfg[:production_db_host] = db_config['host']
cfg[:production_db_socket] = db_config['socket']
if (cfg[:production_db_host].nil? || cfg[:production_db_host].empty?) &&
(cfg[:production_db_host] != 'localhost' || cfg[:production_db_host] != '127.0.0.1') &&
(cfg[:production_db_socket].nil? || cfg[:production_db_socket].empty?)
raise "ERROR: missing database config. Make sure database.yml contains a 'production' section with either 'host: localhost' or 'socket: /var/run/mysqld/mysqld.sock'."
end
[cfg[:production_db_name], cfg[:production_db_user], cfg[:production_db_password]].each do |s|
if s.nil? || s.empty?
raise "ERROR: missing database config. Make sure database.yml contains a 'production' section with a database name, user, and password."
elsif s.match(/['"]/)
raise "ERROR: database config string '#{s}' contains quotes."
end
end
end
desc <<-DESC
Create the MySQL production database. Assumes there is no MySQL root \
password. To create a MySQL root password create a task that's run \
after this task using an after hook.
DESC
task :create, :roles => :db do
on_rollback { drop }
load_config
run "echo 'create database #{cfg[:production_db_name]};' | mysql -u root"
run "echo \"grant all on #{cfg[:production_db_name]}.* to '#{cfg[:production_db_user]}'@'%' identified by '#{cfg[:production_db_password]}';\" | mysql -u root"
end
desc <<-DESC
Drop the MySQL production database. Assumes there is no MySQL root \
password. If there is a MySQL root password, create a task that removes \
it and run that task before this one using a before hook.
DESC
task :drop, :roles => :db do
load_config
run "echo 'drop database if exists #{cfg[:production_db_name]};' | mysql -u root"
end
desc <<-DESC
db:drop and db:create.
DESC
task :recreate, :roles => :db do
drop
create
end
desc <<-DESC
Set a root password for MySQL, using the variable mysql_root_password \
if it is set. If this is done db:drop won't work.
DESC
task :set_root_password, :roles => :db do
if cfg[:mysql_root_password]
run "echo 'set password for root@localhost=password('#{cfg[:mysql_root_password]}');' | mysql -u root"
end
end
desc <<-DESC
Dump the MySQL database to the S3 bucket specified by \
ec2onrails_config[:archive_to_bucket]. The filename will be \
"app-.sql.gz".
DESC
task :archive, :roles => [:db] do
run "/usr/local/ec2onrails/bin/backup_app_db.rb #{cfg[:archive_to_bucket]} app-#{Time.new.strftime('%y-%m-%d--%H-%M-%S')}.sql.gz"
end
desc <<-DESC
Restore the MySQL database from the S3 bucket specified by \
ec2onrails_config[:restore_from_bucket]. The archive filename is \
expected to be the default, "app.sql.gz".
DESC
task :restore, :roles => [:db] do
run "/usr/local/ec2onrails/bin/restore_app_db.rb #{cfg[:restore_from_bucket]}"
end
end
namespace :server do
desc <<-DESC
Tell the servers what roles they are in. This configures them with \
the appropriate settings for each role, and starts and/or stops the \
relevant services.
DESC
task :set_roles, :roles => [:web_admin, :db_admin, :app_admin] do
# TODO generate this based on the roles that actually exist so arbitrary new ones can be added
sudo "/usr/local/ec2onrails/bin/set_roles.rb web=#{hostnames_for_role(:web)} app=#{hostnames_for_role(:app)} db_primary=#{hostnames_for_role(:db, :primary => true)}"
end
desc <<-DESC
Upgrade to the newest versions of all Ubuntu packages.
DESC
task :upgrade_packages, :roles => [:web_admin, :db_admin, :app_admin] do
sudo "aptitude -q update"
run "export DEBIAN_FRONTEND=noninteractive; sudo aptitude -q -y dist-upgrade"
end
desc <<-DESC
Upgrade to the newest versions of all rubygems.
DESC
task :upgrade_gems, :roles => [:web_admin, :db_admin, :app_admin] do
sudo "gem update -y --no-rdoc --no-ri"
end
desc <<-DESC
Install extra Ubuntu packages. Set ec2onrails_config[:packages], it \
should be an array of strings.
NOTE: the package installation will be non-interactive, if the packages \
require configuration either log in as 'admin' and run \
'dpkg-reconfigure packagename' or replace the package's config files \
using the 'ec2onrails:deploy_config_files' task.
DESC
task :install_packages, :roles => [:web_admin, :db_admin, :app_admin] do
if cfg[:packages] && cfg[:packages].any?
run "export DEBIAN_FRONTEND=noninteractive; sudo aptitude -q -y install #{cfg[:packages].join(' ')}"
end
end
desc <<-DESC
Install extra rubygems. Set ec2onrails_config[:rubygems], it should \
be with an array of strings.
DESC
task :install_gems, :roles => [:web_admin, :db_admin, :app_admin] do
if cfg[:rubygems] && cfg[:rubygems].any?
sudo "gem install #{cfg[:rubygems].join(' ')} -y --no-rdoc --no-ri" do |ch, str, data|
ch[:data] ||= ""
ch[:data] << data
if data =~ />\s*$/
puts "The gem command is asking for a number:"
choice = STDIN.gets
ch.send_data(choice)
else
puts data
end
end
end
end
desc <<-DESC
A convenience task to upgrade existing packages and gems and install \
specified new ones.
DESC
task :upgrade_and_install_all, :roles => [:web_admin, :db_admin, :app_admin] do
upgrade_packages
upgrade_gems
install_packages
install_gems
end
desc <<-DESC
Set the timezone using the value of the variable named timezone. \
Valid options for timezone can be determined by the contents of \
/usr/share/zoneinfo, which can be seen here: \
http://packages.ubuntu.com/cgi-bin/search_contents.pl?searchmode=filelist&word=tzdata&version=gutsy&arch=all&page=1&number=all \
Remove 'usr/share/zoneinfo/' from the filename, and use the last \
directory and file as the value. For example 'Africa/Abidjan' or \
'posix/GMT' or 'Canada/Eastern'.
DESC
task :set_timezone, :roles => [:web_admin, :db_admin, :app_admin] do
if cfg[:timezone]
sudo "bash -c 'echo #{cfg[:timezone]} > /etc/timezone'"
sudo "cp /usr/share/zoneinfo/#{cfg[:timezone]} /etc/localtime"
end
end
desc <<-DESC
Deploy a set of config files to the server, the files will be owned by \
root. This doesn't delete any files from the server.
DESC
task :deploy_files, :roles => [:web_admin, :db_admin, :app_admin] do
if cfg[:server_config_files_root]
begin
# TODO use Zlib to support Windows
file = '/tmp/config_files.tgz'
run_local "tar zcf #{file} -C '#{cfg[:server_config_files_root]}' ."
put File.read(file), file
sudo "tar zxvf #{file} -o -C /"
ensure
rm_rf file
sudo "rm -f #{file}"
end
end
end
desc <<-DESC
Restart a set of services. Set ec2onrails_config[:services_to_restart] \
to an array of strings. It's assumed that each service has a script \
in /etc/init.d
DESC
task :restart_services, :roles => [:web_admin, :db_admin, :app_admin] do
if cfg[:services_to_restart] && cfg[:services_to_restart].any?
cfg[:services_to_restart].each do |service|
sudo "/etc/init.d/#{service} restart"
end
end
end
desc <<-DESC
DESC
task :enable_mail_server, :roles => [:web_admin, :db_admin, :app_admin] do
# TODO
end
desc <<-DESC
DESC
task :add_user, :roles => [:web_admin, :db_admin, :app_admin] do
# TODO
end
desc <<-DESC
DESC
task :run_script, :roles => [:web_admin, :db_admin, :app_admin] do
# TODO
end
desc <<-DESC
DESC
task :archive_logs, :roles => [:web_admin, :db_admin, :app_admin] do
# TODO
end
end
end
end