sshake 1.0.2 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
  - - ">="