sshkit 1.7.1 → 1.8.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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -2
  3. data/BREAKING_API_WISHLIST.md +14 -0
  4. data/CHANGELOG.md +74 -0
  5. data/CONTRIBUTING.md +43 -0
  6. data/EXAMPLES.md +265 -169
  7. data/Gemfile +7 -0
  8. data/README.md +274 -9
  9. data/RELEASING.md +16 -8
  10. data/Rakefile +8 -0
  11. data/lib/sshkit.rb +0 -9
  12. data/lib/sshkit/all.rb +6 -4
  13. data/lib/sshkit/backends/abstract.rb +42 -42
  14. data/lib/sshkit/backends/connection_pool.rb +57 -8
  15. data/lib/sshkit/backends/local.rb +21 -50
  16. data/lib/sshkit/backends/netssh.rb +45 -98
  17. data/lib/sshkit/backends/printer.rb +3 -23
  18. data/lib/sshkit/backends/skipper.rb +4 -8
  19. data/lib/sshkit/color.rb +51 -20
  20. data/lib/sshkit/command.rb +68 -47
  21. data/lib/sshkit/configuration.rb +38 -5
  22. data/lib/sshkit/deprecation_logger.rb +17 -0
  23. data/lib/sshkit/formatters/abstract.rb +28 -4
  24. data/lib/sshkit/formatters/black_hole.rb +1 -2
  25. data/lib/sshkit/formatters/dot.rb +3 -10
  26. data/lib/sshkit/formatters/pretty.rb +31 -56
  27. data/lib/sshkit/formatters/simple_text.rb +6 -44
  28. data/lib/sshkit/host.rb +5 -6
  29. data/lib/sshkit/logger.rb +0 -1
  30. data/lib/sshkit/mapping_interaction_handler.rb +47 -0
  31. data/lib/sshkit/runners/parallel.rb +1 -1
  32. data/lib/sshkit/runners/sequential.rb +1 -1
  33. data/lib/sshkit/version.rb +1 -1
  34. data/sshkit.gemspec +0 -1
  35. data/test/functional/backends/test_local.rb +14 -1
  36. data/test/functional/backends/test_netssh.rb +58 -50
  37. data/test/helper.rb +2 -2
  38. data/test/unit/backends/test_abstract.rb +145 -0
  39. data/test/unit/backends/test_connection_pool.rb +27 -2
  40. data/test/unit/backends/test_printer.rb +47 -47
  41. data/test/unit/formatters/test_custom.rb +65 -0
  42. data/test/unit/formatters/test_dot.rb +25 -32
  43. data/test/unit/formatters/test_pretty.rb +114 -22
  44. data/test/unit/formatters/test_simple_text.rb +83 -0
  45. data/test/unit/test_color.rb +69 -5
  46. data/test/unit/test_command.rb +53 -18
  47. data/test/unit/test_command_map.rb +0 -4
  48. data/test/unit/test_configuration.rb +47 -7
  49. data/test/unit/test_coordinator.rb +45 -52
  50. data/test/unit/test_deprecation_logger.rb +38 -0
  51. data/test/unit/test_host.rb +3 -4
  52. data/test/unit/test_logger.rb +0 -1
  53. data/test/unit/test_mapping_interaction_handler.rb +101 -0
  54. metadata +37 -41
  55. data/lib/sshkit/utils/capture_output_methods.rb +0 -13
  56. data/test/functional/test_coordinator.rb +0 -17
@@ -6,6 +6,7 @@ module SSHKit
6
6
  class TestConnectionPool < UnitTest
7
7
 
8
8
  def setup
9
+ super
9
10
  pool.flush_connections
10
11
  end
11
12
 
@@ -14,11 +15,11 @@ module SSHKit
14
15
  end
15
16
 
16
17
  def connect
17
- ->(*args) { Object.new }
18
+ ->(*_args) { Object.new }
18
19
  end
19
20
 
20
21
  def connect_and_close
21
- ->(*args) { OpenStruct.new(:closed? => true) }
22
+ ->(*_args) { OpenStruct.new(:closed? => true) }
22
23
  end
23
24
 
24
25
  def echo_args
@@ -94,6 +95,18 @@ module SSHKit
94
95
  refute_equal conn1, conn2
95
96
  end
96
97
 
98
+ def test_expired_connection_is_closed
99
+ pool.idle_timeout = 0.1
100
+ conn1 = mock
101
+ conn1.expects(:closed?).returns(false)
102
+ conn1.expects(:close)
103
+
104
+ entry1 = pool.checkout("conn1"){|*args| conn1 }
105
+ pool.checkin entry1
106
+ sleep(pool.idle_timeout)
107
+ pool.checkout("conn2"){|*args| Object.new}
108
+ end
109
+
97
110
  def test_closed_connection_is_not_reused
98
111
  conn1 = pool.checkout("conn", &connect_and_close)
99
112
  pool.checkin conn1
@@ -110,6 +123,18 @@ module SSHKit
110
123
  refute_equal conn1, conn2
111
124
  end
112
125
 
126
+ def test_close_connections
127
+ conn1 = mock
128
+ conn1.expects(:closed?).returns(false)
129
+ conn1.expects(:close)
130
+ entry1 = pool.checkout("conn1"){|*args| conn1 }
131
+ pool.checkin entry1
132
+ entry2 = pool.checkout("conn2", &connect)
133
+ # entry2 isn't closed if close_connections is called
134
+
135
+ pool.close_connections
136
+ end
137
+
113
138
  end
114
139
  end
115
140
  end
@@ -1,73 +1,73 @@
1
1
  require 'helper'
2
2
 
3
3
  module SSHKit
4
-
5
4
  module Backend
6
-
7
5
  class TestPrinter < UnitTest
8
6
 
9
- def block_to_run
10
- lambda do |host|
11
- execute :ls, '-l', '/some/directory'
12
- end
13
- end
14
-
15
- def backend
16
- @backend ||= Printer
7
+ def setup
8
+ super
9
+ SSHKit.config.output = SSHKit::Formatter::Pretty.new(output)
10
+ SSHKit.config.output_verbosity = Logger::DEBUG
11
+ Command.any_instance.stubs(:uuid).returns('aaaaaa')
17
12
  end
18
13
 
19
- def teardown
20
- @backend = nil
14
+ def output
15
+ @output ||= String.new
21
16
  end
22
17
 
23
18
  def printer
24
- Printer.new(Host.new(:'example.com'), &block_to_run)
19
+ @printer ||= Printer.new(Host.new('example.com'))
25
20
  end
26
21
 
27
- def setup
28
- SSHKit.config.output_verbosity = :debug
22
+ def test_execute
23
+ printer.execute 'uname -a'
24
+ assert_output_lines(
25
+ ' INFO [aaaaaa] Running /usr/bin/env uname -a on example.com',
26
+ ' DEBUG [aaaaaa] Command: uname -a'
27
+ )
29
28
  end
30
29
 
31
- def test_simple_printing
32
- result = StringIO.new
33
- SSHKit.capture_output(result) do
34
- printer.run
35
- end
36
- result.rewind
37
- assert_equal "/usr/bin/env ls -l /some/directory\n", result.read
38
- end
30
+ def test_test_method
31
+ printer.test '[ -d /some/file ]'
39
32
 
40
- def test_printer_respond_to_configure
41
- assert backend.respond_to?(:configure)
33
+ assert_output_lines(
34
+ ' DEBUG [aaaaaa] Running /usr/bin/env [ -d /some/file ] on example.com',
35
+ ' DEBUG [aaaaaa] Command: [ -d /some/file ]'
36
+ )
42
37
  end
43
38
 
44
- def test_printer_any_params_config
45
- backend.configure do |ssh|
46
- ssh.pty = true
47
- ssh.connection_timeout = 30
48
- ssh.ssh_options = {
49
- keys: %w(/home/user/.ssh/id_rsa),
50
- forward_agent: false,
51
- auth_methods: %w(publickey password)
52
- }
53
- end
39
+ def test_capture
40
+ result = printer.capture 'ls -l'
54
41
 
55
- assert_equal 30, backend.config.connection_timeout
56
- assert_equal true, backend.config.pty
42
+ assert_equal '', result
57
43
 
58
- assert_equal %w(/home/user/.ssh/id_rsa), backend.config.ssh_options[:keys]
59
- assert_equal false, backend.config.ssh_options[:forward_agent]
60
- assert_equal %w(publickey password), backend.config.ssh_options[:auth_methods]
44
+ assert_output_lines(
45
+ ' DEBUG [aaaaaa] Running /usr/bin/env ls -l on example.com',
46
+ ' DEBUG [aaaaaa] Command: ls -l'
47
+ )
61
48
  end
62
49
 
63
- def test_invoke_raises_no_method_error
64
- assert_raises NoMethodError do
65
- printer.invoke :echo
66
- end
50
+ def test_upload
51
+ printer.upload! '/some/file', '/remote'
52
+ assert_output_lines(
53
+ ' INFO [aaaaaa] Running /usr/bin/env /some/file /remote on example.com',
54
+ ' DEBUG [aaaaaa] Command: /usr/bin/env /some/file /remote'
55
+ )
67
56
  end
68
57
 
69
- end
58
+ def test_download
59
+ printer.download! 'remote/file', '/local/path'
60
+ assert_output_lines(
61
+ ' INFO [aaaaaa] Running /usr/bin/env remote/file /local/path on example.com',
62
+ ' DEBUG [aaaaaa] Command: /usr/bin/env remote/file /local/path'
63
+ )
64
+ end
70
65
 
71
- end
66
+ private
72
67
 
73
- end
68
+ def assert_output_lines(*expected_lines)
69
+ assert_equal(expected_lines, output.split("\n"))
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,65 @@
1
+ require 'helper'
2
+
3
+ module SSHKit
4
+ # Try to maintain backwards compatibility with Custom formatters defined by other people
5
+ class TestCustom < UnitTest
6
+
7
+ def setup
8
+ super
9
+ SSHKit.config.output_verbosity = Logger::DEBUG
10
+ end
11
+
12
+ def output
13
+ @output ||= String.new
14
+ end
15
+
16
+ def custom
17
+ @custom ||= CustomFormatter.new(output)
18
+ end
19
+
20
+ {
21
+ log: 'LM 1 Test',
22
+ fatal: 'LM 4 Test',
23
+ error: 'LM 3 Test',
24
+ warn: 'LM 2 Test',
25
+ info: 'LM 1 Test',
26
+ debug: 'LM 0 Test'
27
+ }.each do |level, expected_output|
28
+ define_method("test_#{level}_logging") do
29
+ custom.send(level, 'Test')
30
+ assert_log_output expected_output
31
+ end
32
+ end
33
+
34
+ def test_write_logs_commands
35
+ custom.write(Command.new(:ls))
36
+
37
+ assert_log_output 'C 1 /usr/bin/env ls'
38
+ end
39
+
40
+ def test_double_chevron_logs_commands
41
+ custom << Command.new(:ls)
42
+
43
+ assert_log_output 'C 1 /usr/bin/env ls'
44
+ end
45
+
46
+ private
47
+
48
+ def assert_log_output(expected_output)
49
+ assert_equal expected_output, output
50
+ end
51
+
52
+ end
53
+
54
+ class CustomFormatter < SSHKit::Formatter::Abstract
55
+ def write(obj)
56
+ original_output << case obj
57
+ when SSHKit::Command then "C #{obj.verbosity} #{obj}"
58
+ when SSHKit::LogMessage then "LM #{obj.verbosity} #{obj}"
59
+ end
60
+ end
61
+ alias :<< :write
62
+
63
+ end
64
+
65
+ end
@@ -1,65 +1,58 @@
1
1
  require 'helper'
2
- require 'sshkit'
3
2
 
4
3
  module SSHKit
5
4
  class TestDot < UnitTest
6
5
 
7
6
  def setup
7
+ super
8
8
  SSHKit.config.output_verbosity = Logger::DEBUG
9
9
  end
10
10
 
11
11
  def output
12
- @_output ||= String.new
12
+ @output ||= String.new
13
13
  end
14
14
 
15
15
  def dot
16
- @_dot ||= SSHKit::Formatter::Dot.new(output)
16
+ @dot ||= SSHKit::Formatter::Dot.new(output)
17
17
  end
18
18
 
19
- def teardown
20
- remove_instance_variable :@_dot
21
- remove_instance_variable :@_output
22
- SSHKit.reset_configuration!
19
+ %w(fatal error warn info debug).each do |level|
20
+ define_method("test_#{level}_output") do
21
+ dot.send(level, 'Test')
22
+ assert_log_output('')
23
+ end
23
24
  end
24
25
 
25
- def test_logging_fatal
26
- dot << SSHKit::LogMessage.new(Logger::FATAL, "Test")
27
- assert_equal "", output.strip
26
+ def test_log_command_start
27
+ dot.log_command_start(SSHKit::Command.new(:ls))
28
+ assert_log_output('')
28
29
  end
29
30
 
30
- def test_logging_error
31
- dot << SSHKit::LogMessage.new(Logger::ERROR, "Test")
32
- assert_equal "", output.strip
33
- end
34
-
35
- def test_logging_warn
36
- dot << SSHKit::LogMessage.new(Logger::WARN, "Test")
37
- assert_equal "", output.strip
38
- end
39
-
40
- def test_logging_info
41
- dot << SSHKit::LogMessage.new(Logger::INFO, "Test")
42
- assert_equal "", output.strip
43
- end
44
-
45
- def test_logging_debug
46
- dot << SSHKit::LogMessage.new(Logger::DEBUG, "Test")
47
- assert_equal "", output.strip
31
+ def test_log_command_data
32
+ dot.log_command_data(SSHKit::Command.new(:ls), :stdout, 'Some output')
33
+ assert_log_output('')
48
34
  end
49
35
 
50
36
  def test_command_success
37
+ output.stubs(:tty?).returns(true)
51
38
  command = SSHKit::Command.new(:ls)
52
39
  command.exit_status = 0
53
- dot << command
54
- assert_equal "\e[0;32;49m.\e[0m", output.strip
40
+ dot.log_command_exit(command)
41
+ assert_log_output("\e[0;32;49m.\e[0m")
55
42
  end
56
43
 
57
44
  def test_command_failure
45
+ output.stubs(:tty?).returns(true)
58
46
  command = SSHKit::Command.new(:ls, {raise_on_non_zero_exit: false})
59
47
  command.exit_status = 1
60
- dot << command
61
- assert_equal "\e[0;31;49m.\e[0m", output.strip
48
+ dot.log_command_exit(command)
49
+ assert_log_output("\e[0;31;49m.\e[0m")
62
50
  end
63
51
 
52
+ private
53
+
54
+ def assert_log_output(expected_output)
55
+ assert_equal expected_output, output
56
+ end
64
57
  end
65
58
  end
@@ -1,50 +1,142 @@
1
1
  require 'helper'
2
- require 'sshkit'
3
2
 
4
3
  module SSHKit
5
4
  class TestPretty < UnitTest
6
5
 
7
6
  def setup
7
+ super
8
8
  SSHKit.config.output_verbosity = Logger::DEBUG
9
+ Command.any_instance.stubs(:uuid).returns('aaaaaa')
9
10
  end
10
11
 
11
12
  def output
12
- @_output ||= String.new
13
+ @output ||= String.new
13
14
  end
14
15
 
15
16
  def pretty
16
- @_pretty ||= SSHKit::Formatter::Pretty.new(output)
17
+ @pretty ||= SSHKit::Formatter::Pretty.new(output)
17
18
  end
18
19
 
19
- def teardown
20
- remove_instance_variable :@_pretty
21
- remove_instance_variable :@_output
22
- SSHKit.reset_configuration!
20
+ {
21
+ log: "\e[0;34;49mINFO\e[0m Test\n",
22
+ fatal: "\e[0;31;49mFATAL\e[0m Test\n",
23
+ error: "\e[0;31;49mERROR\e[0m Test\n",
24
+ warn: "\e[0;33;49mWARN\e[0m Test\n",
25
+ info: "\e[0;34;49mINFO\e[0m Test\n",
26
+ debug: "\e[0;30;49mDEBUG\e[0m Test\n"
27
+ }.each do |level, expected_output|
28
+ define_method("test_#{level}_output_with_color") do
29
+ output.stubs(:tty?).returns(true)
30
+ pretty.send(level, 'Test')
31
+ assert_log_output(expected_output)
32
+ end
23
33
  end
24
34
 
25
- def test_logging_fatal
26
- pretty << SSHKit::LogMessage.new(Logger::FATAL, "Test")
27
- assert_equal output.strip, "\e[0;31;49mFATAL\e[0m Test"
35
+ def test_command_lifecycle_logging_with_color
36
+ output.stubs(:tty?).returns(true)
37
+ simulate_command_lifecycle(pretty)
38
+
39
+ expected_log_lines = [
40
+ "\e[0;34;49mINFO\e[0m [\e[0;32;49maaaaaa\e[0m] Running \e[1;33;49m/usr/bin/env a_cmd some args\e[0m as \e[0;34;49muser\e[0m@\e[0;34;49mlocalhost\e[0m",
41
+ "\e[0;30;49mDEBUG\e[0m [\e[0;32;49maaaaaa\e[0m] Command: \e[0;34;49m/usr/bin/env a_cmd some args\e[0m",
42
+ "\e[0;30;49mDEBUG\e[0m [\e[0;32;49maaaaaa\e[0m] \e[0;32;49m\tstdout message\e[0m",
43
+ "\e[0;30;49mDEBUG\e[0m [\e[0;32;49maaaaaa\e[0m] \e[0;31;49m\tstderr message\e[0m",
44
+ "\e[0;34;49mINFO\e[0m [\e[0;32;49maaaaaa\e[0m] Finished in 1.000 seconds with exit status 0 (\e[1;32;49msuccessful\e[0m)."
45
+ ]
46
+ assert_equal expected_log_lines, output.split("\n")
47
+ end
48
+
49
+ {
50
+ log: " INFO Test\n",
51
+ fatal: " FATAL Test\n",
52
+ error: " ERROR Test\n",
53
+ warn: " WARN Test\n",
54
+ info: " INFO Test\n",
55
+ debug: " DEBUG Test\n"
56
+ }.each do |level, expected_output|
57
+ define_method("test_#{level}_output_without_color") do
58
+ pretty.send(level, "Test")
59
+ assert_log_output expected_output
60
+ end
61
+ end
62
+
63
+ def test_logging_message_with_leading_and_trailing_space
64
+ pretty.log(" some spaces\n\n \t")
65
+ assert_log_output " INFO some spaces\n"
28
66
  end
29
67
 
30
- def test_logging_error
31
- pretty << SSHKit::LogMessage.new(Logger::ERROR, "Test")
32
- assert_equal output.strip, "\e[0;31;49mERROR\e[0m Test"
68
+ def test_can_log_non_strings
69
+ pretty.log(Pathname.new('/var/log/my.log'))
70
+ assert_log_output " INFO /var/log/my.log\n"
33
71
  end
34
72
 
35
- def test_logging_warn
36
- pretty << SSHKit::LogMessage.new(Logger::WARN, "Test")
37
- assert_equal output.strip, "\e[0;33;49mWARN\e[0m Test".strip
73
+ def test_command_lifecycle_logging_without_color
74
+ simulate_command_lifecycle(pretty)
75
+
76
+ expected_log_lines = [
77
+ ' INFO [aaaaaa] Running /usr/bin/env a_cmd some args as user@localhost',
78
+ ' DEBUG [aaaaaa] Command: /usr/bin/env a_cmd some args',
79
+ " DEBUG [aaaaaa] \tstdout message",
80
+ " DEBUG [aaaaaa] \tstderr message",
81
+ ' INFO [aaaaaa] Finished in 1.000 seconds with exit status 0 (successful).'
82
+ ]
83
+
84
+ assert_equal expected_log_lines, output.split("\n")
38
85
  end
39
86
 
40
- def test_logging_info
41
- pretty << SSHKit::LogMessage.new(Logger::INFO, "Test")
42
- assert_equal output.strip, "\e[0;34;49mINFO\e[0m Test".strip
87
+ def test_unsupported_class
88
+ raised_error = assert_raises RuntimeError do
89
+ pretty << Pathname.new('/tmp')
90
+ end
91
+ assert_equal('write only supports formatting SSHKit::LogMessage, called with Pathname: #<Pathname:/tmp>', raised_error.message)
92
+ end
93
+
94
+ def test_does_not_log_message_when_verbosity_is_too_low
95
+ SSHKit.config.output_verbosity = Logger::WARN
96
+ pretty.info('Some info')
97
+ assert_log_output('')
98
+
99
+ SSHKit.config.output_verbosity = Logger::INFO
100
+ pretty.info('Some other info')
101
+ assert_log_output(" INFO Some other info\n")
102
+ end
103
+
104
+ def test_does_not_log_command_when_verbosity_is_too_low
105
+ SSHKit.config.output_verbosity = Logger::WARN
106
+ command = Command.new(:ls, host: Host.new('user@localhost'), verbosity: Logger::INFO)
107
+ pretty.log_command_start(command)
108
+ assert_log_output('')
109
+
110
+ SSHKit.config.output_verbosity = Logger::INFO
111
+ pretty.log_command_start(command)
112
+ assert_log_output(" INFO [aaaaaa] Running /usr/bin/env ls as user@localhost\n")
113
+ end
114
+
115
+
116
+ def test_can_write_to_output_which_just_supports_append
117
+ # Note output doesn't have to be an IO, it only needs to support <<
118
+ output = stub(:<<)
119
+ pretty = SSHKit::Formatter::Pretty.new(output)
120
+ simulate_command_lifecycle(pretty)
121
+ end
122
+
123
+ private
124
+
125
+ def simulate_command_lifecycle(pretty)
126
+ command = SSHKit::Command.new(:a_cmd, 'some args', host: Host.new('user@localhost'))
127
+ command.stubs(:runtime).returns(1)
128
+ pretty.log_command_start(command)
129
+ command.started = true
130
+ command.on_stdout(nil, 'stdout message')
131
+ pretty.log_command_data(command, :stdout, 'stdout message')
132
+ command.on_stderr(nil, 'stderr message')
133
+ pretty.log_command_data(command, :stderr, 'stderr message')
134
+ command.exit_status = 0
135
+ pretty.log_command_exit(command)
43
136
  end
44
137
 
45
- def test_logging_debug
46
- pretty << SSHKit::LogMessage.new(Logger::DEBUG, "Test")
47
- assert_equal output.strip, "\e[0;30;49mDEBUG\e[0m Test".strip
138
+ def assert_log_output(expected_output)
139
+ assert_equal expected_output, output
48
140
  end
49
141
 
50
142
  end