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 +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
|