require 'capistrano/command' module Capistrano class Configuration module Actions module Invocation def self.included(base) #:nodoc: base.extend(ClassMethods) base.send :alias_method, :initialize_without_invocation, :initialize base.send :alias_method, :initialize, :initialize_with_invocation base.default_io_proc = Proc.new do |ch, stream, out| level = stream == :err ? :important : :info ch[:options][:logger].send(level, out, "#{stream} :: #{ch[:server]}") end end module ClassMethods attr_accessor :default_io_proc end def initialize_with_invocation(*args) #:nodoc: initialize_without_invocation(*args) set :default_environment, {} set :default_run_options, {} end # Invokes the given command. If a +via+ key is given, it will be used # to determine what method to use to invoke the command. It defaults # to :run, but may be :sudo, or any other method that conforms to the # same interface as run and sudo. def invoke_command(cmd, options={}, &block) options = options.dup via = options.delete(:via) || :run send(via, cmd, options, &block) end # Execute the given command on all servers that are the target of the # current task. If a block is given, it is invoked for all output # generated by the command, and should accept three parameters: the SSH # channel (which may be used to send data back to the remote process), # the stream identifier (:err for stderr, and :out for # stdout), and the data that was received. def run(cmd, options={}, &block) block ||= self.class.default_io_proc logger.debug "executing #{cmd.strip.inspect}" options = add_default_command_options(options) execute_on_servers(options) do |servers| targets = servers.map { |s| sessions[s] } Command.process(cmd, targets, options.merge(:logger => logger), &block) end end # Like #run, but executes the command via sudo. This assumes # that the sudo password (if required) is the same as the password for # logging in to the server. # # Also, this module accepts a :sudo configuration variable, # which (if specified) will be used as the full path to the sudo # executable on the remote machine: # # set :sudo, "/opt/local/bin/sudo" def sudo(command, options={}, &block) block ||= self.class.default_io_proc options = options.dup as = options.delete(:as) user = as && "-u #{as}" command = [fetch(:sudo, "sudo"), "-p '#{sudo_prompt}'", user, command].compact.join(" ") run(command, options, &sudo_behavior_callback(block)) end # Returns a Proc object that defines the behavior of the sudo # callback. The returned Proc will defer to the +fallback+ argument # (which should also be a Proc) for any output it does not # explicitly handle. def sudo_behavior_callback(fallback) #:nodoc: # in order to prevent _each host_ from prompting when the password # was wrong, let's track which host prompted first and only allow # subsequent prompts from that host. prompt_host = nil Proc.new do |ch, stream, out| if out =~ /^#{Regexp.escape(sudo_prompt)}/ ch.send_data "#{self[:password]}\n" elsif out =~ /try again/ if prompt_host.nil? || prompt_host == ch[:server] prompt_host = ch[:server] logger.important out, "#{stream} :: #{ch[:server]}" reset! :password end else fallback.call(ch, stream, out) end end end # Merges the various default command options into the options hash and # returns the result. The default command options that are understand # are: # # * :default_environment: If the :env key already exists, the :env # key is merged into default_environment and then added back into # options. # * :default_shell: if the :shell key already exists, it will be used. # Otherwise, if the :default_shell key exists in the configuration, # it will be used. Otherwise, no :shell key is added. def add_default_command_options(options) defaults = self[:default_run_options] options = defaults.merge(options) env = self[:default_environment] env = env.merge(options[:env]) if options[:env] options[:env] = env unless env.empty? shell = options[:shell] || self[:default_shell] options[:shell] = shell if shell options end # Returns the prompt text to use with sudo def sudo_prompt fetch(:sudo_prompt, "sudo password: ") end end end end end