sshkit 1.2.0 → 1.3.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/.gitignore +1 -1
- data/.travis.yml +1 -1
- data/CHANGELOG.md +21 -6
- data/FAQ.md +2 -2
- data/Gemfile +5 -0
- data/README.md +31 -8
- data/Rakefile +1 -8
- data/Vagrantfile +12 -14
- data/lib/sshkit/all.rb +1 -0
- data/lib/sshkit/backends/abstract.rb +0 -4
- data/lib/sshkit/backends/connection_pool.rb +71 -0
- data/lib/sshkit/backends/netssh.rb +7 -2
- data/lib/sshkit/configuration.rb +3 -1
- data/lib/sshkit/formatters/dot.rb +1 -1
- data/lib/sshkit/runners/group.rb +1 -1
- data/lib/sshkit/version.rb +1 -1
- data/sshkit.gemspec +2 -6
- data/test/boxes.json +14 -0
- data/test/functional/backends/test_netssh.rb +3 -3
- data/test/helper.rb +55 -71
- data/test/unit/backends/test_connection_pool.rb +77 -0
- data/test/unit/formatters/test_dot.rb +65 -0
- data/test/unit/test_configuration.rb +6 -1
- metadata +12 -60
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6cd81576d8df6878627ee2ca1b298c2079698085
|
4
|
+
data.tar.gz: 9676306af0f0de95b974134d580fed271486004f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4a0cfdd77807a8e280376f8f5a07b0f7bac96339091c67bb2c3e7e2424247e192436451ca189772352095f9132afc57e7c660381180812ca315c2ad0715e58f4
|
7
|
+
data.tar.gz: 0e4037910686419d816927cddae4a065021c8e7ebae2530c55d62ea299414310e1bb9fcb44fd88dabde85de069667e537aec5a4ff2dc5f9dff55cf9bc0bc592f
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -3,29 +3,44 @@
|
|
3
3
|
This file is written in reverse chronological order, newer releases will
|
4
4
|
appear at the top.
|
5
5
|
|
6
|
+
## (Unreleased)
|
7
|
+
|
8
|
+
* Add your entries here, remember to credit yourself however you want to be
|
9
|
+
credited!
|
10
|
+
|
11
|
+
## 1.3.0
|
12
|
+
|
13
|
+
* Connection pooling. SSH connections are reused across multiple invocations
|
14
|
+
of `on()`, which can result in significant performance gains. See:
|
15
|
+
https://github.com/capistrano/sshkit/pull/70. Matt @mbrictson Brictson.
|
16
|
+
* Fixes to the Formatter::Dot and to the formatter class name resolver. @hab287:w
|
17
|
+
* Added the license to the Gemspec. @anatol.
|
18
|
+
* Fix :limit handling for the `in: :groups` run mode. Phil @phs Smith
|
19
|
+
* Doc fixes @seanhandley, @sergey-alekseev.
|
20
|
+
|
6
21
|
## 1.2.0
|
7
22
|
|
8
23
|
* Support picking up a project local SSH config file, if a SSH config file
|
9
24
|
exists at ./.ssh/config it will be merged with the ~/.ssh/config. This is
|
10
|
-
ideal for defining project-local proxies/gateways, etc. Thanks to Alex
|
25
|
+
ideal for defining project-local proxies/gateways, etc. Thanks to Alex
|
11
26
|
@0rca Vzorov.
|
12
|
-
* Tests and general improvements to the Printer backends (mostly used
|
27
|
+
* Tests and general improvements to the Printer backends (mostly used
|
13
28
|
internally). Thanks to Michael @miry Nikitochkin.
|
14
|
-
* Update the net-scp dependency version. Thanks again to Michael @miry
|
29
|
+
* Update the net-scp dependency version. Thanks again to Michael @miry
|
15
30
|
Nikitochkin.
|
16
31
|
* Improved command map. This feature allows mapped variables to be pushed
|
17
32
|
and unshifted onto the mapping so that the Capistrano extensions for
|
18
33
|
rbenv and bundler, etc can work together. For discussion about the reasoning
|
19
|
-
see https://github.com/capistrano/capistrano/issues/639 and
|
34
|
+
see https://github.com/capistrano/capistrano/issues/639 and
|
20
35
|
https://github.com/capistrano/sshkit/pull/45. A big thanks to Kir @kirs
|
21
36
|
Shatrov.
|
22
37
|
* `test()` and `capture()` now behave as expected inside a `run_locally` block
|
23
38
|
meaning that they now run on your local machine, rather than erring out. Thanks
|
24
39
|
to Kentaro @kentaroi Imai.
|
25
|
-
* The `:wait` option is now successfully passed to the runner now. Previously the
|
40
|
+
* The `:wait` option is now successfully passed to the runner now. Previously the
|
26
41
|
`:wait` option was ignored. Thanks to Jordan @jhollinger Hollinger for catching
|
27
42
|
the mistake in our test coverage.
|
28
|
-
* Fixes and general improvements to the `download()` method which until now was
|
43
|
+
* Fixes and general improvements to the `download()` method which until now was
|
29
44
|
quite naïve. Thanks to @chqr.
|
30
45
|
|
31
46
|
## 1.1.0
|
data/FAQ.md
CHANGED
@@ -5,7 +5,7 @@ a toolkit for performing structured commands on groups of servers in a
|
|
5
5
|
repeatable way.
|
6
6
|
|
7
7
|
It provides concurrency handling, sane error checking and control flow that
|
8
|
-
would otherwise be difficult to
|
8
|
+
would otherwise be difficult to achieve with pure *Net::SSH*.
|
9
9
|
|
10
10
|
Since *Capistrano v3.0*, *SSHKit* is used by *Capistrano* to communicate with
|
11
11
|
backend servers. Whilst Capistrano provides the structure for repeatable
|
@@ -24,7 +24,7 @@ descriptors which no longer exist.
|
|
24
24
|
|
25
25
|
The following resources are worth a read to better understand what a process
|
26
26
|
must do in order to daemonize reliably, not all processes perform all of the
|
27
|
-
steps
|
27
|
+
steps necessary:
|
28
28
|
|
29
29
|
* [http://stackoverflow.com/questions/881388/what-is-the-reason-for-performing-a-double-fork-when-creating-a-daemon]
|
30
30
|
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -55,13 +55,13 @@ Helpers such as `runner()` and `rake()` which expand to `execute(:rails, "runner
|
|
55
55
|
Notice on the `on()` call the `in: :sequence` option, the following will do
|
56
56
|
what you might expect:
|
57
57
|
|
58
|
-
on(in: :parallel
|
58
|
+
on(in: :parallel) { ... }
|
59
59
|
on(in: :sequence, wait: 5) { ... }
|
60
60
|
on(in: :groups, limit: 2, wait: 5) { ... }
|
61
61
|
|
62
|
-
The default is to run `in: :parallel`
|
63
|
-
this might be a problem
|
64
|
-
groups
|
62
|
+
The default is to run `in: :parallel` which has no limit. If you have 400 servers,
|
63
|
+
this might be a problem and you might better look at changing that to run in
|
64
|
+
`groups`, or `sequence`.
|
65
65
|
|
66
66
|
Groups were designed in this case to relieve problems (mass Git checkouts)
|
67
67
|
where you rely on a contested resource that you don't want to DDOS by hitting
|
@@ -194,6 +194,29 @@ and defaults to `Logger::INFO`.
|
|
194
194
|
|
195
195
|
At present the `Logger::WARN`, `ERROR` and `FATAL` are not used.
|
196
196
|
|
197
|
+
## Connection Pooling
|
198
|
+
|
199
|
+
SSHKit uses a simple connection pool (enabled by default) to reduce the
|
200
|
+
cost of negotiating a new SSH connection for every `on()` block. Depending on
|
201
|
+
usage and network conditions, this can add up to a significant time savings.
|
202
|
+
In one test, a basic `cap deploy` ran 15-20 seconds faster thanks to the
|
203
|
+
connection pooling added in recent versions of SSHKit.
|
204
|
+
|
205
|
+
To prevent connections from "going stale", an existing pooled connection will
|
206
|
+
be replaced with a new connection if it hasn't been used for more than 30
|
207
|
+
seconds. This timeout can be changed as follows:
|
208
|
+
|
209
|
+
```ruby
|
210
|
+
SSHKit::Backend::Netssh.pool.idle_timeout = 60 # seconds
|
211
|
+
```
|
212
|
+
|
213
|
+
If you suspect the connection pooling is causing problems, you can disable the
|
214
|
+
pooling behavior entirely by setting the idle_timeout to zero:
|
215
|
+
|
216
|
+
```ruby
|
217
|
+
SSHKit::Backend::Netssh.pool.idle_timeout = 0 # disabled
|
218
|
+
```
|
219
|
+
|
197
220
|
## Known Issues
|
198
221
|
|
199
222
|
* No handling of slow / timed out connections
|
@@ -225,14 +248,14 @@ At present the `Logger::WARN`, `ERROR` and `FATAL` are not used.
|
|
225
248
|
a string to/from a remote file.
|
226
249
|
* No closing of connections, the abstract backend class should include a
|
227
250
|
cleanup method which is empty but can be overriden by other implementations.
|
228
|
-
* No conncetion pooling, the `connection` method of the NetSSH backend could
|
251
|
+
* ~~No conncetion pooling, the `connection` method of the NetSSH backend could
|
229
252
|
easily be modified to look into some connection factory for it's objects,
|
230
|
-
saving half a second when running lots of `on()` blocks
|
253
|
+
saving half a second when running lots of `on()` blocks.~~
|
231
254
|
* Documentation! (YARD style)
|
232
255
|
* Wrap all commands in a known shell, that is that `execute('uptime')` should
|
233
256
|
be converted into `sh -c 'uptime'` to ensure that we have a consistent shell
|
234
257
|
experience.
|
235
|
-
* There's no suitable host parser that accepts `Host.new('user@ip:port')`, it
|
236
|
-
will decode a `user@hostname:port`, but IP addresses don't work
|
258
|
+
* ~~There's no suitable host parser that accepts `Host.new('user@ip:port')`, it
|
259
|
+
will decode a `user@hostname:port`, but IP addresses don't work.~~
|
237
260
|
* If Net::SSH raises `IOError` (as it does when authentication fails) this
|
238
261
|
needs to be caught, and re-raised as some kind of ConnectionFailed error.
|
data/Rakefile
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
#!/usr/bin/env rake
|
2
2
|
require "bundler/gem_tasks"
|
3
|
-
require 'debugger'
|
4
3
|
require 'rake/testtask'
|
5
4
|
|
6
5
|
namespace :test do
|
@@ -15,15 +14,9 @@ namespace :test do
|
|
15
14
|
t.test_files = FileList['test/functional/**/test*.rb']
|
16
15
|
end
|
17
16
|
|
18
|
-
Rake::TestTask.new(:integration) do |t|
|
19
|
-
t.libs << "test"
|
20
|
-
t.test_files = FileList['test/integration/**/test*.rb']
|
21
|
-
end
|
22
|
-
|
23
17
|
task :test do
|
24
18
|
Rake::Task['test:units'].execute
|
25
|
-
Rake::Task['test:
|
26
|
-
Rake::Task['test:functional'].execute unless ENV['TRAVIS']
|
19
|
+
Rake::Task['test:functional'].execute
|
27
20
|
end
|
28
21
|
|
29
22
|
task default: :test
|
data/Vagrantfile
CHANGED
@@ -1,19 +1,17 @@
|
|
1
|
-
|
2
|
-
# vi: set ft=ruby
|
3
|
-
#
|
1
|
+
VAGRANTFILE_API_VERSION = "2"
|
4
2
|
|
5
|
-
Vagrant
|
3
|
+
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
4
|
+
config.vm.box = 'precise64'
|
5
|
+
config.vm.box_url = "http://cloud-images.ubuntu.com/vagrant/precise/current/precise-server-cloudimg-amd64-vagrant-disk1.box"
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
config.vm.define :two do |one|
|
12
|
-
one.vm.box = "precise64"
|
13
|
-
end
|
7
|
+
json_config_path = File.join("test", "boxes.json")
|
8
|
+
list = File.open(json_config_path).read
|
9
|
+
list = JSON.parse(list)
|
14
10
|
|
15
|
-
|
16
|
-
|
11
|
+
list.each do |vm|
|
12
|
+
config.vm.define vm["name"] do |web|
|
13
|
+
web.vm.box = "precise64"
|
14
|
+
web.vm.network "forwarded_port", guest: 22, host: vm["port"]
|
15
|
+
end
|
17
16
|
end
|
18
|
-
|
19
17
|
end
|
data/lib/sshkit/all.rb
CHANGED
@@ -23,6 +23,7 @@ require_relative 'runners/group'
|
|
23
23
|
require_relative 'runners/null'
|
24
24
|
|
25
25
|
require_relative 'backends/abstract'
|
26
|
+
require_relative 'backends/connection_pool'
|
26
27
|
require_relative 'backends/printer'
|
27
28
|
require_relative 'backends/netssh'
|
28
29
|
require_relative 'backends/local'
|
@@ -129,10 +129,6 @@ module SSHKit
|
|
129
129
|
SSHKit::Command.new(*[*args, options.merge({in: @pwd.nil? ? nil : File.join(@pwd), env: @env, host: @host, user: @user, group: @group})])
|
130
130
|
end
|
131
131
|
|
132
|
-
def connection
|
133
|
-
raise "No Connection Pool Implementation"
|
134
|
-
end
|
135
|
-
|
136
132
|
end
|
137
133
|
|
138
134
|
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require "monitor"
|
2
|
+
|
3
|
+
module SSHKit
|
4
|
+
|
5
|
+
module Backend
|
6
|
+
|
7
|
+
class ConnectionPool
|
8
|
+
|
9
|
+
attr_accessor :idle_timeout
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
self.idle_timeout = 30
|
13
|
+
@connections = {}
|
14
|
+
@monitor = Monitor.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def create_or_reuse_connection(*new_connection_args, &block)
|
18
|
+
# Optimization: completely bypass the pool if idle_timeout is zero.
|
19
|
+
return yield(*new_connection_args) if idle_timeout == 0
|
20
|
+
|
21
|
+
key = new_connection_args.to_s
|
22
|
+
entry = find_and_reject_invalid(key) { |e| e.expired? || e.closed? }
|
23
|
+
|
24
|
+
if entry.nil?
|
25
|
+
entry = store_entry(key, yield(*new_connection_args))
|
26
|
+
end
|
27
|
+
|
28
|
+
entry.expires_at = Time.now + idle_timeout if idle_timeout
|
29
|
+
entry.connection
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def find_and_reject_invalid(key, &block)
|
35
|
+
synchronize do
|
36
|
+
entry = @connections[key]
|
37
|
+
invalid = entry && yield(entry)
|
38
|
+
|
39
|
+
@connections.delete(entry) if invalid
|
40
|
+
|
41
|
+
invalid ? nil : entry
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def store_entry(key, connection)
|
46
|
+
synchronize do
|
47
|
+
@connections[key] = Entry.new(connection)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def synchronize(&block)
|
52
|
+
@monitor.synchronize(&block)
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
Entry = Struct.new(:connection) do
|
57
|
+
attr_accessor :expires_at
|
58
|
+
|
59
|
+
def expired?
|
60
|
+
expires_at && Time.now > expires_at
|
61
|
+
end
|
62
|
+
|
63
|
+
def closed?
|
64
|
+
connection.respond_to?(:closed?) && connection.closed?
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
@@ -86,7 +86,11 @@ module SSHKit
|
|
86
86
|
ssh.scp.download!(remote, local, options, &summarizer)
|
87
87
|
end
|
88
88
|
|
89
|
+
@pool = SSHKit::Backend::ConnectionPool.new
|
90
|
+
|
89
91
|
class << self
|
92
|
+
attr_accessor :pool
|
93
|
+
|
90
94
|
def configure
|
91
95
|
yield config
|
92
96
|
end
|
@@ -166,10 +170,11 @@ module SSHKit
|
|
166
170
|
def ssh
|
167
171
|
@ssh ||= begin
|
168
172
|
host.ssh_options ||= Netssh.config.ssh_options
|
169
|
-
|
173
|
+
self.class.pool.create_or_reuse_connection(
|
170
174
|
String(host.hostname),
|
171
175
|
host.username,
|
172
|
-
host.netssh_options
|
176
|
+
host.netssh_options,
|
177
|
+
&Net::SSH.method(:start)
|
173
178
|
)
|
174
179
|
end
|
175
180
|
end
|
data/lib/sshkit/configuration.rb
CHANGED
@@ -44,7 +44,9 @@ module SSHKit
|
|
44
44
|
end
|
45
45
|
|
46
46
|
def formatter(format)
|
47
|
-
SSHKit::Formatter.
|
47
|
+
SSHKit::Formatter.constants.each do |const|
|
48
|
+
return SSHKit::Formatter.const_get(const).new($stdout) if const.downcase.eql?(format.downcase)
|
49
|
+
end
|
48
50
|
end
|
49
51
|
|
50
52
|
end
|
data/lib/sshkit/runners/group.rb
CHANGED
data/lib/sshkit/version.rb
CHANGED
data/sshkit.gemspec
CHANGED
@@ -7,7 +7,8 @@ Gem::Specification.new do |gem|
|
|
7
7
|
gem.email = ["lee.hambley@gmail.com", "seenmyfate@gmail.com"]
|
8
8
|
gem.summary = %q{SSHKit makes it easy to write structured, testable SSH commands in Ruby}
|
9
9
|
gem.description = %q{A comprehensive toolkit for remotely running commands in a structured manner on groups of servers.}
|
10
|
-
gem.homepage = "http://
|
10
|
+
gem.homepage = "http://github.com/capistrano/sshkit"
|
11
|
+
gem.license = "GPL3"
|
11
12
|
|
12
13
|
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
13
14
|
gem.files = `git ls-files`.split("\n")
|
@@ -25,10 +26,5 @@ Gem::Specification.new do |gem|
|
|
25
26
|
gem.add_development_dependency('turn')
|
26
27
|
gem.add_development_dependency('unindent')
|
27
28
|
gem.add_development_dependency('mocha')
|
28
|
-
gem.add_development_dependency('debugger')
|
29
|
-
gem.add_development_dependency('vagrant')
|
30
|
-
|
31
|
-
gem.add_development_dependency('yard')
|
32
|
-
gem.add_development_dependency('redcarpet')
|
33
29
|
|
34
30
|
end
|
data/test/boxes.json
ADDED
@@ -28,7 +28,7 @@ module SSHKit
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def a_host
|
31
|
-
|
31
|
+
VagrantWrapper.hosts["one"]
|
32
32
|
end
|
33
33
|
|
34
34
|
def printer
|
@@ -56,9 +56,9 @@ module SSHKit
|
|
56
56
|
SSHKit.capture_output(dnull) do
|
57
57
|
captured_command_result = ""
|
58
58
|
Netssh.new(a_host) do |host|
|
59
|
-
captured_command_result = capture(:
|
59
|
+
captured_command_result = capture(:uname)
|
60
60
|
end.run
|
61
|
-
assert_equal "
|
61
|
+
assert_equal "Linux", captured_command_result
|
62
62
|
end
|
63
63
|
end
|
64
64
|
end
|
data/test/helper.rb
CHANGED
@@ -1,49 +1,50 @@
|
|
1
1
|
require 'rubygems'
|
2
|
-
require 'bundler'
|
3
|
-
begin
|
4
|
-
Bundler.setup(:default, :development)
|
5
|
-
rescue Bundler::BundlerError => e
|
6
|
-
$stderr.puts e.message
|
7
|
-
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
-
exit e.status_code
|
9
|
-
end
|
2
|
+
require 'bundler/setup'
|
10
3
|
require 'tempfile'
|
11
4
|
require 'minitest/unit'
|
12
5
|
require 'mocha/setup'
|
13
6
|
require 'turn'
|
14
7
|
require 'unindent'
|
15
|
-
require 'debugger'
|
16
|
-
require 'vagrant'
|
17
8
|
require 'stringio'
|
9
|
+
require 'json'
|
18
10
|
|
19
11
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
20
12
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
21
13
|
require 'sshkit'
|
22
14
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
15
|
+
class VagrantWrapper
|
16
|
+
class << self
|
17
|
+
def hosts
|
18
|
+
@vm_hosts ||= begin
|
19
|
+
result = {}
|
20
|
+
boxes_list.each do |vm|
|
21
|
+
host = SSHKit::Host.new("vagrant@localhost:#{vm["port"]}").tap do |h|
|
22
|
+
h.password = 'vagrant'
|
30
23
|
end
|
24
|
+
|
25
|
+
result[vm["name"]] = host
|
31
26
|
end
|
32
|
-
|
33
|
-
|
34
|
-
raise Vagrant::Errors::SCPPermissionDenied, :path => from.to_s
|
27
|
+
|
28
|
+
result
|
35
29
|
end
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
rescue Net::SCP::Error => e
|
43
|
-
raise Vagrant::Errors::SCPUnavailable if e.message =~ /\(127\)/
|
44
|
-
raise
|
30
|
+
end
|
31
|
+
|
32
|
+
def running?
|
33
|
+
@running ||= begin
|
34
|
+
status = `#{vagrant_binary} status`
|
35
|
+
status.include?('running')
|
45
36
|
end
|
46
37
|
end
|
38
|
+
|
39
|
+
def boxes_list
|
40
|
+
json_config_path = File.join("test", "boxes.json")
|
41
|
+
boxes = File.open(json_config_path).read
|
42
|
+
JSON.parse(boxes)
|
43
|
+
end
|
44
|
+
|
45
|
+
def vagrant_binary
|
46
|
+
'vagrant'
|
47
|
+
end
|
47
48
|
end
|
48
49
|
end
|
49
50
|
|
@@ -57,69 +58,52 @@ end
|
|
57
58
|
|
58
59
|
class FunctionalTest < MiniTest::Unit::TestCase
|
59
60
|
|
60
|
-
attr_accessor :venv
|
61
|
-
|
62
61
|
def setup
|
63
|
-
|
64
|
-
|
65
|
-
warn "#{name} (of #{venv.vms.size}) needs to be booted, please wait" unless vm.state == :running
|
62
|
+
unless VagrantWrapper.running?
|
63
|
+
raise "Vagrant VMs are not running. Please, start it manually with `vagrant up`"
|
66
64
|
end
|
67
|
-
venv.cli "up"
|
68
65
|
end
|
69
66
|
|
70
67
|
private
|
71
68
|
|
72
|
-
def vm_hosts
|
73
|
-
venv.vms.collect do |name, vm|
|
74
|
-
port = vm.env.config.for_vm(name).vm.forwarded_ports.first[:hostport]
|
75
|
-
SSHKit::Host.new("vagrant@localhost:#{port}").tap do |h|
|
76
|
-
h.password = 'vagrant'
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
69
|
def create_user_with_key(username, password = :secret)
|
82
70
|
username, password = username.to_s, password.to_s
|
83
|
-
keys = venv.vms.collect do |hostname, vm|
|
84
71
|
|
85
|
-
|
86
|
-
|
87
|
-
vm.channel.sudo("userdel -rf #{username}; true") # The `rescue nil` of the shell world
|
88
|
-
vm.channel.sudo("useradd -m #{username}")
|
89
|
-
vm.channel.sudo("echo y | ssh-keygen -b 1024 -f #{username} -N ''")
|
90
|
-
vm.channel.sudo("chown vagrant:vagrant #{username}*")
|
91
|
-
vm.channel.sudo("echo #{username}:#{password} | chpasswd")
|
72
|
+
keys = VagrantWrapper.hosts.collect do |name, host|
|
73
|
+
Net::SSH.start(host.hostname, host.user, port: host.port, password: host.password) do |ssh|
|
92
74
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
75
|
+
# Remove the user, make it again, force-generate a key for him
|
76
|
+
# short keys save us a few microseconds
|
77
|
+
ssh.exec!("sudo userdel -rf #{username}; true") # The `rescue nil` of the shell world
|
78
|
+
ssh.exec!("sudo useradd -m #{username}")
|
79
|
+
ssh.exec!("sudo echo y | ssh-keygen -b 1024 -f #{username} -N ''")
|
80
|
+
ssh.exec!("sudo chown vagrant:vagrant #{username}*")
|
81
|
+
ssh.exec!("sudo echo #{username}:#{password} | chpasswd")
|
97
82
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
83
|
+
# Make the .ssh directory, change the ownership and the
|
84
|
+
ssh.exec!("sudo mkdir -p /home/#{username}/.ssh")
|
85
|
+
ssh.exec!("sudo chown #{username}:#{username} /home/#{username}/.ssh")
|
86
|
+
ssh.exec!("sudo chmod 700 /home/#{username}/.ssh")
|
102
87
|
|
103
|
-
|
88
|
+
# Move the key to authorized keys and chown and chmod it
|
89
|
+
ssh.exec!("sudo cat #{username}.pub > /home/#{username}/.ssh/authorized_keys")
|
90
|
+
ssh.exec!("sudo chown #{username}:#{username} /home/#{username}/.ssh/authorized_keys")
|
91
|
+
ssh.exec!("sudo chmod 600 /home/#{username}/.ssh/authorized_keys")
|
104
92
|
|
105
|
-
|
106
|
-
vm.channel.sudo("rm #{username} #{username}.pub")
|
93
|
+
key = ssh.exec!("cat /home/vagrant/#{username}")
|
107
94
|
|
108
|
-
|
109
|
-
|
95
|
+
# Clean Up Files
|
96
|
+
ssh.exec!("sudo rm #{username} #{username}.pub")
|
110
97
|
|
98
|
+
key
|
99
|
+
end
|
111
100
|
end
|
112
101
|
|
113
|
-
Hash[
|
114
|
-
|
102
|
+
Hash[VagrantWrapper.hosts.collect { |n, h| n.to_sym }.zip(keys)]
|
115
103
|
end
|
116
104
|
|
117
105
|
end
|
118
106
|
|
119
|
-
class IntegrationTest < MiniTest::Unit::TestCase
|
120
|
-
|
121
|
-
end
|
122
|
-
|
123
107
|
#
|
124
108
|
# Force colours in Autotest
|
125
109
|
#
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
module SSHKit
|
5
|
+
module Backend
|
6
|
+
class TestConnectionPool < UnitTest
|
7
|
+
|
8
|
+
def pool
|
9
|
+
@pool ||= SSHKit::Backend::ConnectionPool.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def connect
|
13
|
+
->(*args) { Object.new }
|
14
|
+
end
|
15
|
+
|
16
|
+
def connect_and_close
|
17
|
+
->(*args) { OpenStruct.new(:closed? => true) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def echo_args
|
21
|
+
->(*args) { args }
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_default_idle_timeout
|
25
|
+
assert_equal 30, pool.idle_timeout
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_connection_factory_receives_args
|
29
|
+
args = %w(a b c)
|
30
|
+
conn = pool.create_or_reuse_connection(*args, &echo_args)
|
31
|
+
|
32
|
+
assert_equal args, conn
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_connections_are_reused
|
36
|
+
conn1 = pool.create_or_reuse_connection("conn", &connect)
|
37
|
+
conn2 = pool.create_or_reuse_connection("conn", &connect)
|
38
|
+
|
39
|
+
assert_equal conn1, conn2
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_zero_idle_timeout_disables_resuse
|
43
|
+
pool.idle_timeout = 0
|
44
|
+
|
45
|
+
conn1 = pool.create_or_reuse_connection("conn", &connect)
|
46
|
+
conn2 = pool.create_or_reuse_connection("conn", &connect)
|
47
|
+
|
48
|
+
refute_equal conn1, conn2
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_expired_connection_is_not_reused
|
52
|
+
pool.idle_timeout = 0.1
|
53
|
+
|
54
|
+
conn1 = pool.create_or_reuse_connection("conn", &connect)
|
55
|
+
sleep(pool.idle_timeout)
|
56
|
+
conn2 = pool.create_or_reuse_connection("conn", &connect)
|
57
|
+
|
58
|
+
refute_equal conn1, conn2
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_closed_connection_is_not_reused
|
62
|
+
conn1 = pool.create_or_reuse_connection("conn", &connect_and_close)
|
63
|
+
conn2 = pool.create_or_reuse_connection("conn", &connect)
|
64
|
+
|
65
|
+
refute_equal conn1, conn2
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_connections_with_different_args_are_not_reused
|
69
|
+
conn1 = pool.create_or_reuse_connection("conn1", &connect)
|
70
|
+
conn2 = pool.create_or_reuse_connection("conn2", &connect)
|
71
|
+
|
72
|
+
refute_equal conn1, conn2
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'sshkit'
|
3
|
+
|
4
|
+
module SSHKit
|
5
|
+
class TestDot < UnitTest
|
6
|
+
|
7
|
+
def setup
|
8
|
+
SSHKit.config.output_verbosity = Logger::DEBUG
|
9
|
+
end
|
10
|
+
|
11
|
+
def output
|
12
|
+
@_output ||= String.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def dot
|
16
|
+
@_dot ||= SSHKit::Formatter::Dot.new(output)
|
17
|
+
end
|
18
|
+
|
19
|
+
def teardown
|
20
|
+
remove_instance_variable :@_dot
|
21
|
+
remove_instance_variable :@_output
|
22
|
+
SSHKit.reset_configuration!
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_logging_fatal
|
26
|
+
dot << SSHKit::LogMessage.new(Logger::FATAL, "Test")
|
27
|
+
assert_equal "", output.strip
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_logging_error
|
31
|
+
dot << SSHKit::LogMessage.new(Logger::ERROR, "Test")
|
32
|
+
assert_equal "", output.strip
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_logging_warn
|
36
|
+
dot << SSHKit::LogMessage.new(Logger::WARN, "Test")
|
37
|
+
assert_equal "", output.strip
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_logging_info
|
41
|
+
dot << SSHKit::LogMessage.new(Logger::INFO, "Test")
|
42
|
+
assert_equal "", output.strip
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_logging_debug
|
46
|
+
dot << SSHKit::LogMessage.new(Logger::DEBUG, "Test")
|
47
|
+
assert_equal "", output.strip
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_command_success
|
51
|
+
command = SSHKit::Command.new(:ls)
|
52
|
+
command.exit_status = 0
|
53
|
+
dot << command
|
54
|
+
assert_equal "\e[32m.\e[0m", output.strip
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_command_failure
|
58
|
+
command = SSHKit::Command.new(:ls, {raise_on_non_zero_exit: false})
|
59
|
+
command.exit_status = 1
|
60
|
+
dot << command
|
61
|
+
assert_equal "\e[31m.\e[0m", output.strip
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
@@ -51,10 +51,15 @@ module SSHKit
|
|
51
51
|
assert_equal "/opt/sites/example/current/bin ruby", SSHKit.config.command_map[:ruby]
|
52
52
|
end
|
53
53
|
|
54
|
-
def
|
54
|
+
def test_setting_formatter_to_dot
|
55
55
|
assert SSHKit.config.format = :dot
|
56
56
|
assert SSHKit.config.output.is_a? SSHKit::Formatter::Dot
|
57
57
|
end
|
58
|
+
|
59
|
+
def test_setting_formatter_to_blackhole
|
60
|
+
assert SSHKit.config.format = :BlackHole
|
61
|
+
assert SSHKit.config.output.is_a? SSHKit::Formatter::BlackHole
|
62
|
+
end
|
58
63
|
end
|
59
64
|
|
60
65
|
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.3.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: 2013-
|
12
|
+
date: 2013-12-19 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: net-ssh
|
@@ -129,62 +129,6 @@ dependencies:
|
|
129
129
|
- - '>='
|
130
130
|
- !ruby/object:Gem::Version
|
131
131
|
version: '0'
|
132
|
-
- !ruby/object:Gem::Dependency
|
133
|
-
name: debugger
|
134
|
-
requirement: !ruby/object:Gem::Requirement
|
135
|
-
requirements:
|
136
|
-
- - '>='
|
137
|
-
- !ruby/object:Gem::Version
|
138
|
-
version: '0'
|
139
|
-
type: :development
|
140
|
-
prerelease: false
|
141
|
-
version_requirements: !ruby/object:Gem::Requirement
|
142
|
-
requirements:
|
143
|
-
- - '>='
|
144
|
-
- !ruby/object:Gem::Version
|
145
|
-
version: '0'
|
146
|
-
- !ruby/object:Gem::Dependency
|
147
|
-
name: vagrant
|
148
|
-
requirement: !ruby/object:Gem::Requirement
|
149
|
-
requirements:
|
150
|
-
- - '>='
|
151
|
-
- !ruby/object:Gem::Version
|
152
|
-
version: '0'
|
153
|
-
type: :development
|
154
|
-
prerelease: false
|
155
|
-
version_requirements: !ruby/object:Gem::Requirement
|
156
|
-
requirements:
|
157
|
-
- - '>='
|
158
|
-
- !ruby/object:Gem::Version
|
159
|
-
version: '0'
|
160
|
-
- !ruby/object:Gem::Dependency
|
161
|
-
name: yard
|
162
|
-
requirement: !ruby/object:Gem::Requirement
|
163
|
-
requirements:
|
164
|
-
- - '>='
|
165
|
-
- !ruby/object:Gem::Version
|
166
|
-
version: '0'
|
167
|
-
type: :development
|
168
|
-
prerelease: false
|
169
|
-
version_requirements: !ruby/object:Gem::Requirement
|
170
|
-
requirements:
|
171
|
-
- - '>='
|
172
|
-
- !ruby/object:Gem::Version
|
173
|
-
version: '0'
|
174
|
-
- !ruby/object:Gem::Dependency
|
175
|
-
name: redcarpet
|
176
|
-
requirement: !ruby/object:Gem::Requirement
|
177
|
-
requirements:
|
178
|
-
- - '>='
|
179
|
-
- !ruby/object:Gem::Version
|
180
|
-
version: '0'
|
181
|
-
type: :development
|
182
|
-
prerelease: false
|
183
|
-
version_requirements: !ruby/object:Gem::Requirement
|
184
|
-
requirements:
|
185
|
-
- - '>='
|
186
|
-
- !ruby/object:Gem::Version
|
187
|
-
version: '0'
|
188
132
|
description: A comprehensive toolkit for remotely running commands in a structured
|
189
133
|
manner on groups of servers.
|
190
134
|
email:
|
@@ -214,6 +158,7 @@ files:
|
|
214
158
|
- lib/sshkit.rb
|
215
159
|
- lib/sshkit/all.rb
|
216
160
|
- lib/sshkit/backends/abstract.rb
|
161
|
+
- lib/sshkit/backends/connection_pool.rb
|
217
162
|
- lib/sshkit/backends/local.rb
|
218
163
|
- lib/sshkit/backends/netssh.rb
|
219
164
|
- lib/sshkit/backends/printer.rb
|
@@ -237,14 +182,17 @@ files:
|
|
237
182
|
- lib/sshkit/runners/sequential.rb
|
238
183
|
- lib/sshkit/version.rb
|
239
184
|
- sshkit.gemspec
|
185
|
+
- test/boxes.json
|
240
186
|
- test/functional/backends/test_local.rb
|
241
187
|
- test/functional/backends/test_netssh.rb
|
242
188
|
- test/functional/test_coordinator.rb
|
243
189
|
- test/functional/test_ssh_server_comes_up_for_functional_tests.rb
|
244
190
|
- test/helper.rb
|
191
|
+
- test/unit/backends/test_connection_pool.rb
|
245
192
|
- test/unit/backends/test_netssh.rb
|
246
193
|
- test/unit/backends/test_printer.rb
|
247
194
|
- test/unit/core_ext/test_string.rb
|
195
|
+
- test/unit/formatters/test_dot.rb
|
248
196
|
- test/unit/formatters/test_pretty.rb
|
249
197
|
- test/unit/test_command.rb
|
250
198
|
- test/unit/test_command_map.rb
|
@@ -252,8 +200,9 @@ files:
|
|
252
200
|
- test/unit/test_coordinator.rb
|
253
201
|
- test/unit/test_host.rb
|
254
202
|
- test/unit/test_logger.rb
|
255
|
-
homepage: http://
|
256
|
-
licenses:
|
203
|
+
homepage: http://github.com/capistrano/sshkit
|
204
|
+
licenses:
|
205
|
+
- GPL3
|
257
206
|
metadata: {}
|
258
207
|
post_install_message:
|
259
208
|
rdoc_options: []
|
@@ -276,14 +225,17 @@ signing_key:
|
|
276
225
|
specification_version: 4
|
277
226
|
summary: SSHKit makes it easy to write structured, testable SSH commands in Ruby
|
278
227
|
test_files:
|
228
|
+
- test/boxes.json
|
279
229
|
- test/functional/backends/test_local.rb
|
280
230
|
- test/functional/backends/test_netssh.rb
|
281
231
|
- test/functional/test_coordinator.rb
|
282
232
|
- test/functional/test_ssh_server_comes_up_for_functional_tests.rb
|
283
233
|
- test/helper.rb
|
234
|
+
- test/unit/backends/test_connection_pool.rb
|
284
235
|
- test/unit/backends/test_netssh.rb
|
285
236
|
- test/unit/backends/test_printer.rb
|
286
237
|
- test/unit/core_ext/test_string.rb
|
238
|
+
- test/unit/formatters/test_dot.rb
|
287
239
|
- test/unit/formatters/test_pretty.rb
|
288
240
|
- test/unit/test_command.rb
|
289
241
|
- test/unit/test_command_map.rb
|