sshkit 1.8.1 → 1.9.0.rc1

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