1
module Net; module SSH; module Multi
3
# This module represents the actions that are available on session
4
# collections. Any class that includes this module needs only provide a
5
# +servers+ method that returns a list of Net::SSH::Multi::Server
6
# instances, and the rest just works. See Net::SSH::Multi::Session and
7
# Net::SSH::Multi::Subsession for consumers of this module.
9
# Returns the session that is the "master". This defaults to +self+, but
10
# classes that include this module may wish to change this if they are
11
# subsessions that depend on a master session.
16
# Connections are normally established lazily, as soon as they are needed.
17
# This method forces all servers in the current container to have their
18
# connections established immediately, blocking until the connections have
25
# Returns +true+ if any server in the current container has an open SSH
26
# session that is currently processing any channels. If +include_invisible+
27
# is +false+ (the default) then invisible channels (such as those created
28
# by port forwarding) will not be counted; otherwise, they will be.
29
def busy?(include_invisible=false)
30
servers.any? { |server| server.busy?(include_invisible) }
33
# Returns an array of all SSH sessions, blocking until all sessions have
36
threads = servers.map { |server| Thread.new { server.session(true) } if server.session.nil? }
37
threads.each { |thread| thread.join if thread }
38
servers.map { |server| server.session }.compact
41
# Sends a global request to the sessions for all contained servers
42
# (see #sessions). This can be used to (e.g.) ping the remote servers to
43
# prevent them from timing out.
45
# session.send_global_request("keep-alive@openssh.com")
47
# If a block is given, it will be invoked when the server responds, with
48
# two arguments: the Net::SSH connection that is responding, and a boolean
49
# indicating whether the request succeeded or not.
50
def send_global_request(type, *extra, &callback)
51
sessions.each { |ssh| ssh.send_global_request(type, *extra, &callback) }
55
# Asks all sessions for all contained servers (see #sessions) to open a
56
# new channel. When each server responds, the +on_confirm+ block will be
57
# invoked with a single argument, the channel object for that server. This
58
# means that the block will be invoked one time for each session.
60
# All new channels will be collected and returned, aggregated into a new
61
# Net::SSH::Multi::Channel instance.
63
# Note that the channels are "enhanced" slightly--they have two properties
64
# set on them automatically, to make dealing with them in a multi-session
65
# environment slightly easier:
67
# * :server => the Net::SSH::Multi::Server instance that spawned the channel
68
# * :host => the host name of the server
70
# Having access to these things lets you more easily report which host
71
# (e.g.) data was received from:
73
# session.open_channel do |channel|
74
# channel.exec "command" do |ch, success|
75
# ch.on_data do |ch, data|
76
# puts "got data #{data} from #{ch[:host]}"
80
def open_channel(type="session", *extra, &on_confirm)
81
channels = sessions.map do |ssh|
82
ssh.open_channel(type, *extra) do |c|
83
c[:server] = c.connection[:server]
84
c[:host] = c.connection[:server].host
85
on_confirm[c] if on_confirm
88
Multi::Channel.new(master, channels)
91
# A convenience method for executing a command on multiple hosts and
92
# either displaying or capturing the output. It opens a channel on all
93
# active sessions (see #open_channel and #active_sessions), and then
94
# executes a command on each channel (Net::SSH::Connection::Channel#exec).
96
# If a block is given, it will be invoked whenever data is received across
97
# the channel, with three arguments: the channel object, a symbol identifying
98
# which output stream the data was received on (+:stdout+ or +:stderr+)
99
# and a string containing the data that was received:
101
# session.exec("command") do |ch, stream, data|
102
# puts "[#{ch[:host]} : #{stream}] #{data}"
105
# If no block is given, all output will be written to +$stdout+ or
106
# +$stderr+, as appropriate.
108
# Note that #exec will also capture the exit status of the process in the
109
# +:exit_status+ property of each channel. Since #exec returns all of the
110
# channels in a Net::SSH::Multi::Channel object, you can check for the
111
# exit status like this:
113
# channel = session.exec("command") { ... }
116
# if channel.any? { |c| c[:exit_status] != 0 }
117
# puts "executing failed on at least one host!"
119
def exec(command, &block)
120
open_channel do |channel|
121
channel.exec(command) do |ch, success|
122
raise "could not execute command: #{command.inspect} (#{ch[:host]})" unless success
124
channel.on_data do |ch, data|
126
block.call(ch, :stdout, data)
128
data.chomp.each_line do |line|
129
$stdout.puts("[#{ch[:host]}] #{line}")
134
channel.on_extended_data do |ch, type, data|
136
block.call(ch, :stderr, data)
138
data.chomp.each_line do |line|
139
$stderr.puts("[#{ch[:host]}] #{line}")
144
channel.on_request("exit-status") do |ch, data|
145
ch[:exit_status] = data.read_long
b'\\ No newline at end of file'