sshkit 1.7.1 → 1.8.0

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