sshake 1.0.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/sshake/base_session.rb +29 -29
- data/lib/sshake/error.rb +6 -1
- data/lib/sshake/execution_options.rb +14 -5
- data/lib/sshake/execution_options_dsl.rb +4 -0
- data/lib/sshake/klogger.rb +14 -0
- data/lib/sshake/mock/command.rb +3 -3
- data/lib/sshake/mock/command_set.rb +2 -0
- data/lib/sshake/mock/environment.rb +5 -5
- data/lib/sshake/mock/executed_command.rb +3 -3
- data/lib/sshake/mock/session.rb +10 -13
- data/lib/sshake/mock/unsupported_command_error.rb +2 -0
- data/lib/sshake/recorded_session.rb +47 -0
- data/lib/sshake/recorder.rb +94 -0
- data/lib/sshake/recording.rb +29 -0
- data/lib/sshake/response.rb +14 -10
- data/lib/sshake/session.rb +107 -65
- data/lib/sshake/version.rb +3 -1
- metadata +33 -9
- data/lib/sshake/logger.rb +0 -7
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 95a0f6a6db803ff62826997e18e0b5d5ec81977046833bc32c10e29ebdbfd883
         | 
| 4 | 
            +
              data.tar.gz: 8a2c9aa526b96827b3d50d25cd8b479bf3310eb6857822790410e79a38ac8423
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: dc53e93b8e027f96c6078acb1626a056512dbf932a5650a1e4e4dfa53d28d72c219cff4acb392ca3d46fcbd5cd136aa7114b3edca27884817f86ace0648ec0c5
         | 
| 7 | 
            +
              data.tar.gz: 25e70b953ed507800fa3f1a0531c926023d383f8a73caf8de201e12db2b4dd4e864af0080bf657952ad00112528572368e6c9eced39adf254858897e9487a6e3
         | 
    
        data/lib/sshake/base_session.rb
    CHANGED
    
    | @@ -1,13 +1,17 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            require 'securerandom'
         | 
| 2 | 
            -
            require ' | 
| 4 | 
            +
            require 'klogger'
         | 
| 5 | 
            +
            require 'sshake/klogger'
         | 
| 3 6 | 
             
            require 'sshake/execution_options'
         | 
| 4 7 |  | 
| 5 8 | 
             
            module SSHake
         | 
| 6 9 | 
             
              class BaseSession
         | 
| 10 | 
            +
             | 
| 7 11 | 
             
                # A logger for this session
         | 
| 8 12 | 
             
                #
         | 
| 9 | 
            -
                # @return [ | 
| 10 | 
            -
                attr_accessor : | 
| 13 | 
            +
                # @return [Klogger, nil]
         | 
| 14 | 
            +
                attr_accessor :klogger
         | 
| 11 15 |  | 
| 12 16 | 
             
                # An ID for this session
         | 
| 13 17 | 
             
                #
         | 
| @@ -19,58 +23,65 @@ module SSHake | |
| 19 23 | 
             
                # @return [Boolean]
         | 
| 20 24 | 
             
                attr_accessor :raise_on_error
         | 
| 21 25 |  | 
| 22 | 
            -
                def initialize(* | 
| 26 | 
            +
                def initialize(*_args, klogger: nil)
         | 
| 23 27 | 
             
                  @id = SecureRandom.hex(4)
         | 
| 28 | 
            +
                  @klogger = klogger || SSHake.klogger
         | 
| 24 29 | 
             
                end
         | 
| 25 30 |  | 
| 26 31 | 
             
                # Connect to the SSH server
         | 
| 27 32 | 
             
                #
         | 
| 28 33 | 
             
                # @return [void]
         | 
| 29 34 | 
             
                def connect
         | 
| 30 | 
            -
                  raise  | 
| 35 | 
            +
                  raise 'Override #connect in sub-sessions'
         | 
| 31 36 | 
             
                end
         | 
| 32 37 |  | 
| 33 38 | 
             
                # Is there an established SSH connection
         | 
| 34 39 | 
             
                #
         | 
| 35 40 | 
             
                # @return [Boolean]
         | 
| 36 41 | 
             
                def connected?
         | 
| 37 | 
            -
                  raise  | 
| 42 | 
            +
                  raise 'Override #connected? in sub-sessions'
         | 
| 38 43 | 
             
                end
         | 
| 39 44 |  | 
| 40 45 | 
             
                # Disconnect the underlying SSH connection
         | 
| 41 46 | 
             
                #
         | 
| 42 47 | 
             
                # @return [void]
         | 
| 43 48 | 
             
                def disconnect
         | 
| 44 | 
            -
                  raise  | 
| 49 | 
            +
                  raise 'Override #disconnect in sub-sessions'
         | 
| 45 50 | 
             
                end
         | 
| 46 51 |  | 
| 47 52 | 
             
                # Kill the underlying connection
         | 
| 48 53 | 
             
                def kill!
         | 
| 49 | 
            -
                  raise  | 
| 54 | 
            +
                  raise 'Override #kill! in sub-sessions'
         | 
| 50 55 | 
             
                end
         | 
| 51 56 |  | 
| 52 57 | 
             
                # Execute a command
         | 
| 53 58 | 
             
                #
         | 
| 54 | 
            -
                def execute( | 
| 55 | 
            -
                  raise  | 
| 59 | 
            +
                def execute(_commands, _options = nil)
         | 
| 60 | 
            +
                  raise 'Override #execute in sub-sessions'
         | 
| 56 61 | 
             
                end
         | 
| 57 62 |  | 
| 58 | 
            -
                def write_data( | 
| 59 | 
            -
                  raise  | 
| 63 | 
            +
                def write_data(_path, _data, _options = nil)
         | 
| 64 | 
            +
                  raise 'Override #write_data in sub-sessions'
         | 
| 60 65 | 
             
                end
         | 
| 61 66 |  | 
| 62 67 | 
             
                private
         | 
| 63 68 |  | 
| 64 | 
            -
                def add_sudo_to_commands_array(commands, user)
         | 
| 69 | 
            +
                def add_sudo_to_commands_array(commands, user, password = nil)
         | 
| 70 | 
            +
                  sudo_prefix = "sudo -u #{user}"
         | 
| 71 | 
            +
                  unless password.nil?
         | 
| 72 | 
            +
                    sudo_prefix += " --stdin -p '[sshake-sudo-password]: ' "
         | 
| 73 | 
            +
                  end
         | 
| 65 74 | 
             
                  commands.map do |command|
         | 
| 66 | 
            -
                    " | 
| 75 | 
            +
                    "#{sudo_prefix} #{command}"
         | 
| 67 76 | 
             
                  end
         | 
| 68 77 | 
             
                end
         | 
| 69 78 |  | 
| 70 79 | 
             
                def create_options(hash, block)
         | 
| 71 80 | 
             
                  if block && hash
         | 
| 72 81 | 
             
                    raise Error, 'You cannot provide a block and options'
         | 
| 73 | 
            -
                   | 
| 82 | 
            +
                  end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                  if block
         | 
| 74 85 | 
             
                    ExecutionOptions.from_block(&block)
         | 
| 75 86 | 
             
                  elsif hash.is_a?(Hash)
         | 
| 76 87 | 
             
                    ExecutionOptions.from_hash(hash)
         | 
| @@ -79,23 +90,12 @@ module SSHake | |
| 79 90 | 
             
                  end
         | 
| 80 91 | 
             
                end
         | 
| 81 92 |  | 
| 82 | 
            -
                def log(type, text, options = {})
         | 
| 83 | 
            -
                  logger = @logger || SSHake.logger
         | 
| 84 | 
            -
                  return unless logger
         | 
| 85 | 
            -
             | 
| 86 | 
            -
                  prefix = "[#{@id}] [#{@host}] "
         | 
| 87 | 
            -
             | 
| 88 | 
            -
                  text.split(/\n/).each do |line|
         | 
| 89 | 
            -
                    logger.send(type, prefix + line)
         | 
| 90 | 
            -
                  end
         | 
| 91 | 
            -
                end
         | 
| 92 | 
            -
             | 
| 93 93 | 
             
                def prepare_commands(commands, execution_options, **options)
         | 
| 94 94 | 
             
                  commands = [commands] unless commands.is_a?(Array)
         | 
| 95 95 |  | 
| 96 96 | 
             
                  # Map sudo onto command
         | 
| 97 97 | 
             
                  if execution_options.sudo_user && options[:add_sudo] != false
         | 
| 98 | 
            -
                    commands = add_sudo_to_commands_array(commands, execution_options.sudo_user)
         | 
| 98 | 
            +
                    commands = add_sudo_to_commands_array(commands, execution_options.sudo_user, execution_options.sudo_password)
         | 
| 99 99 | 
             
                  end
         | 
| 100 100 |  | 
| 101 101 | 
             
                  # Construct a full command string to execute
         | 
| @@ -105,9 +105,9 @@ module SSHake | |
| 105 105 | 
             
                def handle_response(response, options)
         | 
| 106 106 | 
             
                  if !response.success? && ((options.raise_on_error.nil? && @raise_on_error) || options.raise_on_error?)
         | 
| 107 107 | 
             
                    raise ExecutionError, response
         | 
| 108 | 
            -
                  else
         | 
| 109 | 
            -
                    response
         | 
| 110 108 | 
             
                  end
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                  response
         | 
| 111 111 | 
             
                end
         | 
| 112 112 |  | 
| 113 113 | 
             
              end
         | 
    
        data/lib/sshake/error.rb
    CHANGED
    
    | @@ -1,10 +1,12 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 3 | 
             
            module SSHake
         | 
| 4 | 
            +
             | 
| 4 5 | 
             
              class Error < StandardError
         | 
| 5 6 | 
             
              end
         | 
| 6 7 |  | 
| 7 8 | 
             
              class ExecutionError < Error
         | 
| 9 | 
            +
             | 
| 8 10 | 
             
                def initialize(response)
         | 
| 9 11 | 
             
                  @response = response
         | 
| 10 12 | 
             
                end
         | 
| @@ -16,7 +18,10 @@ module SSHake | |
| 16 18 | 
             
                end
         | 
| 17 19 |  | 
| 18 20 | 
             
                def message
         | 
| 19 | 
            -
                  "Failed to execute command: #{@response.command}  | 
| 21 | 
            +
                  "Failed to execute command: #{@response.command} " \
         | 
| 22 | 
            +
                    "(stderr: #{@response.stderr}) (exit code: #{@response.exit_code})"
         | 
| 20 23 | 
             
                end
         | 
| 24 | 
            +
             | 
| 21 25 | 
             
              end
         | 
| 26 | 
            +
             | 
| 22 27 | 
             
            end
         | 
| @@ -1,7 +1,10 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            require 'sshake/execution_options_dsl'
         | 
| 2 4 |  | 
| 3 5 | 
             
            module SSHake
         | 
| 4 6 | 
             
              class ExecutionOptions
         | 
| 7 | 
            +
             | 
| 5 8 | 
             
                # The timeout
         | 
| 6 9 | 
             
                #
         | 
| 7 10 | 
             
                # @return [Integer]
         | 
| @@ -44,7 +47,7 @@ module SSHake | |
| 44 47 | 
             
                # A file that you wish to stream to the remote channel
         | 
| 45 48 | 
             
                # with the current commend
         | 
| 46 49 | 
             
                #
         | 
| 47 | 
            -
                # | 
| 50 | 
            +
                # @return [File]
         | 
| 48 51 | 
             
                attr_accessor :file_to_stream
         | 
| 49 52 |  | 
| 50 53 | 
             
                # Should errors be raised
         | 
| @@ -55,6 +58,7 @@ module SSHake | |
| 55 58 | 
             
                end
         | 
| 56 59 |  | 
| 57 60 | 
             
                class << self
         | 
| 61 | 
            +
             | 
| 58 62 | 
             
                  # Return the default timeout
         | 
| 59 63 | 
             
                  #
         | 
| 60 64 | 
             
                  # @return [Integer]
         | 
| @@ -70,15 +74,18 @@ module SSHake | |
| 70 74 | 
             
                  def from_hash(hash)
         | 
| 71 75 | 
             
                    options = new
         | 
| 72 76 | 
             
                    options.timeout = hash[:timeout]
         | 
| 73 | 
            -
                     | 
| 77 | 
            +
                    case hash[:sudo]
         | 
| 78 | 
            +
                    when String
         | 
| 74 79 | 
             
                      options.sudo_user = hash[:sudo]
         | 
| 75 | 
            -
                     | 
| 76 | 
            -
                      options.sudo_user = hash[:sudo][:user]
         | 
| 80 | 
            +
                    when Hash
         | 
| 81 | 
            +
                      options.sudo_user = hash[:sudo][:user] || 'root'
         | 
| 77 82 | 
             
                      options.sudo_password = hash[:sudo][:password]
         | 
| 78 | 
            -
                     | 
| 83 | 
            +
                    when true
         | 
| 79 84 | 
             
                      options.sudo_user = 'root'
         | 
| 80 85 | 
             
                    end
         | 
| 86 | 
            +
                    # rubocop:disable Style/DoubleNegation
         | 
| 81 87 | 
             
                    options.raise_on_error = !!hash[:raise_on_error]
         | 
| 88 | 
            +
                    # rubocop:enable Style/DoubleNegation
         | 
| 82 89 | 
             
                    options.stdin = hash[:stdin]
         | 
| 83 90 | 
             
                    options.stdout = hash[:stdout]
         | 
| 84 91 | 
             
                    options.stderr = hash[:stderr]
         | 
| @@ -95,6 +102,8 @@ module SSHake | |
| 95 102 | 
             
                    yield dsl
         | 
| 96 103 | 
             
                    options
         | 
| 97 104 | 
             
                  end
         | 
| 105 | 
            +
             | 
| 98 106 | 
             
                end
         | 
| 107 | 
            +
             | 
| 99 108 | 
             
              end
         | 
| 100 109 | 
             
            end
         | 
| @@ -2,6 +2,7 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            module SSHake
         | 
| 4 4 | 
             
              class ExecutionOptionsDSL
         | 
| 5 | 
            +
             | 
| 5 6 | 
             
                def initialize(options)
         | 
| 6 7 | 
             
                  @options = options
         | 
| 7 8 | 
             
                end
         | 
| @@ -15,9 +16,11 @@ module SSHake | |
| 15 16 | 
             
                  @options.sudo_password = options[:password]
         | 
| 16 17 | 
             
                end
         | 
| 17 18 |  | 
| 19 | 
            +
                # rubocop:disable Style/OptionalBooleanParameter
         | 
| 18 20 | 
             
                def raise_on_error(bool = true)
         | 
| 19 21 | 
             
                  @options.raise_on_error = bool
         | 
| 20 22 | 
             
                end
         | 
| 23 | 
            +
                # rubocop:enable Style/OptionalBooleanParameter
         | 
| 21 24 |  | 
| 22 25 | 
             
                def dont_raise_on_error
         | 
| 23 26 | 
             
                  @options.raise_on_error = false
         | 
| @@ -38,5 +41,6 @@ module SSHake | |
| 38 41 | 
             
                def file_to_stream(file)
         | 
| 39 42 | 
             
                  @options.file_to_stream = file
         | 
| 40 43 | 
             
                end
         | 
| 44 | 
            +
             | 
| 41 45 | 
             
              end
         | 
| 42 46 | 
             
            end
         | 
    
        data/lib/sshake/mock/command.rb
    CHANGED
    
    | @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            require 'sshake/response'
         | 
| 2 4 |  | 
| 3 5 | 
             
            module SSHake
         | 
| @@ -24,9 +26,7 @@ module SSHake | |
| 24 26 | 
             
                  def make_response(environment)
         | 
| 25 27 | 
             
                    response = SSHake::Response.new
         | 
| 26 28 | 
             
                    response.start_time = Time.now
         | 
| 27 | 
            -
                     | 
| 28 | 
            -
                      @block.call(response, environment)
         | 
| 29 | 
            -
                    end
         | 
| 29 | 
            +
                    @block&.call(response, environment)
         | 
| 30 30 | 
             
                    response.finish_time = Time.now
         | 
| 31 31 | 
             
                    response
         | 
| 32 32 | 
             
                  end
         | 
| @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            module SSHake
         | 
| 2 4 | 
             
              module Mock
         | 
| 3 5 | 
             
                class Environment
         | 
| @@ -7,16 +9,14 @@ module SSHake | |
| 7 9 | 
             
                    @captures = []
         | 
| 8 10 | 
             
                  end
         | 
| 9 11 |  | 
| 10 | 
            -
                  attr_accessor :command
         | 
| 11 | 
            -
                  attr_accessor :options
         | 
| 12 | 
            -
                  attr_accessor :captures
         | 
| 12 | 
            +
                  attr_accessor :command, :options, :captures
         | 
| 13 13 |  | 
| 14 14 | 
             
                  def store
         | 
| 15 | 
            -
                    @session | 
| 15 | 
            +
                    @session&.store
         | 
| 16 16 | 
             
                  end
         | 
| 17 17 |  | 
| 18 18 | 
             
                  def written_files
         | 
| 19 | 
            -
                    @session | 
| 19 | 
            +
                    @session&.written_files
         | 
| 20 20 | 
             
                  end
         | 
| 21 21 |  | 
| 22 22 | 
             
                end
         | 
| @@ -1,10 +1,10 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            module SSHake
         | 
| 2 4 | 
             
              module Mock
         | 
| 3 5 | 
             
                class ExecutedCommand
         | 
| 4 6 |  | 
| 5 | 
            -
                  attr_reader :command
         | 
| 6 | 
            -
                  attr_reader :environment
         | 
| 7 | 
            -
                  attr_reader :response
         | 
| 7 | 
            +
                  attr_reader :command, :environment, :response
         | 
| 8 8 |  | 
| 9 9 | 
             
                  def initialize(command, environment, response)
         | 
| 10 10 | 
             
                    @command = command
         | 
    
        data/lib/sshake/mock/session.rb
    CHANGED
    
    | @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            require 'net/ssh/errors'
         | 
| 2 4 | 
             
            require 'sshake/base_session'
         | 
| 3 5 | 
             
            require 'sshake/mock/command_set'
         | 
| @@ -9,10 +11,7 @@ module SSHake | |
| 9 11 | 
             
              module Mock
         | 
| 10 12 | 
             
                class Session < BaseSession
         | 
| 11 13 |  | 
| 12 | 
            -
                  attr_reader :command_set
         | 
| 13 | 
            -
                  attr_reader :store
         | 
| 14 | 
            -
                  attr_reader :written_files
         | 
| 15 | 
            -
                  attr_reader :executed_commands
         | 
| 14 | 
            +
                  attr_reader :command_set, :store, :written_files, :executed_commands
         | 
| 16 15 |  | 
| 17 16 | 
             
                  def initialize(**options)
         | 
| 18 17 | 
             
                    @options = options
         | 
| @@ -57,25 +56,21 @@ module SSHake | |
| 57 56 | 
             
                    environment = Environment.new(self)
         | 
| 58 57 |  | 
| 59 58 | 
             
                    environment.options = create_options(options, block)
         | 
| 60 | 
            -
                    environment.command = prepare_commands(commands, environment.options, : | 
| 59 | 
            +
                    environment.command = prepare_commands(commands, environment.options, add_sudo: false)
         | 
| 61 60 |  | 
| 62 61 | 
             
                    command, environment.captures = @command_set.match(environment.command)
         | 
| 63 62 |  | 
| 64 | 
            -
                    if command.nil?
         | 
| 65 | 
            -
                      raise UnsupportedCommandError.new(environment.command)
         | 
| 66 | 
            -
                    end
         | 
| 63 | 
            +
                    raise UnsupportedCommandError, environment.command if command.nil?
         | 
| 67 64 |  | 
| 68 65 | 
             
                    response = command.make_response(environment)
         | 
| 69 66 |  | 
| 70 | 
            -
                    if environment.options.file_to_stream
         | 
| 71 | 
            -
                      response.bytes_streamed = environment.options.file_to_stream.size
         | 
| 72 | 
            -
                    end
         | 
| 67 | 
            +
                    response.bytes_streamed = environment.options.file_to_stream.size if environment.options.file_to_stream
         | 
| 73 68 |  | 
| 74 69 | 
             
                    @executed_commands << ExecutedCommand.new(command, environment, response)
         | 
| 75 70 | 
             
                    handle_response(response, environment.options)
         | 
| 76 71 | 
             
                  end
         | 
| 77 72 |  | 
| 78 | 
            -
                  def write_data(path, data,  | 
| 73 | 
            +
                  def write_data(path, data, _options = nil)
         | 
| 79 74 | 
             
                    connect unless connected?
         | 
| 80 75 | 
             
                    @written_files[path] = data
         | 
| 81 76 | 
             
                    true
         | 
| @@ -92,9 +87,11 @@ module SSHake | |
| 92 87 | 
             
                    end
         | 
| 93 88 | 
             
                  end
         | 
| 94 89 |  | 
| 90 | 
            +
                  # rubocop:disable Naming/PredicateName
         | 
| 95 91 | 
             
                  def has_executed_command?(matcher)
         | 
| 96 | 
            -
                    find_executed_commands(matcher).size | 
| 92 | 
            +
                    find_executed_commands(matcher).size.positive?
         | 
| 97 93 | 
             
                  end
         | 
| 94 | 
            +
                  # rubocop:enable Naming/PredicateName
         | 
| 98 95 |  | 
| 99 96 | 
             
                end
         | 
| 100 97 | 
             
              end
         | 
| @@ -0,0 +1,47 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'sshake/base_session'
         | 
| 4 | 
            +
            require 'sshake/recorder'
         | 
| 5 | 
            +
            require 'sshake/session'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            module SSHake
         | 
| 8 | 
            +
              class RecordedSession < BaseSession
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                attr_reader :recorder
         | 
| 11 | 
            +
                attr_reader :session
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                def initialize(recorder, session, **options)
         | 
| 14 | 
            +
                  super
         | 
| 15 | 
            +
                  @recorder = recorder
         | 
| 16 | 
            +
                  @session = session
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                def execute(commands, options = nil, &block)
         | 
| 20 | 
            +
                  options = create_options(options, block)
         | 
| 21 | 
            +
                  command_to_execute = prepare_commands(commands, options)
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  cached_response = @recorder.play(command_to_execute, options: options, connection: connection_hash)
         | 
| 24 | 
            +
                  return cached_response if cached_response
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  response = @session.execute(commands, options)
         | 
| 27 | 
            +
                  record(command_to_execute, options, response)
         | 
| 28 | 
            +
                  response
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                private
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                def record(command, options, response)
         | 
| 34 | 
            +
                  @recorder.record(command, response, options: options, connection: connection_hash)
         | 
| 35 | 
            +
                  @recorder.save
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                def connection_hash
         | 
| 39 | 
            +
                  {
         | 
| 40 | 
            +
                    host: @session.host,
         | 
| 41 | 
            +
                    user: @session.user,
         | 
| 42 | 
            +
                    port: @session.port
         | 
| 43 | 
            +
                  }
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
              end
         | 
| 47 | 
            +
            end
         | 
| @@ -0,0 +1,94 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'yaml'
         | 
| 4 | 
            +
            module SSHake
         | 
| 5 | 
            +
              class Recorder
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                class << self
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  # Return the root where all recorded sessions should be stored
         | 
| 10 | 
            +
                  #
         | 
| 11 | 
            +
                  # @return [nil, String]
         | 
| 12 | 
            +
                  attr_accessor :save_root
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                attr_reader :name
         | 
| 17 | 
            +
                attr_reader :cache
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                def initialize(name, cache: nil)
         | 
| 20 | 
            +
                  @name = name
         | 
| 21 | 
            +
                  @cache = cache || {}
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                def load
         | 
| 25 | 
            +
                  return if self.class.save_root.nil?
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  @cache = YAML.load_file(File.join(self.class.save_root, "#{name}.yml"))
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                def save
         | 
| 31 | 
            +
                  return if self.class.save_root.nil?
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  FileUtils.mkdir_p(self.class.save_root)
         | 
| 34 | 
            +
                  File.write(File.join(self.class.save_root, "#{name}.yml"), @cache.to_yaml)
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                def play(command, connection: {}, options: nil)
         | 
| 38 | 
            +
                  possibilities = @cache[command]
         | 
| 39 | 
            +
                  return nil if possibilities.nil?
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  options_as_hash = options_to_hash(options)
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  possibility = possibilities.find do |p|
         | 
| 44 | 
            +
                    p[:options] == options_as_hash &&
         | 
| 45 | 
            +
                      p[:connection] == connection
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                  return nil if possibility.nil?
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                  response = Response.new(cached: true)
         | 
| 51 | 
            +
                  possibility[:response].each do |key, value|
         | 
| 52 | 
            +
                    response.public_send("#{key}=", value)
         | 
| 53 | 
            +
                  end
         | 
| 54 | 
            +
                  response
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                def record(command, response, connection: {}, options: nil)
         | 
| 58 | 
            +
                  @cache[command] ||= []
         | 
| 59 | 
            +
                  @cache[command] << {
         | 
| 60 | 
            +
                    connection: connection,
         | 
| 61 | 
            +
                    options: options_to_hash(options),
         | 
| 62 | 
            +
                    response: response_to_hash(response)
         | 
| 63 | 
            +
                  }
         | 
| 64 | 
            +
                  save
         | 
| 65 | 
            +
                end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                private
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                def response_to_hash(response)
         | 
| 70 | 
            +
                  {
         | 
| 71 | 
            +
                    stdout: response.stdout,
         | 
| 72 | 
            +
                    stderr: response.stderr,
         | 
| 73 | 
            +
                    exit_code: response.exit_code,
         | 
| 74 | 
            +
                    start_time: response.start_time.to_i,
         | 
| 75 | 
            +
                    finish_time: response.finish_time.to_i
         | 
| 76 | 
            +
                  }
         | 
| 77 | 
            +
                end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                def options_to_hash(options)
         | 
| 80 | 
            +
                  options = ExecutionOptions.from_hash({}) if options.nil?
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                  hash = {}
         | 
| 83 | 
            +
                  hash[:timeout] = options.timeout if options.timeout
         | 
| 84 | 
            +
                  hash[:sudo_user] = options.sudo_user if options.sudo_user
         | 
| 85 | 
            +
                  hash[:sudo_password] = options.sudo_password if options.sudo_password
         | 
| 86 | 
            +
                  hash[:raise_on_error] = true if options.raise_on_error?
         | 
| 87 | 
            +
                  hash[:stdin] = Digest::SHA1.hexdigest(options.stdin) if options.stdin
         | 
| 88 | 
            +
                  hash[:file_to_stream] = Digest::SHA1.hexdigest(options.file_to_stream.read) if options.file_to_stream
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                  hash
         | 
| 91 | 
            +
                end
         | 
| 92 | 
            +
             | 
| 93 | 
            +
              end
         | 
| 94 | 
            +
            end
         | 
| @@ -0,0 +1,29 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'sshake/recorder'
         | 
| 4 | 
            +
            require 'sshake/recorded_session'
         | 
| 5 | 
            +
            require 'sshake/error'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            module SSHake
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              class NestedRecordingsUnsupportedError < Error
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              class << self
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                def record(name)
         | 
| 15 | 
            +
                  if Thread.current[:sshake_recorder]
         | 
| 16 | 
            +
                    raise NestedRecordingsUnsupportedError, 'You cannot nest SSHake.record blocks'
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  recorder = Recorder.new(name)
         | 
| 20 | 
            +
                  recorder.load
         | 
| 21 | 
            +
                  Thread.current[:sshake_recorder] = recorder
         | 
| 22 | 
            +
                  yield
         | 
| 23 | 
            +
                ensure
         | 
| 24 | 
            +
                  Thread.current[:sshake_recorder] = nil
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            end
         | 
    
        data/lib/sshake/response.rb
    CHANGED
    
    | @@ -2,24 +2,27 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            module SSHake
         | 
| 4 4 | 
             
              class Response
         | 
| 5 | 
            -
             | 
| 5 | 
            +
             | 
| 6 | 
            +
                def initialize(cached: false)
         | 
| 6 7 | 
             
                  @stdout = ''
         | 
| 7 8 | 
             
                  @stderr = ''
         | 
| 8 9 | 
             
                  @exit_code = 0
         | 
| 9 10 | 
             
                  @bytes_streamed = 0
         | 
| 11 | 
            +
                  @cached = cached
         | 
| 10 12 | 
             
                end
         | 
| 11 13 |  | 
| 12 | 
            -
                attr_accessor :command
         | 
| 13 | 
            -
                attr_accessor :stdout
         | 
| 14 | 
            -
                attr_accessor :stderr
         | 
| 15 | 
            -
                attr_accessor :exit_code
         | 
| 16 | 
            -
                attr_accessor :exit_signal
         | 
| 17 | 
            -
                attr_accessor :start_time
         | 
| 18 | 
            -
                attr_accessor :finish_time
         | 
| 19 | 
            -
                attr_accessor :bytes_streamed
         | 
| 14 | 
            +
                attr_accessor :command, :stdout, :stderr, :exit_code, :exit_signal, :start_time, :finish_time, :bytes_streamed
         | 
| 20 15 |  | 
| 21 16 | 
             
                def success?
         | 
| 22 | 
            -
                  @exit_code | 
| 17 | 
            +
                  @exit_code.zero?
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                def cached?
         | 
| 21 | 
            +
                  @cached == true
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                def cached!
         | 
| 25 | 
            +
                  @cached = true
         | 
| 23 26 | 
             
                end
         | 
| 24 27 |  | 
| 25 28 | 
             
                def time
         | 
| @@ -33,5 +36,6 @@ module SSHake | |
| 33 36 | 
             
                def timeout!
         | 
| 34 37 | 
             
                  @exit_code = -255
         | 
| 35 38 | 
             
                end
         | 
| 39 | 
            +
             | 
| 36 40 | 
             
              end
         | 
| 37 41 | 
             
            end
         | 
    
        data/lib/sshake/session.rb
    CHANGED
    
    | @@ -9,27 +9,48 @@ require 'sshake/base_session' | |
| 9 9 |  | 
| 10 10 | 
             
            module SSHake
         | 
| 11 11 | 
             
              class Session < BaseSession
         | 
| 12 | 
            +
             | 
| 12 13 | 
             
                # The underlying net/ssh session
         | 
| 13 14 | 
             
                #
         | 
| 14 15 | 
             
                # @return [Net::SSH::Session]
         | 
| 15 16 | 
             
                attr_reader :session
         | 
| 16 17 |  | 
| 18 | 
            +
                # Return the host to connect to
         | 
| 19 | 
            +
                #
         | 
| 20 | 
            +
                # @return [String]
         | 
| 21 | 
            +
                attr_reader :host
         | 
| 22 | 
            +
             | 
| 17 23 | 
             
                # Create a new SSH session
         | 
| 18 24 | 
             
                #
         | 
| 19 25 | 
             
                # @return [Sshake::Session]
         | 
| 20 | 
            -
                def initialize(host,  | 
| 26 | 
            +
                def initialize(host, username = nil, **options)
         | 
| 21 27 | 
             
                  super
         | 
| 22 28 | 
             
                  @host = host
         | 
| 23 | 
            -
                  @ | 
| 29 | 
            +
                  @username = username
         | 
| 30 | 
            +
                  @session_options = options
         | 
| 31 | 
            +
                  @session_options.delete(:klogger)
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                # Return the username for the connection
         | 
| 35 | 
            +
                #
         | 
| 36 | 
            +
                # @return [String]
         | 
| 37 | 
            +
                def user
         | 
| 38 | 
            +
                  @user || ENV.fetch('USER', nil)
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                # Return the port that will be connected to
         | 
| 42 | 
            +
                #
         | 
| 43 | 
            +
                # @return [Integer]
         | 
| 44 | 
            +
                def port
         | 
| 45 | 
            +
                  @session_options[:port] || 22
         | 
| 24 46 | 
             
                end
         | 
| 25 47 |  | 
| 26 48 | 
             
                # Connect to the SSH server
         | 
| 27 49 | 
             
                #
         | 
| 28 50 | 
             
                # @return [void]
         | 
| 29 51 | 
             
                def connect
         | 
| 30 | 
            -
                   | 
| 31 | 
            -
                   | 
| 32 | 
            -
                  @session = Net::SSH.start(@host, *@session_options)
         | 
| 52 | 
            +
                  klogger.debug 'Connecting', id: @id, host: @host, user: @user, port: @session_options[:port] || 22
         | 
| 53 | 
            +
                  @session = Net::SSH.start(@host, user, @session_options)
         | 
| 33 54 | 
             
                  true
         | 
| 34 55 | 
             
                end
         | 
| 35 56 |  | 
| @@ -47,11 +68,11 @@ module SSHake | |
| 47 68 | 
             
                  return false if @session.nil?
         | 
| 48 69 |  | 
| 49 70 | 
             
                  begin
         | 
| 50 | 
            -
                     | 
| 71 | 
            +
                    klogger.debug 'Closing connection', id: @id, host: @host
         | 
| 51 72 | 
             
                    @session.close
         | 
| 52 | 
            -
                     | 
| 73 | 
            +
                    klogger.debug 'Connection closed', id: @id, host: @host
         | 
| 53 74 | 
             
                  rescue StandardError => e
         | 
| 54 | 
            -
                     | 
| 75 | 
            +
                    logger.exception(e, 'Connection not closed')
         | 
| 55 76 | 
             
                    nil
         | 
| 56 77 | 
             
                  end
         | 
| 57 78 | 
             
                  @session = nil
         | 
| @@ -60,12 +81,13 @@ module SSHake | |
| 60 81 |  | 
| 61 82 | 
             
                # Kill the underlying connection
         | 
| 62 83 | 
             
                def kill!
         | 
| 63 | 
            -
                   | 
| 84 | 
            +
                  klogger.debug 'Attemping to shutdown', id: @id, host: @host
         | 
| 64 85 | 
             
                  @session.shutdown!
         | 
| 65 | 
            -
                   | 
| 86 | 
            +
                  klogger.debug 'Shutdown success', id: @id, host: @host
         | 
| 66 87 | 
             
                  @session = nil
         | 
| 67 88 | 
             
                end
         | 
| 68 89 |  | 
| 90 | 
            +
                # rubocop:disable Metrics/AbcSize
         | 
| 69 91 | 
             
                def execute(commands, options = nil, &block)
         | 
| 70 92 | 
             
                  options = create_options(options, block)
         | 
| 71 93 | 
             
                  command_to_execute = prepare_commands(commands, options)
         | 
| @@ -75,91 +97,111 @@ module SSHake | |
| 75 97 | 
             
                  response.command = command_to_execute
         | 
| 76 98 | 
             
                  connect unless connected?
         | 
| 77 99 |  | 
| 78 | 
            -
                   | 
| 79 | 
            -
             | 
| 80 | 
            -
                  log :debug, "Timeout: #{options.timeout}"
         | 
| 100 | 
            +
                  klogger.group(id: @id, host: @host) do
         | 
| 101 | 
            +
                    klogger.info 'Executing command', command: command_to_execute, timeout: options.timeout
         | 
| 81 102 |  | 
| 82 | 
            -
             | 
| 83 | 
            -
             | 
| 84 | 
            -
                    Timeout.timeout(options.timeout) do
         | 
| 85 | 
            -
                      channel = @session.open_channel do |ch|
         | 
| 86 | 
            -
                        response.start_time = Time.now
         | 
| 87 | 
            -
                        channel.exec(command_to_execute) do |_, success|
         | 
| 88 | 
            -
                          raise "Command \"#{command_to_execute}\" was unable to execute" unless success
         | 
| 89 | 
            -
             | 
| 90 | 
            -
                          if options.stdin
         | 
| 91 | 
            -
                            ch.send_data(options.stdin)
         | 
| 92 | 
            -
                          end
         | 
| 103 | 
            +
                    begin
         | 
| 104 | 
            +
                      channel = nil
         | 
| 93 105 |  | 
| 94 | 
            -
             | 
| 95 | 
            -
             | 
| 96 | 
            -
                           | 
| 106 | 
            +
                      Timeout.timeout(options.timeout) do
         | 
| 107 | 
            +
                        channel = @session.open_channel do |ch|
         | 
| 108 | 
            +
                          response.start_time = Time.now
         | 
| 97 109 |  | 
| 98 | 
            -
                           | 
| 99 | 
            -
                             | 
| 100 | 
            -
             | 
| 101 | 
            -
                             | 
| 102 | 
            -
                          end
         | 
| 110 | 
            +
                          channel.exec(command_to_execute) do |_, success|
         | 
| 111 | 
            +
                            raise "Command \"#{command_to_execute}\" was unable to execute" unless success
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                            ch.send_data(options.stdin) if options.stdin
         | 
| 103 114 |  | 
| 104 | 
            -
             | 
| 105 | 
            -
             | 
| 106 | 
            -
             | 
| 107 | 
            -
                            log :debug, data
         | 
| 108 | 
            -
                            if data =~ /^\[sudo\] password for/
         | 
| 109 | 
            -
                              log :debug, 'Sending sudo password'
         | 
| 110 | 
            -
                              ch.send_data "#{options.sudo_password}\n"
         | 
| 115 | 
            +
                            if options.file_to_stream.nil? && options.sudo_password.nil?
         | 
| 116 | 
            +
                              klogger.debug 'Sending EOF to channel'
         | 
| 117 | 
            +
                              ch.eof!
         | 
| 111 118 | 
             
                            end
         | 
| 112 | 
            -
                          end
         | 
| 113 119 |  | 
| 114 | 
            -
             | 
| 115 | 
            -
             | 
| 116 | 
            -
             | 
| 117 | 
            -
             | 
| 120 | 
            +
                            ch.on_data do |_, data|
         | 
| 121 | 
            +
                              response.stdout += data
         | 
| 122 | 
            +
                              options.stdout&.call(data)
         | 
| 123 | 
            +
                              klogger.debug "[stdout] #{data.gsub(/\r/, '').strip}"
         | 
| 124 | 
            +
                            end
         | 
| 118 125 |  | 
| 119 | 
            -
             | 
| 120 | 
            -
             | 
| 121 | 
            -
             | 
| 126 | 
            +
                            ch.on_extended_data do |_, _, data|
         | 
| 127 | 
            +
                              response.stderr += data.delete("\r")
         | 
| 128 | 
            +
                              options.stderr&.call(data)
         | 
| 129 | 
            +
                              klogger.debug "[stderr] #{data.gsub(/\r/, '').strip}"
         | 
| 130 | 
            +
                              if options.sudo_password && data =~ /^\[sshake-sudo-password\]:\s\z/
         | 
| 131 | 
            +
                                klogger.debug 'Sending sudo password', length: options.sudo_password.length
         | 
| 132 | 
            +
                                ch.send_data "#{options.sudo_password}\n"
         | 
| 122 133 |  | 
| 123 | 
            -
             | 
| 124 | 
            -
             | 
| 125 | 
            -
                              next if ch.eof?
         | 
| 126 | 
            -
                              if ch.output.length < 128 * 1024
         | 
| 127 | 
            -
                                if data = options.file_to_stream.read(1024 * 1024)
         | 
| 128 | 
            -
                                  ch.send_data(data)
         | 
| 129 | 
            -
                                  response.bytes_streamed += data.bytesize
         | 
| 130 | 
            -
                                else
         | 
| 134 | 
            +
                                if options.file_to_stream.nil?
         | 
| 135 | 
            +
                                  klogger.debug 'Sending EOF after password'
         | 
| 131 136 | 
             
                                  ch.eof!
         | 
| 132 137 | 
             
                                end
         | 
| 133 138 | 
             
                              end
         | 
| 134 139 | 
             
                            end
         | 
| 140 | 
            +
             | 
| 141 | 
            +
                            ch.on_request('exit-status') do |_, data|
         | 
| 142 | 
            +
                              response.exit_code = data.read_long&.to_i
         | 
| 143 | 
            +
                              klogger.info 'Exited', exit_code: response.exit_code
         | 
| 144 | 
            +
                            end
         | 
| 145 | 
            +
             | 
| 146 | 
            +
                            ch.on_request('exit-signal') do |_, data|
         | 
| 147 | 
            +
                              response.exit_signal = data.read_long
         | 
| 148 | 
            +
                            end
         | 
| 149 | 
            +
             | 
| 150 | 
            +
                            if options.file_to_stream
         | 
| 151 | 
            +
                              ch.on_process do |_, data|
         | 
| 152 | 
            +
                                next if ch.eof?
         | 
| 153 | 
            +
             | 
| 154 | 
            +
                                if ch.output.length < 128 * 1024
         | 
| 155 | 
            +
                                  if data = options.file_to_stream.read(1024 * 1024)
         | 
| 156 | 
            +
                                    ch.send_data(data)
         | 
| 157 | 
            +
                                    response.bytes_streamed += data.bytesize
         | 
| 158 | 
            +
                                  else
         | 
| 159 | 
            +
                                    ch.eof!
         | 
| 160 | 
            +
                                  end
         | 
| 161 | 
            +
                                end
         | 
| 162 | 
            +
                              end
         | 
| 163 | 
            +
                            end
         | 
| 135 164 | 
             
                          end
         | 
| 136 165 | 
             
                        end
         | 
| 166 | 
            +
                        channel.wait
         | 
| 137 167 | 
             
                      end
         | 
| 138 | 
            -
             | 
| 168 | 
            +
                    rescue Timeout::Error
         | 
| 169 | 
            +
                      klogger.debug 'Command timed out'
         | 
| 170 | 
            +
                      kill!
         | 
| 171 | 
            +
                      response.timeout!
         | 
| 172 | 
            +
                    ensure
         | 
| 173 | 
            +
                      response.finish_time = Time.now
         | 
| 139 174 | 
             
                    end
         | 
| 140 | 
            -
                  rescue Timeout::Error => e
         | 
| 141 | 
            -
                    log :debug, "Got timeout error while executing command"
         | 
| 142 | 
            -
                    kill!
         | 
| 143 | 
            -
                    response.timeout!
         | 
| 144 | 
            -
                  ensure
         | 
| 145 | 
            -
                    response.finish_time = Time.now
         | 
| 146 175 | 
             
                  end
         | 
| 147 176 |  | 
| 148 177 | 
             
                  handle_response(response, options)
         | 
| 149 178 | 
             
                end
         | 
| 179 | 
            +
                # rubocop:enable Metrics/AbcSize
         | 
| 150 180 |  | 
| 151 181 | 
             
                def write_data(path, data, options = nil, &block)
         | 
| 152 182 | 
             
                  connect unless connected?
         | 
| 153 183 | 
             
                  tmp_path = "/tmp/sshake-tmp-file-#{SecureRandom.hex(32)}"
         | 
| 154 184 | 
             
                  @session.sftp.file.open(tmp_path, 'w') do |f|
         | 
| 155 185 | 
             
                    d = data.dup.force_encoding('BINARY')
         | 
| 156 | 
            -
                    until d.empty?
         | 
| 157 | 
            -
                      f.write(d.slice!(0, 1024))
         | 
| 158 | 
            -
                    end
         | 
| 186 | 
            +
                    f.write(d.slice!(0, 1024)) until d.empty?
         | 
| 159 187 | 
             
                  end
         | 
| 160 188 | 
             
                  response = execute("mv #{tmp_path} #{path}", options, &block)
         | 
| 161 189 | 
             
                  response.success?
         | 
| 162 190 | 
             
                end
         | 
| 163 191 |  | 
| 192 | 
            +
                class << self
         | 
| 193 | 
            +
             | 
| 194 | 
            +
                  def create(*args)
         | 
| 195 | 
            +
                    session = new(*args)
         | 
| 196 | 
            +
             | 
| 197 | 
            +
                    if recorder = Thread.current[:sshake_recorder]
         | 
| 198 | 
            +
                      return RecordedSession.new(recorder, session)
         | 
| 199 | 
            +
                    end
         | 
| 200 | 
            +
             | 
| 201 | 
            +
                    session
         | 
| 202 | 
            +
                  end
         | 
| 203 | 
            +
             | 
| 204 | 
            +
                end
         | 
| 205 | 
            +
             | 
| 164 206 | 
             
              end
         | 
| 165 207 | 
             
            end
         | 
    
        data/lib/sshake/version.rb
    CHANGED
    
    
    
        metadata
    CHANGED
    
    | @@ -1,15 +1,35 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: sshake
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version:  | 
| 4 | 
            +
              version: 2.0.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Adam Cooke
         | 
| 8 | 
            -
            autorequire: | 
| 8 | 
            +
            autorequire:
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date:  | 
| 11 | 
            +
            date: 2023-03-09 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 14 | 
            +
              name: klogger-logger
         | 
| 15 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 16 | 
            +
                requirements:
         | 
| 17 | 
            +
                - - ">="
         | 
| 18 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 19 | 
            +
                    version: '1'
         | 
| 20 | 
            +
                - - "<"
         | 
| 21 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 22 | 
            +
                    version: '2'
         | 
| 23 | 
            +
              type: :runtime
         | 
| 24 | 
            +
              prerelease: false
         | 
| 25 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 26 | 
            +
                requirements:
         | 
| 27 | 
            +
                - - ">="
         | 
| 28 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 29 | 
            +
                    version: '1'
         | 
| 30 | 
            +
                - - "<"
         | 
| 31 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 32 | 
            +
                    version: '2'
         | 
| 13 33 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 34 | 
             
              name: net-sftp
         | 
| 15 35 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -50,21 +70,25 @@ files: | |
| 50 70 | 
             
            - lib/sshake/error.rb
         | 
| 51 71 | 
             
            - lib/sshake/execution_options.rb
         | 
| 52 72 | 
             
            - lib/sshake/execution_options_dsl.rb
         | 
| 53 | 
            -
            - lib/sshake/ | 
| 73 | 
            +
            - lib/sshake/klogger.rb
         | 
| 54 74 | 
             
            - lib/sshake/mock/command.rb
         | 
| 55 75 | 
             
            - lib/sshake/mock/command_set.rb
         | 
| 56 76 | 
             
            - lib/sshake/mock/environment.rb
         | 
| 57 77 | 
             
            - lib/sshake/mock/executed_command.rb
         | 
| 58 78 | 
             
            - lib/sshake/mock/session.rb
         | 
| 59 79 | 
             
            - lib/sshake/mock/unsupported_command_error.rb
         | 
| 80 | 
            +
            - lib/sshake/recorded_session.rb
         | 
| 81 | 
            +
            - lib/sshake/recorder.rb
         | 
| 82 | 
            +
            - lib/sshake/recording.rb
         | 
| 60 83 | 
             
            - lib/sshake/response.rb
         | 
| 61 84 | 
             
            - lib/sshake/session.rb
         | 
| 62 85 | 
             
            - lib/sshake/version.rb
         | 
| 63 86 | 
             
            homepage: https://github.com/adamcooke/sshake
         | 
| 64 87 | 
             
            licenses:
         | 
| 65 88 | 
             
            - MIT
         | 
| 66 | 
            -
            metadata: | 
| 67 | 
            -
             | 
| 89 | 
            +
            metadata:
         | 
| 90 | 
            +
              rubygems_mfa_required: 'true'
         | 
| 91 | 
            +
            post_install_message:
         | 
| 68 92 | 
             
            rdoc_options: []
         | 
| 69 93 | 
             
            require_paths:
         | 
| 70 94 | 
             
            - lib
         | 
| @@ -72,15 +96,15 @@ required_ruby_version: !ruby/object:Gem::Requirement | |
| 72 96 | 
             
              requirements:
         | 
| 73 97 | 
             
              - - ">="
         | 
| 74 98 | 
             
                - !ruby/object:Gem::Version
         | 
| 75 | 
            -
                  version: ' | 
| 99 | 
            +
                  version: '2.6'
         | 
| 76 100 | 
             
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 77 101 | 
             
              requirements:
         | 
| 78 102 | 
             
              - - ">="
         | 
| 79 103 | 
             
                - !ruby/object:Gem::Version
         | 
| 80 104 | 
             
                  version: '0'
         | 
| 81 105 | 
             
            requirements: []
         | 
| 82 | 
            -
            rubygems_version: 3. | 
| 83 | 
            -
            signing_key: | 
| 106 | 
            +
            rubygems_version: 3.2.32
         | 
| 107 | 
            +
            signing_key:
         | 
| 84 108 | 
             
            specification_version: 4
         | 
| 85 109 | 
             
            summary: A wrapper for net/ssh to make running commands more fun
         | 
| 86 110 | 
             
            test_files: []
         |