sshkit 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/.yardopts +1 -1
- data/CHANGELOG.md +4 -0
- data/CONTRIBUTING.md +4 -0
- data/Gemfile.lock +1 -1
- data/README.md +30 -8
- data/RELEASING.md +10 -0
- data/assets/images/example_output.png +0 -0
- data/lib/sshkit/all.rb +1 -0
- data/lib/sshkit/backends/printer.rb +1 -1
- data/lib/sshkit/connection_manager.rb +56 -42
- data/lib/sshkit/formatters/dot.rb +28 -0
- data/lib/sshkit/version.rb +1 -1
- data/test/unit/backends/test_printer.rb +7 -19
- data/test/unit/test_connection_manager.rb +34 -21
- metadata +6 -6
- data/.yardoc/checksums +0 -13
- data/.yardoc/object_types +0 -0
- data/.yardoc/objects/root.dat +0 -0
- data/.yardoc/proxy_types +0 -0
data/.gitignore
CHANGED
data/.yardopts
CHANGED
@@ -1 +1 @@
|
|
1
|
-
--no-private - README.md CHANGELOG.md FAQ.md LICENSE.md EXAMPLES.md
|
1
|
+
--no-private - README.md CHANGELOG.md FAQ.md LICENSE.md EXAMPLES.md CONTRIBUTING.md RELEASING.md
|
data/CHANGELOG.md
CHANGED
data/CONTRIBUTING.md
ADDED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
![SSHKit Logo](/
|
2
|
-
|
1
|
+
![SSHKit Logo](https://raw.github.com/wacku/sshkit/master/assets/images/logo.png)
|
3
2
|
|
4
3
|
**SSHKit** is a toolkit for running commands in a structured way on one or
|
5
4
|
more servers.
|
@@ -48,7 +47,7 @@ the raised error.
|
|
48
47
|
Helpers such as `runner()` and `rake()` which expand to `execute(:rails, "runner", ...)` and
|
49
48
|
`execute(:rake, ...)` are convenience helpers for Ruby, and Rails based apps.
|
50
49
|
|
51
|
-
##
|
50
|
+
## Parallel
|
52
51
|
|
53
52
|
Notice on the `on()` call the `in: :sequence` option, the following will do
|
54
53
|
what you might expect:
|
@@ -156,6 +155,8 @@ first argument before attempting to find it in the *command map*.
|
|
156
155
|
|
157
156
|
## Output Handling
|
158
157
|
|
158
|
+
![Example Output](https://raw.github.com/wacku/sshkit/master/assets/images/example_output.png)
|
159
|
+
|
159
160
|
The output handling comprises two objects, first is the output itself, by
|
160
161
|
default this is *$stdout*, but can be any object responding to a
|
161
162
|
*StringIO*-like interface. The second part is the *formatter*.
|
@@ -173,9 +174,30 @@ should be printed.
|
|
173
174
|
## Known Issues
|
174
175
|
|
175
176
|
* No handling of slow / timed out connections
|
176
|
-
* No handling
|
177
|
+
* No handling of slow / hung remote commands
|
177
178
|
* No built-in way to background() something (execute and background the
|
178
|
-
process)
|
179
|
-
* No environment handling
|
180
|
-
* No arbitrary `Host` properties
|
181
|
-
|
179
|
+
process).
|
180
|
+
* No environment handling (sshkit might not need to care)
|
181
|
+
* No arbitrary `Host` properties (example storing `roles` on servers, or other
|
182
|
+
metadata that might be useful in the `on()` block)
|
183
|
+
* No log/warning facility (passing Log messages to the output would work)
|
184
|
+
A log object could be made available globally which would emit a LogMessage
|
185
|
+
type object which would be recognised by the formatters that need to care
|
186
|
+
about them.
|
187
|
+
* No verbosity control, commands should have a `Logger::LEVEL` on them,
|
188
|
+
user-generated should be at a high level, the commands auto-generated from
|
189
|
+
the guards and checks from as() and within() should have a lower level.
|
190
|
+
* Decide if `execute()` (and friends) should raise on non-zero exit statuses or
|
191
|
+
not, perhaps a family of similarly named bang methods should be the ones to
|
192
|
+
raise. (Perhaps `test()` should be a way to `execute()` without raising, and
|
193
|
+
`execute()` and friends should always raise)
|
194
|
+
* It would be nice to be able to say `SSHKit.config.formatter = :pretty` and
|
195
|
+
have that method setter do the legwork of updating `SSHKit.config.output` to
|
196
|
+
be an instance of the correct formatter class wrapping the existing output
|
197
|
+
stream.
|
198
|
+
* No closing of connections, the abstract backend class should include a
|
199
|
+
cleanup method which is empty but can be overriden by
|
200
|
+
* No conncetion pooling, the `connection` method of the NetSSH backend could
|
201
|
+
easily be modified to look into some connection factory for it's objects,
|
202
|
+
saving half a second when running lots of `on()` blocks.
|
203
|
+
* Documentation! (YARD style)
|
data/RELEASING.md
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# Releasing
|
2
|
+
|
3
|
+
* **Ensure the tests are passing.**
|
4
|
+
* Determine which would be the correct next version number according to [semver](http://semver.org/).
|
5
|
+
* Update the version in `./lib/sshkit/version.rb`.
|
6
|
+
* Update the `CHANGELOG`.
|
7
|
+
* Commit the changelog and version in a single commit, the message should be "Preparing vX.Y.Z"
|
8
|
+
* Tag the commit `git tag vX.Y.Z` (if tagging a historical commit, `git tag` can take a *SHA1* after the tag name)
|
9
|
+
* Push new commits, and tags to Github.
|
10
|
+
* Push the gem to [rubygems](http://rubygems.org).
|
Binary file
|
data/lib/sshkit/all.rb
CHANGED
@@ -11,6 +11,7 @@ require_relative 'connection_manager'
|
|
11
11
|
require_relative 'formatters/abstract'
|
12
12
|
require_relative 'formatters/black_hole'
|
13
13
|
require_relative 'formatters/pretty'
|
14
|
+
require_relative 'formatters/dot'
|
14
15
|
|
15
16
|
require_relative 'backends/abstract'
|
16
17
|
require_relative 'backends/printer'
|
@@ -1,54 +1,68 @@
|
|
1
1
|
require 'timeout'
|
2
2
|
|
3
|
-
|
3
|
+
module SSHKit
|
4
4
|
|
5
|
-
|
5
|
+
module Runner
|
6
6
|
|
7
|
-
|
8
|
-
@hosts = Array(hosts)
|
9
|
-
@block = block
|
10
|
-
end
|
7
|
+
class Abstract
|
11
8
|
|
12
|
-
|
9
|
+
attr_reader :hosts, :block
|
10
|
+
|
11
|
+
def initialize(hosts, &block)
|
12
|
+
@hosts = Array(hosts)
|
13
|
+
@block = block
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
13
17
|
|
14
|
-
|
15
|
-
|
16
|
-
threads = []
|
17
|
-
hosts.each do |host|
|
18
|
-
threads << Thread.new(host) do |h|
|
19
|
-
SSHKit.config.backend.new(host, &block).run
|
18
|
+
def backend(host, &block)
|
19
|
+
SSHKit.config.backend.new(host, &block)
|
20
20
|
end
|
21
|
+
|
21
22
|
end
|
22
|
-
threads.map(&:join)
|
23
|
-
end
|
24
|
-
end
|
25
23
|
|
26
|
-
class
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
24
|
+
class Parallel < Abstract
|
25
|
+
def execute
|
26
|
+
threads = []
|
27
|
+
hosts.each do |host|
|
28
|
+
threads << Thread.new(host) do |h|
|
29
|
+
backend(host, &block).run
|
30
|
+
end
|
31
|
+
end
|
32
|
+
threads.map(&:join)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class Sequential < Abstract
|
37
|
+
attr_writer :wait_interval
|
38
|
+
def execute
|
39
|
+
hosts.each do |host|
|
40
|
+
backend(host, &block).run
|
41
|
+
sleep wait_interval
|
42
|
+
end
|
43
|
+
end
|
44
|
+
private
|
45
|
+
def wait_interval
|
46
|
+
@wait_interval ||= 2
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class Group < Sequential
|
51
|
+
attr_writer :group_size
|
52
|
+
def execute
|
53
|
+
hosts.each_slice(group_size).collect do |group_hosts|
|
54
|
+
Parallel.new(group_hosts, &block).execute
|
55
|
+
sleep wait_interval
|
56
|
+
end.flatten
|
57
|
+
end
|
58
|
+
private
|
59
|
+
def group_size
|
60
|
+
@group_size ||= 2
|
61
|
+
end
|
32
62
|
end
|
33
|
-
end
|
34
|
-
private
|
35
|
-
def wait_interval
|
36
|
-
@wait_interval ||= 2
|
37
|
-
end
|
38
|
-
end
|
39
63
|
|
40
|
-
class GroupRunner < SequentialRunner
|
41
|
-
attr_writer :group_size
|
42
|
-
def execute
|
43
|
-
hosts.each_slice(group_size).collect do |group_hosts|
|
44
|
-
ParallelRunner.new(group_hosts, &block).execute
|
45
|
-
sleep wait_interval
|
46
|
-
end.flatten
|
47
|
-
end
|
48
|
-
private
|
49
|
-
def group_size
|
50
|
-
@group_size ||= 2
|
51
64
|
end
|
65
|
+
|
52
66
|
end
|
53
67
|
|
54
68
|
module SSHKit
|
@@ -68,9 +82,9 @@ module SSHKit
|
|
68
82
|
def each(options={}, &block)
|
69
83
|
options = default_options.merge(options)
|
70
84
|
case options[:in]
|
71
|
-
when :parallel then
|
72
|
-
when :sequence then
|
73
|
-
when :groups then
|
85
|
+
when :parallel then Runner::Parallel
|
86
|
+
when :sequence then Runner::Sequential
|
87
|
+
when :groups then Runner::Group
|
74
88
|
else
|
75
89
|
raise RuntimeError, "Don't know how to handle run style #{options[:in].inspect}"
|
76
90
|
end.new(hosts, &block).execute
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'term/ansicolor'
|
2
|
+
|
3
|
+
module SSHKit
|
4
|
+
|
5
|
+
module Formatter
|
6
|
+
|
7
|
+
class Dot < Abstract
|
8
|
+
|
9
|
+
def write(obj)
|
10
|
+
if obj.is_a? SSHKit::Command
|
11
|
+
if obj.finished?
|
12
|
+
original_output << (obj.failure? ? c.red('.') : c.green('.'))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
alias :<< :write
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def c
|
21
|
+
@c ||= Term::ANSIColor
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
data/lib/sshkit/version.rb
CHANGED
@@ -4,18 +4,6 @@ module SSHKit
|
|
4
4
|
|
5
5
|
module Backend
|
6
6
|
|
7
|
-
class ToSIoFormatter < StringIO
|
8
|
-
extend Forwardable
|
9
|
-
attr_reader :original_output
|
10
|
-
def_delegators :@original_output, :read, :rewind
|
11
|
-
def initialize(oio)
|
12
|
-
@original_output = oio
|
13
|
-
end
|
14
|
-
def write(obj)
|
15
|
-
original_output.write "> Executing #{obj}\n"
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
7
|
class TestPrinter < UnitTest
|
20
8
|
|
21
9
|
def block_to_run
|
@@ -39,19 +27,19 @@ module SSHKit
|
|
39
27
|
end
|
40
28
|
|
41
29
|
def test_simple_printing
|
42
|
-
sio =
|
30
|
+
sio = StringIO.new
|
43
31
|
SSHKit.capture_output(sio) do
|
44
32
|
printer.run
|
45
33
|
end
|
46
34
|
sio.rewind
|
47
35
|
result = sio.read
|
48
36
|
assert_equal <<-EOEXPECTED.unindent, result
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
37
|
+
if test ! -d /opt/sites/example.com; then echo "Directory does not exist '/opt/sites/example.com'" 1>&2; false; fi
|
38
|
+
cd /opt/sites/example.com && /usr/bin/env date
|
39
|
+
cd /opt/sites/example.com && /usr/bin/env ls -l /some/directory
|
40
|
+
if test ! -d /opt/sites/example.com/tmp; then echo "Directory does not exist '/opt/sites/example.com/tmp'" 1>&2; false; fi
|
41
|
+
if ! sudo su root -c whoami > /dev/null; then echo "You cannot switch to user 'root' using sudo, please check the sudoers file" 1>&2; false; fi
|
42
|
+
cd /opt/sites/example.com/tmp && ( RAILS_ENV=production sudo su root -c /usr/bin/env touch restart.txt )
|
55
43
|
EOEXPECTED
|
56
44
|
end
|
57
45
|
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'time'
|
1
2
|
require 'helper'
|
2
3
|
|
3
4
|
module SSHKit
|
@@ -5,7 +6,18 @@ module SSHKit
|
|
5
6
|
class TestConnectionManager < UnitTest
|
6
7
|
|
7
8
|
def setup
|
8
|
-
|
9
|
+
@s = String.new
|
10
|
+
SSHKit.config.backend = SSHKit::Backend::Printer
|
11
|
+
end
|
12
|
+
|
13
|
+
def tearddown
|
14
|
+
@s = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def block_to_run
|
18
|
+
lambda do |host|
|
19
|
+
execute "echo #{Time.now}"
|
20
|
+
end
|
9
21
|
end
|
10
22
|
|
11
23
|
def test_connection_manager_handles_a_single_argument
|
@@ -26,40 +38,38 @@ module SSHKit
|
|
26
38
|
end
|
27
39
|
|
28
40
|
def test_the_connection_manager_yields_the_host_to_each_connection_instance
|
29
|
-
spy = lambda do |host
|
30
|
-
|
41
|
+
spy = lambda do |host|
|
42
|
+
execute "echo #{host.hostname}"
|
43
|
+
end
|
44
|
+
String.new.tap do |str|
|
45
|
+
SSHKit.capture_output str do
|
46
|
+
ConnectionManager.new(%w{1.example.com}).each &spy
|
47
|
+
end
|
48
|
+
assert_equal "echo 1.example.com", str.strip
|
31
49
|
end
|
32
|
-
ConnectionManager.new(%w{1.example.com}).each &spy
|
33
50
|
end
|
34
51
|
|
35
52
|
def test_the_connection_manaager_runs_things_in_parallel_by_default
|
36
|
-
|
37
|
-
|
38
|
-
results << Time.now
|
53
|
+
SSHKit.capture_output @s do
|
54
|
+
ConnectionManager.new(%w{1.example.com 2.example.com}).each &block_to_run
|
39
55
|
end
|
40
|
-
ConnectionManager.new(%w{1.example.com 2.example.com}).each &command
|
41
56
|
assert_equal 2, results.length
|
42
57
|
assert_equal *results.map(&:to_i)
|
43
58
|
end
|
44
59
|
|
45
60
|
def test_the_connection_manager_can_run_things_in_sequence
|
46
|
-
|
47
|
-
|
48
|
-
results << Time.now
|
61
|
+
SSHKit.capture_output @s do
|
62
|
+
ConnectionManager.new(%w{1.example.com 2.example.com}).each in: :sequence, &block_to_run
|
49
63
|
end
|
50
|
-
ConnectionManager.new(%w{1.example.com 2.example.com}).each(in: :sequence, &command)
|
51
64
|
assert_equal 2, results.length
|
52
65
|
assert_operator results.first.to_i, :<, results.last.to_i
|
53
66
|
end
|
54
67
|
|
55
68
|
def test_the_connection_manager_can_run_things_in_groups
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
results << Time.now
|
69
|
+
SSHKit.capture_output @s do
|
70
|
+
ConnectionManager.new(%w{1.example.com 2.example.com 3.example.com
|
71
|
+
4.example.com 5.example.com 6.example.com}).each in: :groups, &block_to_run
|
60
72
|
end
|
61
|
-
ConnectionManager.new(%w{1.example.com 2.example.com 3.example.com
|
62
|
-
4.example.com 5.example.com 6.example.com}).each(in: :groups, &command)
|
63
73
|
assert_equal 6, results.length
|
64
74
|
assert_equal *results[0..1].map(&:to_i)
|
65
75
|
assert_equal *results[2..3].map(&:to_i)
|
@@ -68,9 +78,12 @@ module SSHKit
|
|
68
78
|
assert_operator results[3].to_i, :<, results[4].to_i
|
69
79
|
end
|
70
80
|
|
71
|
-
|
72
|
-
|
73
|
-
|
81
|
+
private
|
82
|
+
|
83
|
+
def results
|
84
|
+
@s.lines.collect do |line|
|
85
|
+
Time.parse(line.split[1..-1].join(' '))
|
86
|
+
end
|
74
87
|
end
|
75
88
|
|
76
89
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sshkit
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2013-01-
|
13
|
+
date: 2013-01-17 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: net-ssh
|
@@ -221,20 +221,19 @@ extra_rdoc_files: []
|
|
221
221
|
files:
|
222
222
|
- .gitignore
|
223
223
|
- .travis.yml
|
224
|
-
- .yardoc/checksums
|
225
|
-
- .yardoc/object_types
|
226
|
-
- .yardoc/objects/root.dat
|
227
|
-
- .yardoc/proxy_types
|
228
224
|
- .yardopts
|
229
225
|
- CHANGELOG.md
|
226
|
+
- CONTRIBUTING.md
|
230
227
|
- EXAMPLES.md
|
231
228
|
- FAQ.md
|
232
229
|
- Gemfile
|
233
230
|
- Gemfile.lock
|
234
231
|
- LICENSE.md
|
235
232
|
- README.md
|
233
|
+
- RELEASING.md
|
236
234
|
- Rakefile
|
237
235
|
- Vagrantfile
|
236
|
+
- assets/images/example_output.png
|
238
237
|
- assets/images/logo.png
|
239
238
|
- example.rb
|
240
239
|
- lib/core_ext/array.rb
|
@@ -250,6 +249,7 @@ files:
|
|
250
249
|
- lib/sshkit/dsl.rb
|
251
250
|
- lib/sshkit/formatters/abstract.rb
|
252
251
|
- lib/sshkit/formatters/black_hole.rb
|
252
|
+
- lib/sshkit/formatters/dot.rb
|
253
253
|
- lib/sshkit/formatters/pretty.rb
|
254
254
|
- lib/sshkit/host.rb
|
255
255
|
- lib/sshkit/version.rb
|
data/.yardoc/checksums
DELETED
@@ -1,13 +0,0 @@
|
|
1
|
-
lib/deploy.rb 8f584561b611345114f38176ec53bf00f9d5550f
|
2
|
-
lib/deploy/all.rb 9fc0b15f0968612fbd2ffaf0bba9dff6f788e3d1
|
3
|
-
lib/deploy/host.rb 777a8deedcdd5b41dceab8773c992bafa5ee9f92
|
4
|
-
lib/core_ext/hash.rb b7a0f0d1ab3b83f6b251e2f865ad6fa3766124a0
|
5
|
-
lib/deploy/command.rb 1a04acc7d5abd1b288bbea3676c287434849ef05
|
6
|
-
lib/core_ext/array.rb 3d495a96a0d1566877bf2ebb70ab9ea10a7d32e1
|
7
|
-
lib/deploy/version.rb 30e41688e07f7ee74377aaef147250340df4a3f0
|
8
|
-
lib/deploy/configuration.rb 3e9f042e0e9e9860d3950a1406a988586434de16
|
9
|
-
lib/deploy/backends/netssh.rb c931441edd28ba7e134b0d3ae2fbf40efb347b81
|
10
|
-
lib/deploy/backends/printer.rb db41b51e9624105efd7cb7f1fef426b506ebadaa
|
11
|
-
lib/deploy/backends/abstract.rb 74260020d3a6c4913f7a3d9294968e1b2a1304a9
|
12
|
-
lib/deploy/connection_manager.rb 28c8ef12a8a5923aeaa497ed02fd22869f57186b
|
13
|
-
lib/deploy/dsl.rb a487b4e65ab52955b12a5fe55bda99dec61e16b9
|
data/.yardoc/object_types
DELETED
Binary file
|
data/.yardoc/objects/root.dat
DELETED
Binary file
|
data/.yardoc/proxy_types
DELETED
Binary file
|