sshake 1.0.2 → 2.0.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: 95a0f6a6db803ff62826997e18e0b5d5ec81977046833bc32c10e29ebdbfd883
4
+ data.tar.gz: 8a2c9aa526b96827b3d50d25cd8b479bf3310eb6857822790410e79a38ac8423
5
5
  SHA512:
6
- metadata.gz: a2310a628972c8062ae526c569b135dd2cc4ee37a057f2924d81e43605f2aaaa8d42fb39c3078cf20e766857fbdc8c081bf6efad9c61d184f04ac1b0fafd33b9
7
- data.tar.gz: 67cb1c5689970aaac6c7987bb4e19d15152bf18dd36e2fa0bdaada484019ffd690b35025489c9cc7e8128c6c0d151c5f887aa62d9802d09c089c78d630c1fc55
6
+ metadata.gz: dc53e93b8e027f96c6078acb1626a056512dbf932a5650a1e4e4dfa53d28d72c219cff4acb392ca3d46fcbd5cd136aa7114b3edca27884817f86ace0648ec0c5
7
+ data.tar.gz: 25e70b953ed507800fa3f1a0531c926023d383f8a73caf8de201e12db2b4dd4e864af0080bf657952ad00112528572368e6c9eced39adf254858897e9487a6e3
@@ -1,13 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'securerandom'
2
- require 'sshake/logger'
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 [Logger, nil]
10
- attr_accessor :logger
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(*args)
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 "Override #connect in sub-sessions"
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 "Override #connected? in sub-sessions"
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 "Override #disconnect in sub-sessions"
49
+ raise 'Override #disconnect in sub-sessions'
45
50
  end
46
51
 
47
52
  # Kill the underlying connection
48
53
  def kill!
49
- raise "Override #kill! in sub-sessions"
54
+ raise 'Override #kill! in sub-sessions'
50
55
  end
51
56
 
52
57
  # Execute a command
53
58
  #
54
- def execute(commands, options = nil, &block)
55
- raise "Override #execute in sub-sessions"
59
+ def execute(_commands, _options = nil)
60
+ raise 'Override #execute in sub-sessions'
56
61
  end
57
62
 
58
- def write_data(path, data, options = nil, &block)
59
- raise "Override #write_data in sub-sessions"
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
- "sudo -u #{user} --stdin #{command}"
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
- elsif block
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} (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
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SSHake
4
+
5
+ class << self
6
+
7
+ def klogger
8
+ @klogger ||= Klogger.new(:ssh, destination: $stdout, formatter: :go)
9
+ end
10
+ attr_writer :klogger
11
+
12
+ end
13
+
14
+ 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,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 ? @session.store : nil
15
+ @session&.store
16
16
  end
17
17
 
18
18
  def written_files
19
- @session ? @session.written_files : nil
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
@@ -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,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, *args)
26
+ def initialize(host, username = nil, **options)
21
27
  super
22
28
  @host = host
23
- @session_options = args
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
- log :debug, "Creating connection to #{@host}"
31
- log :debug, "Session options: #{@session_options.inspect}"
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
- log :debug, 'Closing connectiong'
71
+ klogger.debug 'Closing connection', id: @id, host: @host
51
72
  @session.close
52
- log :debug, 'Connection closed successfully'
73
+ klogger.debug 'Connection closed', id: @id, host: @host
53
74
  rescue StandardError => e
54
- log :debug, "Connection not closed: #{e.message} (#{e.class})"
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
- log :debug, "Attempting kill/shutdown of session"
84
+ klogger.debug 'Attemping to shutdown', id: @id, host: @host
64
85
  @session.shutdown!
65
- log :debug, "Session shutdown success"
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
- # Log the command
79
- log :info, "Executing: #{command_to_execute}"
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
- begin
83
- channel = nil
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
- if options.file_to_stream.nil?
95
- ch.eof!
96
- end
106
+ Timeout.timeout(options.timeout) do
107
+ channel = @session.open_channel do |ch|
108
+ response.start_time = Time.now
97
109
 
98
- ch.on_data do |_, data|
99
- response.stdout += data
100
- options.stdout&.call(data)
101
- log :debug, data.gsub(/[\r]/, '')
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
- ch.on_extended_data do |_, _, data|
105
- response.stderr += data.delete("\r")
106
- options.stderr&.call(data)
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
- ch.on_request('exit-status') do |_, data|
115
- response.exit_code = data.read_long&.to_i
116
- log :debug, "Exit code: #{response.exit_code}"
117
- end
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
- ch.on_request('exit-signal') do |_, data|
120
- response.exit_signal = data.read_long
121
- end
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
- if options.file_to_stream
124
- ch.on_process do |_, data|
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
- channel.wait
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
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SSHake
4
- VERSION = '1.0.2'
4
+
5
+ VERSION = '2.0.0'
6
+
5
7
  end
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: 1.0.2
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: 2020-07-04 00:00:00.000000000 Z
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/logger.rb
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
- post_install_message:
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: '0'
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.0.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: []
data/lib/sshake/logger.rb DELETED
@@ -1,7 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SSHake
4
- class << self
5
- attr_accessor :logger
6
- end
7
- end