sshake 1.0.2 → 1.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 314760390e24b395431def2b202b606569f833dce1148bdedc556c7ca7869c3f
4
- data.tar.gz: 82eee2aa19795c54837ebe9b3c20c5d2349c7f8402240ac3bc13c07692ee46d2
3
+ metadata.gz: 7f07fcd2695da038870ae9a7c15715524ed1cbd1912a38c9cbd4c3d3d9f39d96
4
+ data.tar.gz: c941f1f5ae3dd0348da4c694f769fdad2fcaa026777cf0c8a5cd6278d179a87a
5
5
  SHA512:
6
- metadata.gz: a2310a628972c8062ae526c569b135dd2cc4ee37a057f2924d81e43605f2aaaa8d42fb39c3078cf20e766857fbdc8c081bf6efad9c61d184f04ac1b0fafd33b9
7
- data.tar.gz: 67cb1c5689970aaac6c7987bb4e19d15152bf18dd36e2fa0bdaada484019ffd690b35025489c9cc7e8128c6c0d151c5f887aa62d9802d09c089c78d630c1fc55
6
+ metadata.gz: 0c7636ffe034fc59aa89043bfb44d6e78d156567283f5a77b82eeb98df91f8977e561c2435f2512261166a0242fe0d75b5c7ca8369b9babd6f96729f29323dc5
7
+ data.tar.gz: bc5eb231a03e4e207a9c478bdb4b3764366f48d82576d6d60b2daa3571cce7383c9f8d6d4d49d786b8c6fbf6582fee132cdb7039f7a40c9a90a44ffd5083856f
@@ -1,9 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'securerandom'
2
4
  require 'sshake/logger'
3
5
  require 'sshake/execution_options'
4
6
 
5
7
  module SSHake
6
8
  class BaseSession
9
+
7
10
  # A logger for this session
8
11
  #
9
12
  # @return [Logger, nil]
@@ -19,7 +22,7 @@ module SSHake
19
22
  # @return [Boolean]
20
23
  attr_accessor :raise_on_error
21
24
 
22
- def initialize(*args)
25
+ def initialize(*_args)
23
26
  @id = SecureRandom.hex(4)
24
27
  end
25
28
 
@@ -27,50 +30,56 @@ module SSHake
27
30
  #
28
31
  # @return [void]
29
32
  def connect
30
- raise "Override #connect in sub-sessions"
33
+ raise 'Override #connect in sub-sessions'
31
34
  end
32
35
 
33
36
  # Is there an established SSH connection
34
37
  #
35
38
  # @return [Boolean]
36
39
  def connected?
37
- raise "Override #connected? in sub-sessions"
40
+ raise 'Override #connected? in sub-sessions'
38
41
  end
39
42
 
40
43
  # Disconnect the underlying SSH connection
41
44
  #
42
45
  # @return [void]
43
46
  def disconnect
44
- raise "Override #disconnect in sub-sessions"
47
+ raise 'Override #disconnect in sub-sessions'
45
48
  end
46
49
 
47
50
  # Kill the underlying connection
48
51
  def kill!
49
- raise "Override #kill! in sub-sessions"
52
+ raise 'Override #kill! in sub-sessions'
50
53
  end
51
54
 
52
55
  # Execute a command
53
56
  #
54
- def execute(commands, options = nil, &block)
55
- raise "Override #execute in sub-sessions"
57
+ def execute(_commands, _options = nil)
58
+ raise 'Override #execute in sub-sessions'
56
59
  end
57
60
 
58
- def write_data(path, data, options = nil, &block)
59
- raise "Override #write_data in sub-sessions"
61
+ def write_data(_path, _data, _options = nil)
62
+ raise 'Override #write_data in sub-sessions'
60
63
  end
61
64
 
62
65
  private
63
66
 
64
- def add_sudo_to_commands_array(commands, user)
67
+ def add_sudo_to_commands_array(commands, user, password = nil)
68
+ sudo_prefix = "sudo -u #{user}"
69
+ unless password.nil?
70
+ sudo_prefix += " --stdin -p '[sshake-sudo-password]: ' "
71
+ end
65
72
  commands.map do |command|
66
- "sudo -u #{user} --stdin #{command}"
73
+ "#{sudo_prefix} #{command}"
67
74
  end
68
75
  end
69
76
 
70
77
  def create_options(hash, block)
71
78
  if block && hash
72
79
  raise Error, 'You cannot provide a block and options'
73
- elsif block
80
+ end
81
+
82
+ if block
74
83
  ExecutionOptions.from_block(&block)
75
84
  elsif hash.is_a?(Hash)
76
85
  ExecutionOptions.from_hash(hash)
@@ -79,7 +88,7 @@ module SSHake
79
88
  end
80
89
  end
81
90
 
82
- def log(type, text, options = {})
91
+ def log(type, text, _options = {})
83
92
  logger = @logger || SSHake.logger
84
93
  return unless logger
85
94
 
@@ -95,7 +104,7 @@ module SSHake
95
104
 
96
105
  # Map sudo onto command
97
106
  if execution_options.sudo_user && options[:add_sudo] != false
98
- commands = add_sudo_to_commands_array(commands, execution_options.sudo_user)
107
+ commands = add_sudo_to_commands_array(commands, execution_options.sudo_user, execution_options.sudo_password)
99
108
  end
100
109
 
101
110
  # Construct a full command string to execute
@@ -105,9 +114,9 @@ module SSHake
105
114
  def handle_response(response, options)
106
115
  if !response.success? && ((options.raise_on_error.nil? && @raise_on_error) || options.raise_on_error?)
107
116
  raise ExecutionError, response
108
- else
109
- response
110
117
  end
118
+
119
+ response
111
120
  end
112
121
 
113
122
  end
@@ -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} (stderr: #{@response.stderr}) (exit code: #{@response.exit_code})"
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
- # @return [File]
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
- if hash[:sudo].is_a?(String)
77
+ case hash[:sudo]
78
+ when String
74
79
  options.sudo_user = hash[:sudo]
75
- elsif hash[:sudo].is_a?(Hash)
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
- elsif hash[:sudo] == true
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
@@ -1,7 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SSHake
4
+
4
5
  class << self
6
+
5
7
  attr_accessor :logger
8
+
6
9
  end
10
+
7
11
  end
@@ -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
- if @block
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
  require 'sshake/mock/command'
2
4
 
3
5
  module SSHake
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SSHake
2
4
  module Mock
3
5
  class Environment
@@ -7,9 +9,7 @@ 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
15
  @session ? @session.store : nil
@@ -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
@@ -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, :add_sudo => false)
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, options = nil, &block)
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 > 0
92
+ find_executed_commands(matcher).size.positive?
97
93
  end
94
+ # rubocop:enable Naming/PredicateName
98
95
 
99
96
  end
100
97
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'sshake/error'
2
4
 
3
5
  module SSHake
@@ -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
@@ -2,24 +2,27 @@
2
2
 
3
3
  module SSHake
4
4
  class Response
5
- def initialize
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 == 0
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
@@ -9,18 +9,44 @@ 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, *args)
26
+ def initialize(host, username_or_options = {}, options_with_username = {})
21
27
  super
22
28
  @host = host
23
- @session_options = args
29
+ if username_or_options.is_a?(String)
30
+ @user = username_or_options
31
+ @session_options = options_with_username
32
+ else
33
+ @user = username_or_options.delete(:user)
34
+ @session_options = username_or_options
35
+ end
36
+ end
37
+
38
+ # Return the username for the connection
39
+ #
40
+ # @return [String]
41
+ def user
42
+ @user || ENV['USER']
43
+ end
44
+
45
+ # Return the port that will be connected to
46
+ #
47
+ # @return [Integer]
48
+ def port
49
+ @session_options[:port] || 22
24
50
  end
25
51
 
26
52
  # Connect to the SSH server
@@ -29,7 +55,7 @@ module SSHake
29
55
  def connect
30
56
  log :debug, "Creating connection to #{@host}"
31
57
  log :debug, "Session options: #{@session_options.inspect}"
32
- @session = Net::SSH.start(@host, *@session_options)
58
+ @session = Net::SSH.start(@host, user, @session_options)
33
59
  true
34
60
  end
35
61
 
@@ -60,9 +86,9 @@ module SSHake
60
86
 
61
87
  # Kill the underlying connection
62
88
  def kill!
63
- log :debug, "Attempting kill/shutdown of session"
89
+ log :debug, 'Attempting kill/shutdown of session'
64
90
  @session.shutdown!
65
- log :debug, "Session shutdown success"
91
+ log :debug, 'Session shutdown success'
66
92
  @session = nil
67
93
  end
68
94
 
@@ -87,27 +113,31 @@ module SSHake
87
113
  channel.exec(command_to_execute) do |_, success|
88
114
  raise "Command \"#{command_to_execute}\" was unable to execute" unless success
89
115
 
90
- if options.stdin
91
- ch.send_data(options.stdin)
92
- end
116
+ ch.send_data(options.stdin) if options.stdin
93
117
 
94
- if options.file_to_stream.nil?
118
+ if options.file_to_stream.nil? && options.sudo_password.nil?
119
+ log :debug, 'Sending EOF to channel'
95
120
  ch.eof!
96
121
  end
97
122
 
98
123
  ch.on_data do |_, data|
99
124
  response.stdout += data
100
125
  options.stdout&.call(data)
101
- log :debug, data.gsub(/[\r]/, '')
126
+ log :debug, data.gsub(/\r/, '')
102
127
  end
103
128
 
104
129
  ch.on_extended_data do |_, _, data|
105
130
  response.stderr += data.delete("\r")
106
131
  options.stderr&.call(data)
107
132
  log :debug, data
108
- if data =~ /^\[sudo\] password for/
133
+ if options.sudo_password && data =~ /^\[sshake-sudo-password\]:\s\z/
109
134
  log :debug, 'Sending sudo password'
110
135
  ch.send_data "#{options.sudo_password}\n"
136
+
137
+ if options.file_to_stream.nil?
138
+ log :debug, 'Sending EOF after sudo password because no file'
139
+ ch.eof!
140
+ end
111
141
  end
112
142
  end
113
143
 
@@ -123,6 +153,7 @@ module SSHake
123
153
  if options.file_to_stream
124
154
  ch.on_process do |_, data|
125
155
  next if ch.eof?
156
+
126
157
  if ch.output.length < 128 * 1024
127
158
  if data = options.file_to_stream.read(1024 * 1024)
128
159
  ch.send_data(data)
@@ -137,8 +168,8 @@ module SSHake
137
168
  end
138
169
  channel.wait
139
170
  end
140
- rescue Timeout::Error => e
141
- log :debug, "Got timeout error while executing command"
171
+ rescue Timeout::Error
172
+ log :debug, 'Got timeout error while executing command'
142
173
  kill!
143
174
  response.timeout!
144
175
  ensure
@@ -153,13 +184,25 @@ module SSHake
153
184
  tmp_path = "/tmp/sshake-tmp-file-#{SecureRandom.hex(32)}"
154
185
  @session.sftp.file.open(tmp_path, 'w') do |f|
155
186
  d = data.dup.force_encoding('BINARY')
156
- until d.empty?
157
- f.write(d.slice!(0, 1024))
158
- end
187
+ f.write(d.slice!(0, 1024)) until d.empty?
159
188
  end
160
189
  response = execute("mv #{tmp_path} #{path}", options, &block)
161
190
  response.success?
162
191
  end
163
192
 
193
+ class << self
194
+
195
+ def create(*args)
196
+ session = new(*args)
197
+
198
+ if recorder = Thread.current[:sshake_recorder]
199
+ return RecordedSession.new(recorder, session)
200
+ end
201
+
202
+ session
203
+ end
204
+
205
+ end
206
+
164
207
  end
165
208
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SSHake
4
- VERSION = '1.0.2'
4
+
5
+ VERSION = '1.1.0'
6
+
5
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sshake
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adam Cooke
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-04 00:00:00.000000000 Z
11
+ date: 2020-11-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: net-sftp
@@ -57,6 +57,9 @@ files:
57
57
  - lib/sshake/mock/executed_command.rb
58
58
  - lib/sshake/mock/session.rb
59
59
  - lib/sshake/mock/unsupported_command_error.rb
60
+ - lib/sshake/recorded_session.rb
61
+ - lib/sshake/recorder.rb
62
+ - lib/sshake/recording.rb
60
63
  - lib/sshake/response.rb
61
64
  - lib/sshake/session.rb
62
65
  - lib/sshake/version.rb
@@ -72,7 +75,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
72
75
  requirements:
73
76
  - - ">="
74
77
  - !ruby/object:Gem::Version
75
- version: '0'
78
+ version: '2.6'
76
79
  required_rubygems_version: !ruby/object:Gem::Requirement
77
80
  requirements:
78
81
  - - ">="