sshkit 1.4.0 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +0 -1
- data/CHANGELOG.md +24 -1
- data/EXAMPLES.md +1 -20
- data/README.md +3 -46
- data/lib/sshkit.rb +2 -2
- data/lib/sshkit/all.rb +3 -0
- data/lib/sshkit/backends/abstract.rb +0 -4
- data/lib/sshkit/backends/connection_pool.rb +30 -25
- data/lib/sshkit/backends/local.rb +1 -0
- data/lib/sshkit/backends/netssh.rb +59 -49
- data/lib/sshkit/color.rb +13 -0
- data/lib/sshkit/command.rb +1 -2
- data/lib/sshkit/coordinator.rb +0 -2
- data/lib/sshkit/exception.rb +21 -0
- data/lib/sshkit/formatters/dot.rb +1 -3
- data/lib/sshkit/formatters/pretty.rb +2 -6
- data/lib/sshkit/runners/parallel.rb +6 -1
- data/lib/sshkit/runners/sequential.rb +6 -1
- data/lib/sshkit/version.rb +1 -1
- data/sshkit.gemspec +1 -2
- data/test/functional/backends/test_netssh.rb +0 -15
- data/test/support/vagrant_wrapper.rb +2 -3
- data/test/unit/backends/test_connection_pool.rb +51 -15
- data/test/unit/backends/test_local.rb +4 -0
- data/test/unit/backends/test_printer.rb +1 -1
- data/test/unit/core_ext/test_string.rb +2 -2
- data/test/unit/formatters/test_dot.rb +4 -4
- data/test/unit/formatters/test_pretty.rb +5 -5
- data/test/unit/test_command.rb +4 -24
- data/test/unit/test_command_map.rb +1 -1
- data/test/unit/test_configuration.rb +1 -1
- data/test/unit/test_coordinator.rb +2 -2
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2be29e1ebec8acbfa5605fddeb959a1803f06581
|
4
|
+
data.tar.gz: 51115f162047ee443d2383e05d49851ae1e9b056
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8f0e074042930ca893c2953712bce4abb47bc04387d98b0339aed07df8e2dfccef783ecaf6696b00d4d1d9c88b8d51bc6982fd9bc60aaa6343950e9da02a3260
|
7
|
+
data.tar.gz: 5bf7f417df04ab339fe70dcb12377b248cf2eef18bf9a881ea9e23dd7eb2d62b717f6f2713dd68878173aef4f50af6a3920624fc6986b5972bce1b79e4672e7e
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -8,11 +8,28 @@ appear at the top.
|
|
8
8
|
* Add your entries here, remember to credit yourself however you want to be
|
9
9
|
credited!
|
10
10
|
|
11
|
+
## 1.5.0
|
12
|
+
|
13
|
+
* Deprecate background helper - too many badly behaved pseudo-daemons. Lee Hambley
|
14
|
+
* Don't colourize unless $stdout is a tty. Lee Hambley
|
15
|
+
* Remove out of date "Known Issues" section from README. Lee Hambley
|
16
|
+
* Dealy variable interpolation inside `as()` block. Nick Townsend
|
17
|
+
* Fixes for functional tests under modern Vagrant. Lewis Marshal
|
18
|
+
* Fixes for connection pooling. Chris Heald
|
19
|
+
* Add `localhost` hostname to local backend. Adam Mckaig
|
20
|
+
* Wrap execptions to include hostname. Brecht Hoflack
|
21
|
+
* Remove `shellwords` stdlib dependency Bruno Sutic
|
22
|
+
* Remove unused `cooldown` accessor. Bruno Sutic
|
23
|
+
* Replace Term::ANSIColor with a lighter solution. Tom Clements
|
24
|
+
* Documentation fixes. Matt Brictson
|
25
|
+
|
11
26
|
## 1.4.0
|
12
27
|
|
28
|
+
https://github.com/capistrano/sshkit/compare/v1.3.0...v1.4.0
|
29
|
+
|
13
30
|
* Removed `invoke` alias for [`SSHKit::Backend::Printer.execute`](https://github.com/capistrano/sshkit/blob/master/lib/sshkit/backends/printer.rb#L20). This is to prevent collisions with
|
14
31
|
methods in capistrano with similar names, and to provide a cleaner API. See [capistrano issue 912](https://github.com/capistrano/capistrano/issues/912) and [issue 107](https://github.com/capistrano/sshkit/issues/107) for more details.
|
15
|
-
* Connection pooling now uses a thread local to store connection pool, giving each thread its own connection pool. Thank you @
|
32
|
+
* Connection pooling now uses a thread local to store connection pool, giving each thread its own connection pool. Thank you @mbrictson see [#101](https://github.com/capistrano/sshkit/pull/101) for more.
|
16
33
|
* Command map indifferent towards strings and symbols thanks to @thomasfedb see [#91](https://github.com/capistrano/sshkit/pull/91)
|
17
34
|
* Moved vagrant wrapper to `support` directory, added ability to run tests with vagrant using ssh. @miry see [#64](https://github.com/capistrano/sshkit/pull/64)
|
18
35
|
* Removed unnecessary require `require_relative '../sshkit'` in `lib/sshkit/dsl.rb` prevents warnings thanks @brabic.
|
@@ -20,6 +37,8 @@ appear at the top.
|
|
20
37
|
|
21
38
|
## 1.3.0
|
22
39
|
|
40
|
+
https://github.com/capistrano/sshkit/compare/v1.2.0...v1.3.0
|
41
|
+
|
23
42
|
* Connection pooling. SSH connections are reused across multiple invocations
|
24
43
|
of `on()`, which can result in significant performance gains. See:
|
25
44
|
https://github.com/capistrano/sshkit/pull/70. Matt @mbrictson Brictson.
|
@@ -30,6 +49,8 @@ appear at the top.
|
|
30
49
|
|
31
50
|
## 1.2.0
|
32
51
|
|
52
|
+
https://github.com/capistrano/sshkit/compare/v1.1.0...v1.2.0
|
53
|
+
|
33
54
|
* Support picking up a project local SSH config file, if a SSH config file
|
34
55
|
exists at ./.ssh/config it will be merged with the ~/.ssh/config. This is
|
35
56
|
ideal for defining project-local proxies/gateways, etc. Thanks to Alex
|
@@ -55,6 +76,8 @@ appear at the top.
|
|
55
76
|
|
56
77
|
## 1.1.0
|
57
78
|
|
79
|
+
https://github.com/capistrano/sshkit/compare/v1.0.0...v1.1.0
|
80
|
+
|
58
81
|
* Please see the Git history. `git rebase` ate our changelog (we should have been
|
59
82
|
more careful)
|
60
83
|
|
data/EXAMPLES.md
CHANGED
@@ -158,25 +158,6 @@ leading slashes. It may be misleading as the `File.join()` is performed on the
|
|
158
158
|
machine running the code, if that's a Windows box, the paths may be incorrectly
|
159
159
|
joined according to the expectations of the machine receiving the commands.
|
160
160
|
|
161
|
-
## Running a task in the background
|
162
|
-
|
163
|
-
on hosts do
|
164
|
-
within '/opt/sites/example.com' do
|
165
|
-
background :rails, :server
|
166
|
-
end
|
167
|
-
end
|
168
|
-
|
169
|
-
This will run something like `nohup /usr/bin/env rails server > /dev/null &`,
|
170
|
-
backgrounding the Rails process, and making sure we don't leave nohup log
|
171
|
-
files littering the filesystem.
|
172
|
-
|
173
|
-
**Note:** The `background()` method won't do what you expect if you pass a
|
174
|
-
string `sleep 5`, according to the rules of processing commands, you must call
|
175
|
-
`background(:sleep, "5")` (that is, command: sleep, args: 5).
|
176
|
-
|
177
|
-
**Further Note:** The background() task wraps the given command in `nohup .... &` under some
|
178
|
-
circumstances the program will hang anyway when the SSH session exits.
|
179
|
-
|
180
161
|
## Do not care about the host block
|
181
162
|
|
182
163
|
on hosts do
|
@@ -217,7 +198,7 @@ circumstances the program will hang anyway when the SSH session exits.
|
|
217
198
|
execute!(:echo, '"Example Message!" 1>&2; false')
|
218
199
|
end
|
219
200
|
|
220
|
-
This will raise `SSHKit::Command:Failed` with the `#message` "Example Message!"
|
201
|
+
This will raise `SSHKit::Command:Failed` with the `#message` "Example Message!"
|
221
202
|
which will cause the command to abort.
|
222
203
|
|
223
204
|
## Make a test, or run a command which may fail without raising an error:
|
data/README.md
CHANGED
@@ -201,15 +201,15 @@ first argument before attempting to find it in the *command map*.
|
|
201
201
|
|
202
202
|
![Example Output](https://raw.github.com/leehambley/sshkit/master/assets/images/example_output.png)
|
203
203
|
|
204
|
-
By default, the output format is set to `:pretty`:
|
204
|
+
By default, the output format is set to `:pretty`:
|
205
205
|
|
206
206
|
```ruby
|
207
207
|
SSHKit.config.format = :pretty
|
208
208
|
```
|
209
209
|
|
210
|
-
However, if you prefer minimal output, `:dot` format will simply output red or green dots based on the success or failure of operations.
|
210
|
+
However, if you prefer minimal output, `:dot` format will simply output red or green dots based on the success or failure of operations.
|
211
211
|
|
212
|
-
To output directly to $stdout without any formatting, you can use:
|
212
|
+
To output directly to $stdout without any formatting, you can use:
|
213
213
|
|
214
214
|
```ruby
|
215
215
|
SSHKit.config.output = $stdout
|
@@ -248,46 +248,3 @@ pooling behaviour entirely by setting the idle_timeout to zero:
|
|
248
248
|
```ruby
|
249
249
|
SSHKit::Backend::Netssh.pool.idle_timeout = 0 # disabled
|
250
250
|
```
|
251
|
-
|
252
|
-
## Known Issues
|
253
|
-
|
254
|
-
* No handling of slow / timed out connections
|
255
|
-
* No handling of slow / hung remote commands
|
256
|
-
* ~~No built-in way to background() something (execute and background the
|
257
|
-
process).~~
|
258
|
-
* No environment handling (sshkit might not need to care)
|
259
|
-
* ~~No arbitrary `Host` properties (example storing `roles` on servers, or other
|
260
|
-
metadata that might be useful in the `on()` block)~~
|
261
|
-
* ~~No log/warning facility (passing Log messages to the output would work)
|
262
|
-
A log object could be made available globally which would emit a LogMessage
|
263
|
-
type object which would be recognised by the formatters that need to care
|
264
|
-
about them.~~
|
265
|
-
* ~~No verbosity control, commands should have a `Logger::LEVEL` on them,
|
266
|
-
user-generated should be at a high level, the commands auto-generated from
|
267
|
-
the guards and checks from as() and within() should have a lower level.~~
|
268
|
-
* ~~Decide if `execute()` (and friends) should raise on non-zero exit statuses or
|
269
|
-
not, perhaps a family of similarly named bang methods should be the ones to
|
270
|
-
raise. (Perhaps `test()` should be a way to `execute()` without raising, and
|
271
|
-
`execute()` and friends should always raise)~~
|
272
|
-
* ~~It would be nice to be able to say `SSHKit.config.formatter = :pretty` and
|
273
|
-
have that method setter do the legwork of updating `SSHKit.config.output` to
|
274
|
-
be an instance of the correct formatter class wrapping the existing output
|
275
|
-
stream.~~
|
276
|
-
* No "trace" level debugging for internal stuff, the debug level should be
|
277
|
-
reserved for client-level debugging, with trace being (int -1) used
|
278
|
-
internally for logging about connection opening, closing, timing out, etc.
|
279
|
-
* No method for uploading or downloading files, or the same for saving/loading
|
280
|
-
a string to/from a remote file.
|
281
|
-
* No closing of connections, the abstract backend class should include a
|
282
|
-
cleanup method which is empty but can be overridden by other implementations.
|
283
|
-
* ~~No connection pooling, the `connection` method of the NetSSH backend could
|
284
|
-
easily be modified to look into some connection factory for it's objects,
|
285
|
-
saving half a second when running lots of `on()` blocks.~~
|
286
|
-
* Documentation! (YARD style)
|
287
|
-
* Wrap all commands in a known shell, that is that `execute('uptime')` should
|
288
|
-
be converted into `sh -c 'uptime'` to ensure that we have a consistent shell
|
289
|
-
experience.
|
290
|
-
* ~~There's no suitable host parser that accepts `Host.new('user@ip:port')`, it
|
291
|
-
will decode a `user@hostname:port`, but IP addresses don't work.~~
|
292
|
-
* If Net::SSH raises `IOError` (as it does when authentication fails) this
|
293
|
-
needs to be caught, and re-raised as some kind of ConnectionFailed error.
|
data/lib/sshkit.rb
CHANGED
@@ -3,7 +3,7 @@ module SSHKit
|
|
3
3
|
StandardError = Class.new(::StandardError)
|
4
4
|
|
5
5
|
class << self
|
6
|
-
|
6
|
+
|
7
7
|
attr_accessor :config
|
8
8
|
|
9
9
|
def capture_output(io, &block)
|
@@ -27,7 +27,7 @@ module SSHKit
|
|
27
27
|
def reset_configuration!
|
28
28
|
@@config = nil
|
29
29
|
end
|
30
|
-
|
30
|
+
|
31
31
|
end
|
32
32
|
|
33
33
|
end
|
data/lib/sshkit/all.rb
CHANGED
@@ -3,11 +3,14 @@ require_relative '../core_ext/hash'
|
|
3
3
|
|
4
4
|
require_relative 'host'
|
5
5
|
|
6
|
+
require_relative 'color'
|
6
7
|
require_relative 'command'
|
7
8
|
require_relative 'command_map'
|
8
9
|
require_relative 'configuration'
|
9
10
|
require_relative 'coordinator'
|
10
11
|
|
12
|
+
require_relative 'exception'
|
13
|
+
|
11
14
|
require_relative 'logger'
|
12
15
|
require_relative 'log_message'
|
13
16
|
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require "
|
1
|
+
require "thread"
|
2
2
|
|
3
3
|
module SSHKit
|
4
4
|
|
@@ -10,47 +10,53 @@ module SSHKit
|
|
10
10
|
|
11
11
|
def initialize
|
12
12
|
self.idle_timeout = 30
|
13
|
-
@
|
13
|
+
@mutex = Mutex.new
|
14
|
+
@pool = {}
|
14
15
|
end
|
15
16
|
|
16
|
-
def
|
17
|
+
def checkout(*new_connection_args, &block)
|
17
18
|
# Optimization: completely bypass the pool if idle_timeout is zero.
|
18
|
-
return yield(*new_connection_args) if idle_timeout == 0
|
19
|
-
|
20
19
|
key = new_connection_args.to_s
|
21
|
-
|
20
|
+
return create_new_entry(new_connection_args, key, &block) if idle_timeout == 0
|
22
21
|
|
23
|
-
|
24
|
-
|
25
|
-
end
|
22
|
+
find_live_entry(key) || create_new_entry(new_connection_args, key, &block)
|
23
|
+
end
|
26
24
|
|
25
|
+
def checkin(entry)
|
27
26
|
entry.expires_at = Time.now + idle_timeout if idle_timeout
|
28
|
-
|
27
|
+
@mutex.synchronize do
|
28
|
+
@pool[entry.key] ||= []
|
29
|
+
@pool[entry.key] << entry
|
30
|
+
end
|
29
31
|
end
|
30
32
|
|
31
|
-
|
32
|
-
|
33
|
-
def connections
|
34
|
-
Thread.current[:sshkit_pool] ||= {}
|
33
|
+
def flush_connections
|
34
|
+
@mutex.synchronize { @pool.clear }
|
35
35
|
end
|
36
36
|
|
37
|
-
|
38
|
-
entry = connections[key]
|
39
|
-
invalid = entry && yield(entry)
|
40
|
-
|
41
|
-
connections.delete(entry) if invalid
|
37
|
+
private
|
42
38
|
|
43
|
-
|
39
|
+
def find_live_entry(key)
|
40
|
+
@mutex.synchronize do
|
41
|
+
return nil unless @pool.key?(key)
|
42
|
+
while entry = @pool[key].shift
|
43
|
+
return entry if entry.live?
|
44
|
+
end
|
45
|
+
end
|
46
|
+
nil
|
44
47
|
end
|
45
48
|
|
46
|
-
def
|
47
|
-
|
49
|
+
def create_new_entry(args, key, &block)
|
50
|
+
Entry.new block.call(*args), key
|
48
51
|
end
|
49
52
|
|
50
|
-
|
51
|
-
Entry = Struct.new(:connection) do
|
53
|
+
Entry = Struct.new(:connection, :key) do
|
52
54
|
attr_accessor :expires_at
|
53
55
|
|
56
|
+
def live?
|
57
|
+
!expired? && !closed?
|
58
|
+
end
|
59
|
+
|
54
60
|
def expired?
|
55
61
|
expires_at && Time.now > expires_at
|
56
62
|
end
|
@@ -61,6 +67,5 @@ module SSHKit
|
|
61
67
|
end
|
62
68
|
|
63
69
|
end
|
64
|
-
|
65
70
|
end
|
66
71
|
end
|
@@ -67,6 +67,7 @@ module SSHKit
|
|
67
67
|
end
|
68
68
|
|
69
69
|
def background(*args)
|
70
|
+
warn "[Deprecated] The background method is deprecated. Blame badly behaved pseudo-daemons!"
|
70
71
|
options = args.extract_options!.merge(run_in_background: true)
|
71
72
|
_execute(*[*args, options]).success?
|
72
73
|
end
|
@@ -78,12 +79,16 @@ module SSHKit
|
|
78
79
|
|
79
80
|
def upload!(local, remote, options = {})
|
80
81
|
summarizer = transfer_summarizer('Uploading')
|
81
|
-
|
82
|
+
with_ssh do |ssh|
|
83
|
+
ssh.scp.upload!(local, remote, options, &summarizer)
|
84
|
+
end
|
82
85
|
end
|
83
86
|
|
84
87
|
def download!(remote, local=nil, options = {})
|
85
88
|
summarizer = transfer_summarizer('Downloading')
|
86
|
-
|
89
|
+
with_ssh do |ssh|
|
90
|
+
ssh.scp.download!(remote, local, options, &summarizer)
|
91
|
+
end
|
87
92
|
end
|
88
93
|
|
89
94
|
@pool = SSHKit::Backend::ConnectionPool.new
|
@@ -123,59 +128,64 @@ module SSHKit
|
|
123
128
|
command(*args).tap do |cmd|
|
124
129
|
output << cmd
|
125
130
|
cmd.started = true
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
chan.
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
131
|
+
with_ssh do |ssh|
|
132
|
+
ssh.open_channel do |chan|
|
133
|
+
chan.request_pty if Netssh.config.pty
|
134
|
+
chan.exec cmd.to_command do |ch, success|
|
135
|
+
chan.on_data do |ch, data|
|
136
|
+
cmd.stdout = data
|
137
|
+
cmd.full_stdout += data
|
138
|
+
output << cmd
|
139
|
+
end
|
140
|
+
chan.on_extended_data do |ch, type, data|
|
141
|
+
cmd.stderr = data
|
142
|
+
cmd.full_stderr += data
|
143
|
+
output << cmd
|
144
|
+
end
|
145
|
+
chan.on_request("exit-status") do |ch, data|
|
146
|
+
cmd.stdout = ''
|
147
|
+
cmd.stderr = ''
|
148
|
+
cmd.exit_status = data.read_long
|
149
|
+
output << cmd
|
150
|
+
end
|
151
|
+
#chan.on_request("exit-signal") do |ch, data|
|
152
|
+
# # TODO: This gets called if the program is killed by a signal
|
153
|
+
# # might also be a worthwhile thing to report
|
154
|
+
# exit_signal = data.read_string.to_i
|
155
|
+
# warn ">>> " + exit_signal.inspect
|
156
|
+
# output << cmd
|
157
|
+
#end
|
158
|
+
chan.on_open_failed do |ch|
|
159
|
+
# TODO: What do do here?
|
160
|
+
# I think we should raise something
|
161
|
+
end
|
162
|
+
chan.on_process do |ch|
|
163
|
+
# TODO: I don't know if this is useful
|
164
|
+
end
|
165
|
+
chan.on_eof do |ch|
|
166
|
+
# TODO: chan sends EOF before the exit status has been
|
167
|
+
# writtend
|
168
|
+
end
|
162
169
|
end
|
170
|
+
chan.wait
|
163
171
|
end
|
164
|
-
|
172
|
+
ssh.loop
|
165
173
|
end
|
166
|
-
ssh.loop
|
167
174
|
end
|
168
175
|
end
|
169
176
|
|
170
|
-
def
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
177
|
+
def with_ssh
|
178
|
+
host.ssh_options ||= Netssh.config.ssh_options
|
179
|
+
conn = self.class.pool.checkout(
|
180
|
+
String(host.hostname),
|
181
|
+
host.username,
|
182
|
+
host.netssh_options,
|
183
|
+
&Net::SSH.method(:start)
|
184
|
+
)
|
185
|
+
begin
|
186
|
+
yield conn.connection
|
187
|
+
ensure
|
188
|
+
self.class.pool.checkin conn
|
179
189
|
end
|
180
190
|
end
|
181
191
|
|
data/lib/sshkit/color.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'colorize'
|
2
|
+
module Color
|
3
|
+
STYLES = [String::COLORS, String::MODES].flat_map(&:keys)
|
4
|
+
|
5
|
+
STYLES.each do |style|
|
6
|
+
instance_eval %{
|
7
|
+
def #{style}(string='')
|
8
|
+
string = yield if block_given?
|
9
|
+
$stdout.tty? ? string.colorize(:#{style}) : string
|
10
|
+
end
|
11
|
+
}
|
12
|
+
end
|
13
|
+
end
|
data/lib/sshkit/command.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'shellwords'
|
2
1
|
require 'digest/sha1'
|
3
2
|
require 'securerandom'
|
4
3
|
|
@@ -164,7 +163,7 @@ module SSHKit
|
|
164
163
|
|
165
164
|
def user(&block)
|
166
165
|
return yield unless options[:user]
|
167
|
-
"sudo su #{options[:user]} -c
|
166
|
+
"sudo su #{options[:user]} -c '%s'" % %Q{#{yield}}
|
168
167
|
end
|
169
168
|
|
170
169
|
def in_background(&block)
|
data/lib/sshkit/coordinator.rb
CHANGED
@@ -0,0 +1,21 @@
|
|
1
|
+
module SSHKit
|
2
|
+
|
3
|
+
module Runner
|
4
|
+
|
5
|
+
class ExecuteError < StandardError
|
6
|
+
attr_reader :cause
|
7
|
+
|
8
|
+
def initialize cause
|
9
|
+
@cause = cause
|
10
|
+
end
|
11
|
+
|
12
|
+
def backtrace
|
13
|
+
@cause.backtrace
|
14
|
+
end
|
15
|
+
|
16
|
+
def backtrace_locations
|
17
|
+
@cause.backtrace_locations
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'term/ansicolor'
|
2
|
-
|
3
1
|
module SSHKit
|
4
2
|
|
5
3
|
module Formatter
|
@@ -53,7 +51,7 @@ module SSHKit
|
|
53
51
|
end
|
54
52
|
|
55
53
|
def c
|
56
|
-
@c ||=
|
54
|
+
@c ||= Color
|
57
55
|
end
|
58
56
|
|
59
57
|
def uuid(obj)
|
@@ -61,9 +59,7 @@ module SSHKit
|
|
61
59
|
end
|
62
60
|
|
63
61
|
def level(verbosity)
|
64
|
-
|
65
|
-
# by term-ansicolor
|
66
|
-
sprintf "%14s ", c.send(level_formatting(verbosity), level_names(verbosity))
|
62
|
+
c.send(level_formatting(verbosity), level_names(verbosity))
|
67
63
|
end
|
68
64
|
|
69
65
|
def level_formatting(level_num)
|
@@ -9,7 +9,12 @@ module SSHKit
|
|
9
9
|
threads = []
|
10
10
|
hosts.each do |host|
|
11
11
|
threads << Thread.new(host) do |h|
|
12
|
-
|
12
|
+
begin
|
13
|
+
backend(h, &block).run
|
14
|
+
rescue Exception => e
|
15
|
+
e2 = ExecuteError.new e
|
16
|
+
raise e2, "Exception while executing on host #{host}: #{e.message}"
|
17
|
+
end
|
13
18
|
end
|
14
19
|
end
|
15
20
|
threads.map(&:join)
|
@@ -6,7 +6,12 @@ module SSHKit
|
|
6
6
|
attr_writer :wait_interval
|
7
7
|
def execute
|
8
8
|
hosts.each do |host|
|
9
|
-
|
9
|
+
begin
|
10
|
+
backend(host, &block).run
|
11
|
+
rescue Exception => e
|
12
|
+
e2 = ExecuteError.new e
|
13
|
+
raise e2, "Exception while executing on host #{host}: #{e.message}"
|
14
|
+
end
|
10
15
|
sleep wait_interval
|
11
16
|
end
|
12
17
|
end
|
data/lib/sshkit/version.rb
CHANGED
data/sshkit.gemspec
CHANGED
@@ -19,12 +19,11 @@ Gem::Specification.new do |gem|
|
|
19
19
|
|
20
20
|
gem.add_runtime_dependency('net-ssh', '>= 2.8.0')
|
21
21
|
gem.add_runtime_dependency('net-scp', '>= 1.1.2')
|
22
|
-
gem.add_runtime_dependency('
|
22
|
+
gem.add_runtime_dependency('colorize')
|
23
23
|
|
24
24
|
gem.add_development_dependency('minitest', ['>= 2.11.3', '< 2.12.0'])
|
25
25
|
gem.add_development_dependency('rake')
|
26
26
|
gem.add_development_dependency('turn')
|
27
27
|
gem.add_development_dependency('unindent')
|
28
28
|
gem.add_development_dependency('mocha')
|
29
|
-
|
30
29
|
end
|
@@ -80,21 +80,6 @@ module SSHKit
|
|
80
80
|
end.run
|
81
81
|
end
|
82
82
|
|
83
|
-
def test_backgrounding_a_process
|
84
|
-
#SSHKit.config.output = SSHKit::Formatter::Pretty.new($stdout)
|
85
|
-
process_list = ""
|
86
|
-
time = Benchmark.measure do
|
87
|
-
Netssh.new(a_host) do
|
88
|
-
background :sleep, 5
|
89
|
-
end.run
|
90
|
-
Netssh.new(a_host) do
|
91
|
-
process_list = capture :ps, "aux | grep sleep | grep -v grep; true"
|
92
|
-
end.run
|
93
|
-
end
|
94
|
-
assert_operator time.real, :<, 1
|
95
|
-
assert_match "sleep 5", process_list
|
96
|
-
end
|
97
|
-
|
98
83
|
def test_upload_file
|
99
84
|
file_contents = ""
|
100
85
|
file_name = File.join("/tmp", SecureRandom.uuid)
|
@@ -45,11 +45,10 @@ class VagrantWrapper
|
|
45
45
|
host_options = {
|
46
46
|
user: vm['user'] || 'vagrant',
|
47
47
|
hostname: vm['hostname'] || 'localhost',
|
48
|
-
port: vm['port'] || '22'
|
48
|
+
port: vm['port'] || '22',
|
49
|
+
password: vm['password'] || 'vagrant'
|
49
50
|
}
|
50
51
|
|
51
|
-
|
52
|
-
|
53
52
|
SSHKit::Host.new(host_options)
|
54
53
|
end
|
55
54
|
end
|
@@ -5,6 +5,10 @@ module SSHKit
|
|
5
5
|
module Backend
|
6
6
|
class TestConnectionPool < UnitTest
|
7
7
|
|
8
|
+
def setup
|
9
|
+
pool.flush_connections
|
10
|
+
end
|
11
|
+
|
8
12
|
def pool
|
9
13
|
@pool ||= SSHKit::Backend::ConnectionPool.new
|
10
14
|
end
|
@@ -27,23 +31,54 @@ module SSHKit
|
|
27
31
|
|
28
32
|
def test_connection_factory_receives_args
|
29
33
|
args = %w(a b c)
|
30
|
-
conn = pool.
|
34
|
+
conn = pool.checkout(*args, &echo_args)
|
31
35
|
|
32
|
-
assert_equal args, conn
|
36
|
+
assert_equal args, conn.connection
|
33
37
|
end
|
34
38
|
|
35
|
-
def
|
36
|
-
conn1 = pool.
|
37
|
-
conn2 = pool.
|
39
|
+
def test_connections_are_not_reused_if_not_checked_in
|
40
|
+
conn1 = pool.checkout("conn", &connect)
|
41
|
+
conn2 = pool.checkout("conn", &connect)
|
42
|
+
|
43
|
+
refute_equal conn1, conn2
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_connections_are_reused_if_checked_in
|
47
|
+
conn1 = pool.checkout("conn", &connect)
|
48
|
+
pool.checkin conn1
|
49
|
+
conn2 = pool.checkout("conn", &connect)
|
38
50
|
|
39
51
|
assert_equal conn1, conn2
|
40
52
|
end
|
41
53
|
|
54
|
+
def test_connections_are_reused_across_threads_multiple_times
|
55
|
+
t1 = Thread.new {
|
56
|
+
Thread.current[:conn] = pool.checkout("conn", &connect)
|
57
|
+
pool.checkin Thread.current[:conn]
|
58
|
+
}.join
|
59
|
+
|
60
|
+
t2 = Thread.new {
|
61
|
+
Thread.current[:conn] = pool.checkout("conn", &connect)
|
62
|
+
pool.checkin Thread.current[:conn]
|
63
|
+
}.join
|
64
|
+
|
65
|
+
t3 = Thread.new {
|
66
|
+
Thread.current[:conn] = pool.checkout("conn", &connect)
|
67
|
+
pool.checkin Thread.current[:conn]
|
68
|
+
}.join
|
69
|
+
|
70
|
+
refute_equal t1[:conn], nil
|
71
|
+
assert_equal t1[:conn], t2[:conn]
|
72
|
+
assert_equal t2[:conn], t3[:conn]
|
73
|
+
end
|
74
|
+
|
42
75
|
def test_zero_idle_timeout_disables_reuse
|
43
76
|
pool.idle_timeout = 0
|
44
77
|
|
45
|
-
conn1 = pool.
|
46
|
-
|
78
|
+
conn1 = pool.checkout("conn", &connect)
|
79
|
+
pool.checkin conn1
|
80
|
+
|
81
|
+
conn2 = pool.checkout("conn", &connect)
|
47
82
|
|
48
83
|
refute_equal conn1, conn2
|
49
84
|
end
|
@@ -51,25 +86,26 @@ module SSHKit
|
|
51
86
|
def test_expired_connection_is_not_reused
|
52
87
|
pool.idle_timeout = 0.1
|
53
88
|
|
54
|
-
conn1 = pool.
|
89
|
+
conn1 = pool.checkout("conn", &connect)
|
90
|
+
pool.checkin conn1
|
55
91
|
sleep(pool.idle_timeout)
|
56
|
-
conn2 = pool.
|
92
|
+
conn2 = pool.checkout("conn", &connect)
|
57
93
|
|
58
94
|
refute_equal conn1, conn2
|
59
95
|
end
|
60
96
|
|
61
97
|
def test_closed_connection_is_not_reused
|
62
|
-
|
63
|
-
pool.
|
64
|
-
|
65
|
-
conn2 = pool.create_or_reuse_connection("conn", &connect)
|
98
|
+
conn1 = pool.checkout("conn", &connect_and_close)
|
99
|
+
pool.checkin conn1
|
100
|
+
conn2 = pool.checkout("conn", &connect)
|
66
101
|
|
67
102
|
refute_equal conn1, conn2
|
68
103
|
end
|
69
104
|
|
70
105
|
def test_connections_with_different_args_are_not_reused
|
71
|
-
conn1 = pool.
|
72
|
-
|
106
|
+
conn1 = pool.checkout("conn1", &connect)
|
107
|
+
pool.checkin conn1
|
108
|
+
conn2 = pool.checkout("conn2", &connect)
|
73
109
|
|
74
110
|
refute_equal conn1, conn2
|
75
111
|
end
|
@@ -59,7 +59,7 @@ module SSHKit
|
|
59
59
|
assert_equal false, backend.config.ssh_options[:forward_agent]
|
60
60
|
assert_equal %w(publickey password), backend.config.ssh_options[:auth_methods]
|
61
61
|
end
|
62
|
-
|
62
|
+
|
63
63
|
def test_invoke_raises_no_method_error
|
64
64
|
assert_raises NoMethodError do
|
65
65
|
printer.invoke :echo
|
@@ -1,9 +1,9 @@
|
|
1
1
|
class String
|
2
2
|
|
3
3
|
def unindent
|
4
|
-
indent = self.split("\n").select do |line|
|
4
|
+
indent = self.split("\n").select do |line|
|
5
5
|
!line.strip.empty?
|
6
|
-
end.map do |line|
|
6
|
+
end.map do |line|
|
7
7
|
line.index(/[^\s]/)
|
8
8
|
end.compact.min || 0
|
9
9
|
self.gsub(/^[[:blank:]]{#{indent}}/, '')
|
@@ -46,19 +46,19 @@ module SSHKit
|
|
46
46
|
dot << SSHKit::LogMessage.new(Logger::DEBUG, "Test")
|
47
47
|
assert_equal "", output.strip
|
48
48
|
end
|
49
|
-
|
49
|
+
|
50
50
|
def test_command_success
|
51
51
|
command = SSHKit::Command.new(:ls)
|
52
52
|
command.exit_status = 0
|
53
53
|
dot << command
|
54
|
-
assert_equal "\e[
|
54
|
+
assert_equal "\e[0;32;49m.\e[0m", output.strip
|
55
55
|
end
|
56
|
-
|
56
|
+
|
57
57
|
def test_command_failure
|
58
58
|
command = SSHKit::Command.new(:ls, {raise_on_non_zero_exit: false})
|
59
59
|
command.exit_status = 1
|
60
60
|
dot << command
|
61
|
-
assert_equal "\e[
|
61
|
+
assert_equal "\e[0;31;49m.\e[0m", output.strip
|
62
62
|
end
|
63
63
|
|
64
64
|
end
|
@@ -24,27 +24,27 @@ module SSHKit
|
|
24
24
|
|
25
25
|
def test_logging_fatal
|
26
26
|
pretty << SSHKit::LogMessage.new(Logger::FATAL, "Test")
|
27
|
-
assert_equal output.strip, "
|
27
|
+
assert_equal output.strip, "\e[0;31;49mFATAL\e[0mTest"
|
28
28
|
end
|
29
29
|
|
30
30
|
def test_logging_error
|
31
31
|
pretty << SSHKit::LogMessage.new(Logger::ERROR, "Test")
|
32
|
-
assert_equal output.strip, "
|
32
|
+
assert_equal output.strip, "\e[0;31;49mERROR\e[0mTest"
|
33
33
|
end
|
34
34
|
|
35
35
|
def test_logging_warn
|
36
36
|
pretty << SSHKit::LogMessage.new(Logger::WARN, "Test")
|
37
|
-
assert_equal output.strip, "
|
37
|
+
assert_equal output.strip, "\e[0;33;49mWARN\e[0mTest".strip
|
38
38
|
end
|
39
39
|
|
40
40
|
def test_logging_info
|
41
41
|
pretty << SSHKit::LogMessage.new(Logger::INFO, "Test")
|
42
|
-
assert_equal output.strip, "
|
42
|
+
assert_equal output.strip, "\e[0;34;49mINFO\e[0mTest".strip
|
43
43
|
end
|
44
44
|
|
45
45
|
def test_logging_debug
|
46
46
|
pretty << SSHKit::LogMessage.new(Logger::DEBUG, "Test")
|
47
|
-
assert_equal output.strip, "
|
47
|
+
assert_equal output.strip, "\e[0;30;49mDEBUG\e[0mTest".strip
|
48
48
|
end
|
49
49
|
|
50
50
|
end
|
data/test/unit/test_command.rb
CHANGED
@@ -78,7 +78,7 @@ module SSHKit
|
|
78
78
|
|
79
79
|
def test_working_as_a_given_user
|
80
80
|
c = Command.new(:whoami, user: :anotheruser)
|
81
|
-
assert_equal "sudo su anotheruser -c
|
81
|
+
assert_equal "sudo su anotheruser -c '/usr/bin/env whoami'", c.to_command
|
82
82
|
end
|
83
83
|
|
84
84
|
def test_working_as_a_given_group
|
@@ -88,27 +88,7 @@ module SSHKit
|
|
88
88
|
|
89
89
|
def test_working_as_a_given_user_and_group
|
90
90
|
c = Command.new(:whoami, user: :anotheruser, group: :devvers)
|
91
|
-
assert_equal "sudo su anotheruser -c
|
92
|
-
end
|
93
|
-
|
94
|
-
def test_backgrounding_a_task
|
95
|
-
c = Command.new(:sleep, 15, run_in_background: true)
|
96
|
-
assert_equal "( nohup /usr/bin/env sleep 15 > /dev/null & )", c.to_command
|
97
|
-
end
|
98
|
-
|
99
|
-
def test_backgrounding_a_task_as_a_given_user
|
100
|
-
c = Command.new(:sleep, 15, run_in_background: true, user: :anotheruser)
|
101
|
-
assert_equal "sudo su anotheruser -c \"( nohup /usr/bin/env sleep 15 > /dev/null & )\"", c.to_command
|
102
|
-
end
|
103
|
-
|
104
|
-
def test_backgrounding_a_task_as_a_given_user_with_env
|
105
|
-
c = Command.new(:sleep, 15, run_in_background: true, user: :anotheruser, env: {a: :b})
|
106
|
-
assert_equal "( A=b sudo su anotheruser -c \"( nohup /usr/bin/env sleep 15 > /dev/null & )\" )", c.to_command
|
107
|
-
end
|
108
|
-
|
109
|
-
def test_backgrounding_a_task_with_working_directory
|
110
|
-
c = Command.new(:sleep, 15, run_in_background: true, in: '/opt')
|
111
|
-
assert_equal "cd /opt && ( nohup /usr/bin/env sleep 15 > /dev/null & )", c.to_command
|
91
|
+
assert_equal "sudo su anotheruser -c 'sg devvers -c \\\"/usr/bin/env whoami\\\"'", c.to_command
|
112
92
|
end
|
113
93
|
|
114
94
|
def test_umask
|
@@ -126,13 +106,13 @@ module SSHKit
|
|
126
106
|
def test_umask_with_working_directory_and_user
|
127
107
|
SSHKit.config.umask = '007'
|
128
108
|
c = Command.new(:touch, 'somefile', in: '/var', user: 'alice')
|
129
|
-
assert_equal "cd /var && umask 007 && sudo su alice -c
|
109
|
+
assert_equal "cd /var && umask 007 && sudo su alice -c '/usr/bin/env touch somefile'", c.to_command
|
130
110
|
end
|
131
111
|
|
132
112
|
def test_umask_with_env_and_working_directory_and_user
|
133
113
|
SSHKit.config.umask = '007'
|
134
114
|
c = Command.new(:touch, 'somefile', user: 'bob', env: {a: 'b'}, in: '/var')
|
135
|
-
assert_equal "cd /var && umask 007 && ( A=b sudo su bob -c
|
115
|
+
assert_equal "cd /var && umask 007 && ( A=b sudo su bob -c '/usr/bin/env touch somefile' )", c.to_command
|
136
116
|
end
|
137
117
|
|
138
118
|
def test_verbosity_defaults_to_logger_info
|
@@ -55,7 +55,7 @@ module SSHKit
|
|
55
55
|
assert SSHKit.config.format = :dot
|
56
56
|
assert SSHKit.config.output.is_a? SSHKit::Formatter::Dot
|
57
57
|
end
|
58
|
-
|
58
|
+
|
59
59
|
def test_setting_formatter_to_blackhole
|
60
60
|
assert SSHKit.config.format = :BlackHole
|
61
61
|
assert SSHKit.config.output.is_a? SSHKit::Formatter::BlackHole
|
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.5.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: 2014-
|
12
|
+
date: 2014-05-30 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: net-ssh
|
@@ -40,7 +40,7 @@ dependencies:
|
|
40
40
|
- !ruby/object:Gem::Version
|
41
41
|
version: 1.1.2
|
42
42
|
- !ruby/object:Gem::Dependency
|
43
|
-
name:
|
43
|
+
name: colorize
|
44
44
|
requirement: !ruby/object:Gem::Requirement
|
45
45
|
requirements:
|
46
46
|
- - '>='
|
@@ -163,11 +163,13 @@ files:
|
|
163
163
|
- lib/sshkit/backends/netssh.rb
|
164
164
|
- lib/sshkit/backends/printer.rb
|
165
165
|
- lib/sshkit/backends/skipper.rb
|
166
|
+
- lib/sshkit/color.rb
|
166
167
|
- lib/sshkit/command.rb
|
167
168
|
- lib/sshkit/command_map.rb
|
168
169
|
- lib/sshkit/configuration.rb
|
169
170
|
- lib/sshkit/coordinator.rb
|
170
171
|
- lib/sshkit/dsl.rb
|
172
|
+
- lib/sshkit/exception.rb
|
171
173
|
- lib/sshkit/formatters/abstract.rb
|
172
174
|
- lib/sshkit/formatters/black_hole.rb
|
173
175
|
- lib/sshkit/formatters/dot.rb
|