sshkit 1.8.1 → 1.9.0.rc1

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.
@@ -0,0 +1,67 @@
1
+ # A Cache holds connections for a given key. Each connection is stored along
2
+ # with an expiration time so that its idle duration can be measured.
3
+ class SSHKit::Backend::ConnectionPool::Cache
4
+ def initialize(idle_timeout, closer)
5
+ @connections = []
6
+ @connections.extend(MonitorMixin)
7
+ @idle_timeout = idle_timeout
8
+ @closer = closer
9
+ end
10
+
11
+ # Remove and return a fresh connection from this Cache. Returns `nil` if
12
+ # the Cache is empty or if all existing connections have gone stale.
13
+ def pop
14
+ connections.synchronize do
15
+ evict
16
+ _, connection = connections.pop
17
+ connection
18
+ end
19
+ end
20
+
21
+ # Return a connection to this Cache.
22
+ def push(conn)
23
+ # No need to cache if the connection has already been closed.
24
+ return if closed?(conn)
25
+
26
+ connections.synchronize do
27
+ connections.push([Time.now + idle_timeout, conn])
28
+ end
29
+ end
30
+
31
+ # Close and remove any connections in this Cache that have been idle for
32
+ # too long.
33
+ def evict
34
+ # Peek at the first connection to see if it is still fresh. If so, we can
35
+ # return right away without needing to use `synchronize`.
36
+ first_expires_at, _connection = connections.first
37
+ return if first_expires_at.nil? || fresh?(first_expires_at)
38
+
39
+ connections.synchronize do
40
+ fresh, stale = connections.partition do |expires_at, _|
41
+ fresh?(expires_at)
42
+ end
43
+ connections.replace(fresh)
44
+ stale.each { |_, conn| closer.call(conn) }
45
+ end
46
+ end
47
+
48
+ # Close all connections and completely clear the cache.
49
+ def clear
50
+ connections.synchronize do
51
+ connections.map(&:last).each(&closer)
52
+ connections.clear
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ attr_reader :connections, :idle_timeout, :closer
59
+
60
+ def fresh?(expires_at)
61
+ expires_at > Time.now
62
+ end
63
+
64
+ def closed?(conn)
65
+ conn.respond_to?(:closed?) && conn.closed?
66
+ end
67
+ end
@@ -0,0 +1,11 @@
1
+ # A cache that holds no connections. Any connection provided to this cache
2
+ # is simply closed.
3
+ SSHKit::Backend::ConnectionPool::NilCache = Struct.new(:closer) do
4
+ def pop
5
+ nil
6
+ end
7
+
8
+ def push(conn)
9
+ closer.call(conn)
10
+ end
11
+ end
@@ -129,19 +129,15 @@ module SSHKit
129
129
  end
130
130
  end
131
131
 
132
- def with_ssh
133
- host.ssh_options = Netssh.config.ssh_options.merge(host.ssh_options || {})
134
- conn = self.class.pool.checkout(
132
+ def with_ssh(&block)
133
+ host.ssh_options = self.class.config.ssh_options.merge(host.ssh_options || {})
134
+ self.class.pool.with(
135
+ Net::SSH.method(:start),
135
136
  String(host.hostname),
136
137
  host.username,
137
138
  host.netssh_options,
138
- &Net::SSH.method(:start)
139
+ &block
139
140
  )
140
- begin
141
- yield conn.connection
142
- ensure
143
- self.class.pool.checkin conn
144
- end
145
141
  end
146
142
 
147
143
  end
@@ -10,6 +10,11 @@ module SSHKit
10
10
 
11
11
  alias :upload! :execute
12
12
  alias :download! :execute
13
+
14
+ def test(*)
15
+ super
16
+ true
17
+ end
13
18
  end
14
19
  end
15
20
  end
@@ -206,7 +206,11 @@ module SSHKit
206
206
  end
207
207
 
208
208
  def to_s
209
- [SSHKit.config.command_map[command.to_sym], *Array(args)].join(' ')
209
+ if should_map?
210
+ [SSHKit.config.command_map[command.to_sym], *Array(args)].join(' ')
211
+ else
212
+ command.to_s
213
+ end
210
214
  end
211
215
 
212
216
  private
@@ -28,8 +28,6 @@ module SSHKit
28
28
 
29
29
  def [](command)
30
30
  @storage[command] ||= []
31
-
32
- @storage[command]
33
31
  end
34
32
  end
35
33
 
@@ -3,7 +3,7 @@ module SSHKit
3
3
  class Configuration
4
4
 
5
5
  attr_accessor :umask, :output_verbosity
6
- attr_writer :output, :backend, :default_env
6
+ attr_writer :output, :backend, :default_env, :default_runner
7
7
 
8
8
  def output
9
9
  @output ||= use_format(:pretty)
@@ -22,6 +22,10 @@ module SSHKit
22
22
  @default_env ||= {}
23
23
  end
24
24
 
25
+ def default_runner
26
+ @default_runner ||= :parallel
27
+ end
28
+
25
29
  def backend
26
30
  @backend ||= SSHKit::Backend::Netssh
27
31
  end
@@ -78,7 +82,7 @@ module SSHKit
78
82
  found = SSHKit::Formatter.constants.find do |const|
79
83
  const.to_s.downcase == name
80
84
  end
81
- fail NameError, 'Unrecognized SSHKit::Formatter "#{symbol}"' if found.nil?
85
+ fail NameError, %Q{Unrecognized SSHKit::Formatter "#{symbol}"} if found.nil?
82
86
  SSHKit::Formatter.const_get(found)
83
87
  end
84
88
 
@@ -17,7 +17,7 @@ module SSHKit
17
17
  when :sequence then Runner::Sequential
18
18
  when :groups then Runner::Group
19
19
  else
20
- raise RuntimeError, "Don't know how to handle run style #{options[:in].inspect}"
20
+ options[:in]
21
21
  end.new(hosts, options, &block).execute
22
22
  else
23
23
  Runner::Null.new(hosts, options, &block).execute
@@ -26,13 +26,13 @@ module SSHKit
26
26
 
27
27
  private
28
28
 
29
- def default_options
30
- { in: :parallel }
31
- end
29
+ def default_options
30
+ { in: SSHKit.config.default_runner }
31
+ end
32
32
 
33
- def resolve_hosts
34
- @raw_hosts.collect { |rh| rh.is_a?(Host) ? rh : Host.new(rh) }.uniq
35
- end
33
+ def resolve_hosts
34
+ @raw_hosts.collect { |rh| rh.is_a?(Host) ? rh : Host.new(rh) }.uniq
35
+ end
36
36
 
37
37
  end
38
38
 
@@ -13,5 +13,3 @@ module SSHKit
13
13
  end
14
14
 
15
15
  end
16
-
17
- include SSHKit::DSL
@@ -7,12 +7,13 @@ module SSHKit
7
7
  class Abstract
8
8
 
9
9
  extend Forwardable
10
- attr_reader :original_output
10
+ attr_reader :original_output, :options
11
11
  def_delegators :@original_output, :read, :rewind
12
12
  def_delegators :@color, :colorize
13
13
 
14
- def initialize(output)
14
+ def initialize(output, options={})
15
15
  @original_output = output
16
+ @options = options
16
17
  @color = SSHKit::Color.new(output)
17
18
  end
18
19
 
@@ -23,11 +23,12 @@ module SSHKit
23
23
  end
24
24
 
25
25
  def log_command_data(command, stream_type, stream_data)
26
- color = case stream_type
26
+ color = \
27
+ case stream_type
27
28
  when :stdout then :green
28
29
  when :stderr then :red
29
30
  else raise "Unrecognised stream_type #{stream_type}, expected :stdout or :stderr"
30
- end
31
+ end
31
32
  write_message(Logger::DEBUG, colorize("\t#{stream_data}".chomp, color), command.uuid)
32
33
  end
33
34
 
@@ -4,7 +4,8 @@ module SSHKit
4
4
 
5
5
  def initialize(mapping, log_level=nil)
6
6
  @log_level = log_level
7
- @mapping_proc = case mapping
7
+ @mapping_proc = \
8
+ case mapping
8
9
  when Hash
9
10
  lambda do |server_output|
10
11
  first_matching_key_value = mapping.find { |k, _v| k === server_output }
@@ -14,7 +15,7 @@ module SSHKit
14
15
  mapping
15
16
  else
16
17
  raise "Unsupported mapping type: #{mapping.class} - only Hash and Proc mappings are supported"
17
- end
18
+ end
18
19
  end
19
20
 
20
21
  def on_data(_command, stream_name, data, channel)
@@ -44,4 +45,4 @@ module SSHKit
44
45
 
45
46
  end
46
47
 
47
- end
48
+ end
@@ -6,9 +6,8 @@ module SSHKit
6
6
 
7
7
  class Parallel < Abstract
8
8
  def execute
9
- threads = []
10
- hosts.each do |host|
11
- threads << Thread.new(host) do |h|
9
+ threads = hosts.map do |host|
10
+ Thread.new(host) do |h|
12
11
  begin
13
12
  backend(h, &block).run
14
13
  rescue StandardError => e
@@ -17,7 +16,7 @@ module SSHKit
17
16
  end
18
17
  end
19
18
  end
20
- threads.map(&:join)
19
+ threads.each(&:join)
21
20
  end
22
21
  end
23
22
 
@@ -1,3 +1,3 @@
1
1
  module SSHKit
2
- VERSION = "1.8.1"
2
+ VERSION = "1.9.0.rc1"
3
3
  end
@@ -20,9 +20,10 @@ Gem::Specification.new do |gem|
20
20
  gem.add_runtime_dependency('net-ssh', '>= 2.8.0')
21
21
  gem.add_runtime_dependency('net-scp', '>= 1.1.2')
22
22
 
23
- gem.add_development_dependency('minitest', ['>= 2.11.3', '< 2.12.0'])
23
+ gem.add_development_dependency('minitest', '>= 5.0.0')
24
+ gem.add_development_dependency('minitest-reporters')
24
25
  gem.add_development_dependency('rake')
25
- gem.add_development_dependency('turn')
26
+ gem.add_development_dependency('rubocop')
26
27
  gem.add_development_dependency('unindent')
27
28
  gem.add_development_dependency('mocha')
28
29
  end
@@ -3,7 +3,7 @@ module SSHKit
3
3
 
4
4
  module Backend
5
5
 
6
- class TestLocal < MiniTest::Unit::TestCase
6
+ class TestLocal < Minitest::Test
7
7
 
8
8
  def setup
9
9
  super
@@ -1,9 +1,9 @@
1
1
  require 'rubygems'
2
2
  require 'bundler/setup'
3
3
  require 'tempfile'
4
- require 'minitest/unit'
4
+ require 'minitest/autorun'
5
+ require 'minitest/reporters'
5
6
  require 'mocha/setup'
6
- require 'turn'
7
7
  require 'unindent'
8
8
  require 'stringio'
9
9
  require 'json'
@@ -14,7 +14,7 @@ require 'sshkit'
14
14
 
15
15
  Dir[File.expand_path('test/support/*.rb')].each { |file| require file }
16
16
 
17
- class UnitTest < MiniTest::Unit::TestCase
17
+ class UnitTest < Minitest::Test
18
18
 
19
19
  def setup
20
20
  SSHKit.reset_configuration!
@@ -27,7 +27,7 @@ class UnitTest < MiniTest::Unit::TestCase
27
27
  end
28
28
  end
29
29
 
30
- class FunctionalTest < MiniTest::Unit::TestCase
30
+ class FunctionalTest < Minitest::Test
31
31
 
32
32
  def setup
33
33
  unless VagrantWrapper.running?
@@ -78,7 +78,4 @@ end
78
78
  #
79
79
  # Force colours in Autotest
80
80
  #
81
- Turn.config.ansi = true
82
- Turn.config.format = :pretty
83
-
84
- MiniTest::Unit.autorun
81
+ Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new
@@ -77,6 +77,20 @@ module SSHKit
77
77
  assert_equal "Some stdout\n ", output
78
78
  end
79
79
 
80
+ def test_within_properly_clears
81
+ backend = ExampleBackend.new do
82
+ within 'a' do
83
+ execute :cat, 'file', :strip => false
84
+ end
85
+
86
+ execute :cat, 'file', :strip => false
87
+ end
88
+
89
+ backend.run
90
+
91
+ assert_equal '/usr/bin/env cat file', backend.executed_command.to_command
92
+ end
93
+
80
94
  def test_background_logs_deprecation_warnings
81
95
  deprecation_out = ''
82
96
  SSHKit.config.deprecation_output = deprecation_out
@@ -117,6 +131,28 @@ module SSHKit
117
131
  end
118
132
  end
119
133
 
134
+ def test_current_refers_to_currently_executing_backend
135
+ backend = nil
136
+ current = nil
137
+
138
+ backend = ExampleBackend.new do
139
+ backend = self
140
+ current = SSHKit::Backend.current
141
+ end
142
+ backend.run
143
+
144
+ assert_equal(backend, current)
145
+ end
146
+
147
+ def test_current_is_nil_outside_of_the_block
148
+ backend = ExampleBackend.new do
149
+ # nothing
150
+ end
151
+ backend.run
152
+
153
+ assert_nil(SSHKit::Backend.current)
154
+ end
155
+
120
156
  # Use a concrete ExampleBackend rather than a mock for improved assertion granularity
121
157
  class ExampleBackend < Abstract
122
158
  attr_writer :full_stdout
@@ -32,65 +32,62 @@ module SSHKit
32
32
 
33
33
  def test_connection_factory_receives_args
34
34
  args = %w(a b c)
35
- conn = pool.checkout(*args, &echo_args)
35
+ conn = pool.with(echo_args, *args) { |c| c }
36
36
 
37
- assert_equal args, conn.connection
37
+ assert_equal args, conn
38
38
  end
39
39
 
40
40
  def test_connections_are_not_reused_if_not_checked_in
41
- conn1 = pool.checkout("conn", &connect)
42
- conn2 = pool.checkout("conn", &connect)
41
+ conn1 = nil
42
+ conn2 = nil
43
+
44
+ pool.with(connect, "conn") do |yielded_conn_1|
45
+ conn1 = yielded_conn_1
46
+ conn2 = pool.with(connect, "conn") { |c| c }
47
+ end
43
48
 
44
49
  refute_equal conn1, conn2
45
50
  end
46
51
 
47
52
  def test_connections_are_reused_if_checked_in
48
- conn1 = pool.checkout("conn", &connect)
49
- pool.checkin conn1
50
- conn2 = pool.checkout("conn", &connect)
53
+ conn1 = pool.with(connect, "conn") {}
54
+ conn2 = pool.with(connect, "conn") {}
51
55
 
52
56
  assert_equal conn1, conn2
53
57
  end
54
58
 
55
59
  def test_connections_are_reused_across_threads_multiple_times
56
- t1 = Thread.new {
57
- Thread.current[:conn] = pool.checkout("conn", &connect)
58
- pool.checkin Thread.current[:conn]
59
- }.join
60
+ t1 = Thread.new do
61
+ pool.with(connect, "conn") { |c| c }
62
+ end
60
63
 
61
- t2 = Thread.new {
62
- Thread.current[:conn] = pool.checkout("conn", &connect)
63
- pool.checkin Thread.current[:conn]
64
- }.join
64
+ t2 = Thread.new do
65
+ pool.with(connect, "conn") { |c| c }
66
+ end
65
67
 
66
- t3 = Thread.new {
67
- Thread.current[:conn] = pool.checkout("conn", &connect)
68
- pool.checkin Thread.current[:conn]
69
- }.join
68
+ t3 = Thread.new do
69
+ pool.with(connect, "conn") { |c| c }
70
+ end
70
71
 
71
- refute_equal t1[:conn], nil
72
- assert_equal t1[:conn], t2[:conn]
73
- assert_equal t2[:conn], t3[:conn]
72
+ refute_nil t1.value
73
+ assert_equal t1.value, t2.value
74
+ assert_equal t2.value, t3.value
74
75
  end
75
76
 
76
- def test_zero_idle_timeout_disables_reuse
77
+ def test_zero_idle_timeout_disables_pooling
77
78
  pool.idle_timeout = 0
78
79
 
79
- conn1 = pool.checkout("conn", &connect)
80
- pool.checkin conn1
81
-
82
- conn2 = pool.checkout("conn", &connect)
83
-
80
+ conn1 = pool.with(connect, "conn") { |c| c }
81
+ conn2 = pool.with(connect, "conn") { |c| c }
84
82
  refute_equal conn1, conn2
85
83
  end
86
84
 
87
85
  def test_expired_connection_is_not_reused
88
86
  pool.idle_timeout = 0.1
89
87
 
90
- conn1 = pool.checkout("conn", &connect)
91
- pool.checkin conn1
88
+ conn1 = pool.with(connect, "conn") { |c| c }
92
89
  sleep(pool.idle_timeout)
93
- conn2 = pool.checkout("conn", &connect)
90
+ conn2 = pool.with(connect, "conn") { |c| c }
94
91
 
95
92
  refute_equal conn1, conn2
96
93
  end
@@ -98,43 +95,45 @@ module SSHKit
98
95
  def test_expired_connection_is_closed
99
96
  pool.idle_timeout = 0.1
100
97
  conn1 = mock
101
- conn1.expects(:closed?).returns(false)
98
+ conn1.expects(:closed?).twice.returns(false)
102
99
  conn1.expects(:close)
103
100
 
104
- entry1 = pool.checkout("conn1"){|*args| conn1 }
105
- pool.checkin entry1
106
- sleep(pool.idle_timeout)
107
- pool.checkout("conn2"){|*args| Object.new}
101
+ pool.with(->(*) { conn1 }, "conn1") {}
102
+ # Pause to allow the background thread to wake and close the conn
103
+ sleep(5 + pool.idle_timeout)
108
104
  end
109
105
 
110
106
  def test_closed_connection_is_not_reused
111
- conn1 = pool.checkout("conn", &connect_and_close)
112
- pool.checkin conn1
113
- conn2 = pool.checkout("conn", &connect)
107
+ conn1 = pool.with(connect_and_close, "conn") { |c| c }
108
+ conn2 = pool.with(connect, "conn") { |c| c }
114
109
 
115
110
  refute_equal conn1, conn2
116
111
  end
117
112
 
118
113
  def test_connections_with_different_args_are_not_reused
119
- conn1 = pool.checkout("conn1", &connect)
120
- pool.checkin conn1
121
- conn2 = pool.checkout("conn2", &connect)
114
+ conn1 = pool.with(connect, "conn1") { |c| c }
115
+ conn2 = pool.with(connect, "conn2") { |c| c }
122
116
 
123
117
  refute_equal conn1, conn2
124
118
  end
125
119
 
126
120
  def test_close_connections
127
121
  conn1 = mock
128
- conn1.expects(:closed?).returns(false)
122
+ conn1.expects(:closed?).twice.returns(false)
129
123
  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
124
 
135
- pool.close_connections
136
- end
125
+ conn2 = mock
126
+ conn2.expects(:closed?).returns(false)
127
+ conn2.expects(:close).never
128
+
129
+ pool.with(->(*) { conn1 }, "conn1") {}
137
130
 
131
+ # We are using conn2 when close_connections is called, so it should
132
+ # not be closed.
133
+ pool.with(->(*) { conn2 }, "conn2") do
134
+ pool.close_connections
135
+ end
136
+ end
138
137
  end
139
138
  end
140
139
  end