Class: Capissh::Invocation
- Inherits:
-
Object
- Object
- Capissh::Invocation
- Defined in:
- lib/capissh/invocation.rb
Instance Attribute Summary collapse
-
#configuration ⇒ Object
readonly
Returns the value of attribute configuration.
-
#connection_manager ⇒ Object
readonly
Returns the value of attribute connection_manager.
-
#logger ⇒ Object
readonly
Returns the value of attribute logger.
Instance Method Summary collapse
-
#add_default_command_options(options) ⇒ Object
Merges the various default command options into the options hash and returns the result.
- #continue_execution(tree) ⇒ Object
- #continue_execution_for_branch(branch) ⇒ Object
-
#initialize(configuration, connection_manager, options = {}) ⇒ Invocation
constructor
A new instance of Invocation.
-
#invoke_command(servers, cmd, options = {}, &block) ⇒ Object
Invokes the given command.
-
#parallel(servers, options = {}) ⇒ Object
Executes different commands in parallel.
-
#run(servers, cmd, options = {}, &block) ⇒ Object
Execute the given command on all servers that are the target of the current task.
-
#run_tree(servers, tree, options = {}) ⇒ Object
Executes a Capissh::Command::Tree object.
-
#sudo(servers, command, options = {}, &block) ⇒ Object
Invoked like #run, but executing the command via sudo.
-
#sudo_behavior_callback(fallback) ⇒ Object
Returns a Proc object that defines the behavior of the sudo callback.
-
#sudo_command(options = {}, &block) ⇒ Object
Returns the command string used by capissh to invoke a comamnd via sudo.
-
#sudo_prompt ⇒ Object
Returns the prompt text to use with sudo.
Constructor Details
#initialize(configuration, connection_manager, options = {}) ⇒ Invocation
Returns a new instance of Invocation.
7 8 9 10 11 |
# File 'lib/capissh/invocation.rb', line 7 def initialize(configuration, connection_manager, ={}) @configuration = configuration @connection_manager = connection_manager @logger = [:logger] end |
Instance Attribute Details
#configuration ⇒ Object (readonly)
Returns the value of attribute configuration.
5 6 7 |
# File 'lib/capissh/invocation.rb', line 5 def configuration @configuration end |
#connection_manager ⇒ Object (readonly)
Returns the value of attribute connection_manager.
5 6 7 |
# File 'lib/capissh/invocation.rb', line 5 def connection_manager @connection_manager end |
#logger ⇒ Object (readonly)
Returns the value of attribute logger.
5 6 7 |
# File 'lib/capissh/invocation.rb', line 5 def logger @logger end |
Instance Method Details
#add_default_command_options(options) ⇒ Object
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.
238 239 240 241 242 243 244 245 246 247 248 249 250 |
# File 'lib/capissh/invocation.rb', line 238 def () defaults = configuration.fetch(:default_run_options, {}) = defaults.merge() env = configuration.fetch(:default_environment, {}) env = env.merge([:env]) if [:env] [:env] = env unless env.empty? shell = [:shell] || configuration.fetch(:default_shell, nil) [:shell] = shell unless shell.nil? end |
#continue_execution(tree) ⇒ Object
257 258 259 260 261 262 263 264 |
# File 'lib/capissh/invocation.rb', line 257 def continue_execution(tree) if tree.branches.length == 1 continue_execution_for_branch(tree.branches.first) else tree.each { |branch| branch.skip! unless continue_execution_for_branch(branch) } tree.any? { |branch| !branch.skip? } end end |
#continue_execution_for_branch(branch) ⇒ Object
266 267 268 269 270 271 272 273 274 275 |
# File 'lib/capissh/invocation.rb', line 266 def continue_execution_for_branch(branch) case Capissh::CLI.debug_prompt(branch) when "y" true when "n" false when "a" exit(-1) end end |
#invoke_command(servers, cmd, options = {}, &block) ⇒ Object
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.
67 68 69 70 71 |
# File 'lib/capissh/invocation.rb', line 67 def invoke_command(servers, cmd, ={}, &block) = .dup via = .delete(:via) || :run send(via, servers, cmd, , &block) end |
#parallel(servers, options = {}) ⇒ Object
Executes different commands in parallel. This is useful for commands that need to be different on different hosts, but which could be otherwise run in parallel.
The options
parameter is currently unused.
Example:
parallel do |session|
session.when "in?(:app)", "/path/to/restart/mongrel"
session.when "in?(:web)", "/path/to/restart/apache"
session.when "in?(:db)", "/path/to/restart/mysql"
end
Each command may have its own callback block, for capturing and responding to output, with semantics identical to #run:
session.when "in?(:app)", "/path/to/restart/mongrel" do |ch, stream, data|
# ch is the SSH channel for this command, used to send data
# back to the command (e.g. ch.send_data("password\n"))
# stream is either :out or :err, for which stream the data arrived on
# data is a string containing data sent from the remote command
end
Also, you can specify a fallback command, to use when none of the conditions match a server:
session.else "/execute/something/else"
The string specified as the first argument to when
may be any valid Ruby code. It has access to the following variables and methods:
-
server
is the ServerDefinition object for the server. This can be used to get the host-name, etc. -
configuration
is the current Capissh::Configuration object, which you can use to get the value of variables, etc.
For example:
session.when "server.host =~ /app/", "/some/command"
session.when "server.host == configuration[:some_var]", "/another/command"
session.when "server.role?(:web) || server.role?(:app)", "/more/commands"
See #run for a description of the valid options
.
57 58 59 60 61 |
# File 'lib/capissh/invocation.rb', line 57 def parallel(servers, ={}) raise ArgumentError, "parallel() requires a block" unless block_given? tree = Command::Tree.new(configuration) { |t| yield t } run_tree(servers, tree, ) end |
#run(servers, cmd, options = {}, &block) ⇒ Object
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.
The options
hash may include any of the following keys:
-
:on_no_matching_servers - if :continue, will continue to execute tasks if no matching servers are found for the host criteria. The default is to raise a NoMatchingServersError exception.
-
:max_hosts - specifies the maximum number of hosts that should be selected at a time. If this value is less than the number of hosts that are selected to run, then the hosts will be run in groups of max_hosts. The default is nil, which indicates that there is no maximum host limit. Please note this does not limit the number of SSH channels that can be open, only the number of hosts upon which this will be called.
-
:shell - says which shell should be used to invoke commands. This defaults to “sh”. Setting this to false causes Capissh to invoke the commands directly, without wrapping them in a shell invocation.
-
:data - if not nil (the default), this should be a string that will be passed to the command’s stdin stream.
-
:pty - if true, a pseudo-tty will be allocated for each command. The default is false. Note that there are benefits and drawbacks both ways. Empirically, it appears that if a pty is allocated, the SSH server daemon will not read user shell start-up scripts (e.g. bashrc, etc.). However, if a pty is not allocated, some commands will refuse to run in interactive mode and will not prompt for (e.g.) passwords.
-
:env - a hash of environment variable mappings that should be made available to the command. The keys should be environment variable names, and the values should be their corresponding values. The default is empty, but may be modified by changing the
default_environment
Capissh variable. -
:eof - if true, the standard input stream will be closed after sending any data specified in the :data option. If false, the input stream is left open. The default is to close the input stream only if no block is passed.
Note that if you set these keys in the default_run_options
Capissh variable, they will apply for all invocations of #run, #invoke_command, and #parallel.
115 116 117 118 119 120 121 122 |
# File 'lib/capissh/invocation.rb', line 115 def run(servers, cmd, ={}, &block) if [:eof].nil? && !cmd.include?(sudo_command) = .merge(:eof => !block_given?) end block ||= Command.default_io_proc tree = Command::Tree.twig(configuration, cmd, &block) run_tree(servers, tree, ) end |
#run_tree(servers, tree, options = {}) ⇒ Object
Executes a Capissh::Command::Tree object. This is not for direct use, but should instead be called indirectly, via #run or #parallel, or #invoke_command.
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 |
# File 'lib/capissh/invocation.rb', line 127 def run_tree(servers, tree, ={}) if tree.branches.empty? && tree.fallback logger.debug "executing #{tree.fallback}" unless [:silent] elsif tree.branches.any? logger.debug "executing multiple commands in parallel" tree.each do |branch| logger.trace "-> #{branch}" end else raise ArgumentError, "attempt to execute without specifying a command" end return if configuration.dry_run || (configuration.debug && continue_execution(tree) == false) = () tree.each do |branch| if branch.command.include?(sudo_command) branch.callback = sudo_behavior_callback(branch.callback) end end connection_manager.execute_on_servers(servers, ) do |sessions| Command.process(tree, sessions, .merge(:logger => logger)) end end |
#sudo(servers, command, options = {}, &block) ⇒ Object
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 :sudo
configuration variable, which (if specified) will be used as the full path to the sudo executable on the remote machine:
Capissh.new(sudo: "/opt/local/bin/sudo")
If you know what you’re doing, you can also set :sudo_prompt
, which tells capissh which prompt sudo should use when asking for a password. (This is so that capissh knows what prompt to look for in the output.) If you set :sudo_prompt to an empty string, Capissh will not send a preferred prompt.
171 172 173 |
# File 'lib/capissh/invocation.rb', line 171 def sudo(servers, command, ={}, &block) run(servers, "#{sudo_command()} #{command}", , &block) end |
#sudo_behavior_callback(fallback) ⇒ Object
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.
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 |
# File 'lib/capissh/invocation.rb', line 205 def sudo_behavior_callback(fallback) # 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 =~ /^Sorry, try again/ if prompt_host.nil? || prompt_host == ch[:server] prompt_host = ch[:server] logger.important out, "#{stream} :: #{ch[:server]}" reset! :password end end if out =~ /^#{Regexp.escape(sudo_prompt)}/ ch.send_data "#{configuration.fetch(:password,nil)}\n" elsif fallback fallback.call(ch, stream, out) end end end |
#sudo_command(options = {}, &block) ⇒ Object
Returns the command string used by capissh to invoke a comamnd via sudo.
run "#{sudo_command :as => 'bob'} mkdir /path/to/dir"
Also, this method understands a :sudo
configuration variable, which (if specified) will be used as the full path to the sudo executable on the remote machine:
Capissh.new(sudo: "/opt/local/bin/sudo")
If you know what you’re doing, you can also set :sudo_prompt
, which tells capissh which prompt sudo should use when asking for a password. (This is so that capissh knows what prompt to look for in the output.) If you set :sudo_prompt to an empty string, Capissh will not send a preferred prompt.
191 192 193 194 195 196 |
# File 'lib/capissh/invocation.rb', line 191 def sudo_command(={}, &block) user = [:as] && "-u #{.delete(:as)}" sudo_prompt_option = "-p '#{sudo_prompt}'" unless sudo_prompt.empty? [configuration.fetch(:sudo, "sudo"), sudo_prompt_option, user].compact.join(" ") end |
#sudo_prompt ⇒ Object
Returns the prompt text to use with sudo
253 254 255 |
# File 'lib/capissh/invocation.rb', line 253 def sudo_prompt configuration.fetch(:sudo_prompt, "sudo password: ") end |