rutty 2.1.3 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +3 -0
- data/Gemfile.lock +6 -0
- data/README.md +0 -3
- data/Rakefile +3 -0
- data/VERSION +1 -1
- data/bin/rutty +17 -11
- data/lib/rutty.rb +32 -4
- data/lib/rutty/actions.rb +120 -23
- data/lib/rutty/consts.rb +5 -0
- data/lib/rutty/errors.rb +7 -0
- data/lib/rutty/version.rb +2 -2
- data/rutty.gemspec +15 -2
- data/test/helper.rb +7 -1
- data/test/test_action_add_node.rb +6 -1
- data/test/test_action_dsh.rb +53 -0
- data/test/test_action_init.rb +14 -6
- data/test/test_action_list_nodes.rb +78 -0
- data/test/test_nodes.rb +2 -2
- metadata +63 -11
data/Gemfile
CHANGED
@@ -1,10 +1,13 @@
|
|
1
1
|
source :gemcutter
|
2
2
|
|
3
3
|
gem "commander", '~> 4.0.3'
|
4
|
+
gem "terminal-table", '~> 1.4.2'
|
4
5
|
gem "net-ssh", '~> 2.0.23'
|
5
6
|
gem "net-scp", '~> 1.0.4'
|
7
|
+
gem "builder", '~> 2.1.2'
|
6
8
|
|
7
9
|
group :development do
|
8
10
|
gem "jeweler", '~> 1.4.0'
|
9
11
|
gem "thoughtbot-shoulda"
|
12
|
+
gem "xml-simple", '~> 1.0.12'
|
10
13
|
end
|
data/Gemfile.lock
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
GEM
|
2
2
|
remote: http://rubygems.org/
|
3
3
|
specs:
|
4
|
+
builder (2.1.2)
|
4
5
|
commander (4.0.3)
|
5
6
|
highline (>= 1.5.0)
|
6
7
|
gemcutter (0.5.0)
|
@@ -17,14 +18,19 @@ GEM
|
|
17
18
|
net-ssh (2.0.23)
|
18
19
|
rubyforge (2.0.4)
|
19
20
|
json_pure (>= 1.1.7)
|
21
|
+
terminal-table (1.4.2)
|
20
22
|
thoughtbot-shoulda (2.11.1)
|
23
|
+
xml-simple (1.0.12)
|
21
24
|
|
22
25
|
PLATFORMS
|
23
26
|
ruby
|
24
27
|
|
25
28
|
DEPENDENCIES
|
29
|
+
builder (~> 2.1.2)
|
26
30
|
commander (~> 4.0.3)
|
27
31
|
jeweler (~> 1.4.0)
|
28
32
|
net-scp (~> 1.0.4)
|
29
33
|
net-ssh (~> 2.0.23)
|
34
|
+
terminal-table (~> 1.4.2)
|
30
35
|
thoughtbot-shoulda
|
36
|
+
xml-simple (~> 1.0.12)
|
data/README.md
CHANGED
@@ -80,10 +80,7 @@ TODO
|
|
80
80
|
|
81
81
|
* Cleanup dsh action code
|
82
82
|
* Expand the tag searching semantics to allow for AND and/or OR queries
|
83
|
-
* Add a special printout for > 0 exit codes on dsh action
|
84
83
|
* Implement delete_node command
|
85
|
-
* Make better printouts
|
86
|
-
* Documentation
|
87
84
|
|
88
85
|
Copyright
|
89
86
|
---------
|
data/Rakefile
CHANGED
@@ -17,9 +17,12 @@ begin
|
|
17
17
|
gem.add_development_dependency "bundler", ">= 1.0.0"
|
18
18
|
gem.add_development_dependency "jeweler", ">= 1.4.0"
|
19
19
|
gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
|
20
|
+
gem.add_development_dependency "xml-simple", ">= 1.0.12"
|
20
21
|
gem.add_dependency "commander", ">= 4.0.3"
|
22
|
+
gem.add_dependency "terminal-table", ">= 1.4.2"
|
21
23
|
gem.add_dependency "net-ssh", ">= 2.0.23"
|
22
24
|
gem.add_dependency "net-scp", ">= 1.0.4"
|
25
|
+
gem.add_dependency "builder", ">= 2.1.2"
|
23
26
|
end
|
24
27
|
Jeweler::GemcutterTasks.new
|
25
28
|
rescue LoadError
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.
|
1
|
+
2.2.0
|
data/bin/rutty
CHANGED
@@ -9,10 +9,16 @@ $r = Rutty::Runner.new
|
|
9
9
|
|
10
10
|
# Helpers
|
11
11
|
add_filter_options = lambda { |c|
|
12
|
-
c.option('--keypath PATH', String, 'Path to a private key')
|
13
|
-
c.option('--user NAME', String, 'User login')
|
14
|
-
c.option('--port NUMBER', Integer, 'SSH port number')
|
15
|
-
c.option('--tags TAG1[,TAG2,...]', Array, 'Comma-separated list of tags')
|
12
|
+
c.option('-k', '--keypath PATH', String, 'Path to a private key')
|
13
|
+
c.option('-u', '--user NAME', String, 'User login')
|
14
|
+
c.option('-p', '--port NUMBER', Integer, 'SSH port number')
|
15
|
+
c.option('-t', '--tags TAG1[,TAG2,...]', Array, 'Comma-separated list of tags')
|
16
|
+
}
|
17
|
+
|
18
|
+
add_remote_options = lambda { |c|
|
19
|
+
c.option('-t', '--tags TAG1[,TAG2,...]', Array, 'Comma-separated list of tags to run the command on')
|
20
|
+
c.option('-a', 'Run the command on ALL nodes')
|
21
|
+
c.option('-d', '--debug', 'Enable debug output')
|
16
22
|
}
|
17
23
|
|
18
24
|
# Commander config
|
@@ -23,10 +29,14 @@ program :help_formatter, Commander::HelpFormatter::TerminalCompact
|
|
23
29
|
|
24
30
|
default_command :dsh
|
25
31
|
|
26
|
-
global_option '-c', '--config DIR',
|
32
|
+
global_option '-c', '--config DIR', "Directory to load configs from, defaults to #{Rutty::Consts::CONF_DIR}" do |dir|
|
27
33
|
$r.config_dir = dir
|
28
34
|
end
|
29
35
|
|
36
|
+
global_option '-o', '--output FORMAT', "Format to output into, defaults to #{Rutty::Consts::DEFAULT_OUTPUT_FORMAT}" do |format|
|
37
|
+
$r.output_format = format
|
38
|
+
end
|
39
|
+
|
30
40
|
# Commander commands
|
31
41
|
command :init do |c|
|
32
42
|
c.syntax = "rutty init [DIR]"
|
@@ -79,9 +89,7 @@ command :dsh do |c|
|
|
79
89
|
c.example "Get a list of all users logged in to all your web and app nodes", "rutty --tags web,app w"
|
80
90
|
c.example "See all your nodes' current memory footprint", "rutty -a \"free -m\""
|
81
91
|
|
82
|
-
|
83
|
-
c.option('-a', 'Run the command on ALL nodes')
|
84
|
-
c.option('-d', '--debug', 'Enable debug output')
|
92
|
+
add_remote_options.call(c)
|
85
93
|
|
86
94
|
c.when_called do |args, options|
|
87
95
|
$r.dsh args, options
|
@@ -93,9 +101,7 @@ command :scp do |c|
|
|
93
101
|
c.summary = "Uploads a file to the nodes according to [options]."
|
94
102
|
c.description = "#{c.summary} Unlike the actual scp command, this action is one-way: upload only."
|
95
103
|
|
96
|
-
|
97
|
-
c.option('-a', 'Run the command on ALL nodes')
|
98
|
-
c.option('-d', '--debug', 'Enable debug output')
|
104
|
+
add_remote_options.call(c)
|
99
105
|
|
100
106
|
c.when_called do |args, options|
|
101
107
|
$r.dsh args, options
|
data/lib/rutty.rb
CHANGED
@@ -22,6 +22,9 @@ module Rutty
|
|
22
22
|
# @since 2.0.0
|
23
23
|
class Runner
|
24
24
|
attr_writer :config_dir
|
25
|
+
attr :config
|
26
|
+
attr :nodes
|
27
|
+
attr :output_format
|
25
28
|
|
26
29
|
include Rutty::Consts
|
27
30
|
include Rutty::Helpers
|
@@ -39,7 +42,7 @@ module Rutty
|
|
39
42
|
# Lazy-load the {Rutty::Config} object for this instance, based on the config_dir param
|
40
43
|
# passed to {#initialize}.
|
41
44
|
#
|
42
|
-
# @return [Rutty::Config]
|
45
|
+
# @return [Rutty::Config] The loaded and parsed config object
|
43
46
|
def config
|
44
47
|
@config ||= Rutty::Config.load_config self.config_dir
|
45
48
|
end
|
@@ -48,18 +51,43 @@ module Rutty
|
|
48
51
|
# Lazy-load the {Rutty::Nodes} object containing the user-defined nodes' connection info.
|
49
52
|
# Loads from the config_dir param passed to {#initialize}
|
50
53
|
#
|
51
|
-
# @return [Rutty::Nodes]
|
54
|
+
# @return [Rutty::Nodes] The Nodes loaded from the config
|
52
55
|
def nodes
|
53
56
|
@nodes ||= Rutty::Nodes.load_config self.config_dir
|
54
57
|
end
|
55
58
|
|
56
59
|
##
|
57
|
-
#
|
60
|
+
# The user-specified config directory, falling back to the default on nil.
|
61
|
+
# Used by {Rutty::Nodes.load_config} and {Rutty::Config.load_config} to determine
|
62
|
+
# where to look for config files.
|
58
63
|
#
|
59
64
|
# @see Rutty::Consts::CONF_DIR
|
60
|
-
# @return [String] The user-specified config directory, falling back to the default on nil
|
65
|
+
# @return [String] The user-specified config directory, falling back to the default on nil.
|
61
66
|
def config_dir
|
62
67
|
(@config_dir.nil? && Rutty::Consts::CONF_DIR) || @config_dir
|
63
68
|
end
|
69
|
+
|
70
|
+
##
|
71
|
+
# The tool's output format, passed by the user via the rutty bin's flag.
|
72
|
+
# Must be one of the elements of {Rutty::Consts::OUTPUT_FORMATS}.
|
73
|
+
# Defaults to {Rutty::Consts::DEFAULT_OUTPUT_FORMAT}
|
74
|
+
#
|
75
|
+
# @see Rutty::Consts::OUTPUT_FORMATS
|
76
|
+
# @see Rutty::Consts::DEFAULT_OUTPUT_FORMAT
|
77
|
+
# @return [String] The configured output format
|
78
|
+
def output_format
|
79
|
+
(@output_format.nil? && Rutty::Consts::DEFAULT_OUTPUT_FORMAT) || @output_format
|
80
|
+
end
|
81
|
+
|
82
|
+
##
|
83
|
+
# Sets @output_format, or raises an exception if the value is not included in {Rutty::Consts::OUTPUT_FORMATS}.
|
84
|
+
#
|
85
|
+
# @see Rutty::Consts::OUTPUT_FORMATS
|
86
|
+
# @raise [Rutty::InvalidOutputFormat] On a disallowed output format string
|
87
|
+
def output_format= format
|
88
|
+
raise Rutty::InvalidOutputFormat.new "Invalid output format: #{format}" unless Rutty::Consts::OUTPUT_FORMATS.include? format
|
89
|
+
|
90
|
+
@output_format = format
|
91
|
+
end
|
64
92
|
end
|
65
93
|
end
|
data/lib/rutty/actions.rb
CHANGED
@@ -22,16 +22,16 @@ module Rutty
|
|
22
22
|
nodes_file = File.join(dir, Rutty::Consts::NODES_CONF_FILE)
|
23
23
|
|
24
24
|
if File.exists? dir
|
25
|
-
log "exists", dir
|
25
|
+
log "\t<%= color('exists', :cyan) %>", dir
|
26
26
|
else
|
27
|
-
log "create", dir
|
27
|
+
log "\t<%= color('create', :green) %>", dir
|
28
28
|
Dir.mkdir dir
|
29
29
|
end
|
30
30
|
|
31
31
|
if File.exists? general_file
|
32
|
-
log "exists", general_file
|
32
|
+
log "\t<%= color('exists', :cyan) %>", general_file
|
33
33
|
else
|
34
|
-
log "create", general_file
|
34
|
+
log "\t<%= color('create', :green) %>", general_file
|
35
35
|
|
36
36
|
defaults_hash = {
|
37
37
|
:user => 'root',
|
@@ -45,9 +45,9 @@ module Rutty
|
|
45
45
|
end
|
46
46
|
|
47
47
|
if File.exists? nodes_file
|
48
|
-
log "exists", nodes_file
|
48
|
+
log "\t<%= color('exists', :cyan) %>", nodes_file
|
49
49
|
else
|
50
|
-
log "create", nodes_file
|
50
|
+
log "\t<%= color('create', :green) %>", nodes_file
|
51
51
|
|
52
52
|
File.open(nodes_file, 'w') do |f|
|
53
53
|
YAML.dump([], f)
|
@@ -73,6 +73,8 @@ module Rutty
|
|
73
73
|
|
74
74
|
self.nodes << Rutty::Node.new(hash, self.config.to_hash)
|
75
75
|
self.nodes.write_config self.config_dir
|
76
|
+
|
77
|
+
say "<%= color('Added #{hash[:host]}', :green) %>"
|
76
78
|
end
|
77
79
|
|
78
80
|
##
|
@@ -81,9 +83,51 @@ module Rutty
|
|
81
83
|
# @see (see #add_node)
|
82
84
|
# @param (see #add_node)
|
83
85
|
def list_nodes args, options
|
84
|
-
|
85
|
-
|
86
|
-
|
86
|
+
check_installed!
|
87
|
+
|
88
|
+
if self.nodes.empty?
|
89
|
+
say "<%= color('No nodes defined', :yellow) %>"
|
90
|
+
else
|
91
|
+
output = case self.output_format
|
92
|
+
when 'human-readable'
|
93
|
+
require 'terminal-table/import'
|
94
|
+
|
95
|
+
table do |t|
|
96
|
+
t.headings = 'Host', 'Key', 'User', 'Port', 'Tags'
|
97
|
+
self.nodes.each do |node|
|
98
|
+
tags = node.tags.nil? ? [] : node.tags
|
99
|
+
t << [node.host, node.keypath, node.user, node.port, tags.join(', ')]
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
when 'json'
|
104
|
+
require 'json'
|
105
|
+
JSON.dump self.nodes.collect { |node| node.to_hash }
|
106
|
+
|
107
|
+
when 'xml'
|
108
|
+
require 'builder'
|
109
|
+
xml = Builder::XmlMarkup.new(:indent => 2)
|
110
|
+
|
111
|
+
xml.instruct!
|
112
|
+
xml.nodes do
|
113
|
+
self.nodes.each do |node|
|
114
|
+
xml.node do
|
115
|
+
xml.host node.host
|
116
|
+
xml.user node.user
|
117
|
+
xml.port node.port
|
118
|
+
xml.keypath node.keypath
|
119
|
+
xml.tags do
|
120
|
+
node.tags.each do |tag|
|
121
|
+
xml.tag tag
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
puts output
|
130
|
+
end
|
87
131
|
end
|
88
132
|
|
89
133
|
##
|
@@ -93,19 +137,23 @@ module Rutty
|
|
93
137
|
# @see (see #add_node)
|
94
138
|
# @param (see #add_node)
|
95
139
|
def dsh args, options
|
96
|
-
# TODO: Clean this up, it's pretty hard to read and follow
|
97
|
-
|
98
140
|
check_installed!
|
99
141
|
raise Rutty::BadUsage.new "Must supply a command to run. See `rutty help dsh' for usage" if args.empty?
|
100
142
|
raise Rutty::BadUsage.new "One of -a or --tags must be passed" if options.a.nil? and options.tags.nil?
|
101
143
|
raise Rutty::BadUsage.new "Use either -a or --tags, not both" if !options.a.nil? and !options.tags.nil?
|
102
144
|
raise Rutty::BadUsage.new "Multi-word commands must be enclosed in quotes (ex. rutty -a \"ps -ef | grep httpd\")" if args.length > 1
|
103
145
|
|
146
|
+
if self.nodes.empty?
|
147
|
+
say "<%= color('No nodes defined', :yellow) %>"
|
148
|
+
exit
|
149
|
+
end
|
150
|
+
|
151
|
+
HighLine.color_scheme = HighLine::SampleColorScheme.new
|
152
|
+
|
104
153
|
com_str = args.pop
|
105
154
|
|
106
155
|
require 'logger'
|
107
156
|
require 'net/ssh'
|
108
|
-
require 'pp'
|
109
157
|
|
110
158
|
@returns = {}
|
111
159
|
connections = []
|
@@ -142,18 +190,21 @@ module Rutty
|
|
142
190
|
}
|
143
191
|
|
144
192
|
self.nodes.filter(options).each do |node|
|
145
|
-
@returns[node.host] = { :out => '' }
|
193
|
+
@returns[node.host] = { :exit => 0, :out => '' }
|
146
194
|
begin
|
147
195
|
connections << Net::SSH.start(node.host, node.user, :port => node.port, :paranoid => false,
|
148
|
-
:user_known_hosts_file => '/dev/null', :keys => [node.keypath],
|
196
|
+
:user_known_hosts_file => '/dev/null', :keys => [node.keypath], :timeout => 5,
|
149
197
|
:logger => Logger.new(options.debug.nil? ? $stderr : $stdout),
|
150
198
|
:verbose => (options.debug.nil? ? Logger::FATAL : Logger::DEBUG))
|
151
199
|
rescue Errno::ECONNREFUSED
|
152
|
-
|
153
|
-
@returns
|
200
|
+
@returns[node.host][:out] = "ERROR: Connection refused"
|
201
|
+
@returns[node.host][:exit] = 10000
|
154
202
|
rescue SocketError
|
155
|
-
|
156
|
-
@returns
|
203
|
+
@returns[node.host][:out] = "ERROR: no nodename nor servname provided, or not known"
|
204
|
+
@returns[node.host][:exit] = 20000
|
205
|
+
rescue Timeout::Error
|
206
|
+
@returns[node.host][:out] = "ERROR: Connection timeout"
|
207
|
+
@returns[node.host][:exit] = 30000
|
157
208
|
end
|
158
209
|
end
|
159
210
|
|
@@ -163,11 +214,57 @@ module Rutty
|
|
163
214
|
connections.delete_if { |ssh| !ssh.process(0.1) { |s| s.busy? } }
|
164
215
|
break if connections.empty?
|
165
216
|
end
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
217
|
+
|
218
|
+
output = case self.output_format
|
219
|
+
when 'human-readable'
|
220
|
+
min_width = 0
|
221
|
+
@returns.each do |host, hash|
|
222
|
+
min_width = host.length if host.length > min_width
|
223
|
+
end
|
224
|
+
|
225
|
+
buffer = ''
|
226
|
+
@returns.each do |host, hash|
|
227
|
+
padded_host = host.dup
|
228
|
+
|
229
|
+
if hash[:exit] >= 10000
|
230
|
+
padded_host = "<%= color('#{padded_host}', :critical) %>"
|
231
|
+
hash[:out] = "<%= color('#{hash[:out]}', :red) %>"
|
232
|
+
elsif hash[:exit] > 0
|
233
|
+
padded_host = "<%= color('#{padded_host}', :error) %>"
|
234
|
+
else
|
235
|
+
padded_host = "<%= color('#{padded_host}', :green) %>"
|
236
|
+
end
|
237
|
+
|
238
|
+
padded_host << (" " * (min_width - host.length)) if host.length < min_width
|
239
|
+
buffer << padded_host << "\t\t"
|
240
|
+
|
241
|
+
buffer << hash[:out].lstrip
|
242
|
+
end
|
243
|
+
|
244
|
+
buffer
|
245
|
+
|
246
|
+
when 'json'
|
247
|
+
require 'json'
|
248
|
+
JSON.dump @returns
|
249
|
+
|
250
|
+
when 'xml'
|
251
|
+
require 'builder'
|
252
|
+
|
253
|
+
xml = Builder::XmlMarkup.new(:indent => 2)
|
254
|
+
|
255
|
+
xml.instruct!
|
256
|
+
xml.nodes do
|
257
|
+
@returns.each do |host, hash|
|
258
|
+
xml.node do
|
259
|
+
xml.host host
|
260
|
+
xml.exit hash[:exit]
|
261
|
+
xml.out hash[:out].strip
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
say output
|
171
268
|
end
|
172
269
|
|
173
270
|
##
|
data/lib/rutty/consts.rb
CHANGED
@@ -5,6 +5,11 @@ module Rutty
|
|
5
5
|
# @author Josh Lindsey
|
6
6
|
# @since 2.0.0
|
7
7
|
module Consts
|
8
|
+
## List of possible output formats
|
9
|
+
OUTPUT_FORMATS = %w(human-readable json xml)
|
10
|
+
## Default output format
|
11
|
+
DEFAULT_OUTPUT_FORMAT = 'human-readable'
|
12
|
+
|
8
13
|
## Name of the general (defaults) config file
|
9
14
|
GENERAL_CONF_FILE = 'defaults.yaml'
|
10
15
|
## Name of the datastore file for user-defined nodes
|
data/lib/rutty/errors.rb
CHANGED
@@ -12,4 +12,11 @@ module Rutty
|
|
12
12
|
# @author Josh Lindsey
|
13
13
|
# @since 2.0.0
|
14
14
|
class BadUsage < StandardError; end
|
15
|
+
|
16
|
+
##
|
17
|
+
# Raised by {Rutty::Runner} if a bad output format is passed.
|
18
|
+
#
|
19
|
+
# @author Josh Lindsey
|
20
|
+
# @since 2.1.4
|
21
|
+
class InvalidOutputFormat < StandardError; end
|
15
22
|
end
|
data/lib/rutty/version.rb
CHANGED
data/rutty.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{rutty}
|
8
|
-
s.version = "2.
|
8
|
+
s.version = "2.2.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Josh Lindsey"]
|
12
|
-
s.date = %q{2010-11-
|
12
|
+
s.date = %q{2010-11-13}
|
13
13
|
s.default_executable = %q{rutty}
|
14
14
|
s.description = %q{
|
15
15
|
RuTTY is a DSH (distributed / dancer's shell) written in Ruby. It's used to run commands
|
@@ -43,7 +43,9 @@ Gem::Specification.new do |s|
|
|
43
43
|
"rutty.gemspec",
|
44
44
|
"test/helper.rb",
|
45
45
|
"test/test_action_add_node.rb",
|
46
|
+
"test/test_action_dsh.rb",
|
46
47
|
"test/test_action_init.rb",
|
48
|
+
"test/test_action_list_nodes.rb",
|
47
49
|
"test/test_actions.rb",
|
48
50
|
"test/test_config.rb",
|
49
51
|
"test/test_consts.rb",
|
@@ -60,7 +62,9 @@ Gem::Specification.new do |s|
|
|
60
62
|
s.test_files = [
|
61
63
|
"test/helper.rb",
|
62
64
|
"test/test_action_add_node.rb",
|
65
|
+
"test/test_action_dsh.rb",
|
63
66
|
"test/test_action_init.rb",
|
67
|
+
"test/test_action_list_nodes.rb",
|
64
68
|
"test/test_actions.rb",
|
65
69
|
"test/test_config.rb",
|
66
70
|
"test/test_consts.rb",
|
@@ -78,24 +82,33 @@ Gem::Specification.new do |s|
|
|
78
82
|
s.add_development_dependency(%q<bundler>, [">= 1.0.0"])
|
79
83
|
s.add_development_dependency(%q<jeweler>, [">= 1.4.0"])
|
80
84
|
s.add_development_dependency(%q<thoughtbot-shoulda>, [">= 0"])
|
85
|
+
s.add_development_dependency(%q<xml-simple>, [">= 1.0.12"])
|
81
86
|
s.add_runtime_dependency(%q<commander>, [">= 4.0.3"])
|
87
|
+
s.add_runtime_dependency(%q<terminal-table>, [">= 1.4.2"])
|
82
88
|
s.add_runtime_dependency(%q<net-ssh>, [">= 2.0.23"])
|
83
89
|
s.add_runtime_dependency(%q<net-scp>, [">= 1.0.4"])
|
90
|
+
s.add_runtime_dependency(%q<builder>, [">= 2.1.2"])
|
84
91
|
else
|
85
92
|
s.add_dependency(%q<bundler>, [">= 1.0.0"])
|
86
93
|
s.add_dependency(%q<jeweler>, [">= 1.4.0"])
|
87
94
|
s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
|
95
|
+
s.add_dependency(%q<xml-simple>, [">= 1.0.12"])
|
88
96
|
s.add_dependency(%q<commander>, [">= 4.0.3"])
|
97
|
+
s.add_dependency(%q<terminal-table>, [">= 1.4.2"])
|
89
98
|
s.add_dependency(%q<net-ssh>, [">= 2.0.23"])
|
90
99
|
s.add_dependency(%q<net-scp>, [">= 1.0.4"])
|
100
|
+
s.add_dependency(%q<builder>, [">= 2.1.2"])
|
91
101
|
end
|
92
102
|
else
|
93
103
|
s.add_dependency(%q<bundler>, [">= 1.0.0"])
|
94
104
|
s.add_dependency(%q<jeweler>, [">= 1.4.0"])
|
95
105
|
s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
|
106
|
+
s.add_dependency(%q<xml-simple>, [">= 1.0.12"])
|
96
107
|
s.add_dependency(%q<commander>, [">= 4.0.3"])
|
108
|
+
s.add_dependency(%q<terminal-table>, [">= 1.4.2"])
|
97
109
|
s.add_dependency(%q<net-ssh>, [">= 2.0.23"])
|
98
110
|
s.add_dependency(%q<net-scp>, [">= 1.0.4"])
|
111
|
+
s.add_dependency(%q<builder>, [">= 2.1.2"])
|
99
112
|
end
|
100
113
|
end
|
101
114
|
|
data/test/helper.rb
CHANGED
@@ -24,7 +24,13 @@ class Test::Unit::TestCase
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def seed_nodes
|
27
|
-
%x(#{RUTTY_BIN} add_node localhost -c #{TEST_CONF_DIR} -k ~/.ssh/id_rsa
|
27
|
+
out = %x(#{RUTTY_BIN} add_node localhost -c #{TEST_CONF_DIR} -k ~/.ssh/id_rsa -u #{ENV['USER']} --tags localhost,test -p 22)
|
28
|
+
assert_match /Added localhost/, out
|
29
|
+
end
|
30
|
+
|
31
|
+
def seed_bad_node
|
32
|
+
out = %x(#{RUTTY_BIN} add_node example.com -c #{TEST_CONF_DIR} --tags example,test,broken)
|
33
|
+
assert_match /Added example\.com/, out
|
28
34
|
end
|
29
35
|
|
30
36
|
def ensure_fresh_config!
|
@@ -8,7 +8,12 @@ class TestActionAddNode < Test::Unit::TestCase
|
|
8
8
|
should "properly create a new node entry when called" do
|
9
9
|
require 'yaml'
|
10
10
|
|
11
|
-
%x(#{RUTTY_BIN} add_node -c #{TEST_CONF_DIR} example.com -k /home/user/.ssh/id_rsa -u root -p 22333 --tags example,testing)
|
11
|
+
output = %x(#{RUTTY_BIN} add_node -c #{TEST_CONF_DIR} example.com -k /home/user/.ssh/id_rsa -u root -p 22333 --tags example,testing)
|
12
|
+
|
13
|
+
green = '\\e\[32m'
|
14
|
+
clear = '\\e\[0m'
|
15
|
+
|
16
|
+
assert_match /#{green}Added example\.com#{clear}/, output
|
12
17
|
|
13
18
|
nodes = YAML.load(File.open(TEST_NODES_CONF).read)
|
14
19
|
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestActionDSH < Test::Unit::TestCase
|
4
|
+
context "The `rutty dsh' action" do
|
5
|
+
setup { ensure_fresh_config! }
|
6
|
+
teardown { clean_test_config! }
|
7
|
+
|
8
|
+
should "report no defined nodes when no nodes defined" do
|
9
|
+
output = %x(#{RUTTY_BIN} -c #{TEST_CONF_DIR} -a uptime)
|
10
|
+
|
11
|
+
yellow = '\\e\[33m'
|
12
|
+
clear = '\\e\[0m'
|
13
|
+
|
14
|
+
assert_match /^#{yellow}No nodes defined#{clear}$/, output.rstrip
|
15
|
+
end
|
16
|
+
|
17
|
+
should "display a critical error state when unable to connect" do
|
18
|
+
seed_bad_node
|
19
|
+
|
20
|
+
output = %x(#{RUTTY_BIN} -c #{TEST_CONF_DIR} -a uptime)
|
21
|
+
|
22
|
+
red_bg = '\\e\[41m'
|
23
|
+
yellow = '\\e\[33m'
|
24
|
+
red = '\\e\[31m'
|
25
|
+
clear = '\\e\[0m'
|
26
|
+
|
27
|
+
assert_match /^#{yellow}#{red_bg}example\.com#{clear}\s+#{red}ERROR: Connection timeout#{clear}$/, output.rstrip
|
28
|
+
end
|
29
|
+
|
30
|
+
should "display a general error state when an exite code > 0 is returned" do
|
31
|
+
seed_nodes
|
32
|
+
|
33
|
+
output = %x(#{RUTTY_BIN} -c #{TEST_CONF_DIR} -a foobar)
|
34
|
+
|
35
|
+
bold = '\\e\[1m'
|
36
|
+
red = '\\e\[31m'
|
37
|
+
clear = '\\e\[0m'
|
38
|
+
|
39
|
+
assert_match /^#{bold}#{red}localhost#{clear}\s+.*command not found$/, output.rstrip
|
40
|
+
end
|
41
|
+
|
42
|
+
should "display the proper output on success" do
|
43
|
+
seed_nodes
|
44
|
+
|
45
|
+
output = %x(#{RUTTY_BIN} -c #{TEST_CONF_DIR} -a whoami)
|
46
|
+
|
47
|
+
green = '\\e\[32m'
|
48
|
+
clear = '\\e\[0m'
|
49
|
+
|
50
|
+
assert_match /^#{green}localhost#{clear}\s+#{ENV['USER']}$/, output.rstrip
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/test/test_action_init.rb
CHANGED
@@ -18,17 +18,25 @@ class TestActionInit < Test::Unit::TestCase
|
|
18
18
|
|
19
19
|
out = %x(#{RUTTY_BIN} init #{TEST_CONF_DIR})
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
21
|
+
cyan = '\\e\[36m'
|
22
|
+
clear = '\\e\[0m'
|
23
|
+
exists = "#{cyan}exists#{clear}"
|
24
|
+
|
25
|
+
assert_match /^\s+#{exists}\s+#{TEST_CONF_DIR}$/o, out
|
26
|
+
assert_match /^\s+#{exists}\s+#{TEST_GENERAL_CONF}$/o, out
|
27
|
+
assert_match /^\s+#{exists}\s+#{TEST_NODES_CONF}$/o, out
|
24
28
|
end
|
25
29
|
|
26
30
|
should "report that it has created the file structure if it doesn't exist" do
|
27
31
|
out = %x(#{RUTTY_BIN} init #{TEST_CONF_DIR})
|
28
32
|
|
29
|
-
|
30
|
-
|
31
|
-
|
33
|
+
green = '\\e\[32m'
|
34
|
+
clear = '\\e\[0m'
|
35
|
+
create = "#{green}create#{clear}"
|
36
|
+
|
37
|
+
assert_match /^\s+#{create}\s+#{TEST_CONF_DIR}$/o, out
|
38
|
+
assert_match /^\s+#{create}\s+#{TEST_GENERAL_CONF}$/o, out
|
39
|
+
assert_match /^\s+#{create}\s+#{TEST_NODES_CONF}$/o, out
|
32
40
|
end
|
33
41
|
end
|
34
42
|
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestActionListNodes < Test::Unit::TestCase
|
4
|
+
context "The `rutty list_nodes' action" do
|
5
|
+
setup { ensure_fresh_config! }
|
6
|
+
teardown { clean_test_config! }
|
7
|
+
|
8
|
+
should "report no defined nodes when no nodes are defined" do
|
9
|
+
output = %x(#{RUTTY_BIN} list_nodes -c #{TEST_CONF_DIR} 2>&1)
|
10
|
+
|
11
|
+
yellow = '\\e\[33m'
|
12
|
+
clear = '\\e\[0m'
|
13
|
+
|
14
|
+
assert_match /#{yellow}No nodes defined#{clear}/, output
|
15
|
+
end
|
16
|
+
|
17
|
+
should "properly list defined nodes in ASCII table format" do
|
18
|
+
3.times { seed_nodes }
|
19
|
+
output = %x(#{RUTTY_BIN} list_nodes -c #{TEST_CONF_DIR} 2>&1)
|
20
|
+
output = output.split("\n")
|
21
|
+
|
22
|
+
separator = /^(?:\+{1}\-+)+\+$/
|
23
|
+
|
24
|
+
assert_match separator, output.shift
|
25
|
+
assert_match /^\|\sHost\s+\|\sKey\s+\|\sUser\s+\|\sPort\s+\|\sTags\s+\|$/, output.shift
|
26
|
+
assert_match separator, output.shift
|
27
|
+
|
28
|
+
3.times do
|
29
|
+
assert_match /^\|\slocalhost\s+\|\s#{ENV['HOME']}\/\.ssh\/id_rsa\s+\|\s#{ENV['USER']}\s+\|\s22\s+\|\slocalhost,\stest\s+\|$/o, output.shift
|
30
|
+
end
|
31
|
+
|
32
|
+
assert_match separator, output.shift
|
33
|
+
end
|
34
|
+
|
35
|
+
should "properly list defined nodes in JSON format" do
|
36
|
+
3.times { seed_nodes }
|
37
|
+
output = %x(#{RUTTY_BIN} list_nodes -o json -c #{TEST_CONF_DIR} 2>&1)
|
38
|
+
|
39
|
+
require 'json'
|
40
|
+
require 'yaml'
|
41
|
+
|
42
|
+
json_nodes = ''
|
43
|
+
assert_nothing_raised { json_nodes = JSON.parse(output) }
|
44
|
+
loaded_nodes = YAML.load(File.read(TEST_NODES_CONF))
|
45
|
+
|
46
|
+
assert_equal loaded_nodes.length, json_nodes.length
|
47
|
+
|
48
|
+
(0..(loaded_nodes.length - 1)).to_a.each do |i|
|
49
|
+
assert_equal loaded_nodes[i]['host'], json_nodes[i]['host']
|
50
|
+
assert_equal loaded_nodes[i]['keypath'], json_nodes[i]['keypath']
|
51
|
+
assert_equal loaded_nodes[i]['user'], json_nodes[i]['user']
|
52
|
+
assert_equal loaded_nodes[i]['port'], json_nodes[i]['port']
|
53
|
+
assert_equal loaded_nodes[i]['tags'], json_nodes[i]['tags']
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
should "properly list defined nodes in XML format" do
|
58
|
+
3.times { seed_nodes }
|
59
|
+
output = %x(#{RUTTY_BIN} list_nodes -o xml -c #{TEST_CONF_DIR} 2>&1)
|
60
|
+
|
61
|
+
require 'xmlsimple'
|
62
|
+
|
63
|
+
xml_nodes = ''
|
64
|
+
assert_nothing_raised { xml_nodes = XmlSimple.xml_in(output, { 'ForceArray' => false })['node'] }
|
65
|
+
loaded_nodes = YAML.load(File.read(TEST_NODES_CONF))
|
66
|
+
|
67
|
+
assert_equal loaded_nodes.length, xml_nodes.length
|
68
|
+
|
69
|
+
(0..(loaded_nodes.length - 1)).to_a.each do |i|
|
70
|
+
assert_equal loaded_nodes[i]['host'], xml_nodes[i]['host']
|
71
|
+
assert_equal loaded_nodes[i]['keypath'], xml_nodes[i]['keypath']
|
72
|
+
assert_equal loaded_nodes[i]['user'], xml_nodes[i]['user']
|
73
|
+
assert_equal loaded_nodes[i]['port'], xml_nodes[i]['port'].to_i
|
74
|
+
assert_equal loaded_nodes[i]['tags'], xml_nodes[i]['tags']['tag']
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/test/test_nodes.rb
CHANGED
@@ -20,9 +20,9 @@ class TestNodes < Test::Unit::TestCase
|
|
20
20
|
|
21
21
|
node = @nodes.pop
|
22
22
|
|
23
|
-
assert_equal '
|
23
|
+
assert_equal ENV['USER'], node.user
|
24
24
|
assert_equal 22, node.port
|
25
|
-
assert_equal "#{ENV['HOME']}/.ssh/id_rsa
|
25
|
+
assert_equal "#{ENV['HOME']}/.ssh/id_rsa", node.keypath
|
26
26
|
assert_equal %w(localhost test), node.tags
|
27
27
|
end
|
28
28
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rutty
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 7
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 2
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 2.
|
8
|
+
- 2
|
9
|
+
- 0
|
10
|
+
version: 2.2.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Josh Lindsey
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-11-
|
18
|
+
date: 2010-11-13 00:00:00 -05:00
|
19
19
|
default_executable: rutty
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -65,9 +65,25 @@ dependencies:
|
|
65
65
|
type: :development
|
66
66
|
version_requirements: *id003
|
67
67
|
- !ruby/object:Gem::Dependency
|
68
|
-
name:
|
68
|
+
name: xml-simple
|
69
69
|
prerelease: false
|
70
70
|
requirement: &id004 !ruby/object:Gem::Requirement
|
71
|
+
none: false
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
hash: 15
|
76
|
+
segments:
|
77
|
+
- 1
|
78
|
+
- 0
|
79
|
+
- 12
|
80
|
+
version: 1.0.12
|
81
|
+
type: :development
|
82
|
+
version_requirements: *id004
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: commander
|
85
|
+
prerelease: false
|
86
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
71
87
|
none: false
|
72
88
|
requirements:
|
73
89
|
- - ">="
|
@@ -79,11 +95,27 @@ dependencies:
|
|
79
95
|
- 3
|
80
96
|
version: 4.0.3
|
81
97
|
type: :runtime
|
82
|
-
version_requirements: *
|
98
|
+
version_requirements: *id005
|
99
|
+
- !ruby/object:Gem::Dependency
|
100
|
+
name: terminal-table
|
101
|
+
prerelease: false
|
102
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
103
|
+
none: false
|
104
|
+
requirements:
|
105
|
+
- - ">="
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
hash: 3
|
108
|
+
segments:
|
109
|
+
- 1
|
110
|
+
- 4
|
111
|
+
- 2
|
112
|
+
version: 1.4.2
|
113
|
+
type: :runtime
|
114
|
+
version_requirements: *id006
|
83
115
|
- !ruby/object:Gem::Dependency
|
84
116
|
name: net-ssh
|
85
117
|
prerelease: false
|
86
|
-
requirement: &
|
118
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
87
119
|
none: false
|
88
120
|
requirements:
|
89
121
|
- - ">="
|
@@ -95,11 +127,11 @@ dependencies:
|
|
95
127
|
- 23
|
96
128
|
version: 2.0.23
|
97
129
|
type: :runtime
|
98
|
-
version_requirements: *
|
130
|
+
version_requirements: *id007
|
99
131
|
- !ruby/object:Gem::Dependency
|
100
132
|
name: net-scp
|
101
133
|
prerelease: false
|
102
|
-
requirement: &
|
134
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
103
135
|
none: false
|
104
136
|
requirements:
|
105
137
|
- - ">="
|
@@ -111,7 +143,23 @@ dependencies:
|
|
111
143
|
- 4
|
112
144
|
version: 1.0.4
|
113
145
|
type: :runtime
|
114
|
-
version_requirements: *
|
146
|
+
version_requirements: *id008
|
147
|
+
- !ruby/object:Gem::Dependency
|
148
|
+
name: builder
|
149
|
+
prerelease: false
|
150
|
+
requirement: &id009 !ruby/object:Gem::Requirement
|
151
|
+
none: false
|
152
|
+
requirements:
|
153
|
+
- - ">="
|
154
|
+
- !ruby/object:Gem::Version
|
155
|
+
hash: 15
|
156
|
+
segments:
|
157
|
+
- 2
|
158
|
+
- 1
|
159
|
+
- 2
|
160
|
+
version: 2.1.2
|
161
|
+
type: :runtime
|
162
|
+
version_requirements: *id009
|
115
163
|
description: "\n RuTTY is a DSH (distributed / dancer's shell) written in Ruby. It's used to run commands \n on multiple remote servers at once, based on a tagging system. Also allows for multiple\n SCP-style uploads.\n "
|
116
164
|
email: josh@cloudspace.com
|
117
165
|
executables:
|
@@ -142,7 +190,9 @@ files:
|
|
142
190
|
- rutty.gemspec
|
143
191
|
- test/helper.rb
|
144
192
|
- test/test_action_add_node.rb
|
193
|
+
- test/test_action_dsh.rb
|
145
194
|
- test/test_action_init.rb
|
195
|
+
- test/test_action_list_nodes.rb
|
146
196
|
- test/test_actions.rb
|
147
197
|
- test/test_config.rb
|
148
198
|
- test/test_consts.rb
|
@@ -187,7 +237,9 @@ summary: A DSH implementation in Ruby
|
|
187
237
|
test_files:
|
188
238
|
- test/helper.rb
|
189
239
|
- test/test_action_add_node.rb
|
240
|
+
- test/test_action_dsh.rb
|
190
241
|
- test/test_action_init.rb
|
242
|
+
- test/test_action_list_nodes.rb
|
191
243
|
- test/test_actions.rb
|
192
244
|
- test/test_config.rb
|
193
245
|
- test/test_consts.rb
|