Class: SSH

Inherits:
Object
  • Object
show all
Includes:
FakePOpen, Open4
Defined in:
lib/ssh.rb,
lib/ssh_test.rb

Overview

SSH provides a simple streaming ssh command runner. That’s it. This is a one trick pony.

ssh = SSH.new "example.com", "/var/log"
puts ssh.run "ls"

SSH was extracted from rake-remote_task which was extracted from vlad.

SSH’s idea contributed by Joel Parker Henderson.

Defined Under Namespace

Classes: CommandFailedError, Error

Constant Summary collapse

VERSION =
"1.1.0"

Instance Attribute Summary collapse

Attributes included from FakePOpen

#action, #commands, #error, #input, #output

Instance Method Summary collapse

Methods included from FakePOpen

#popen4, #select

Constructor Details

#initialize(target_host = nil, target_dir = nil) ⇒ SSH

Returns a new instance of SSH.



32
33
34
35
36
37
38
39
40
# File 'lib/ssh.rb', line 32

def initialize target_host = nil, target_dir = nil
  self.ssh_cmd       = "ssh"
  self.ssh_flags     = []
  self.target_host   = target_host
  self.target_dir    = target_dir

  self.sudo_prompt   = /^Password:/
  self.sudo_password = nil
end

Instance Attribute Details

#ssh_cmdObject

Returns the value of attribute ssh_cmd.



29
30
31
# File 'lib/ssh.rb', line 29

def ssh_cmd
  @ssh_cmd
end

#ssh_flagsObject

Returns the value of attribute ssh_flags.



29
30
31
# File 'lib/ssh.rb', line 29

def ssh_flags
  @ssh_flags
end

#sudo_passwordObject

Returns the value of attribute sudo_password.



30
31
32
# File 'lib/ssh.rb', line 30

def sudo_password
  @sudo_password
end

#sudo_promptObject

Returns the value of attribute sudo_prompt.



30
31
32
# File 'lib/ssh.rb', line 30

def sudo_prompt
  @sudo_prompt
end

#target_dirObject

Returns the value of attribute target_dir.



29
30
31
# File 'lib/ssh.rb', line 29

def target_dir
  @target_dir
end

#target_hostObject

Returns the value of attribute target_host.



29
30
31
# File 'lib/ssh.rb', line 29

def target_host
  @target_host
end

Instance Method Details

#empty_streams(pid, inn, out, err) ⇒ Object



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
# File 'lib/ssh.rb', line 68

def empty_streams pid, inn, out, err
  result  = []
  inn.sync   = true
  streams    = [out, err]
  out_stream = {
    out => $stdout,
    err => $stderr,
  }

  # Handle process termination ourselves
  status = nil
  Thread.start do
    status = Process.waitpid2(pid).last
  end

  until streams.empty? do
    # don't busy loop
    selected, = select streams, nil, nil, 0.1

    next if selected.nil? or selected.empty?

    selected.each do |stream|
      if stream.eof? then
        streams.delete stream if status # we've quit, so no more writing
        next
      end

      data = stream.readpartial(1024)
      out_stream[stream].write data

      if stream == err and data =~ sudo_prompt then
        inn.puts sudo_password
        data << "\n"
        $stderr.write "\n"
      end

      result << data
    end
  end

  return status, result
end

#run(command) ⇒ Object



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
# File 'lib/ssh.rb', line 42

def run command
  command = "cd #{target_dir} && #{command}" if target_dir
  cmd     = [ssh_cmd, ssh_flags, target_host, command].flatten

  if $DEBUG then
    trace = [ssh_cmd, ssh_flags, target_host, "'#{command}'"]
    warn trace.flatten.join ' '
  end

  pid, inn, out, err = popen4(*cmd)

  status, result = empty_streams pid, inn, out, err

  unless status.success? then
    e = status.exitstatus
    c = cmd.join ' '
    raise(CommandFailedError.new(status), "Failed with status #{e}: #{c}")
  end

  result.join
ensure
  inn.close rescue nil
  out.close rescue nil
  err.close rescue nil
end