Class: Vagrant::SSH::Session

Inherits:
Object
  • Object
show all
Includes:
Util::Retryable
Defined in:
lib/vagrant/ssh/session.rb

Overview

A helper class which wraps around Net::SSH::Connection::Session in order to provide basic command error checking while still providing access to the actual session object.

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Util::Retryable

#retryable

Constructor Details

#initialize(session, env) ⇒ Session

Returns a new instance of Session.



12
13
14
15
# File 'lib/vagrant/ssh/session.rb', line 12

def initialize(session, env)
  @session = session
  @env = env
end

Instance Attribute Details

#envObject (readonly)

Returns the value of attribute env.



10
11
12
# File 'lib/vagrant/ssh/session.rb', line 10

def env
  @env
end

#sessionObject (readonly)

Returns the value of attribute session.



9
10
11
# File 'lib/vagrant/ssh/session.rb', line 9

def session
  @session
end

Instance Method Details

#check_exit_status(exit_status, commands, options = nil, output = nil) ⇒ Object

Checks for an erroroneous exit status and raises an exception if so.



121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/vagrant/ssh/session.rb', line 121

def check_exit_status(exit_status, commands, options=nil, output=nil)
  if exit_status != 0
    output ||= '[no output]'
    options = {
      :_error_class => Errors::VagrantError,
      :_key => :ssh_bad_exit_status,
      :command => [commands].flatten.join("\n"),
      :output => output
    }.merge(options || {})

    raise options[:_error_class], options
  end
end

#exec!(commands, options = nil, &block) ⇒ Object

Executes a given command on the SSH session and blocks until the command completes. This is an almost line for line copy of the actual exec! implementation, except that this implementation also reports :exit_status to the block if given.



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/vagrant/ssh/session.rb', line 63

def exec!(commands, options=nil, &block)
  retryable(:tries => 5, :on => [IOError, Net::SSH::Disconnect], :sleep => 1.0) do
    metach = session.open_channel do |ch|
      ch.exec("#{env.config.ssh.shell} -l") do |ch2, success|
        # Set the terminal
        ch2.send_data "export TERM=vt100\n"

        # Output the commands as if they were entered on the command line
        [commands].flatten.each do |command|
          ch2.send_data "#{command}\n"
        end

        # Remember to exit
        ch2.send_data "exit\n"

        # Setup the callbacks
        setup_channel_callbacks(ch2, commands, options, block)
      end
    end

    metach.wait
    metach[:result]
  end
end

#setup_channel_callbacks(channel, command, options, block) ⇒ Object

Sets up the channel callbacks to properly check exit statuses and callback on stdout/stderr.



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

def setup_channel_callbacks(channel, command, options, block)
  options = { :error_check => true }.merge(options || {})

  block ||= Proc.new do |ch, type, data|
    check_exit_status(data, command, options, ch[:result]) if type == :exit_status && options[:error_check]

    ch[:result] ||= ""
    ch[:result] << data if [:stdout, :stderr].include?(type)
  end

  # Output stdout data to the block
  channel.on_data do |ch2, data|
    # This clears the screen, we want to filter it out.
    data.gsub!("\e[H", "")

    block.call(ch2, :stdout, data)
  end

  # Output stderr data to the block
  channel.on_extended_data do |ch2, type, data|
    block.call(ch2, :stderr, data)
  end

  # Output exit status information to the block
  channel.on_request("exit-status") do |ch2, data|
    block.call(ch2, :exit_status, data.read_long)
  end
end

#sudo!(commands, options = nil, &block) ⇒ Object

Executes a given command on the SSH session using sudo and blocks until the command completes. This takes the same parameters as #exec!. The only difference is that the command can be an array of commands, which will be placed into the same script.

This is different than just calling #exec! with sudo, since this command is tailor-made to be compliant with older versions of sudo.



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/vagrant/ssh/session.rb', line 35

def sudo!(commands, options=nil, &block)
  channel = session.open_channel do |ch|
    ch.exec("sudo -H #{env.config.ssh.shell} -l") do |ch2, success|
      # Set the terminal
      ch2.send_data "export TERM=vt100\n"

      # Output each command as if they were entered on the command line
      [commands].flatten.each do |command|
        ch2.send_data "#{command}\n"
      end

      # Remember to exit or we'll hang!
      ch2.send_data "exit\n"

      # Setup the callbacks with our options so we get all the
      # stdout/stderr and error checking goodies
      setup_channel_callbacks(ch2, commands, options, block)
    end
  end

  channel.wait
  channel[:result]
end

#test?(command) ⇒ Boolean

Executes a given command and simply returns true/false if the command succeeded or not.

Returns:

  • (Boolean)


19
20
21
22
23
24
25
# File 'lib/vagrant/ssh/session.rb', line 19

def test?(command)
  exec!(command) do |ch, type, data|
    return true if type == :exit_status && data == 0
  end

  false
end