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