rutty 2.1.3 → 2.2.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.
- 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
|