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