sshkit 1.4.0 → 1.5.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/.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
|

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