Class: SSHwrap::Main
- Inherits:
-
Object
- Object
- SSHwrap::Main
- Defined in:
- lib/sshwrap/main.rb
Instance Method Summary collapse
-
#initialize(options = {}) ⇒ Main
constructor
A new instance of Main.
- #ssh_execute(cmd, target) ⇒ Object
-
#sshwrap(cmd, targets) ⇒ Object
cmd is a string or array of strings containing the command and arguments targets is an array of remote system hostnames.
Constructor Details
#initialize(options = {}) ⇒ Main
Returns a new instance of Main.
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 |
# File 'lib/sshwrap/main.rb', line 22 def initialize(={}) home = Dir.respond_to?(:home) ? Dir.home : ENV['HOME'] || ENV['LOGDIR'] conffile = "#{home}/.sshwrap.yaml" if File.exist?(conffile) conf = YAML.load_file(conffile) else conf = {} end @mutex = Mutex.new @max_workers = [:max_workers] || 1 @abort_on_failure = [:abort_on_failure] @user = [:user] || Etc.getlogin @ssh_key = [:ssh_key] @debug = [:debug] @prompter = SSHwrap::Prompter.new @ssh_prompt = "Password for #{@user}: " if conf['password_regexp'] @password_regexp = Regexp.new('(' + conf['password_regexp'] + ')') elsif [:password_regexp] @password_regexp = [:password_regexp] else # sudo on Mac OS X: # Password: # sudo on Red Hat, Debian, Ubuntu: # [sudo] password for <user>: @password_regexp = /(Password: |\[sudo\] password for .*: )/ end end |
Instance Method Details
#ssh_execute(cmd, target) ⇒ Object
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 |
# File 'lib/sshwrap/main.rb', line 53 def ssh_execute(cmd, target) exitstatus = nil stdout = [] stderr = [] params = {} if @ssh_key params[:keys] = [@ssh_key] end using_password = false if @prompter.passwords[@ssh_prompt] using_password = true params[:password] = @prompter.passwords[@ssh_prompt] end begin Net::SSH.start(target, @user, params) do |ssh| puts "Connecting to #{target}" if @debug ch = ssh.open_channel do |channel| # Now we request a "pty" (i.e. interactive) session so we can send # data back and forth if needed. It WILL NOT WORK without this, # and it has to be done before any call to exec. channel.request_pty do |ch_pty, success| if !success raise "Could not obtain pty (interactive ssh session) on #{target}" end end channel.exec(cmd) do |ch_exec, success| puts "Executing '#{cmd}' on #{target}" if @debug # 'success' isn't related to process exit codes or anything, but # more about ssh internals. Not sure why it would fail at such # a basic level, but it seems smart to do something about it. if !success raise "SSH unable to execute command on #{target}" end # on_data is a hook that fires when ssh returns output data. This # is what we've been doing all this for; now we can check to see # if it's a password prompt, and interactively return data if so # (see request_pty above). channel.on_data do |ch_data, data| if data =~ @password_regexp prompt = $1 channel.send_data "#{@prompter.prompt(prompt)}\n" else stdout << data unless (data.nil? or data.empty?) end end channel.on_extended_data do |ch_onextdata, type, data| stderr << data unless (data.nil? or data.empty?) end channel.on_request "exit-status" do |ch_onreq, data| exitstatus = data.read_long end end end ch.wait ssh.loop end rescue Net::SSH::AuthenticationFailed if !using_password @prompter.prompt(@ssh_prompt) return ssh_execute(cmd, target) else stderr << "Authentication failed to #{target}" end rescue Exception => e stderr << "SSH connection error: #{e.}" end [exitstatus, stdout, stderr] end |
#sshwrap(cmd, targets) ⇒ Object
cmd is a string or array of strings containing the command and arguments targets is an array of remote system hostnames
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 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 |
# File 'lib/sshwrap/main.rb', line 131 def sshwrap(cmd, targets) cmdstring = nil if cmd.kind_of?(Array) cmdstring = cmd.join(' ') else cmdstring = cmd.to_s end statuses = {} threads = (1..@max_workers).map do |i| Thread.new("worker#{i}") do |tname| while true target = nil @mutex.synchronize do target = targets.shift end if !target break end puts "Thread #{tname} processing target #{target}" if @debug exitstatus, stdout, stderr = ssh_execute(cmdstring, target) statuses[target] = exitstatus @mutex.synchronize do puts '==================================================' if !stdout.empty? puts "Output from #{target}:" puts stdout.join end if !stderr.empty? puts "Error from #{target}:" puts stderr.join end puts "Exit status from #{target}: #{exitstatus}" end if @abort_on_failure && exitstatus != 0 exit exitstatus end end end end threads.each(&:join) statuses end |