mirror of
https://github.com/capistrano/capistrano
synced 2023-03-27 23:21:18 -04:00
SwitchTower: incorporate deployment generator for simple Rails integration
git-svn-id: http://svn.rubyonrails.org/rails/tools/switchtower@2084 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
This commit is contained in:
parent
c8f3edb93d
commit
9678a690f2
7 changed files with 304 additions and 32 deletions
|
@ -32,12 +32,17 @@ module SwitchTower
|
||||||
end
|
end
|
||||||
|
|
||||||
attr_reader :options
|
attr_reader :options
|
||||||
|
attr_reader :args
|
||||||
|
|
||||||
def initialize
|
def initialize(args = ARGV)
|
||||||
|
@args = args
|
||||||
@options = { :verbose => 0, :recipes => [], :actions => [], :vars => {} }
|
@options = { :verbose => 0, :recipes => [], :actions => [], :vars => {} }
|
||||||
|
|
||||||
OptionParser.new do |opts|
|
OptionParser.new do |opts|
|
||||||
opts.banner = "Usage: #{$0} [options]"
|
opts.banner = "Usage: #{$0} [options] [args]"
|
||||||
|
|
||||||
|
opts.separator ""
|
||||||
|
opts.separator "Recipe Options -----------------------"
|
||||||
opts.separator ""
|
opts.separator ""
|
||||||
|
|
||||||
opts.on("-a", "--action ACTION",
|
opts.on("-a", "--action ACTION",
|
||||||
|
@ -50,14 +55,6 @@ module SwitchTower
|
||||||
"(Default: prompt for password)"
|
"(Default: prompt for password)"
|
||||||
) { |value| @options[:password] = value }
|
) { |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",
|
opts.on("-r", "--recipe RECIPE",
|
||||||
"A recipe file to load. Multiple recipes may",
|
"A recipe file to load. Multiple recipes may",
|
||||||
"be specified, and are loaded in the given order."
|
"be specified, and are loaded in the given order."
|
||||||
|
@ -71,17 +68,39 @@ module SwitchTower
|
||||||
@options[:vars][name.to_sym] = value
|
@options[:vars][name.to_sym] = value
|
||||||
end
|
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",
|
opts.on("-v", "--verbose",
|
||||||
"Specify the verbosity of the output.",
|
"Specify the verbosity of the output.",
|
||||||
"May be given multiple times. (Default: silent)"
|
"May be given multiple times. (Default: silent)"
|
||||||
) { @options[:verbose] += 1 }
|
) { @options[:verbose] += 1 }
|
||||||
|
|
||||||
opts.separator ""
|
opts.on("-V", "--version",
|
||||||
opts.on_tail("-h", "--help", "Display this help message") do
|
|
||||||
puts opts
|
|
||||||
exit
|
|
||||||
end
|
|
||||||
opts.on_tail("-V", "--version",
|
|
||||||
"Display the version info for this utility"
|
"Display the version info for this utility"
|
||||||
) do
|
) do
|
||||||
require 'switchtower/version'
|
require 'switchtower/version'
|
||||||
|
@ -89,11 +108,25 @@ module SwitchTower
|
||||||
exit
|
exit
|
||||||
end
|
end
|
||||||
|
|
||||||
opts.parse!
|
opts.separator ""
|
||||||
|
opts.separator <<DETAIL.split(/\n/)
|
||||||
|
You can use the --apply-to switch to generate a minimal set of switchtower
|
||||||
|
scripts and recipes for an application. Just specify the path to the application
|
||||||
|
as the argument to --apply-to, and then specify the name of your application,
|
||||||
|
like this:
|
||||||
|
|
||||||
|
switchtower --apply-to ~/projects/myapp MyApplication
|
||||||
|
|
||||||
|
You'll wind up with a sample deployment recipe in config/deploy.rb, some new
|
||||||
|
rake tasks in config/tasks, and a switchtower script in your script directory.
|
||||||
|
|
||||||
|
(Currently, --apply-to only works with Rails applications.)
|
||||||
|
DETAIL
|
||||||
|
|
||||||
|
opts.parse!(args)
|
||||||
end
|
end
|
||||||
|
|
||||||
abort "You must specify at least one recipe" if @options[:recipes].empty?
|
check_options!
|
||||||
abort "You must specify at least one action" if @options[:actions].empty?
|
|
||||||
|
|
||||||
unless @options.has_key?(:password)
|
unless @options.has_key?(:password)
|
||||||
@options[:password] = Proc.new do
|
@options[:password] = Proc.new do
|
||||||
|
@ -113,6 +146,16 @@ module SwitchTower
|
||||||
end
|
end
|
||||||
|
|
||||||
def execute!
|
def execute!
|
||||||
|
if !@options[:recipes].empty?
|
||||||
|
execute_recipes!
|
||||||
|
elsif @options[:apply_to]
|
||||||
|
execute_apply_to!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def execute_recipes!
|
||||||
config = SwitchTower::Configuration.new
|
config = SwitchTower::Configuration.new
|
||||||
config.logger.level = options[:verbose]
|
config.logger.level = options[:verbose]
|
||||||
config.set :password, options[:password]
|
config.set :password, options[:password]
|
||||||
|
@ -126,5 +169,32 @@ module SwitchTower
|
||||||
actor = config.actor
|
actor = config.actor
|
||||||
options[:actions].each { |action| actor.send action }
|
options[:actions].each { |action| actor.send action }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def execute_apply_to!
|
||||||
|
require 'switchtower/generators/rails/loader'
|
||||||
|
Generators::RailsLoader.load! @options
|
||||||
|
end
|
||||||
|
|
||||||
|
APPLY_TO_OPTIONS = [:apply_to]
|
||||||
|
RECIPE_OPTIONS = [:password]
|
||||||
|
|
||||||
|
def check_options!
|
||||||
|
apply_to_given = !(@options.keys & APPLY_TO_OPTIONS).empty?
|
||||||
|
recipe_given = !(@options.keys & RECIPE_OPTIONS).empty? ||
|
||||||
|
!@options[:recipes].empty? ||
|
||||||
|
!@options[:actions].empty?
|
||||||
|
|
||||||
|
if apply_to_given && recipe_given
|
||||||
|
abort "You cannot specify both recipe options and framework integration options."
|
||||||
|
elsif !apply_to_given
|
||||||
|
abort "You must specify at least one recipe" if @options[:recipes].empty?
|
||||||
|
abort "You must specify at least one action" if @options[:actions].empty?
|
||||||
|
else
|
||||||
|
@options[:application] = args.shift
|
||||||
|
@options[:recipe_file] = args.shift
|
||||||
|
|
||||||
|
abort "You must specify the name of the application" if @options[:application].nil?
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
class DeploymentGenerator < Rails::Generator::NamedBase
|
||||||
|
attr_reader :recipe_file
|
||||||
|
|
||||||
|
def initialize(runtime_args, runtime_options = {})
|
||||||
|
super
|
||||||
|
@recipe_file = @args.shift || "deploy"
|
||||||
|
end
|
||||||
|
|
||||||
|
def manifest
|
||||||
|
record do |m|
|
||||||
|
m.directory "script"
|
||||||
|
m.file "switchtower", File.join("script", "switchtower")
|
||||||
|
m.directory "config/tasks"
|
||||||
|
m.template "deploy.rb", File.join("config", "#{recipe_file}.rb")
|
||||||
|
m.template "switchtower.rake", File.join("config", "tasks", "switchtower.rake")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
# Override with your own usage banner.
|
||||||
|
def banner
|
||||||
|
"Usage: #{$0} deployment ApplicationName [recipe-name]\n" +
|
||||||
|
" (recipe-name defaults to \"deploy\")"
|
||||||
|
end
|
||||||
|
end
|
116
lib/switchtower/generators/rails/deployment/templates/deploy.rb
Normal file
116
lib/switchtower/generators/rails/deployment/templates/deploy.rb
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
# 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, "<%= singular_name %>"
|
||||||
|
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 <<DESC
|
||||||
|
An imaginary backup task. (Execute the 'show_tasks' task to display all
|
||||||
|
available tasks.)
|
||||||
|
DESC
|
||||||
|
task :backup, :roles => :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
|
|
@ -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!
|
|
@ -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
|
20
lib/switchtower/generators/rails/loader.rb
Normal file
20
lib/switchtower/generators/rails/loader.rb
Normal file
|
@ -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
|
|
@ -1,7 +1,7 @@
|
||||||
module SwitchTower
|
module SwitchTower
|
||||||
module Version #:nodoc:
|
module Version #:nodoc:
|
||||||
MAJOR = 0
|
MAJOR = 0
|
||||||
MINOR = 8
|
MINOR = 9
|
||||||
TINY = 0
|
TINY = 0
|
||||||
|
|
||||||
STRING = [MAJOR, MINOR, TINY].join(".")
|
STRING = [MAJOR, MINOR, TINY].join(".")
|
||||||
|
|
Loading…
Reference in a new issue