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 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.3
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', 'Directory to load configs from, defaults to ~/.rutty' do |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
- c.option('--tags TAG1[,TAG2,...]', Array, 'Comma-separated list of tags to run the command on')
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
- c.option('--tags TAG1[,TAG2,...]', Array, 'Comma-separated list of tags to run the command on')
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
- # If @config_dir is nil, returns {Rutty::Consts::CONF_DIR}. Otherwise return @config_dir.
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
- require 'pp'
85
-
86
- pp self.nodes.filter(options)
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
- $stderr.puts "ERROR: Connection refused on #{node.host}"
153
- @returns.delete node.host
200
+ @returns[node.host][:out] = "ERROR: Connection refused"
201
+ @returns[node.host][:exit] = 10000
154
202
  rescue SocketError
155
- $stderr.puts "ERROR: nodename nor servname provided, or not known for #{node[:host]}"
156
- @returns.delete node.host
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
- # TODO: Print this out in a better way
168
- # TODO: Print a special alert for exit codes > 0
169
-
170
- pp @returns
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
@@ -8,8 +8,8 @@ module Rutty
8
8
  # @see http://semver.org
9
9
  module Version
10
10
  MAJOR = 2
11
- MINOR = 1
12
- PATCH = 3
11
+ MINOR = 2
12
+ PATCH = 0
13
13
  BUILD = nil
14
14
 
15
15
  STRING = [MAJOR, MINOR, PATCH, BUILD].compact.join('.')
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.1.3"
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}
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.pem -u root --tags localhost,test -p 22)
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
@@ -18,17 +18,25 @@ class TestActionInit < Test::Unit::TestCase
18
18
 
19
19
  out = %x(#{RUTTY_BIN} init #{TEST_CONF_DIR})
20
20
 
21
- assert_match /^\s+exists\s+#{TEST_CONF_DIR}$/, out
22
- assert_match /^\s+exists\s+#{TEST_GENERAL_CONF}$/, out
23
- assert_match /^\s+exists\s+#{TEST_NODES_CONF}$/, out
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
- assert_match /^\s+create\s+#{TEST_CONF_DIR}$/, out
30
- assert_match /^\s+create\s+#{TEST_GENERAL_CONF}$/, out
31
- assert_match /^\s+create\s+#{TEST_NODES_CONF}$/, out
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 'root', node.user
23
+ assert_equal ENV['USER'], node.user
24
24
  assert_equal 22, node.port
25
- assert_equal "#{ENV['HOME']}/.ssh/id_rsa.pem", node.keypath
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: 13
4
+ hash: 7
5
5
  prerelease: false
6
6
  segments:
7
7
  - 2
8
- - 1
9
- - 3
10
- version: 2.1.3
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-12 00:00:00 -05:00
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: commander
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: *id004
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: &id005 !ruby/object:Gem::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: *id005
130
+ version_requirements: *id007
99
131
  - !ruby/object:Gem::Dependency
100
132
  name: net-scp
101
133
  prerelease: false
102
- requirement: &id006 !ruby/object:Gem::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: *id006
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