/
invocation.rb
161 lines (139 loc) · 5.85 KB
/
invocation.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
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 (<tt>:err</tt> for stderr, and <tt>:out</tt> 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}"
return if debug && continue_execution(cmd) == false
options = add_default_command_options(options)
if cmd.include?(sudo)
block = sudo_behavior_callback(block)
end
execute_on_servers(options) do |servers|
targets = servers.map { |s| sessions[s] }
Command.process(cmd, targets, options.merge(:logger => logger), &block)
end
end
# Returns the command string used by capistrano to invoke a comamnd via
# sudo.
#
# run "#{sudo :as => 'bob'} mkdir /path/to/dir"
#
# It can also be invoked like #run, but executing the command via sudo.
# This assumes that the sudo password (if required) is the same as the
# password for logging in to the server.
#
# sudo "mkdir /path/to/dir"
#
# Also, this method understands a <tt>:sudo</tt> 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(*parameters, &block)
options = parameters.last.is_a?(Hash) ? parameters.pop.dup : {}
command = parameters.first
user = options[:as] && "-u #{options.delete(:as)}"
sudo_command = [fetch(:sudo, "sudo"), "-p '#{sudo_prompt}'", user].compact.join(" ")
if command
command = sudo_command + " " + command
run(command, options, &block)
else
return sudo_command
end
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 =~ /^Sorry, try again/
if prompt_host.nil? || prompt_host == ch[:server]
prompt_host = ch[:server]
logger.important out, "#{stream} :: #{ch[:server]}"
reset! :password
end
elsif fallback
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 unless shell.nil?
options
end
# Returns the prompt text to use with sudo
def sudo_prompt
fetch(:sudo_prompt, "sudo password: ")
end
def continue_execution(cmd)
case Capistrano::CLI.debug_prompt(cmd)
when "y"
true
when "n"
false
when "a"
exit(-1)
end
end
end
end
end
end