hirura/hrr_rb_ssh

View on GitHub
lib/hrr_rb_ssh/client.rb

Summary

Maintainability
C
1 day
Test Coverage
require 'socket'
require 'hrr_rb_ssh/mode'
require 'hrr_rb_ssh/transport'
require 'hrr_rb_ssh/authentication'
require 'hrr_rb_ssh/connection'

module HrrRbSsh
  class Client
    include Loggable

    def self.start target, options={}
      client = self.new target, options
      client.start
      if block_given?
        begin
          yield client
        ensure
          client.close unless client.closed?
        end
      else
        client
      end
    end

    def initialize target, tmp_options={}
      @closed = true
      options = initialize_options tmp_options
      io = case target
           when IO
             target
           when Array
             io = TCPSocket.new *target
           when String
             port = 22
             io = TCPSocket.new target, port
           end
      transport      = HrrRbSsh::Transport.new      io, HrrRbSsh::Mode::CLIENT, options, logger: logger
      authentication = HrrRbSsh::Authentication.new transport, HrrRbSsh::Mode::CLIENT, options, logger: logger
      @connection    = HrrRbSsh::Connection.new     authentication, HrrRbSsh::Mode::CLIENT, options, logger: logger
    end

    def initialize_options tmp_options
      tmp_options = Hash[tmp_options.map{|k, v| [k.to_s, v]}]
      self.logger = tmp_options['logger']
      options = {}
      options['username'] = tmp_options['username']
      options['authentication_preferred_authentication_methods'] = tmp_options['authentication_preferred_authentication_methods']
      options['client_authentication_password']                  = tmp_options['password']
      options['client_authentication_publickey']                 = tmp_options['publickey']
      options['client_authentication_keyboard_interactive']      = tmp_options['keyboard_interactive']
      options['transport_preferred_encryption_algorithms']       = tmp_options['transport_preferred_encryption_algorithms']
      options['transport_preferred_server_host_key_algorithms']  = tmp_options['transport_preferred_server_host_key_algorithms']
      options['transport_preferred_kex_algorithms']              = tmp_options['transport_preferred_kex_algorithms']
      options['transport_preferred_mac_algorithms']              = tmp_options['transport_preferred_mac_algorithms']
      options['transport_preferred_compression_algorithms']      = tmp_options['transport_preferred_compression_algorithms']
      options
    end

    def start
      @connection.start foreground: false
      @closed = false
    end

    def loop
      @connection.loop
    end

    def closed?
      @closed
    end

    def close
      log_info { "closing client" }
      @closed = true
      @connection.close
      log_info { "client closed" }
    end

    def exec! command, pty: false, env: {}
      log_info { "start exec!: #{command}" }
      out_buf = StringIO.new
      err_buf = StringIO.new
      begin
        log_info { "Opning channel" }
        channel = @connection.request_channel_open "session"
        log_info { "Channel opened" }
        if pty
          channel.send_channel_request_pty_req 'xterm', 80, 24, 580, 336, ''
        end
        env.each{ |variable_name, variable_value|
          channel.send_channel_request_env variable_name, variable_value
        }
        channel.send_channel_request_exec command
        out_t = Thread.new {
          while true
            begin
              out_buf.write channel.io[1].readpartial(10240)
            rescue
              break
            end
          end
        }
        err_t = Thread.new {
          while true
            begin
              err_buf.write channel.io[2].readpartial(10240)
            rescue
              break
            end
          end
        }
        [out_t, err_t].each{ |t|
          begin
            t.join
          rescue => e
            log_warn { [e.backtrace[0], ": ", e.message, " (", e.class.to_s, ")\n\t", e.backtrace[1..-1].join("\n\t")].join }
          end
        }
      rescue => e
        log_error { "Failed opening channel" }
        log_error { [e.backtrace[0], ": ", e.message, " (", e.class.to_s, ")\n\t", e.backtrace[1..-1].join("\n\t")].join }
        raise "Error in exec!"
      ensure
        if channel
          log_info { "closing channel IOs" }
          channel.io.each{ |io| io.close rescue nil }
          log_info { "channel IOs closed" }
          log_info { "closing channel" }
          log_info { "wait until threads closed in channel" }
          channel.wait_until_closed
          channel.close
          log_info { "channel closed" }
        end
      end
      [out_buf.string, err_buf.string]
    end

    def exec command, pty: false, env: {}
      log_info { "start exec: #{command}" }
      begin
        log_info { "Opning channel" }
        channel = @connection.request_channel_open "session"
        log_info { "Channel opened" }
        if pty
          channel.send_channel_request_pty_req 'xterm', 80, 24, 580, 336, ''
        end
        env.each{ |variable_name, variable_value|
          channel.send_channel_request_env variable_name, variable_value
        }
        channel.send_channel_request_exec command
        yield channel.io
      rescue => e
        log_error { "Failed opening channel" }
        log_error { [e.backtrace[0], ": ", e.message, " (", e.class.to_s, ")\n\t", e.backtrace[1..-1].join("\n\t")].join }
        raise "Error in shell"
      ensure
        if channel
          log_info { "closing channel IOs" }
          channel.io.each{ |io| io.close rescue nil }
          log_info { "channel IOs closed" }
          log_info { "closing channel" }
          log_info { "wait until threads closed in channel" }
          channel.wait_until_closed
          channel.close
          log_info { "channel closed" }
        end
      end
      channel_exit_status = channel.exit_status rescue nil
    end

    def shell env: {}
      log_info { "start shell" }
      begin
        log_info { "Opning channel" }
        channel = @connection.request_channel_open "session"
        log_info { "Channel opened" }
        channel.send_channel_request_pty_req 'xterm', 80, 24, 580, 336, ''
        env.each{ |variable_name, variable_value|
          channel.send_channel_request_env variable_name, variable_value
        }
        channel.send_channel_request_shell
        yield channel.io
      rescue => e
        log_error { "Failed opening channel" }
        log_error { [e.backtrace[0], ": ", e.message, " (", e.class.to_s, ")\n\t", e.backtrace[1..-1].join("\n\t")].join }
        raise "Error in shell"
      ensure
        if channel
          log_info { "closing channel IOs" }
          channel.io.each{ |io| io.close rescue nil }
          log_info { "channel IOs closed" }
          log_info { "closing channel" }
          log_info { "wait until threads closed in channel" }
          channel.wait_until_closed
          channel.close
          log_info { "channel closed" }
        end
      end
      channel_exit_status = channel.exit_status rescue nil
    end

    def subsystem name
      log_info { "start subsystem" }
      begin
        log_info { "Opning channel" }
        channel = @connection.request_channel_open "session"
        log_info { "Channel opened" }
        channel.send_channel_request_subsystem name
        yield channel.io
      rescue => e
        log_error { "Failed opening channel" }
        log_error { [e.backtrace[0], ": ", e.message, " (", e.class.to_s, ")\n\t", e.backtrace[1..-1].join("\n\t")].join }
        raise "Error in subsystem"
      ensure
        if channel
          log_info { "closing channel IOs" }
          channel.io.each{ |io| io.close rescue nil }
          log_info { "channel IOs closed" }
          log_info { "closing channel" }
          log_info { "wait until threads closed in channel" }
          channel.wait_until_closed
          channel.close
          log_info { "channel closed" }
        end
      end
      channel_exit_status = channel.exit_status rescue nil
    end
  end
end