sshkit 1.12.0 → 1.13.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
  SHA1:
3
- metadata.gz: 2075a49a2e5d52e26409761fffbb3da8dd6bc46d
4
- data.tar.gz: d9579584daf0ac0f3d2170fa4dc0c2bc57d3ac55
3
+ metadata.gz: 63716bfbb3150fe84235f1161bc7ecc9d303155c
4
+ data.tar.gz: 91103b89d73d2c9f56ade1ebd21ae80f85f6fb24
5
5
  SHA512:
6
- metadata.gz: 4a84ef1bf0b996cf4a3ecb0d855f8d7b872c1d0bc31f46804908b3f51f31cb1f2af0ff7d39e7bc498f8a7aecf05576116bff008b8955b853b6e39c3fcb6f71bf
7
- data.tar.gz: b2e42bf3216b4fdfbdd531329a09e9534775b71eb15dd2853319dca4ac358623b494474f179f136034beee435fd424f03b13d64fe6eb64dd283da39fcd6ae3f8
6
+ metadata.gz: 0cd4182399d780bc1a760be58589ee74bcbd1a8428d6ba1007c1f5a47ff587d3ce5f4a93258cac4be3d38f3e9a5f0bbc6e546ce5b4b72469b31195bdf2060dee
7
+ data.tar.gz: b022fccb0e7c55965b5a58420878fd6340eea1fd3a0ac5a47a50573e1149719fc784970c1f1956cfd3ea7ccb5b11ae413082c306806030059b5e0d2e0b0ada64
data/CHANGELOG.md CHANGED
@@ -7,6 +7,21 @@ appear at the top.
7
7
 
8
8
  * Your contribution here!
9
9
 
10
+ ## [1.13.0][] (2017-03-24)
11
+
12
+ ### Breaking changes
13
+
14
+ * None
15
+
16
+ ### New features
17
+
18
+ * [#372](https://github.com/capistrano/sshkit/pull/372): Use cp_r in local backend with recursive option - [@okuramasafumi](https://github.com/okuramasafumi)
19
+
20
+ ### Bug fixes
21
+
22
+ * [#390](https://github.com/capistrano/sshkit/pull/390): Properly wrap Ruby StandardError w/ add'l context - [@mattbrictson](https://github.com/mattbrictson)
23
+ * [#392](https://github.com/capistrano/sshkit/pull/392): Fix open two connections with changed cache key - [@shirosaki](https://github.com/shirosaki)
24
+
10
25
  ## [1.12.0][] (2017-02-10)
11
26
 
12
27
  ### Breaking changes
data/Dangerfile CHANGED
@@ -1,54 +1 @@
1
- # Adapted from https://github.com/ruby-grape/danger/blob/master/Dangerfile
2
- # Q: What is a Dangerfile, anyway? A: See http://danger.systems/
3
-
4
- # ------------------------------------------------------------------------------
5
- # Additional pull request data
6
- # ------------------------------------------------------------------------------
7
- project_name = github.pr_json["base"]["repo"]["name"]
8
- pr_number = github.pr_json["number"]
9
- pr_url = github.pr_json["_links"]["html"]["href"]
10
-
11
- # ------------------------------------------------------------------------------
12
- # What changed?
13
- # ------------------------------------------------------------------------------
14
- has_lib_changes = !git.modified_files.grep(/^lib/).empty?
15
- has_test_changes = !git.modified_files.grep(/^test/).empty?
16
- has_changelog_changes = git.modified_files.include?("CHANGELOG.md")
17
-
18
- # ------------------------------------------------------------------------------
19
- # You've made changes to lib, but didn't write any tests?
20
- # ------------------------------------------------------------------------------
21
- if has_lib_changes && !has_test_changes
22
- warn("There are code changes, but no corresponding tests. "\
23
- "Please include tests if this PR introduces any modifications in "\
24
- "#{project_name}'s behavior.",
25
- :sticky => false)
26
- end
27
-
28
- # ------------------------------------------------------------------------------
29
- # Have you updated CHANGELOG.md?
30
- # ------------------------------------------------------------------------------
31
- if !has_changelog_changes && has_lib_changes
32
- markdown <<-MARKDOWN
33
- Here's an example of a CHANGELOG.md entry (place it immediately under the `* Your contribution here!` line):
34
-
35
- ```markdown
36
- * [##{pr_number}](#{pr_url}): #{github.pr_title} - [@#{github.pr_author}](https://github.com/#{github.pr_author}).
37
- ```
38
- MARKDOWN
39
- warn("Please update CHANGELOG.md with a description of your changes. "\
40
- "If this PR is not a user-facing change (e.g. just refactoring), "\
41
- "you can disregard this.", :sticky => false)
42
- end
43
-
44
- # ------------------------------------------------------------------------------
45
- # Did you remove the CHANGELOG's "Your contribution here!" line?
46
- # ------------------------------------------------------------------------------
47
- if has_changelog_changes
48
- unless IO.read("CHANGELOG.md") =~ /^\s*\* Your contribution here/i
49
- fail(
50
- "Please put the `* Your contribution here!` line back into CHANGELOG.md.",
51
- :sticky => false
52
- )
53
- end
54
- end
1
+ danger.import_dangerfile(github: "capistrano/danger")
@@ -1,7 +1,10 @@
1
1
  # A Cache holds connections for a given key. Each connection is stored along
2
2
  # with an expiration time so that its idle duration can be measured.
3
3
  class SSHKit::Backend::ConnectionPool::Cache
4
- def initialize(idle_timeout, closer)
4
+ attr_accessor :key
5
+
6
+ def initialize(key, idle_timeout, closer)
7
+ @key = key
5
8
  @connections = []
6
9
  @connections.extend(MonitorMixin)
7
10
  @idle_timeout = idle_timeout
@@ -53,6 +56,10 @@ class SSHKit::Backend::ConnectionPool::Cache
53
56
  end
54
57
  end
55
58
 
59
+ def same_key?(other_key)
60
+ key == other_key
61
+ end
62
+
56
63
  protected
57
64
 
58
65
  attr_reader :connections, :idle_timeout, :closer
@@ -8,4 +8,8 @@ SSHKit::Backend::ConnectionPool::NilCache = Struct.new(:closer) do
8
8
  def push(conn)
9
9
  closer.call(conn)
10
10
  end
11
+
12
+ def same_key?(_key)
13
+ true
14
+ end
11
15
  end
@@ -61,6 +61,9 @@ class SSHKit::Backend::ConnectionPool
61
61
  yield(conn)
62
62
  ensure
63
63
  cache.push(conn) unless conn.nil?
64
+ # Sometimes the args mutate as a result of opening a connection. In this
65
+ # case we need to update the cache key to match the new args.
66
+ update_key_if_args_changed(cache, args)
64
67
  end
65
68
 
66
69
  # Immediately remove all cached connections, without closing them. This only
@@ -84,6 +87,10 @@ class SSHKit::Backend::ConnectionPool
84
87
 
85
88
  private
86
89
 
90
+ def cache_key_for_connection_args(args)
91
+ args.to_s
92
+ end
93
+
87
94
  def cache_enabled?
88
95
  idle_timeout && idle_timeout > 0
89
96
  end
@@ -91,7 +98,7 @@ class SSHKit::Backend::ConnectionPool
91
98
  # Look up a Cache that matches the given connection arguments.
92
99
  def find_cache(args)
93
100
  if cache_enabled?
94
- key = args.to_s
101
+ key = cache_key_for_connection_args(args)
95
102
  caches[key] || thread_safe_find_or_create_cache(key)
96
103
  else
97
104
  NilCache.new(method(:silently_close_connection))
@@ -103,11 +110,22 @@ class SSHKit::Backend::ConnectionPool
103
110
  def thread_safe_find_or_create_cache(key)
104
111
  caches.synchronize do
105
112
  caches[key] ||= begin
106
- Cache.new(idle_timeout, method(:silently_close_connection_later))
113
+ Cache.new(key, idle_timeout, method(:silently_close_connection_later))
107
114
  end
108
115
  end
109
116
  end
110
117
 
118
+ # Update cache key with changed args to prevent cache miss
119
+ def update_key_if_args_changed(cache, args)
120
+ new_key = cache_key_for_connection_args(args)
121
+ return if cache.same_key?(new_key)
122
+
123
+ caches.synchronize do
124
+ caches[new_key] = caches.delete(cache.key)
125
+ cache.key = new_key
126
+ end
127
+ end
128
+
111
129
  # Loops indefinitely to close connections and to find abandoned connections
112
130
  # that need to be closed.
113
131
  def run_eviction_loop
@@ -10,9 +10,13 @@ module SSHKit
10
10
  super(Host.new(:local), &block)
11
11
  end
12
12
 
13
- def upload!(local, remote, _options = {})
13
+ def upload!(local, remote, options = {})
14
14
  if local.is_a?(String)
15
- FileUtils.cp(local, remote)
15
+ if options[:recursive]
16
+ FileUtils.cp_r(local, remote)
17
+ else
18
+ FileUtils.cp(local, remote)
19
+ end
16
20
  else
17
21
  File.open(remote, "wb") do |f|
18
22
  IO.copy_stream(local, f)
@@ -36,12 +36,20 @@ module SSHKit
36
36
  if Net::SSH::VALID_OPTIONS.include?(:known_hosts)
37
37
  def default_options
38
38
  @default_options ||= {known_hosts: SSHKit::Backend::Netssh::KnownHosts.new}
39
+ assign_defaults
39
40
  end
40
41
  else
41
42
  def default_options
42
43
  @default_options ||= {}
44
+ assign_defaults
43
45
  end
44
46
  end
47
+
48
+ # Set default options early for ConnectionPool cache key
49
+ def assign_defaults
50
+ Net::SSH.assign_defaults(@default_options)
51
+ @default_options
52
+ end
45
53
  end
46
54
 
47
55
  def upload!(local, remote, options = {})
@@ -10,7 +10,7 @@ module SSHKit
10
10
  Thread.new(host) do |h|
11
11
  begin
12
12
  backend(h, &block).run
13
- rescue StandardError => e
13
+ rescue ::StandardError => e
14
14
  e2 = ExecuteError.new e
15
15
  raise e2, "Exception while executing #{host.user ? "as #{host.user}@" : "on host "}#{host}: #{e.message}"
16
16
  end
@@ -26,7 +26,7 @@ module SSHKit
26
26
  private
27
27
  def run_backend(host, &block)
28
28
  backend(host, &block).run
29
- rescue StandardError => e
29
+ rescue ::StandardError => e
30
30
  e2 = ExecuteError.new e
31
31
  raise e2, "Exception while executing #{host.user ? "as #{host.user}@" : "on host "}#{host}: #{e.message}"
32
32
  end
@@ -1,3 +1,3 @@
1
1
  module SSHKit
2
- VERSION = "1.12.0".freeze
2
+ VERSION = "1.13.0".freeze
3
3
  end
@@ -10,6 +10,30 @@ module SSHKit
10
10
  SSHKit.config.output = SSHKit::Formatter::BlackHole.new($stdout)
11
11
  end
12
12
 
13
+ def test_upload
14
+ Dir.mktmpdir do |dir|
15
+ File.new("#{dir}/local", 'w')
16
+ Local.new do
17
+ upload!("#{dir}/local", "#{dir}/remote")
18
+ end.run
19
+ assert File.exist?("#{dir}/remote")
20
+ end
21
+ end
22
+
23
+ def test_upload_recursive
24
+ Dir.mktmpdir do |dir|
25
+ Dir.mkdir("#{dir}/local")
26
+ File.new("#{dir}/local/file1", 'w')
27
+ File.new("#{dir}/local/file2", 'w')
28
+ Local.new do
29
+ upload!("#{dir}/local", "#{dir}/remote", recursive: true)
30
+ end.run
31
+ assert File.directory?("#{dir}/remote")
32
+ assert File.exist?("#{dir}/remote/file1")
33
+ assert File.exist?("#{dir}/remote/file2")
34
+ end
35
+ end
36
+
13
37
  def test_capture
14
38
  captured_command_result = ''
15
39
  Local.new do
@@ -59,7 +59,7 @@ module SSHKit
59
59
  capture(:uname)
60
60
  host_ssh_options = host.ssh_options
61
61
  end.run
62
- assert_equal [:forward_agent, :paranoid, :known_hosts].sort, host_ssh_options.keys.sort
62
+ assert_equal [:forward_agent, :paranoid, :known_hosts, :logger, :password_prompt].sort, host_ssh_options.keys.sort
63
63
  assert_equal false, host_ssh_options[:forward_agent]
64
64
  assert_equal true, host_ssh_options[:paranoid]
65
65
  assert_instance_of SSHKit::Backend::Netssh::KnownHosts, host_ssh_options[:known_hosts]
@@ -134,6 +134,15 @@ module SSHKit
134
134
  pool.close_connections
135
135
  end
136
136
  end
137
+
138
+ def test_connections_with_changed_args_is_reused
139
+ options = { known_hosts: "foo" }
140
+ connect_change_options = ->(*args) { args.last[:known_hosts] = "bar"; Object.new }
141
+ conn1 = pool.with(connect_change_options, "arg", options) { |c| c }
142
+ conn2 = pool.with(connect_change_options, "arg", options) { |c| c }
143
+
144
+ assert_equal conn1, conn2
145
+ end
137
146
  end
138
147
  end
139
148
  end
@@ -0,0 +1,17 @@
1
+ require "helper"
2
+ require "sshkit"
3
+
4
+ module SSHKit
5
+ module Runner
6
+ class TestGroup < UnitTest
7
+ def test_wraps_ruby_standard_error_in_execute_error
8
+ localhost = Host.new(:local)
9
+ runner = Group.new([localhost]) { raise "oh no!" }
10
+ error = assert_raises(SSHKit::Runner::ExecuteError) do
11
+ runner.execute
12
+ end
13
+ assert_match(/while executing.*localhost/, error.message)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,18 @@
1
+ require "helper"
2
+ require "sshkit"
3
+
4
+ module SSHKit
5
+ module Runner
6
+ class TestParallel < UnitTest
7
+ def test_wraps_ruby_standard_error_in_execute_error
8
+ host = Host.new("deployer@example")
9
+ runner = Parallel.new([host]) { raise "oh no!" }
10
+ error = assert_raises(SSHKit::Runner::ExecuteError) do
11
+ runner.execute
12
+ end
13
+ assert_match(/deployer@example/, error.message)
14
+ assert_match(/oh no!/, error.message)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ require "helper"
2
+ require "sshkit"
3
+
4
+ module SSHKit
5
+ module Runner
6
+ class TestSequential < UnitTest
7
+ def test_wraps_ruby_standard_error_in_execute_error
8
+ host = Host.new("deployer@example")
9
+ runner = Sequential.new([host]) { raise "oh no!" }
10
+ error = assert_raises(SSHKit::Runner::ExecuteError) do
11
+ runner.execute
12
+ end
13
+ assert_match(/deployer@example/, error.message)
14
+ assert_match(/oh no!/, error.message)
15
+ end
16
+ end
17
+ end
18
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sshkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.12.0
4
+ version: 1.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lee Hambley
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2017-02-10 00:00:00.000000000 Z
12
+ date: 2017-03-25 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: net-ssh
@@ -218,6 +218,9 @@ files:
218
218
  - test/unit/formatters/test_dot.rb
219
219
  - test/unit/formatters/test_pretty.rb
220
220
  - test/unit/formatters/test_simple_text.rb
221
+ - test/unit/runners/test_group.rb
222
+ - test/unit/runners/test_parallel.rb
223
+ - test/unit/runners/test_sequential.rb
221
224
  - test/unit/test_color.rb
222
225
  - test/unit/test_command.rb
223
226
  - test/unit/test_command_map.rb
@@ -248,7 +251,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
248
251
  version: '0'
249
252
  requirements: []
250
253
  rubyforge_project:
251
- rubygems_version: 2.6.10
254
+ rubygems_version: 2.6.11
252
255
  signing_key:
253
256
  specification_version: 4
254
257
  summary: SSHKit makes it easy to write structured, testable SSH commands in Ruby
@@ -271,6 +274,9 @@ test_files:
271
274
  - test/unit/formatters/test_dot.rb
272
275
  - test/unit/formatters/test_pretty.rb
273
276
  - test/unit/formatters/test_simple_text.rb
277
+ - test/unit/runners/test_group.rb
278
+ - test/unit/runners/test_parallel.rb
279
+ - test/unit/runners/test_sequential.rb
274
280
  - test/unit/test_color.rb
275
281
  - test/unit/test_command.rb
276
282
  - test/unit/test_command_map.rb