diff --git a/railties/CHANGELOG b/railties/CHANGELOG index 0f5f82feca..37fca392df 100644 --- a/railties/CHANGELOG +++ b/railties/CHANGELOG @@ -1,5 +1,7 @@ *SVN* +* Add integration with SwitchTower (rake tasks, default deploy.rb recipe) + * Allow ERb in the database.yml file (just like with fixtures), so you can pull out the database configuration in environment variables #1822 [Duane Johnson] * Added convenience controls for FCGI processes (especially when managed remotely): spinner, spawner, and reaper. They reside in script/process. More details can be had by calling them with -h/--help. diff --git a/railties/bin/switchtower b/railties/bin/switchtower new file mode 100644 index 0000000000..741be775cc --- /dev/null +++ b/railties/bin/switchtower @@ -0,0 +1,4 @@ +#!/usr/local/bin/ruby +if not system "switchtower #{ARGV.join(" ")}" + abort "Could not execute switchtower. Is it installed?" +end diff --git a/railties/bin/switchtower_for_gem b/railties/bin/switchtower_for_gem new file mode 100644 index 0000000000..57d79bc06c --- /dev/null +++ b/railties/bin/switchtower_for_gem @@ -0,0 +1,4 @@ +#!/usr/local/bin/ruby +require 'rubygems' +require_gem 'switchtower' +load 'switchtower' diff --git a/railties/configs/deploy.rb b/railties/configs/deploy.rb new file mode 100644 index 0000000000..75e2fc16b5 --- /dev/null +++ b/railties/configs/deploy.rb @@ -0,0 +1,115 @@ +# This defines a deployment "recipe" that you can feed to switchtower +# (http://manuals.rubyonrails.com/read/book/17). It allows you to automate +# (among other things) the deployment of your application. + +# ============================================================================= +# REQUIRED VARIABLES +# ============================================================================= +# You must always specify the application and repository for every recipe. The +# repository must be the URL of the repository you want this recipe to +# correspond to. The deploy_to path must be the path on each machine that will +# form the root of the application path. + +set :application, "<%= File.basename(destination_root) %>" +set :repository, "http://svn.yourhost.com/#{application}/trunk" + +# ============================================================================= +# ROLES +# ============================================================================= +# You can define any number of roles, each of which contains any number of +# machines. Roles might include such things as :web, or :app, or :db, defining +# what the purpose of each machine is. You can also specify options that can +# be used to single out a specific subset of boxes in a particular role, like +# :primary => true. + +role :web, "www01.example.com", "www02.example.com" +role :app, "app01.example.com", "app02.example.com", "app03.example.com" +role :db, "db01.example.com", :primary => true +role :db, "db02.example.com", "db03.example.com" + +# ============================================================================= +# OPTIONAL VARIABLES +# ============================================================================= +# set :deploy_to, "/path/to/app" # defaults to "/u/apps/#{application}" +# set :user, "flippy" # defaults to the currently logged in user +# set :scm, :darcs # defaults to :subversion +# set :svn, "/path/to/svn" # defaults to searching the PATH +# set :darcs, "/path/to/darcs" # defaults to searching the PATH +# set :gateway, "gate.host.com" # default to no gateway + +# ============================================================================= +# TASKS +# ============================================================================= +# Define tasks that run on all (or only some) of the machines. You can specify +# a role (or set of roles) that each task should be executed on. You can also +# narrow the set of servers to a subset of a role by specifying options, which +# must match the options given for the servers to select (like :primary => true) + +desc < :db, :only => { :primary => true } do + # the on_rollback handler is only executed if this task is executed within + # a transaction (see below), AND it or a subsequent task fails. + on_rollback { delete "/tmp/dump.sql" } + + run "mysqldump -u theuser -p thedatabase > /tmp/dump.sql" do |ch, stream, out| + ch.send_data "thepassword\n" if out =~ /^Enter password:/ + end +end + +# Tasks may take advantage of several different helper methods to interact +# with the remote server(s). These are: +# +# * run(command, options={}, &block): execute the given command on all servers +# associated with the current task, in parallel. The block, if given, should +# accept three parameters: the communication channel, a symbol identifying the +# type of stream (:err or :out), and the data. The block is invoked for all +# output from the command, allowing you to inspect output and act +# accordingly. +# * sudo(command, options={}, &block): same as run, but it executes the command +# via sudo. +# * delete(path, options={}): deletes the given file or directory from all +# associated servers. If :recursive => true is given in the options, the +# delete uses "rm -rf" instead of "rm -f". +# * put(buffer, path, options={}): creates or overwrites a file at "path" on +# all associated servers, populating it with the contents of "buffer". You +# can specify :mode as an integer value, which will be used to set the mode +# on the file. +# * render(template, options={}) or render(options={}): renders the given +# template and returns a string. Alternatively, if the :template key is given, +# it will be treated as the contents of the template to render. Any other keys +# are treated as local variables, which are made available to the (ERb) +# template. + +desc "Demonstrates the various helper methods available to recipes." +task :helper_demo do + # "setup" is a standard task which sets up the directory structure on the + # remote servers. It is a good idea to run the "setup" task at least once + # at the beginning of your app's lifetime (it is non-destructive). + setup + + buffer = render("maintenance.rhtml", :deadline => ENV['UNTIL']) + put buffer, "#{shared_path}/system/maintenance.html", :mode => 0644 + sudo "killall -USR1 dispatch.fcgi" + run "#{release_path}/script/spin" + delete "#{shared_path}/system/maintenance.html" +end + +# You can use "transaction" to indicate that if any of the tasks within it fail, +# all should be rolled back (for each task that specifies an on_rollback +# handler). + +desc "A task demonstrating the use of transactions." +task :long_deploy do + transaction do + update_code + disable_web + symlink + migrate + end + + restart + enable_web +end diff --git a/railties/fresh_rakefile b/railties/fresh_rakefile index 34b0d24c4d..766a10237c 100755 --- a/railties/fresh_rakefile +++ b/railties/fresh_rakefile @@ -219,3 +219,28 @@ task :load_fixtures => :environment do Fixtures.create_fixtures('test/fixtures', File.basename(fixture_file, '.*')) end end + +desc "Push the latest revision into production using the release manager" +task :deploy do + system "script/switchtower -vvvv -r config/deploy -a deploy" +end + +desc "Rollback to the release before the current release in production" +task :rollback do + system "script/switchtower -vvvv -r config/deploy -a rollback" +end + +desc "Enumerate all available deployment tasks" +task :show_deploy_tasks do + system "script/switchtower -r config/deploy -a show_tasks" +end + +desc "Execute a specific action using the release manager" +task :remote_exec do + unless ENV['ACTION'] + raise "Please specify an action (or comma separated list of actions) via the ACTION environment variable" + end + + actions = ENV['ACTION'].split(",").map { |a| "-a #{a}" }.join(" ") + system "script/switchtower -vvvv -r config/deploy #{actions}" +end diff --git a/railties/lib/rails_generator/generators/applications/app/app_generator.rb b/railties/lib/rails_generator/generators/applications/app/app_generator.rb index 7750d563d3..d5e932ac1d 100644 --- a/railties/lib/rails_generator/generators/applications/app/app_generator.rb +++ b/railties/lib/rails_generator/generators/applications/app/app_generator.rb @@ -34,6 +34,7 @@ class AppGenerator < Rails::Generator::Base # database.yml and .htaccess m.template "configs/database.yml", "config/database.yml" m.template "configs/routes.rb", "config/routes.rb" + m.template "configs/deploy.rb", "config/deploy.rb" m.template "configs/apache.conf", "public/.htaccess" # Environments @@ -48,8 +49,10 @@ class AppGenerator < Rails::Generator::Base end if options[:gem] m.file "bin/breakpointer_for_gem", "script/breakpointer", script_options + m.file "bin/switchtower_for_gem", "script/switchtower", script_options else m.file "bin/breakpointer", "script/breakpointer", script_options + m.file "bin/switchtower", "script/switchtower", script_options end # Dispatches