~ubuntu-branches/ubuntu/vivid/ruby-net-ssh-multi/vivid

« back to all changes in this revision

Viewing changes to lib/net/ssh/multi/session_actions.rb

  • Committer: Bazaar Package Importer
  • Author(s): Lucas Nussbaum
  • Date: 2011-04-16 09:56:28 UTC
  • Revision ID: james.westby@ubuntu.com-20110416095628-fqa3s532t1y89siq
Tags: upstream-1.1
ImportĀ upstreamĀ versionĀ 1.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
module Net; module SSH; module Multi
 
2
 
 
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.
 
8
  module SessionActions
 
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.
 
12
    def master
 
13
      self
 
14
    end
 
15
 
 
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
 
19
    # been made.
 
20
    def connect!
 
21
      sessions
 
22
      self
 
23
    end
 
24
 
 
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) }
 
31
    end
 
32
 
 
33
    # Returns an array of all SSH sessions, blocking until all sessions have
 
34
    # connected.
 
35
    def sessions
 
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
 
39
    end
 
40
 
 
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.
 
44
    #
 
45
    #   session.send_global_request("keep-alive@openssh.com")
 
46
    #
 
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) }
 
52
      self
 
53
    end
 
54
 
 
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.
 
59
    #
 
60
    # All new channels will be collected and returned, aggregated into a new
 
61
    # Net::SSH::Multi::Channel instance.
 
62
    #
 
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:
 
66
    #
 
67
    # * :server => the Net::SSH::Multi::Server instance that spawned the channel
 
68
    # * :host => the host name of the server
 
69
    #
 
70
    # Having access to these things lets you more easily report which host
 
71
    # (e.g.) data was received from:
 
72
    #
 
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]}"
 
77
    #       end
 
78
    #     end
 
79
    #   end
 
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
 
86
        end
 
87
      end
 
88
      Multi::Channel.new(master, channels)
 
89
    end
 
90
 
 
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).
 
95
    #
 
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:
 
100
    #
 
101
    #   session.exec("command") do |ch, stream, data|
 
102
    #     puts "[#{ch[:host]} : #{stream}] #{data}"
 
103
    #   end
 
104
    #
 
105
    # If no block is given, all output will be written to +$stdout+ or
 
106
    # +$stderr+, as appropriate.
 
107
    #
 
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:
 
112
    #
 
113
    #   channel = session.exec("command") { ... }
 
114
    #   channel.wait
 
115
    #
 
116
    #   if channel.any? { |c| c[:exit_status] != 0 }
 
117
    #     puts "executing failed on at least one host!"
 
118
    #   end
 
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
 
123
 
 
124
          channel.on_data do |ch, data|
 
125
            if block
 
126
              block.call(ch, :stdout, data)
 
127
            else
 
128
              data.chomp.each_line do |line|
 
129
                $stdout.puts("[#{ch[:host]}] #{line}")
 
130
              end
 
131
            end
 
132
          end
 
133
 
 
134
          channel.on_extended_data do |ch, type, data|
 
135
            if block
 
136
              block.call(ch, :stderr, data)
 
137
            else
 
138
              data.chomp.each_line do |line|
 
139
                $stderr.puts("[#{ch[:host]}] #{line}")
 
140
              end
 
141
            end
 
142
          end
 
143
 
 
144
          channel.on_request("exit-status") do |ch, data|
 
145
            ch[:exit_status] = data.read_long
 
146
          end
 
147
        end
 
148
      end
 
149
    end
 
150
 
 
151
  end
 
152
 
 
153
end; end; end
 
 
b'\\ No newline at end of file'