sshkit 1.12.0 → 1.13.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -0
- data/Dangerfile +1 -54
- data/lib/sshkit/backends/connection_pool/cache.rb +8 -1
- data/lib/sshkit/backends/connection_pool/nil_cache.rb +4 -0
- data/lib/sshkit/backends/connection_pool.rb +20 -2
- data/lib/sshkit/backends/local.rb +6 -2
- data/lib/sshkit/backends/netssh.rb +8 -0
- data/lib/sshkit/runners/parallel.rb +1 -1
- data/lib/sshkit/runners/sequential.rb +1 -1
- data/lib/sshkit/version.rb +1 -1
- data/test/functional/backends/test_local.rb +24 -0
- data/test/functional/backends/test_netssh.rb +1 -1
- data/test/unit/backends/test_connection_pool.rb +9 -0
- data/test/unit/runners/test_group.rb +17 -0
- data/test/unit/runners/test_parallel.rb +18 -0
- data/test/unit/runners/test_sequential.rb +18 -0
- metadata +9 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 63716bfbb3150fe84235f1161bc7ecc9d303155c
|
4
|
+
data.tar.gz: 91103b89d73d2c9f56ade1ebd21ae80f85f6fb24
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
-
|
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
|
@@ -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
|
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,
|
13
|
+
def upload!(local, remote, options = {})
|
14
14
|
if local.is_a?(String)
|
15
|
-
|
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
|
data/lib/sshkit/version.rb
CHANGED
@@ -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.
|
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-
|
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.
|
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
|