lib/sprinkle/actors/capistrano.rb
require 'capistrano/cli'
module Sprinkle
module Actors
# The Capistrano actor uses Capistrano to define your roles and deliver
# commands to your remote servers. You'll need the capistrano gem installed.
#
# The only configuration option is to specify a recipe.
#
# deployment do
# delivery :capistrano do
# recipe 'deploy'
# recipe 'more'
# end
# end
#
# Recipes is given a list of files which capistrano will include and load.
# These recipes are mainly to set variables such as :user, :password, and to
# set the app domain which will be sprinkled.
class Capistrano < Actor
attr_accessor :config, :loaded_recipes #:nodoc:
def initialize(&block) #:nodoc:
@installer = nil
@config = ::Capistrano::Configuration.new
@config.logger.level = Sprinkle::OPTIONS[:verbose] ? ::Capistrano::Logger::INFO : ::Capistrano::Logger::IMPORTANT
@config.set(:password) { ::Capistrano::CLI.password_prompt }
# default sudo to false, we must turn it on
@config.set(:run_method) { @config.fetch(:use_sudo, false) ? :sudo : :run }
@config.set(:_sprinkle_actor, self)
def @config.recipes(script)
_sprinkle_actor.recipes(script)
end
if block
@config.instance_eval(&block)
else
@config.load "Capfile" if File.exist?("Capfile")
end
end
def sudo? #:nodoc:
@config.fetch(:run_method) == :sudo
end
def sudo_command #:nodoc:
@config.sudo
end
# Determines if there are any servers for the given roles
def servers_for_role?(roles) #:nodoc:
roles=Array(roles)
roles.any? { |r| @config.roles.keys.include?(r) }
end
# Defines a recipe file which will be included by capistrano. Use these
# recipe files to set capistrano specific configurations. Default recipe
# included is "deploy." But if any other recipe is specified, it will
# include that instead. Multiple recipes may be specified through multiple
# recipes calls, an example:
#
# deployment do
# delivery :capistrano do
# recipe 'deploy'
# recipes 'magic_beans', 'normal_beans'
# end
# end
def recipe(scripts)
@loaded_recipes ||= []
Array(scripts).each do |script|
@config.load script
@loaded_recipes << script
end
end
def recipes(scripts) #:nodoc:
recipe(scripts)
end
def install(installer, roles, opts = {}) #:nodoc:
@installer = installer
process(installer.package.name, installer.install_sequence, roles, opts)
rescue ::Capistrano::CommandError => e
raise_error(e)
ensure
@installer = nil
end
def verify(verifier, roles, opts = {}) #:nodoc:
process(verifier.package.name, verifier.commands, roles)
rescue ::Capistrano::CommandError
return false
end
def process(name, commands, roles, opts = {}) #:nodoc:
inst=@installer
@log_recorder = log_recorder = Sprinkle::Utility::LogRecorder.new
commands = commands.map {|x| rewrite_command(x)}
define_task(name, roles) do
via = fetch(:run_method)
commands.each do |command|
if command.is_a? Commands::Transfer
upload command.source, command.destination, :via => :scp,
:recursive => command.recursive?
elsif command.is_a? Commands::Reconnect
teardown_connections_to(sessions.keys)
else
# this reset the log
log_recorder.reset command
invoke_command(command, {:via => via}) do |ch, stream, out|
::Capistrano::Configuration.default_io_proc.call(ch, stream, out) if Sprinkle::OPTIONS[:verbose]
log_recorder.log(stream, out)
end
end
end
end
run_task(name, opts)
end
private
# rip out any double sudos from the beginning of the command
def rewrite_command(cmd)
return cmd if cmd.is_a?(Symbol)
via = @config.fetch(:run_method)
if via == :sudo and cmd =~ /^#{sudo_command}/
cmd.gsub(/^#{sudo_command}\s?/,"")
else
cmd
end
end
def raise_error(e)
details={:command => @log_recorder.command, :code => "??",
:message => e.message,
:hosts => e.hosts,
:error => @log_recorder.err, :stdout => @log_recorder.out}
raise Sprinkle::Errors::RemoteCommandFailure.new(@installer, details, e)
end
def run_task(task, opts={})
run(task)
true
end
# REVISIT: can we set the description somehow?
def define_task(name, roles, &block)
@config.task task_sym(name), :roles => roles, &block
end
def run(task)
@config.send task_sym(task)
end
def task_sym(name)
"install_#{name.to_task_name}".to_sym
end
end
end
end
=begin
# channel: the SSH channel object used for this response
# stream: either :err or :out, for stderr or stdout responses
# output: the text that the server is sending, might be in chunks
run "apt-get update" do |channel, stream, output|
if output =~ /Are you sure?/
answer = Capistrano::CLI.ui.ask("Are you sure: ")
channel.send_data(answer + "\n")
else
# allow the default callback to be processed
Capistrano::Configuration.default_io_proc.call[channel, stream, output]
end
end
You can tell subversion to use a different username+password by
setting a couple variables:
set :svn_username, "my svn username"
set :svn_password, "my svn password"
If you don't want to set the password explicitly in your recipe like
that, you can make capistrano prompt you for it like this:
set(:svn_password) { Capistrano::CLI.password_prompt("Subversion
password: ") }
- Jamis
=end