sshkit 1.3.0 → 1.4.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/CHANGELOG.md +10 -0
- data/EXAMPLES.md +8 -8
- data/FAQ.md +0 -5
- data/Gemfile +1 -1
- data/README.md +109 -77
- data/Rakefile +2 -1
- data/lib/sshkit.rb +23 -20
- data/lib/sshkit/all.rb +3 -0
- data/lib/sshkit/backends/connection_pool.rb +9 -14
- data/lib/sshkit/backends/local.rb +8 -2
- data/lib/sshkit/backends/netssh.rb +1 -1
- data/lib/sshkit/backends/printer.rb +1 -2
- data/lib/sshkit/command.rb +8 -4
- data/lib/sshkit/command_map.rb +24 -3
- data/lib/sshkit/coordinator.rb +3 -3
- data/lib/sshkit/dsl.rb +0 -2
- data/lib/sshkit/formatters/simple_text.rb +58 -0
- data/lib/sshkit/runners/parallel.rb +1 -1
- data/lib/sshkit/utils/capture_output_methods.rb +13 -0
- data/lib/sshkit/version.rb +1 -1
- data/sshkit.gemspec +1 -1
- data/test/boxes.json +4 -1
- data/test/functional/backends/test_local.rb +1 -1
- data/test/functional/backends/test_netssh.rb +6 -5
- data/test/functional/test_ssh_server_comes_up_for_functional_tests.rb +1 -0
- data/test/helper.rb +7 -36
- data/test/support/vagrant_wrapper.rb +56 -0
- data/test/unit/backends/test_connection_pool.rb +3 -1
- data/test/unit/backends/test_local.rb +18 -0
- data/test/unit/backends/test_printer.rb +6 -0
- data/test/unit/test_command.rb +9 -4
- data/test/unit/test_command_map.rb +16 -0
- data/test/unit/test_coordinator.rb +6 -0
- metadata +10 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a17c8de5efa195cef64bac5bd868dce70e430939
|
4
|
+
data.tar.gz: 90f2200bf01fa80d3549ed747a4e8ef545806ffe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ce8989e38ff9dab3be7673183786c838ea9fa71d174dfec0c5ae0d812a47619fcc1a2b768939cb531b9cf8dbabd4993b3f46c60bee81d01c7aff9976d5b5d77f
|
7
|
+
data.tar.gz: 906b9b7317960551ed9b137f9cf1611e11821d3a042cc0d990853cedfbc5e970c1b4a4d1a92a0b4896b662e162395fc3244b91db8f29ba92a52a596c9145cc06
|
data/CHANGELOG.md
CHANGED
@@ -8,6 +8,16 @@ 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.4.0
|
12
|
+
|
13
|
+
* 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
|
+
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 @mcbriston see [#101](https://github.com/capistrano/sshkit/pull/101) for more.
|
16
|
+
* Command map indifferent towards strings and symbols thanks to @thomasfedb see [#91](https://github.com/capistrano/sshkit/pull/91)
|
17
|
+
* 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
|
+
* Removed unnecessary require `require_relative '../sshkit'` in `lib/sshkit/dsl.rb` prevents warnings thanks @brabic.
|
19
|
+
* Doc fixes thanks @seanhandley @vojto
|
20
|
+
|
11
21
|
## 1.3.0
|
12
22
|
|
13
23
|
* Connection pooling. SSH connections are reused across multiple invocations
|
data/EXAMPLES.md
CHANGED
@@ -64,21 +64,21 @@ This will output:
|
|
64
64
|
**Note:** This example is a bit misleading, as the `www-data` user doesn't
|
65
65
|
have a shell defined, one cannot switch to that user.
|
66
66
|
|
67
|
-
##
|
67
|
+
## Upload a file from disk
|
68
68
|
|
69
69
|
on hosts do |host|
|
70
|
-
upload! '/config/database.yml', '/opt/my_project/shared/
|
70
|
+
upload! '/config/database.yml', '/opt/my_project/shared/database.yml'
|
71
71
|
end
|
72
72
|
|
73
73
|
**Note:** The `upload!()` method doesn't honor the values of `within()`, `as()`
|
74
74
|
etc, this will be improved as the library matures, but we're not there yet.
|
75
75
|
|
76
|
-
##
|
76
|
+
## Upload a file from a stream
|
77
77
|
|
78
78
|
on hosts do |host|
|
79
79
|
file = File.open('/config/database.yml')
|
80
80
|
io = StringIO.new(....)
|
81
|
-
upload! file, '/opt/my_project/shared/
|
81
|
+
upload! file, '/opt/my_project/shared/database.yml'
|
82
82
|
upload! io, '/opt/my_project/shared/io.io.io'
|
83
83
|
end
|
84
84
|
|
@@ -110,7 +110,7 @@ available to `Net::{SCP,SFTP}`.
|
|
110
110
|
Setting global SSH options, these will be overwritten by options set on the
|
111
111
|
individual hosts:
|
112
112
|
|
113
|
-
Netssh.configure do |ssh|
|
113
|
+
SSHKit::Backend::Netssh.configure do |ssh|
|
114
114
|
ssh.connection_timeout = 30
|
115
115
|
ssh.ssh_options = {
|
116
116
|
keys: %w(/home/user/.ssh/id_rsa),
|
@@ -119,7 +119,7 @@ individual hosts:
|
|
119
119
|
}
|
120
120
|
end
|
121
121
|
|
122
|
-
##
|
122
|
+
## Run a command with a different effective group ID
|
123
123
|
|
124
124
|
on hosts do |host|
|
125
125
|
as user: 'www-data', group: 'project-group' do
|
@@ -233,7 +233,7 @@ which will cause the command to abort.
|
|
233
233
|
end
|
234
234
|
|
235
235
|
The `test()` command behaves exactly the same as execute however will return
|
236
|
-
false if the command exits with a non-zero exit (as `man 1 test` does). As
|
236
|
+
false if the command exits with a non-zero exit (as `man 1 test` does). As it
|
237
237
|
returns boolean it can be used to direct the control flow within the block.
|
238
238
|
|
239
239
|
## Do something different on one host, or another depending on a host property
|
@@ -294,7 +294,7 @@ An extension of the behaviour above, if you write a command like this:
|
|
294
294
|
known test cases, it works. The key thing is that `if` is not mapped to
|
295
295
|
`/usr/bin/env if`, which would break with a syntax error.
|
296
296
|
|
297
|
-
##
|
297
|
+
## Using with Rake
|
298
298
|
|
299
299
|
Into the `Rakefile` simply put something like:
|
300
300
|
|
data/FAQ.md
CHANGED
@@ -11,11 +11,6 @@ Since *Capistrano v3.0*, *SSHKit* is used by *Capistrano* to communicate with
|
|
11
11
|
backend servers. Whilst Capistrano provides the structure for repeatable
|
12
12
|
deployments.
|
13
13
|
|
14
|
-
## Production Ready?
|
15
|
-
|
16
|
-
It's in private Beta use, and the documentation could use more work, but this
|
17
|
-
is open source, that's more or less how it works.
|
18
|
-
|
19
14
|
## Why does <something> stop responding after I started it with `background()`?
|
20
15
|
|
21
16
|
The answer is complicated, but it can be summed up by saying that under
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -3,25 +3,28 @@
|
|
3
3
|
**SSHKit** is a toolkit for running commands in a structured way on one or
|
4
4
|
more servers.
|
5
5
|
|
6
|
-
[](https://travis-ci.org/capistrano/sshkit)
|
7
7
|
[](https://gemnasium.com/leehambley/sshkit)
|
8
8
|
|
9
9
|
## How might it work?
|
10
10
|
|
11
11
|
The typical use-case looks something like this:
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
13
|
+
```ruby
|
14
|
+
require 'sshkit/dsl'
|
15
|
+
|
16
|
+
on %w{1.example.com 2.example.com}, in: :sequence, wait: 5 do
|
17
|
+
within "/opt/sites/example.com" do
|
18
|
+
as :deploy do
|
19
|
+
with rails_env: :production do
|
20
|
+
rake "assets:precompile"
|
21
|
+
runner "S3::Sync.notify"
|
22
|
+
execute "node", "socket_server.js"
|
23
23
|
end
|
24
24
|
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
```
|
25
28
|
|
26
29
|
One will notice that it's quite low level, but exposes a convenient API, the
|
27
30
|
`as()`/`within()`/`with()` are nestable in any order, repeatable, and stackable.
|
@@ -55,9 +58,11 @@ Helpers such as `runner()` and `rake()` which expand to `execute(:rails, "runner
|
|
55
58
|
Notice on the `on()` call the `in: :sequence` option, the following will do
|
56
59
|
what you might expect:
|
57
60
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
+
```ruby
|
62
|
+
on(in: :parallel) { ... }
|
63
|
+
on(in: :sequence, wait: 5) { ... }
|
64
|
+
on(in: :groups, limit: 2, wait: 5) { ... }
|
65
|
+
```
|
61
66
|
|
62
67
|
The default is to run `in: :parallel` which has no limit. If you have 400 servers,
|
63
68
|
this might be a problem and you might better look at changing that to run in
|
@@ -77,49 +82,68 @@ for all servers to complete before it returns.
|
|
77
82
|
|
78
83
|
For example:
|
79
84
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
85
|
+
```ruby
|
86
|
+
all_servers = %w{one.example.com two.example.com three.example.com}
|
87
|
+
site_dir = '/opt/sites/example.com'
|
88
|
+
|
89
|
+
# Let's simulate a backup task, assuming that some servers take longer
|
90
|
+
# then others to complete
|
91
|
+
on all_servers do |host|
|
92
|
+
in site_dir do
|
93
|
+
execute :tar, '-czf', "backup-#{host.hostname}.tar.gz", 'current'
|
94
|
+
# Will run: "/usr/bin/env tar -czf backup-one.example.com.tar.gz current"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Now we can do something with those backups, safe in the knowledge that
|
99
|
+
# they will all exist (all tar commands exited with a success status, or
|
100
|
+
# that we will have raised an exception if one of them failed.
|
101
|
+
on all_servers do |host|
|
102
|
+
in site_dir do
|
103
|
+
backup_filename = "backup-#{host.hostname}.tar.gz"
|
104
|
+
target_filename = "backups/#{Time.now.utc.iso8601}/#{host.hostname}.tar.gz"
|
105
|
+
puts capture(:s3cmd, 'put', backup_filename, target_filename)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
```
|
102
109
|
|
103
110
|
## The Command Map
|
104
111
|
|
105
|
-
It's often a problem that
|
106
|
-
variables as sessions
|
112
|
+
It's often a problem that programmatic SSH sessions don't have the same environment
|
113
|
+
variables as interactive sessions.
|
107
114
|
|
108
|
-
|
109
|
-
the `$PATH
|
110
|
-
configuration
|
115
|
+
A problem often arises when calling out to executables expected to be on
|
116
|
+
the `$PATH`. Under conditions without dotfiles or other environmental
|
117
|
+
configuration, `$PATH` may not be set as expected, and thus executables are not found where expected.
|
111
118
|
|
112
119
|
To try and solve this there is the `with()` helper which takes a hash of variables and makes them
|
113
120
|
available to the environment.
|
114
121
|
|
115
|
-
|
116
|
-
|
117
|
-
|
122
|
+
```ruby
|
123
|
+
with path: '/usr/local/bin/rbenv/shims:$PATH' do
|
124
|
+
execute :ruby, '--version'
|
125
|
+
end
|
126
|
+
```
|
118
127
|
|
119
128
|
Will execute:
|
120
129
|
|
121
130
|
( PATH=/usr/local/bin/rbenv/shims:$PATH /usr/bin/env ruby --version )
|
122
131
|
|
132
|
+
By contrast, the following won't modify the command at all:
|
133
|
+
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
with path: '/usr/local/bin/rbenv/shims:$PATH' do
|
137
|
+
execute 'ruby --version'
|
138
|
+
end
|
139
|
+
```
|
140
|
+
|
141
|
+
Will execute, without mapping the environmental variables, or querying the command map:
|
142
|
+
|
143
|
+
ruby --version
|
144
|
+
|
145
|
+
(This behaviour is sometimes considered confusing, but it has mostly to do with shell escaping: in the case of whitespace in your command, or newlines, we have no way of reliably composing a correct shell command from the input given.)
|
146
|
+
|
123
147
|
**Often more preferable is to use the *command map*.**
|
124
148
|
|
125
149
|
The *command map* is used by default when instantiating a *Command* object
|
@@ -128,36 +152,43 @@ The *command map* exists on the configuration object, and in principle is
|
|
128
152
|
quite simple, it's a *Hash* structure with a default key factory block
|
129
153
|
specified, for example:
|
130
154
|
|
131
|
-
|
132
|
-
|
155
|
+
```ruby
|
156
|
+
puts SSHKit.config.command_map[:ruby]
|
157
|
+
# => /usr/bin/env ruby
|
158
|
+
```
|
133
159
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
explicit, it was hoped that it might lead people to explore the documentation.
|
160
|
+
To make clear the environment is being deferred to, the `/usr/bin/env` prefix is applied to all commands.
|
161
|
+
Although this is what happens anyway when one would simply attempt to execute `ruby`, making it
|
162
|
+
explicit hopefully leads people to explore the documentation.
|
138
163
|
|
139
164
|
One can override the hash map for individual commands:
|
140
165
|
|
141
|
-
|
142
|
-
|
143
|
-
|
166
|
+
```ruby
|
167
|
+
SSHKit.config.command_map[:rake] = "/usr/local/rbenv/shims/rake"
|
168
|
+
puts SSHKit.config.command_map[:rake]
|
169
|
+
# => /usr/local/rbenv/shims/rake
|
170
|
+
```
|
144
171
|
|
145
|
-
Another
|
172
|
+
Another opportunity is to add command prefixes:
|
146
173
|
|
147
|
-
|
148
|
-
|
149
|
-
|
174
|
+
```ruby
|
175
|
+
SSHKit.config.command_map.prefix[:rake].push("bundle exec")
|
176
|
+
puts SSHKit.config.command_map[:rake]
|
177
|
+
# => bundle exec rake
|
150
178
|
|
151
|
-
|
152
|
-
|
153
|
-
|
179
|
+
SSHKit.config.command_map.prefix[:rake].unshift("/usr/local/rbenv/bin exec")
|
180
|
+
puts SSHKit.config.command_map[:rake]
|
181
|
+
# => /usr/local/rbenv/bin exec bundle exec rake
|
182
|
+
```
|
154
183
|
|
155
184
|
One can also override the command map completely, this may not be wise, but it
|
156
185
|
would be possible, for example:
|
157
186
|
|
158
|
-
|
159
|
-
|
160
|
-
|
187
|
+
```ruby
|
188
|
+
SSHKit.config.command_map = Hash.new do |hash, command|
|
189
|
+
hash[command] = "/usr/local/rbenv/shims/#{command}"
|
190
|
+
end
|
191
|
+
```
|
161
192
|
|
162
193
|
This would effectively make it impossible to call any commands which didn't
|
163
194
|
provide an executable in that directory, but in some cases that might be
|
@@ -170,26 +201,27 @@ first argument before attempting to find it in the *command map*.
|
|
170
201
|
|
171
202
|

|
172
203
|
|
173
|
-
|
174
|
-
default this is *$stdout*, but can be any object responding to a
|
175
|
-
*StringIO*-like interface. The second part is the *formatter*.
|
204
|
+
By default, the output format is set to `:pretty`:
|
176
205
|
|
177
|
-
|
206
|
+
```ruby
|
207
|
+
SSHKit.config.format = :pretty
|
208
|
+
```
|
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.
|
178
211
|
|
179
|
-
|
212
|
+
To output directly to $stdout without any formatting, you can use:
|
180
213
|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
should be printed.
|
214
|
+
```ruby
|
215
|
+
SSHKit.config.output = $stdout
|
216
|
+
```
|
185
217
|
|
186
|
-
## Verbosity
|
218
|
+
## Output Verbosity
|
187
219
|
|
188
220
|
By default calls to `capture()` and `test()` are not logged, they are used
|
189
221
|
*so* frequently by backend tasks to check environmental settings that it
|
190
222
|
produces a large amount of noise. They are tagged with a verbosity option on
|
191
223
|
the `Command` instances of `Logger::DEBUG`. The default configuration for
|
192
|
-
output verbosity is
|
224
|
+
output verbosity is available to override with `SSHKit.config.output_verbosity=`,
|
193
225
|
and defaults to `Logger::INFO`.
|
194
226
|
|
195
227
|
At present the `Logger::WARN`, `ERROR` and `FATAL` are not used.
|
@@ -211,7 +243,7 @@ SSHKit::Backend::Netssh.pool.idle_timeout = 60 # seconds
|
|
211
243
|
```
|
212
244
|
|
213
245
|
If you suspect the connection pooling is causing problems, you can disable the
|
214
|
-
pooling
|
246
|
+
pooling behaviour entirely by setting the idle_timeout to zero:
|
215
247
|
|
216
248
|
```ruby
|
217
249
|
SSHKit::Backend::Netssh.pool.idle_timeout = 0 # disabled
|
@@ -247,8 +279,8 @@ SSHKit::Backend::Netssh.pool.idle_timeout = 0 # disabled
|
|
247
279
|
* No method for uploading or downloading files, or the same for saving/loading
|
248
280
|
a string to/from a remote file.
|
249
281
|
* No closing of connections, the abstract backend class should include a
|
250
|
-
cleanup method which is empty but can be
|
251
|
-
* ~~No
|
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
|
252
284
|
easily be modified to look into some connection factory for it's objects,
|
253
285
|
saving half a second when running lots of `on()` blocks.~~
|
254
286
|
* Documentation! (YARD style)
|
data/Rakefile
CHANGED
data/lib/sshkit.rb
CHANGED
@@ -3,28 +3,31 @@ module SSHKit
|
|
3
3
|
StandardError = Class.new(::StandardError)
|
4
4
|
|
5
5
|
class << self
|
6
|
+
|
6
7
|
attr_accessor :config
|
7
|
-
end
|
8
|
-
|
9
|
-
def self.capture_output(io, &block)
|
10
|
-
original_io = config.output
|
11
|
-
config.output = io
|
12
|
-
yield
|
13
|
-
ensure
|
14
|
-
config.output = original_io
|
15
|
-
end
|
16
|
-
|
17
|
-
def self.configure
|
18
|
-
@@config ||= Configuration.new
|
19
|
-
yield config
|
20
|
-
end
|
21
|
-
|
22
|
-
def self.config
|
23
|
-
@@config ||= Configuration.new
|
24
|
-
end
|
25
8
|
|
26
|
-
|
27
|
-
|
9
|
+
def capture_output(io, &block)
|
10
|
+
original_io = config.output
|
11
|
+
config.output = io
|
12
|
+
config.output.extend(SSHKit::Utils::CaptureOutputMethods)
|
13
|
+
yield
|
14
|
+
ensure
|
15
|
+
config.output = original_io
|
16
|
+
end
|
17
|
+
|
18
|
+
def configure
|
19
|
+
@@config ||= Configuration.new
|
20
|
+
yield config
|
21
|
+
end
|
22
|
+
|
23
|
+
def config
|
24
|
+
@@config ||= Configuration.new
|
25
|
+
end
|
26
|
+
|
27
|
+
def reset_configuration!
|
28
|
+
@@config = nil
|
29
|
+
end
|
30
|
+
|
28
31
|
end
|
29
32
|
|
30
33
|
end
|
data/lib/sshkit/all.rb
CHANGED
@@ -13,6 +13,7 @@ require_relative 'log_message'
|
|
13
13
|
|
14
14
|
require_relative 'formatters/abstract'
|
15
15
|
require_relative 'formatters/black_hole'
|
16
|
+
require_relative 'formatters/simple_text'
|
16
17
|
require_relative 'formatters/pretty'
|
17
18
|
require_relative 'formatters/dot'
|
18
19
|
|
@@ -28,3 +29,5 @@ require_relative 'backends/printer'
|
|
28
29
|
require_relative 'backends/netssh'
|
29
30
|
require_relative 'backends/local'
|
30
31
|
require_relative 'backends/skipper'
|
32
|
+
|
33
|
+
require_relative 'utils/capture_output_methods'
|
@@ -10,7 +10,6 @@ module SSHKit
|
|
10
10
|
|
11
11
|
def initialize
|
12
12
|
self.idle_timeout = 30
|
13
|
-
@connections = {}
|
14
13
|
@monitor = Monitor.new
|
15
14
|
end
|
16
15
|
|
@@ -31,25 +30,21 @@ module SSHKit
|
|
31
30
|
|
32
31
|
private
|
33
32
|
|
33
|
+
def connections
|
34
|
+
Thread.current[:sshkit_pool] ||= {}
|
35
|
+
end
|
36
|
+
|
34
37
|
def find_and_reject_invalid(key, &block)
|
35
|
-
|
36
|
-
|
37
|
-
invalid = entry && yield(entry)
|
38
|
+
entry = connections[key]
|
39
|
+
invalid = entry && yield(entry)
|
38
40
|
|
39
|
-
|
41
|
+
connections.delete(entry) if invalid
|
40
42
|
|
41
|
-
|
42
|
-
end
|
43
|
+
invalid ? nil : entry
|
43
44
|
end
|
44
45
|
|
45
46
|
def store_entry(key, connection)
|
46
|
-
|
47
|
-
@connections[key] = Entry.new(connection)
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
def synchronize(&block)
|
52
|
-
@monitor.synchronize(&block)
|
47
|
+
connections[key] = Entry.new(connection)
|
53
48
|
end
|
54
49
|
|
55
50
|
|
@@ -26,7 +26,7 @@ module SSHKit
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def capture(*args)
|
29
|
-
options =
|
29
|
+
options = { verbosity: Logger::DEBUG }.merge(args.extract_options!)
|
30
30
|
_execute(*[*args, options]).full_stdout.strip
|
31
31
|
end
|
32
32
|
|
@@ -38,7 +38,13 @@ module SSHKit
|
|
38
38
|
|
39
39
|
cmd.started = Time.now
|
40
40
|
|
41
|
-
stdout, stderr, exit_status =
|
41
|
+
stdout, stderr, exit_status =
|
42
|
+
if RUBY_ENGINE == 'jruby'
|
43
|
+
_, o, e, t = Open3.popen3('/usr/bin/env', 'sh', '-c', cmd.to_command)
|
44
|
+
[o.read, e.read, t.value]
|
45
|
+
else
|
46
|
+
Open3.capture3(cmd.to_command)
|
47
|
+
end
|
42
48
|
|
43
49
|
cmd.stdout = stdout
|
44
50
|
cmd.full_stdout += stdout
|
@@ -11,13 +11,12 @@ module SSHKit
|
|
11
11
|
|
12
12
|
def execute(*args)
|
13
13
|
command(*args).tap do |cmd|
|
14
|
-
output <<
|
14
|
+
output << cmd
|
15
15
|
end
|
16
16
|
end
|
17
17
|
alias :upload! :execute
|
18
18
|
alias :download! :execute
|
19
19
|
alias :test :execute
|
20
|
-
alias :invoke :execute
|
21
20
|
|
22
21
|
def capture(*args)
|
23
22
|
String.new.tap { execute(*args) }
|
data/lib/sshkit/command.rb
CHANGED
@@ -87,10 +87,14 @@ module SSHKit
|
|
87
87
|
def exit_status=(new_exit_status)
|
88
88
|
@finished_at = Time.now
|
89
89
|
@exit_status = new_exit_status
|
90
|
+
|
90
91
|
if options[:raise_on_non_zero_exit] && exit_status > 0
|
91
92
|
message = ""
|
93
|
+
message += "#{command} exit status: " + exit_status.to_s + "\n"
|
92
94
|
message += "#{command} stdout: " + (stdout.strip.empty? ? "Nothing written" : stdout.strip) + "\n"
|
93
|
-
|
95
|
+
|
96
|
+
stderr_message = [stderr.strip, full_stderr.strip].delete_if(&:empty?).first
|
97
|
+
message += "#{command} stderr: " + (stderr_message || 'Nothing written') + "\n"
|
94
98
|
raise Failed, message
|
95
99
|
end
|
96
100
|
end
|
@@ -147,7 +151,7 @@ module SSHKit
|
|
147
151
|
(SSHKit.config.default_env || {}).merge(options[:env] || {})
|
148
152
|
end
|
149
153
|
|
150
|
-
def
|
154
|
+
def environment_string
|
151
155
|
environment_hash.collect do |key,value|
|
152
156
|
"#{key.to_s.upcase}=#{value}"
|
153
157
|
end.join(' ')
|
@@ -155,7 +159,7 @@ module SSHKit
|
|
155
159
|
|
156
160
|
def with(&block)
|
157
161
|
return yield unless environment_hash.any?
|
158
|
-
"( #{
|
162
|
+
"( #{environment_string} %s )" % yield
|
159
163
|
end
|
160
164
|
|
161
165
|
def user(&block)
|
@@ -165,7 +169,7 @@ module SSHKit
|
|
165
169
|
|
166
170
|
def in_background(&block)
|
167
171
|
return yield unless options[:run_in_background]
|
168
|
-
"nohup %s > /dev/null &" % yield
|
172
|
+
"( nohup %s > /dev/null & )" % yield
|
169
173
|
end
|
170
174
|
|
171
175
|
def umask(&block)
|
data/lib/sshkit/command_map.rb
CHANGED
@@ -1,8 +1,29 @@
|
|
1
1
|
module SSHKit
|
2
2
|
class CommandMap
|
3
|
+
class CommandHash
|
4
|
+
def initialize(defaults = {})
|
5
|
+
@storage = {}
|
6
|
+
@defaults = defaults
|
7
|
+
end
|
8
|
+
|
9
|
+
def [](key)
|
10
|
+
@storage[normalize_key(key)] ||= @defaults[key]
|
11
|
+
end
|
12
|
+
|
13
|
+
def []=(key, value)
|
14
|
+
@storage[normalize_key(key)] = value
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def normalize_key(key)
|
20
|
+
key.to_sym
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
3
24
|
class PrefixProvider
|
4
25
|
def initialize
|
5
|
-
@storage =
|
26
|
+
@storage = CommandHash.new
|
6
27
|
end
|
7
28
|
|
8
29
|
def [](command)
|
@@ -13,7 +34,7 @@ module SSHKit
|
|
13
34
|
end
|
14
35
|
|
15
36
|
def initialize(value = nil)
|
16
|
-
@map = value || defaults
|
37
|
+
@map = CommandHash.new(value || defaults)
|
17
38
|
end
|
18
39
|
|
19
40
|
def [](command)
|
@@ -35,7 +56,7 @@ module SSHKit
|
|
35
56
|
end
|
36
57
|
|
37
58
|
def clear
|
38
|
-
@map = defaults
|
59
|
+
@map = CommandHash.new(defaults)
|
39
60
|
end
|
40
61
|
|
41
62
|
def defaults
|
data/lib/sshkit/coordinator.rb
CHANGED
@@ -6,7 +6,7 @@ module SSHKit
|
|
6
6
|
|
7
7
|
def initialize(raw_hosts)
|
8
8
|
@raw_hosts = Array(raw_hosts)
|
9
|
-
|
9
|
+
@hosts = @raw_hosts.any? ? resolve_hosts : []
|
10
10
|
end
|
11
11
|
|
12
12
|
def each(options={}, &block)
|
@@ -32,8 +32,8 @@ module SSHKit
|
|
32
32
|
{ in: :parallel }
|
33
33
|
end
|
34
34
|
|
35
|
-
def resolve_hosts
|
36
|
-
@
|
35
|
+
def resolve_hosts
|
36
|
+
@raw_hosts.collect { |rh| rh.is_a?(Host) ? rh : Host.new(rh) }.uniq
|
37
37
|
end
|
38
38
|
|
39
39
|
end
|
data/lib/sshkit/dsl.rb
CHANGED
@@ -0,0 +1,58 @@
|
|
1
|
+
|
2
|
+
module SSHKit
|
3
|
+
|
4
|
+
module Formatter
|
5
|
+
|
6
|
+
class SimpleText < Abstract
|
7
|
+
|
8
|
+
def write(obj)
|
9
|
+
return if obj.verbosity < SSHKit.config.output_verbosity
|
10
|
+
case obj
|
11
|
+
when SSHKit::Command then write_command(obj)
|
12
|
+
when SSHKit::LogMessage then write_log_message(obj)
|
13
|
+
else
|
14
|
+
original_output << "Output formatter doesn't know how to handle #{obj.class}\n"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
alias :<< :write
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def write_command(command)
|
22
|
+
unless command.started?
|
23
|
+
original_output << "Running #{String(command)} on #{command.host.to_s}\n"
|
24
|
+
if SSHKit.config.output_verbosity == Logger::DEBUG
|
25
|
+
original_output << "Command: #{command.to_command}" + "\n"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
if SSHKit.config.output_verbosity == Logger::DEBUG
|
30
|
+
unless command.stdout.empty?
|
31
|
+
command.stdout.lines.each do |line|
|
32
|
+
original_output << "\t" + line
|
33
|
+
original_output << "\n" unless line[-1] == "\n"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
unless command.stderr.empty?
|
38
|
+
command.stderr.lines.each do |line|
|
39
|
+
original_output << "\t" + line
|
40
|
+
original_output << "\n" unless line[-1] == "\n"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
if command.finished?
|
46
|
+
original_output << "Finished in #{sprintf('%5.3f seconds', command.runtime)} with exit status #{command.exit_status} (#{ command.failure? ? 'failed' : 'successful' }).\n"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def write_log_message(log_message)
|
51
|
+
original_output << log_message.to_s + "\n"
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
data/lib/sshkit/version.rb
CHANGED
data/sshkit.gemspec
CHANGED
@@ -17,7 +17,7 @@ Gem::Specification.new do |gem|
|
|
17
17
|
gem.require_paths = ["lib"]
|
18
18
|
gem.version = SSHKit::VERSION
|
19
19
|
|
20
|
-
gem.add_runtime_dependency('net-ssh')
|
20
|
+
gem.add_runtime_dependency('net-ssh', '>= 2.8.0')
|
21
21
|
gem.add_runtime_dependency('net-scp', '>= 1.1.2')
|
22
22
|
gem.add_runtime_dependency('term-ansicolor')
|
23
23
|
|
data/test/boxes.json
CHANGED
@@ -23,7 +23,7 @@ module SSHKit
|
|
23
23
|
execute :echo, "'Test capturing stderr' 1>&2; false"
|
24
24
|
end.run
|
25
25
|
end
|
26
|
-
assert_equal "echo stdout: Nothing written\necho stderr: Test capturing stderr\n", err.message
|
26
|
+
assert_equal "echo exit status: 256\necho stdout: Nothing written\necho stderr: Test capturing stderr\n", err.message
|
27
27
|
end
|
28
28
|
|
29
29
|
def test_test
|
@@ -28,7 +28,7 @@ module SSHKit
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def a_host
|
31
|
-
VagrantWrapper.hosts[
|
31
|
+
VagrantWrapper.hosts['one']
|
32
32
|
end
|
33
33
|
|
34
34
|
def printer
|
@@ -54,11 +54,13 @@ module SSHKit
|
|
54
54
|
def test_capture
|
55
55
|
File.open('/dev/null', 'w') do |dnull|
|
56
56
|
SSHKit.capture_output(dnull) do
|
57
|
-
captured_command_result =
|
57
|
+
captured_command_result = nil
|
58
58
|
Netssh.new(a_host) do |host|
|
59
59
|
captured_command_result = capture(:uname)
|
60
60
|
end.run
|
61
|
-
|
61
|
+
|
62
|
+
assert captured_command_result
|
63
|
+
assert_match captured_command_result, /Linux|Darwin/
|
62
64
|
end
|
63
65
|
end
|
64
66
|
end
|
@@ -69,7 +71,7 @@ module SSHKit
|
|
69
71
|
execute :echo, "'Test capturing stderr' 1>&2; false"
|
70
72
|
end.run
|
71
73
|
end
|
72
|
-
assert_equal "echo stdout: Nothing written\necho stderr: Test capturing stderr\n", err.message
|
74
|
+
assert_equal "echo exit status: 1\necho stdout: Nothing written\necho stderr: Test capturing stderr\n", err.message
|
73
75
|
end
|
74
76
|
|
75
77
|
def test_test_does_not_raise_on_non_zero_exit_status
|
@@ -130,7 +132,6 @@ module SSHKit
|
|
130
132
|
end.run
|
131
133
|
assert_equal File.open(file_name).read, file_contents
|
132
134
|
end
|
133
|
-
|
134
135
|
end
|
135
136
|
|
136
137
|
end
|
@@ -13,6 +13,7 @@ module SSHKit
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def test_creating_a_user_gives_us_back_his_private_key_as_a_string
|
16
|
+
skip 'It is not safe to create an user for non vagrant envs' unless VagrantWrapper.running?
|
16
17
|
keys = create_user_with_key(:peter)
|
17
18
|
assert_equal [:one, :two, :three], keys.keys
|
18
19
|
assert keys.values.all?
|
data/test/helper.rb
CHANGED
@@ -12,41 +12,7 @@ $LOAD_PATH.unshift(File.dirname(__FILE__))
|
|
12
12
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
13
13
|
require 'sshkit'
|
14
14
|
|
15
|
-
|
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'
|
23
|
-
end
|
24
|
-
|
25
|
-
result[vm["name"]] = host
|
26
|
-
end
|
27
|
-
|
28
|
-
result
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def running?
|
33
|
-
@running ||= begin
|
34
|
-
status = `#{vagrant_binary} status`
|
35
|
-
status.include?('running')
|
36
|
-
end
|
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
|
48
|
-
end
|
49
|
-
end
|
15
|
+
Dir[File.expand_path('test/support/*.rb')].each { |file| require file }
|
50
16
|
|
51
17
|
class UnitTest < MiniTest::Unit::TestCase
|
52
18
|
|
@@ -54,13 +20,18 @@ class UnitTest < MiniTest::Unit::TestCase
|
|
54
20
|
SSHKit.reset_configuration!
|
55
21
|
end
|
56
22
|
|
23
|
+
SSHKit::Backend::ConnectionPool.class_eval do
|
24
|
+
def flush_connections
|
25
|
+
Thread.current[:sshkit_pool] = {}
|
26
|
+
end
|
27
|
+
end
|
57
28
|
end
|
58
29
|
|
59
30
|
class FunctionalTest < MiniTest::Unit::TestCase
|
60
31
|
|
61
32
|
def setup
|
62
33
|
unless VagrantWrapper.running?
|
63
|
-
|
34
|
+
warn "Vagrant VMs are not running. Please, start it manually with `vagrant up`"
|
64
35
|
end
|
65
36
|
end
|
66
37
|
|
@@ -0,0 +1,56 @@
|
|
1
|
+
class VagrantWrapper
|
2
|
+
class << self
|
3
|
+
def hosts
|
4
|
+
@vm_hosts ||= begin
|
5
|
+
result = {}
|
6
|
+
|
7
|
+
boxes = boxes_list
|
8
|
+
|
9
|
+
unless running?
|
10
|
+
boxes.map! do |box|
|
11
|
+
box['user'] = ENV['USER']
|
12
|
+
box['port'] = '22'
|
13
|
+
box
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
boxes.each do |vm|
|
18
|
+
result[vm['name']] = vm_host(vm)
|
19
|
+
end
|
20
|
+
|
21
|
+
result
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def running?
|
26
|
+
@running ||= begin
|
27
|
+
status = `#{vagrant_binary} status`
|
28
|
+
status.include?('running')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def boxes_list
|
33
|
+
json_config_path = File.join('test', 'boxes.json')
|
34
|
+
boxes = File.open(json_config_path).read
|
35
|
+
JSON.parse(boxes)
|
36
|
+
end
|
37
|
+
|
38
|
+
def vagrant_binary
|
39
|
+
'vagrant'
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def vm_host(vm)
|
45
|
+
host_options = {
|
46
|
+
user: vm['user'] || 'vagrant',
|
47
|
+
hostname: vm['hostname'] || 'localhost',
|
48
|
+
port: vm['port'] || '22'
|
49
|
+
}
|
50
|
+
|
51
|
+
|
52
|
+
|
53
|
+
SSHKit::Host.new(host_options)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -39,7 +39,7 @@ module SSHKit
|
|
39
39
|
assert_equal conn1, conn2
|
40
40
|
end
|
41
41
|
|
42
|
-
def
|
42
|
+
def test_zero_idle_timeout_disables_reuse
|
43
43
|
pool.idle_timeout = 0
|
44
44
|
|
45
45
|
conn1 = pool.create_or_reuse_connection("conn", &connect)
|
@@ -59,6 +59,8 @@ module SSHKit
|
|
59
59
|
end
|
60
60
|
|
61
61
|
def test_closed_connection_is_not_reused
|
62
|
+
# Ensure there aren't any other open connections
|
63
|
+
pool.flush_connections()
|
62
64
|
conn1 = pool.create_or_reuse_connection("conn", &connect_and_close)
|
63
65
|
conn2 = pool.create_or_reuse_connection("conn", &connect)
|
64
66
|
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
module SSHKit
|
4
|
+
module Backend
|
5
|
+
class TestLocal < UnitTest
|
6
|
+
|
7
|
+
def local
|
8
|
+
@local ||= Local.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_execute
|
12
|
+
assert_equal true, local.execute('uname -a')
|
13
|
+
assert_equal true, local.execute
|
14
|
+
assert_equal true, local.execute('cd && pwd')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -59,6 +59,12 @@ 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
|
+
|
63
|
+
def test_invoke_raises_no_method_error
|
64
|
+
assert_raises NoMethodError do
|
65
|
+
printer.invoke :echo
|
66
|
+
end
|
67
|
+
end
|
62
68
|
|
63
69
|
end
|
64
70
|
|
data/test/unit/test_command.rb
CHANGED
@@ -93,17 +93,22 @@ module SSHKit
|
|
93
93
|
|
94
94
|
def test_backgrounding_a_task
|
95
95
|
c = Command.new(:sleep, 15, run_in_background: true)
|
96
|
-
assert_equal "nohup /usr/bin/env sleep 15 > /dev/null &", c.to_command
|
96
|
+
assert_equal "( nohup /usr/bin/env sleep 15 > /dev/null & )", c.to_command
|
97
97
|
end
|
98
98
|
|
99
99
|
def test_backgrounding_a_task_as_a_given_user
|
100
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
|
101
|
+
assert_equal "sudo su anotheruser -c \"( nohup /usr/bin/env sleep 15 > /dev/null & )\"", c.to_command
|
102
102
|
end
|
103
103
|
|
104
104
|
def test_backgrounding_a_task_as_a_given_user_with_env
|
105
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
|
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
|
107
112
|
end
|
108
113
|
|
109
114
|
def test_umask
|
@@ -205,7 +210,7 @@ module SSHKit
|
|
205
210
|
error = assert_raises SSHKit::Command::Failed do
|
206
211
|
Command.new(:whoami).exit_status = 1
|
207
212
|
end
|
208
|
-
assert_equal "whoami stdout: Nothing written\nwhoami stderr: Nothing written\n", error.message
|
213
|
+
assert_equal "whoami exit status: 1\nwhoami stdout: Nothing written\nwhoami stderr: Nothing written\n", error.message
|
209
214
|
end
|
210
215
|
|
211
216
|
end
|
@@ -36,5 +36,21 @@ module SSHKit
|
|
36
36
|
assert_equal map[:rake], "/home/vagrant/.rbenv/bin/rbenv exec bundle exec rake"
|
37
37
|
end
|
38
38
|
|
39
|
+
def test_indifferent_setter
|
40
|
+
map = CommandMap.new
|
41
|
+
map[:rake] = "/usr/local/rbenv/shims/rake"
|
42
|
+
map["rake"] = "/usr/local/rbenv/shims/rake2"
|
43
|
+
|
44
|
+
assert_equal "/usr/local/rbenv/shims/rake2", map[:rake]
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_indifferent_prefix
|
48
|
+
map = CommandMap.new
|
49
|
+
map.prefix[:rake].push("/home/vagrant/.rbenv/bin/rbenv exec")
|
50
|
+
map.prefix["rake"].push("bundle exec")
|
51
|
+
|
52
|
+
assert_equal map[:rake], "/home/vagrant/.rbenv/bin/rbenv exec bundle exec rake"
|
53
|
+
end
|
54
|
+
|
39
55
|
end
|
40
56
|
end
|
@@ -21,6 +21,12 @@ module SSHKit
|
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
+
def test_the_connection_manager_handles_empty_argument
|
25
|
+
Coordinator.new([]).each do
|
26
|
+
raise "This should not be executed"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
24
30
|
def test_connection_manager_handles_a_single_argument
|
25
31
|
h = Host.new('1.example.com')
|
26
32
|
Host.expects(:new).with('1.example.com').once().returns(h)
|
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.4.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:
|
12
|
+
date: 2014-04-15 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: net-ssh
|
@@ -17,14 +17,14 @@ dependencies:
|
|
17
17
|
requirements:
|
18
18
|
- - '>='
|
19
19
|
- !ruby/object:Gem::Version
|
20
|
-
version:
|
20
|
+
version: 2.8.0
|
21
21
|
type: :runtime
|
22
22
|
prerelease: false
|
23
23
|
version_requirements: !ruby/object:Gem::Requirement
|
24
24
|
requirements:
|
25
25
|
- - '>='
|
26
26
|
- !ruby/object:Gem::Version
|
27
|
-
version:
|
27
|
+
version: 2.8.0
|
28
28
|
- !ruby/object:Gem::Dependency
|
29
29
|
name: net-scp
|
30
30
|
requirement: !ruby/object:Gem::Requirement
|
@@ -172,6 +172,7 @@ files:
|
|
172
172
|
- lib/sshkit/formatters/black_hole.rb
|
173
173
|
- lib/sshkit/formatters/dot.rb
|
174
174
|
- lib/sshkit/formatters/pretty.rb
|
175
|
+
- lib/sshkit/formatters/simple_text.rb
|
175
176
|
- lib/sshkit/host.rb
|
176
177
|
- lib/sshkit/log_message.rb
|
177
178
|
- lib/sshkit/logger.rb
|
@@ -180,6 +181,7 @@ files:
|
|
180
181
|
- lib/sshkit/runners/null.rb
|
181
182
|
- lib/sshkit/runners/parallel.rb
|
182
183
|
- lib/sshkit/runners/sequential.rb
|
184
|
+
- lib/sshkit/utils/capture_output_methods.rb
|
183
185
|
- lib/sshkit/version.rb
|
184
186
|
- sshkit.gemspec
|
185
187
|
- test/boxes.json
|
@@ -188,7 +190,9 @@ files:
|
|
188
190
|
- test/functional/test_coordinator.rb
|
189
191
|
- test/functional/test_ssh_server_comes_up_for_functional_tests.rb
|
190
192
|
- test/helper.rb
|
193
|
+
- test/support/vagrant_wrapper.rb
|
191
194
|
- test/unit/backends/test_connection_pool.rb
|
195
|
+
- test/unit/backends/test_local.rb
|
192
196
|
- test/unit/backends/test_netssh.rb
|
193
197
|
- test/unit/backends/test_printer.rb
|
194
198
|
- test/unit/core_ext/test_string.rb
|
@@ -231,7 +235,9 @@ test_files:
|
|
231
235
|
- test/functional/test_coordinator.rb
|
232
236
|
- test/functional/test_ssh_server_comes_up_for_functional_tests.rb
|
233
237
|
- test/helper.rb
|
238
|
+
- test/support/vagrant_wrapper.rb
|
234
239
|
- test/unit/backends/test_connection_pool.rb
|
240
|
+
- test/unit/backends/test_local.rb
|
235
241
|
- test/unit/backends/test_netssh.rb
|
236
242
|
- test/unit/backends/test_printer.rb
|
237
243
|
- test/unit/core_ext/test_string.rb
|