From 9678a690f2c57dd2f51b6d8acb9ca9239739972e Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Wed, 31 Aug 2005 15:54:33 +0000 Subject: [PATCH] SwitchTower: incorporate deployment generator for simple Rails integration git-svn-id: http://svn.rubyonrails.org/rails/tools/switchtower@2084 5ecf4fe2-1ee6-0310-87b1-e25e094e27de --- lib/switchtower/cli.rb | 132 ++++++++++++++---- .../rails/deployment/deployment_generator.rb | 26 ++++ .../rails/deployment/templates/deploy.rb | 116 +++++++++++++++ .../rails/deployment/templates/switchtower | 12 ++ .../deployment/templates/switchtower.rake | 28 ++++ lib/switchtower/generators/rails/loader.rb | 20 +++ lib/switchtower/version.rb | 2 +- 7 files changed, 304 insertions(+), 32 deletions(-) create mode 100644 lib/switchtower/generators/rails/deployment/deployment_generator.rb create mode 100644 lib/switchtower/generators/rails/deployment/templates/deploy.rb create mode 100644 lib/switchtower/generators/rails/deployment/templates/switchtower create mode 100644 lib/switchtower/generators/rails/deployment/templates/switchtower.rake create mode 100644 lib/switchtower/generators/rails/loader.rb diff --git a/lib/switchtower/cli.rb b/lib/switchtower/cli.rb index 6adbfbb1..23d89745 100644 --- a/lib/switchtower/cli.rb +++ b/lib/switchtower/cli.rb @@ -32,12 +32,17 @@ module SwitchTower end attr_reader :options + attr_reader :args - def initialize + def initialize(args = ARGV) + @args = args @options = { :verbose => 0, :recipes => [], :actions => [], :vars => {} } OptionParser.new do |opts| - opts.banner = "Usage: #{$0} [options]" + opts.banner = "Usage: #{$0} [options] [args]" + + opts.separator "" + opts.separator "Recipe Options -----------------------" opts.separator "" opts.on("-a", "--action ACTION", @@ -50,14 +55,6 @@ module SwitchTower "(Default: prompt for password)" ) { |value| @options[:password] = value } - opts.on("-P", "--[no-]pretend", - "Run the task(s), but don't actually connect to or", - "execute anything on the servers. (For various reasons", - "this will not necessarily be an accurate depiction", - "of the work that will actually be performed.", - "Default: don't pretend.)" - ) { |value| @options[:pretend] = value } - opts.on("-r", "--recipe RECIPE", "A recipe file to load. Multiple recipes may", "be specified, and are loaded in the given order." @@ -71,17 +68,39 @@ module SwitchTower @options[:vars][name.to_sym] = value end + opts.separator "" + opts.separator "Framework Integration Options --------" + opts.separator "" + + opts.on("-A", "--apply-to [DIRECTORY]", + "Create a minimal set of scripts and recipes to use", + "switchtower with the application at the given", + "directory. (Currently only works with Rails apps.)" + ) { |value| @options[:apply_to] = value } + + opts.separator "" + opts.separator "Miscellaneous Options ----------------" + opts.separator "" + + opts.on("-h", "--help", "Display this help message") do + puts opts + exit + end + + opts.on("-P", "--[no-]pretend", + "Run the task(s), but don't actually connect to or", + "execute anything on the servers. (For various reasons", + "this will not necessarily be an accurate depiction", + "of the work that will actually be performed.", + "Default: don't pretend.)" + ) { |value| @options[:pretend] = value } + opts.on("-v", "--verbose", "Specify the verbosity of the output.", "May be given multiple times. (Default: silent)" ) { @options[:verbose] += 1 } - opts.separator "" - opts.on_tail("-h", "--help", "Display this help message") do - puts opts - exit - end - opts.on_tail("-V", "--version", + opts.on("-V", "--version", "Display the version info for this utility" ) do require 'switchtower/version' @@ -89,11 +108,25 @@ module SwitchTower exit end - opts.parse! + opts.separator "" + opts.separator <" +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 :cvs, "/path/to/cvs" # 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/lib/switchtower/generators/rails/deployment/templates/switchtower b/lib/switchtower/generators/rails/deployment/templates/switchtower new file mode 100644 index 00000000..3fde0d95 --- /dev/null +++ b/lib/switchtower/generators/rails/deployment/templates/switchtower @@ -0,0 +1,12 @@ +#!/usr/local/bin/ruby + +VENDOR = File.dirname(__FILE__) + "/../vendor" +$:.unshift "#{VENDOR}/switchtower/lib" + +begin + require 'rubygems' +rescue LoadError +end + +require 'switchtower/cli' +SwitchTower::CLI.execute! diff --git a/lib/switchtower/generators/rails/deployment/templates/switchtower.rake b/lib/switchtower/generators/rails/deployment/templates/switchtower.rake new file mode 100644 index 00000000..c77486d8 --- /dev/null +++ b/lib/switchtower/generators/rails/deployment/templates/switchtower.rake @@ -0,0 +1,28 @@ +# ============================================================================= +# A set of rake tasks for invoking the SwitchTower automation utility. +# ============================================================================= + +desc "Push the latest revision into production using the release manager" +task :deploy do + system "script/switchtower -vvvv -r config/<%= recipe_file %> -a deploy" +end + +desc "Rollback to the release before the current release in production" +task :rollback do + system "script/switchtower -vvvv -r config/<%= recipe_file %> -a rollback" +end + +desc "Enumerate all available deployment tasks" +task :show_deploy_tasks do + system "script/switchtower -r config/<%= recipe_file %> -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/<%= recipe_file %> #{actions}" +end diff --git a/lib/switchtower/generators/rails/loader.rb b/lib/switchtower/generators/rails/loader.rb new file mode 100644 index 00000000..2cbffd43 --- /dev/null +++ b/lib/switchtower/generators/rails/loader.rb @@ -0,0 +1,20 @@ +module SwitchTower + module Generators + class RailsLoader + def self.load!(options) + require "#{options[:apply_to]}/config/environment" + require "rails_generator" + require "rails_generator/scripts/generate" + + Rails::Generator::Base.sources << Rails::Generator::PathSource.new( + :switchtower, File.dirname(__FILE__)) + + args = ["deployment"] + args << options[:application] || "Application" + args << options[:recipe_file] || "deploy" + + Rails::Generator::Scripts::Generate.new.run(args) + end + end + end +end diff --git a/lib/switchtower/version.rb b/lib/switchtower/version.rb index a5fef131..36541e72 100644 --- a/lib/switchtower/version.rb +++ b/lib/switchtower/version.rb @@ -1,7 +1,7 @@ module SwitchTower module Version #:nodoc: MAJOR = 0 - MINOR = 8 + MINOR = 9 TINY = 0 STRING = [MAJOR, MINOR, TINY].join(".")