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 +4 -4
- data/lib/sshake/base_session.rb +25 -16
- 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/logger.rb +4 -0
- data/lib/sshake/mock/command.rb +3 -3
- data/lib/sshake/mock/command_set.rb +2 -0
- data/lib/sshake/mock/environment.rb +3 -3
- 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 +59 -16
- data/lib/sshake/version.rb +3 -1
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7f07fcd2695da038870ae9a7c15715524ed1cbd1912a38c9cbd4c3d3d9f39d96
|
4
|
+
data.tar.gz: c941f1f5ae3dd0348da4c694f769fdad2fcaa026777cf0c8a5cd6278d179a87a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0c7636ffe034fc59aa89043bfb44d6e78d156567283f5a77b82eeb98df91f8977e561c2435f2512261166a0242fe0d75b5c7ca8369b9babd6f96729f29323dc5
|
7
|
+
data.tar.gz: bc5eb231a03e4e207a9c478bdb4b3764366f48d82576d6d60b2daa3571cce7383c9f8d6d4d49d786b8c6fbf6582fee132cdb7039f7a40c9a90a44ffd5083856f
|
data/lib/sshake/base_session.rb
CHANGED
@@ -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(*
|
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
|
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
|
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
|
47
|
+
raise 'Override #disconnect in sub-sessions'
|
45
48
|
end
|
46
49
|
|
47
50
|
# Kill the underlying connection
|
48
51
|
def kill!
|
49
|
-
raise
|
52
|
+
raise 'Override #kill! in sub-sessions'
|
50
53
|
end
|
51
54
|
|
52
55
|
# Execute a command
|
53
56
|
#
|
54
|
-
def execute(
|
55
|
-
raise
|
57
|
+
def execute(_commands, _options = nil)
|
58
|
+
raise 'Override #execute in sub-sessions'
|
56
59
|
end
|
57
60
|
|
58
|
-
def write_data(
|
59
|
-
raise
|
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
|
-
"
|
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
|
-
|
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,
|
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
|
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/logger.rb
CHANGED
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,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
|
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,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,
|
26
|
+
def initialize(host, username_or_options = {}, options_with_username = {})
|
21
27
|
super
|
22
28
|
@host = host
|
23
|
-
|
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,
|
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,
|
89
|
+
log :debug, 'Attempting kill/shutdown of session'
|
64
90
|
@session.shutdown!
|
65
|
-
log :debug,
|
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(
|
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\]
|
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
|
141
|
-
log :debug,
|
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
|
data/lib/sshake/version.rb
CHANGED
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
|
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-
|
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: '
|
78
|
+
version: '2.6'
|
76
79
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
77
80
|
requirements:
|
78
81
|
- - ">="
|