1
0
Fork 0
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:
Jamis Buck 2005-08-31 15:54:33 +00:00
parent c8f3edb93d
commit 9678a690f2
7 changed files with 304 additions and 32 deletions

View file

@ -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 <<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
abort "You must specify at least one recipe" if @options[:recipes].empty?
abort "You must specify at least one action" if @options[:actions].empty?
check_options!
unless @options.has_key?(:password)
@options[:password] = Proc.new do
@ -113,18 +146,55 @@ module SwitchTower
end
def execute!
config = SwitchTower::Configuration.new
config.logger.level = options[:verbose]
config.set :password, options[:password]
config.set :pretend, options[:pretend]
config.load "standard" # load the standard recipe definition
options[:recipes].each { |recipe| config.load(recipe) }
options[:vars].each { |name, value| config.set(name, value) }
actor = config.actor
options[:actions].each { |action| actor.send action }
if !@options[:recipes].empty?
execute_recipes!
elsif @options[:apply_to]
execute_apply_to!
end
end
private
def execute_recipes!
config = SwitchTower::Configuration.new
config.logger.level = options[:verbose]
config.set :password, options[:password]
config.set :pretend, options[:pretend]
config.load "standard" # load the standard recipe definition
options[:recipes].each { |recipe| config.load(recipe) }
options[:vars].each { |name, value| config.set(name, value) }
actor = config.actor
options[:actions].each { |action| actor.send action }
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

View file

@ -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

View 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

View file

@ -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!

View file

@ -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

View 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

View file

@ -1,7 +1,7 @@
module SwitchTower
module Version #:nodoc:
MAJOR = 0
MINOR = 8
MINOR = 9
TINY = 0
STRING = [MAJOR, MINOR, TINY].join(".")