Class: Knife::Server::SSH

Inherits:
Object
  • Object
show all
Defined in:
lib/knife/server/ssh.rb

Overview

Communicates with an SSH node.

Constant Summary collapse

DEFAULT_OPTIONS =
{ :user => "root", :port => "22" }.freeze
USER_SWITCH_COMMAND =
%{sudo USER=root HOME="$(getent passwd root | cut -d : -f 6)"}.freeze

Instance Method Summary collapse

Constructor Details

#initialize(params) ⇒ SSH

Returns a new instance of SSH.



30
31
32
33
34
35
36
# File 'lib/knife/server/ssh.rb', line 30

def initialize(params)
  options = DEFAULT_OPTIONS.merge(params)

  @host = options.delete(:host)
  @user = options.delete(:user)
  @options = options
end

Instance Method Details

#exec!(cmd) ⇒ Object



38
39
40
41
42
43
44
45
46
47
48
# File 'lib/knife/server/ssh.rb', line 38

def exec!(cmd)
  result = ""
  exit_code = nil
  Net::SSH.start(@host, @user, @options) do |session|
    exit_code = ssh_session(session, full_cmd(cmd), result)
  end
  if exit_code != 0
    raise "SSH exited with code #{exit_code} for [#{full_cmd(cmd)}]"
  end
  result
end

#exec_ssh(wrapper, content) ⇒ Object

rubocop:disable Metrics/MethodLength



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/knife/server/ssh.rb', line 109

def exec_ssh(wrapper, content) # rubocop:disable Metrics/MethodLength
  result = ""
  exit_status = nil

  Net::SSH.start(@host, @user, @options) do |ssh|
    ssh.open_channel do |ch|
      ch.on_open_failed do |_, _, desc|
        raise "Connection Error to #{ip}: #{desc}"
      end

      ch.exec(wrapper) do |channel, _, _|
        # spit out the shell script and close stdin so sh can do its magic
        channel.send_data(content)
        channel.eof!

        # then we just wait for sweet, sweet output
        channel.on_data do |_, data|
          result << data
        end

        channel.on_request("exit-status") do |_, data|
          exit_status = data.read_long
        end
      end

      ch.wait
    end

    ssh.loop
  end

  [result, exit_status]
end

#full_cmd(cmd) ⇒ Object



50
51
52
53
54
55
56
# File 'lib/knife/server/ssh.rb', line 50

def full_cmd(cmd)
  if @user == "root"
    cmd
  else
    [USER_SWITCH_COMMAND, %{bash -c '#{cmd}'}].join(" ")
  end
end

#run_script(content) ⇒ Object

runs a script on the target host by passing it to the stdin of a sh process. returns stdout and the exit status. does not care about stderr.



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/knife/server/ssh.rb', line 86

def run_script(content)
  user_switch = ""

  unless @user == "root"
    user_switch = USER_SWITCH_COMMAND
  end

  wrapper = <<-EOF
  if [ -e /dev/fd/0 ]
  then
    #{user_switch} /bin/sh /dev/fd/0
  elif [ -e /dev/stdin ]
  then
    #{user_switch} /bin/sh /dev/stdin
  else
    echo "Cannot find method of communicating with the shell via stdin"
    exit 1
  fi
  EOF

  exec_ssh(wrapper, content)
end

#ssh_session(session, cmd, result) ⇒ Object



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

def ssh_session(session, cmd, result)
  exit_code = nil
  session.open_channel do |channel|

    channel.request_pty

    channel.exec(cmd) do |_ch, _success|

      channel.on_data do |_ch, data|
        result << data
      end

      channel.on_extended_data do |_ch, _type, data|
        result << data
      end

      channel.on_request("exit-status") do |_ch, data|
        exit_code = data.read_long
      end
    end
  end

  session.loop
  exit_code
end