sshkit 0.0.2 → 0.0.3
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.
- 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
|
-

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

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