mcollective-client 2.0.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of mcollective-client might be problematic. Click here for more details.

Files changed (140) hide show
  1. data/lib/mcollective.rb +32 -23
  2. data/lib/mcollective/agent.rb +5 -0
  3. data/lib/mcollective/agents.rb +5 -16
  4. data/lib/mcollective/aggregate.rb +61 -0
  5. data/lib/mcollective/aggregate/base.rb +40 -0
  6. data/lib/mcollective/aggregate/result.rb +9 -0
  7. data/lib/mcollective/aggregate/result/base.rb +25 -0
  8. data/lib/mcollective/aggregate/result/collection_result.rb +19 -0
  9. data/lib/mcollective/aggregate/result/numeric_result.rb +13 -0
  10. data/lib/mcollective/application.rb +7 -4
  11. data/lib/mcollective/applications.rb +3 -14
  12. data/lib/mcollective/cache.rb +145 -0
  13. data/lib/mcollective/client.rb +10 -87
  14. data/lib/mcollective/config.rb +22 -8
  15. data/lib/mcollective/data.rb +87 -0
  16. data/lib/mcollective/data/base.rb +67 -0
  17. data/lib/mcollective/data/result.rb +40 -0
  18. data/lib/mcollective/ddl.rb +113 -0
  19. data/lib/mcollective/ddl/agentddl.rb +185 -0
  20. data/lib/mcollective/ddl/base.rb +220 -0
  21. data/lib/mcollective/ddl/dataddl.rb +56 -0
  22. data/lib/mcollective/ddl/discoveryddl.rb +52 -0
  23. data/lib/mcollective/ddl/validatorddl.rb +6 -0
  24. data/lib/mcollective/discovery.rb +143 -0
  25. data/lib/mcollective/generators.rb +7 -0
  26. data/lib/mcollective/generators/agent_generator.rb +51 -0
  27. data/lib/mcollective/generators/base.rb +46 -0
  28. data/lib/mcollective/generators/data_generator.rb +51 -0
  29. data/lib/mcollective/generators/templates/action_snippet.erb +13 -0
  30. data/lib/mcollective/generators/templates/data_input_snippet.erb +7 -0
  31. data/lib/mcollective/generators/templates/ddl.erb +8 -0
  32. data/lib/mcollective/generators/templates/plugin.erb +7 -0
  33. data/lib/mcollective/logger/console_logger.rb +15 -15
  34. data/lib/mcollective/matcher.rb +167 -0
  35. data/lib/mcollective/matcher/parser.rb +60 -25
  36. data/lib/mcollective/matcher/scanner.rb +156 -78
  37. data/lib/mcollective/message.rb +47 -6
  38. data/lib/mcollective/monkey_patches.rb +17 -0
  39. data/lib/mcollective/optionparser.rb +18 -1
  40. data/lib/mcollective/pluginmanager.rb +3 -3
  41. data/lib/mcollective/pluginpackager.rb +10 -3
  42. data/lib/mcollective/pluginpackager/agent_definition.rb +28 -20
  43. data/lib/mcollective/pluginpackager/standard_definition.rb +11 -9
  44. data/lib/mcollective/registration/base.rb +3 -1
  45. data/lib/mcollective/rpc.rb +18 -24
  46. data/lib/mcollective/rpc/agent.rb +37 -113
  47. data/lib/mcollective/rpc/client.rb +186 -64
  48. data/lib/mcollective/rpc/helpers.rb +42 -80
  49. data/lib/mcollective/rpc/progress.rb +3 -3
  50. data/lib/mcollective/rpc/reply.rb +37 -13
  51. data/lib/mcollective/rpc/request.rb +17 -6
  52. data/lib/mcollective/rpc/result.rb +9 -5
  53. data/lib/mcollective/rpc/stats.rb +71 -24
  54. data/lib/mcollective/security/base.rb +41 -34
  55. data/lib/mcollective/shell.rb +1 -1
  56. data/lib/mcollective/ssl.rb +34 -0
  57. data/lib/mcollective/util.rb +194 -23
  58. data/lib/mcollective/validator.rb +80 -0
  59. data/spec/fixtures/util/1.in +10 -0
  60. data/spec/fixtures/util/1.out +10 -0
  61. data/spec/fixtures/util/2.in +1 -0
  62. data/spec/fixtures/util/2.out +1 -0
  63. data/spec/fixtures/util/3.in +1 -0
  64. data/spec/fixtures/util/3.out +2 -0
  65. data/spec/fixtures/util/4.in +5 -0
  66. data/spec/fixtures/util/4.out +9 -0
  67. data/spec/spec.opts +1 -1
  68. data/spec/spec_helper.rb +2 -0
  69. data/spec/unit/agents_spec.rb +34 -19
  70. data/spec/unit/aggregate/base_spec.rb +57 -0
  71. data/spec/unit/aggregate/result/base_spec.rb +28 -0
  72. data/spec/unit/aggregate/result/collection_result_spec.rb +18 -0
  73. data/spec/unit/aggregate/result/numeric_result_spec.rb +22 -0
  74. data/spec/unit/aggregate_spec.rb +110 -0
  75. data/spec/unit/application_spec.rb +8 -3
  76. data/spec/unit/applications_spec.rb +2 -2
  77. data/spec/unit/cache_spec.rb +115 -0
  78. data/spec/unit/client_spec.rb +78 -0
  79. data/spec/unit/config_spec.rb +32 -34
  80. data/spec/unit/data/base_spec.rb +90 -0
  81. data/spec/unit/data/result_spec.rb +64 -0
  82. data/spec/unit/data_spec.rb +158 -0
  83. data/spec/unit/ddl/agentddl_spec.rb +217 -0
  84. data/spec/unit/{rpc/ddl_spec.rb → ddl/base_spec.rb} +238 -224
  85. data/spec/unit/ddl/dataddl_spec.rb +65 -0
  86. data/spec/unit/ddl/discoveryddl_spec.rb +58 -0
  87. data/spec/unit/ddl_spec.rb +84 -0
  88. data/spec/unit/discovery_spec.rb +196 -0
  89. data/spec/unit/facts/base_spec.rb +1 -1
  90. data/spec/unit/generators/agent_generator_spec.rb +72 -0
  91. data/spec/unit/generators/base_spec.rb +83 -0
  92. data/spec/unit/generators/data_generator_spec.rb +37 -0
  93. data/spec/unit/generators/snippets/agent_ddl +19 -0
  94. data/spec/unit/generators/snippets/data_ddl +20 -0
  95. data/spec/unit/logger/console_logger_spec.rb +76 -0
  96. data/spec/unit/logger/syslog_logger_spec.rb +2 -2
  97. data/spec/unit/matcher/parser_spec.rb +27 -10
  98. data/spec/unit/matcher/scanner_spec.rb +108 -5
  99. data/spec/unit/matcher_spec.rb +260 -0
  100. data/spec/unit/message_spec.rb +35 -13
  101. data/spec/unit/optionparser_spec.rb +2 -2
  102. data/spec/unit/pluginpackager/agent_definition_spec.rb +59 -42
  103. data/spec/unit/pluginpackager/standard_definition_spec.rb +10 -8
  104. data/spec/unit/pluginpackager_spec.rb +131 -0
  105. data/spec/unit/plugins/mcollective/aggregate/average_spec.rb +45 -0
  106. data/spec/unit/plugins/mcollective/aggregate/sum_spec.rb +31 -0
  107. data/spec/unit/plugins/mcollective/aggregate/summary_spec.rb +45 -0
  108. data/spec/unit/plugins/mcollective/connector/activemq_spec.rb +1 -1
  109. data/spec/unit/plugins/mcollective/connector/rabbitmq_spec.rb +478 -0
  110. data/spec/unit/plugins/mcollective/connector/stomp_spec.rb +2 -0
  111. data/spec/unit/plugins/mcollective/data/agent_data_spec.rb +43 -0
  112. data/spec/unit/plugins/mcollective/data/fstat_data_spec.rb +135 -0
  113. data/spec/unit/plugins/mcollective/discovery/flatfile_spec.rb +48 -0
  114. data/spec/unit/plugins/mcollective/discovery/mc_spec.rb +40 -0
  115. data/spec/unit/plugins/mcollective/packagers/debpackage_packager_spec.rb +41 -15
  116. data/spec/unit/plugins/mcollective/packagers/ospackage_spec.rb +1 -1
  117. data/spec/unit/plugins/mcollective/packagers/rpmpackage_packager_spec.rb +22 -38
  118. data/spec/unit/plugins/mcollective/validator/array_validator_spec.rb +19 -0
  119. data/spec/unit/plugins/mcollective/validator/ipv4address_validator_spec.rb +19 -0
  120. data/spec/unit/plugins/mcollective/validator/ipv6address_validator_spec.rb +19 -0
  121. data/spec/unit/plugins/mcollective/validator/length_validator_spec.rb +19 -0
  122. data/spec/unit/plugins/mcollective/validator/regex_validator_spec.rb +19 -0
  123. data/spec/unit/plugins/mcollective/validator/shellsafe_validator_spec.rb +21 -0
  124. data/spec/unit/plugins/mcollective/validator/typecheck_validator_spec.rb +23 -0
  125. data/spec/unit/registration/base_spec.rb +1 -1
  126. data/spec/unit/rpc/actionrunner_spec.rb +2 -2
  127. data/spec/unit/rpc/agent_spec.rb +41 -65
  128. data/spec/unit/rpc/client_spec.rb +430 -134
  129. data/spec/unit/rpc/reply_spec.rb +31 -1
  130. data/spec/unit/rpc/request_spec.rb +33 -12
  131. data/spec/unit/rpc/result_spec.rb +7 -0
  132. data/spec/unit/rpc/stats_spec.rb +14 -14
  133. data/spec/unit/rpc_spec.rb +16 -0
  134. data/spec/unit/security/base_spec.rb +8 -8
  135. data/spec/unit/ssl_spec.rb +20 -2
  136. data/spec/unit/string_spec.rb +15 -0
  137. data/spec/unit/util_spec.rb +141 -21
  138. data/spec/unit/validator_spec.rb +67 -0
  139. metadata +145 -7
  140. data/lib/mcollective/rpc/ddl.rb +0 -258
@@ -74,26 +74,33 @@ module MCollective
74
74
 
75
75
  when "compound"
76
76
  filter[key].each do |compound|
77
- result = []
78
-
79
- compound.each do |expression|
80
- case expression.keys.first
81
- when "statement"
82
- result << Util.eval_compound_statement(expression).to_s
83
- when "and"
84
- result << "&&"
85
- when "or"
86
- result << "||"
87
- when "("
88
- result << "("
89
- when ")"
90
- result << ")"
91
- when "not"
92
- result << "!"
77
+ result = false
78
+ truth_values = []
79
+
80
+ begin
81
+ compound.each do |expression|
82
+ case expression.keys.first
83
+ when "statement"
84
+ truth_values << Matcher.eval_compound_statement(expression).to_s
85
+ when "fstatement"
86
+ truth_values << Matcher.eval_compound_fstatement(expression.values.first)
87
+ when "and"
88
+ truth_values << "&&"
89
+ when "or"
90
+ truth_values << "||"
91
+ when "("
92
+ truth_values << "("
93
+ when ")"
94
+ truth_values << ")"
95
+ when "not"
96
+ truth_values << "!"
97
+ end
93
98
  end
94
- end
95
99
 
96
- result = eval(result.join(" "))
100
+ result = eval(truth_values.join(" "))
101
+ rescue DDLValidationError
102
+ result = false
103
+ end
97
104
 
98
105
  if result
99
106
  Log.debug("Passing based on class and fact composition")
@@ -161,24 +168,24 @@ module MCollective
161
168
  Log.debug("Encoded a message for request #{reqid}")
162
169
 
163
170
  {:senderid => @config.identity,
164
- :requestid => reqid,
165
- :senderagent => agent,
166
- :msgtime => Time.now.utc.to_i,
167
- :body => body}
171
+ :requestid => reqid,
172
+ :senderagent => agent,
173
+ :msgtime => Time.now.utc.to_i,
174
+ :body => body}
168
175
  end
169
176
 
170
177
  def create_request(reqid, filter, msg, initiated_by, target_agent, target_collective, ttl=60)
171
178
  Log.debug("Encoding a request for agent '#{target_agent}' in collective #{target_collective} with request id #{reqid}")
172
179
 
173
180
  {:body => msg,
174
- :senderid => @config.identity,
175
- :requestid => reqid,
176
- :filter => filter,
177
- :collective => target_collective,
178
- :agent => target_agent,
179
- :callerid => callerid,
180
- :ttl => ttl,
181
- :msgtime => Time.now.utc.to_i}
181
+ :senderid => @config.identity,
182
+ :requestid => reqid,
183
+ :filter => filter,
184
+ :collective => target_collective,
185
+ :agent => target_agent,
186
+ :callerid => callerid,
187
+ :ttl => ttl,
188
+ :msgtime => Time.now.utc.to_i}
182
189
  end
183
190
 
184
191
  # Give a MC::Message instance and a message id this will figure out if you the incoming
@@ -215,22 +222,22 @@ module MCollective
215
222
 
216
223
  # Security providers should provide this, see MCollective::Security::Psk
217
224
  def validrequest?(req)
218
- Log.error("validrequest? is not implimented in #{self.class}")
225
+ Log.error("validrequest? is not implemented in #{self.class}")
219
226
  end
220
227
 
221
228
  # Security providers should provide this, see MCollective::Security::Psk
222
229
  def encoderequest(sender, msg, filter={})
223
- Log.error("encoderequest is not implimented in #{self.class}")
230
+ Log.error("encoderequest is not implemented in #{self.class}")
224
231
  end
225
232
 
226
233
  # Security providers should provide this, see MCollective::Security::Psk
227
234
  def encodereply(sender, msg, requestcallerid=nil)
228
- Log.error("encodereply is not implimented in #{self.class}")
235
+ Log.error("encodereply is not implemented in #{self.class}")
229
236
  end
230
237
 
231
238
  # Security providers should provide this, see MCollective::Security::Psk
232
239
  def decodemsg(msg)
233
- Log.error("decodemsg is not implimented in #{self.class}")
240
+ Log.error("decodemsg is not implemented in #{self.class}")
234
241
  end
235
242
  end
236
243
  end
@@ -7,7 +7,7 @@ module MCollective
7
7
  # s.runcommand
8
8
  # puts s.stdout
9
9
  # puts s.stderr
10
- # puts s.status.exitcode
10
+ # puts s.status.exitstatus
11
11
  #
12
12
  # Options hash can have:
13
13
  #
@@ -204,12 +204,46 @@ module MCollective
204
204
  Digest::MD5.hexdigest(string)
205
205
  end
206
206
 
207
+ # Creates a RFC 4122 version 5 UUID. If string is supplied it will produce repeatable
208
+ # UUIDs for that string else a random 128bit string will be used from OpenSSL::BN
209
+ #
210
+ # Code used with permission from:
211
+ # https://github.com/kwilczynski/puppet-functions/blob/master/lib/puppet/parser/functions/uuid.rb
212
+ #
213
+ def self.uuid(string=nil)
214
+ string ||= OpenSSL::Random.random_bytes(16).unpack('H*').shift
215
+
216
+ uuid_name_space_dns = "\x6b\xa7\xb8\x10\x9d\xad\x11\xd1\x80\xb4\x00\xc0\x4f\xd4\x30\xc8"
217
+
218
+ sha1 = Digest::SHA1.new
219
+ sha1.update(uuid_name_space_dns)
220
+ sha1.update(string)
221
+
222
+ # first 16 bytes..
223
+ bytes = sha1.digest[0, 16].bytes.to_a
224
+
225
+ # version 5 adjustments
226
+ bytes[6] &= 0x0f
227
+ bytes[6] |= 0x50
228
+
229
+ # variant is DCE 1.1
230
+ bytes[8] &= 0x3f
231
+ bytes[8] |= 0x80
232
+
233
+ bytes = [4, 2, 2, 2, 6].collect do |i|
234
+ bytes.slice!(0, i).pack('C*').unpack('H*')
235
+ end
236
+
237
+ bytes.join('-')
238
+ end
239
+
207
240
  # Reads either a :public or :private key from disk, uses an
208
241
  # optional passphrase to read the private key
209
242
  def read_key(type, key=nil, passphrase=nil)
210
243
  return key if key.nil?
211
244
 
212
245
  raise "Could not find key #{key}" unless File.exist?(key)
246
+ raise "#{type} key file '#{key}' is empty" if File.zero?(key)
213
247
 
214
248
  if type == :public
215
249
  begin
@@ -129,10 +129,10 @@ module MCollective
129
129
  # Creates an empty filter
130
130
  def self.empty_filter
131
131
  {"fact" => [],
132
- "cf_class" => [],
133
- "agent" => [],
134
- "identity" => [],
135
- "compound" => []}
132
+ "cf_class" => [],
133
+ "agent" => [],
134
+ "identity" => [],
135
+ "compound" => []}
136
136
  end
137
137
 
138
138
  # Picks a config file defaults to ~/.mcollective
@@ -156,12 +156,14 @@ module MCollective
156
156
 
157
157
  # Creates a standard options hash
158
158
  def self.default_options
159
- {:verbose => false,
160
- :disctimeout => 2,
161
- :timeout => 5,
162
- :config => config_file_for_user,
163
- :collective => nil,
164
- :filter => empty_filter}
159
+ {:verbose => false,
160
+ :disctimeout => nil,
161
+ :timeout => 5,
162
+ :config => config_file_for_user,
163
+ :collective => nil,
164
+ :discovery_method => nil,
165
+ :discovery_options => Config.instance.default_discovery_options,
166
+ :filter => empty_filter}
165
167
  end
166
168
 
167
169
  def self.make_subscriptions(agent, type, collective=nil)
@@ -247,28 +249,197 @@ module MCollective
247
249
  !!(RbConfig::CONFIG['host_os'] =~ /mswin|win32|dos|mingw|cygwin/i)
248
250
  end
249
251
 
250
- def self.eval_compound_statement(expression)
251
- if expression.values.first =~ /^\//
252
- return Util.has_cf_class?(expression.values.first)
253
- elsif expression.values.first =~ />=|<=|=|<|>/
254
- optype = expression.values.first.match(/>=|<=|=|<|>/)
255
- name, value = expression.values.first.split(optype[0])
256
- unless value.split("")[0] == "/"
257
- optype[0] == "=" ? optype = "==" : optype = optype[0]
258
- else
259
- optype = "=~"
260
- end
252
+ # Return color codes, if the config color= option is false
253
+ # just return a empty string
254
+ def self.color(code)
255
+ colorize = Config.instance.color
261
256
 
262
- return Util.has_fact?(name,value, optype).to_s
257
+ colors = {:red => "",
258
+ :green => "",
259
+ :yellow => "",
260
+ :cyan => "",
261
+ :bold => "",
262
+ :reset => ""}
263
+
264
+ if colorize
265
+ return colors[code] || ""
263
266
  else
264
- return Util.has_cf_class?(expression.values.first)
267
+ return ""
265
268
  end
266
269
  end
267
270
 
271
+ # Helper to return a string in specific color
272
+ def self.colorize(code, msg)
273
+ "%s%s%s" % [ color(code), msg, color(:reset) ]
274
+ end
275
+
268
276
  # Returns the current ruby version as per RUBY_VERSION, mostly
269
277
  # doing this here to aid testing
270
278
  def self.ruby_version
271
279
  RUBY_VERSION
272
280
  end
281
+
282
+ def self.mcollective_version
283
+ MCollective::VERSION
284
+ end
285
+
286
+ # Returns an aligned_string of text relative to the size of the terminal
287
+ # window. If a line in the string exceeds the width of the terminal window
288
+ # the line will be chopped off at the whitespace chacter closest to the
289
+ # end of the line and prepended to the next line, keeping all indentation.
290
+ #
291
+ # The terminal size is detected by default, but custom line widths can
292
+ # passed. All strings will also be left aligned with 5 whitespace characters
293
+ # by default.
294
+ def self.align_text(text, console_cols = nil, preamble = 5)
295
+ unless console_cols
296
+ console_cols = terminal_dimensions[0]
297
+
298
+ # if unknown size we default to the typical unix default
299
+ console_cols = 80 if console_cols == 0
300
+ end
301
+
302
+ console_cols -= preamble
303
+
304
+ # Return unaligned text if console window is too small
305
+ return text if console_cols <= 0
306
+
307
+ # If console is 0 this implies unknown so we assume the common
308
+ # minimal unix configuration of 80 characters
309
+ console_cols = 80 if console_cols <= 0
310
+
311
+ text = text.split("\n")
312
+ piece = ''
313
+ whitespace = 0
314
+
315
+ text.each_with_index do |line, i|
316
+ whitespace = 0
317
+
318
+ while whitespace < line.length && line[whitespace].chr == ' '
319
+ whitespace += 1
320
+ end
321
+
322
+ # If the current line is empty, indent it so that a snippet
323
+ # from the previous line is aligned correctly.
324
+ if line == ""
325
+ line = (" " * whitespace)
326
+ end
327
+
328
+ # If text was snipped from the previous line, prepend it to the
329
+ # current line after any current indentation.
330
+ if piece != ''
331
+ # Reset whitespaces to 0 if there are more whitespaces than there are
332
+ # console columns
333
+ whitespace = 0 if whitespace >= console_cols
334
+
335
+ # If the current line is empty and being prepended to, create a new
336
+ # empty line in the text so that formatting is preserved.
337
+ if text[i + 1] && line == (" " * whitespace)
338
+ text.insert(i + 1, "")
339
+ end
340
+
341
+ # Add the snipped text to the current line
342
+ line.insert(whitespace, "#{piece} ")
343
+ end
344
+
345
+ piece = ''
346
+
347
+ # Compare the line length to the allowed line length.
348
+ # If it exceeds it, snip the offending text from the line
349
+ # and store it so that it can be prepended to the next line.
350
+ if line.length > (console_cols + preamble)
351
+ reverse = console_cols
352
+
353
+ while line[reverse].chr != ' '
354
+ reverse -= 1
355
+ end
356
+
357
+ piece = line.slice!(reverse, (line.length - 1)).lstrip
358
+ end
359
+
360
+ # If a snippet exists when all the columns in the text have been
361
+ # updated, create a new line and append the snippet to it, using
362
+ # the same left alignment as the last line in the text.
363
+ if piece != '' && text[i+1].nil?
364
+ text[i+1] = "#{' ' * (whitespace)}#{piece}"
365
+ piece = ''
366
+ end
367
+
368
+ # Add the preamble to the line and add it to the text
369
+ line = ((' ' * preamble) + line)
370
+ text[i] = line
371
+ end
372
+
373
+ text.join("\n")
374
+ end
375
+
376
+ # Figures out the columns and lines of the current tty
377
+ #
378
+ # Returns [0, 0] if it can't figure it out or if you're
379
+ # not running on a tty
380
+ def self.terminal_dimensions(stdout = STDOUT, environment = ENV)
381
+ return [0, 0] unless stdout.tty?
382
+
383
+ return [80, 40] if Util.windows?
384
+
385
+ if environment["COLUMNS"] && environment["LINES"]
386
+ return [environment["COLUMNS"].to_i, environment["LINES"].to_i]
387
+
388
+ elsif environment["TERM"] && command_in_path?("tput")
389
+ return [`tput cols`.to_i, `tput lines`.to_i]
390
+
391
+ elsif command_in_path?('stty')
392
+ return `stty size`.scan(/\d+/).map {|s| s.to_i }
393
+ else
394
+ return [0, 0]
395
+ end
396
+ rescue
397
+ [0, 0]
398
+ end
399
+
400
+ # Checks in PATH returns true if the command is found
401
+ def self.command_in_path?(command)
402
+ found = ENV["PATH"].split(File::PATH_SEPARATOR).map do |p|
403
+ File.exist?(File.join(p, command))
404
+ end
405
+
406
+ found.include?(true)
407
+ end
408
+
409
+ # compare two software versions as commonly found in
410
+ # package versions.
411
+ #
412
+ # returns 0 if a == b
413
+ # returns -1 if a < b
414
+ # returns 1 if a > b
415
+ #
416
+ # Code originally from Puppet but refactored to a more
417
+ # ruby style that fits in better with this code base
418
+ def self.versioncmp(version_a, version_b)
419
+ vre = /[-.]|\d+|[^-.\d]+/
420
+ ax = version_a.scan(vre)
421
+ bx = version_b.scan(vre)
422
+
423
+ until ax.empty? || bx.empty?
424
+ a = ax.shift
425
+ b = bx.shift
426
+
427
+ next if a == b
428
+ next if a == '-' && b == '-'
429
+ return -1 if a == '-'
430
+ return 1 if b == '-'
431
+ next if a == '.' && b == '.'
432
+ return -1 if a == '.'
433
+ return 1 if b == '.'
434
+
435
+ if a =~ /^[^0]\d+$/ && b =~ /^[^0]\d+$/
436
+ return Integer(a) <=> Integer(b)
437
+ else
438
+ return a.upcase <=> b.upcase
439
+ end
440
+ end
441
+
442
+ version_a <=> version_b
443
+ end
273
444
  end
274
445
  end
@@ -0,0 +1,80 @@
1
+ module MCollective
2
+ module Validator
3
+ @last_load = nil
4
+
5
+ # Loads the validator plugins. Validators will only be loaded every 5 minutes
6
+ def self.load_validators
7
+ if load_validators?
8
+ @last_load = Time.now.to_i
9
+ PluginManager.find_and_load("validator")
10
+ end
11
+ end
12
+
13
+ # Returns and instance of the Plugin class from which objects can be created.
14
+ # Valid plugin names are
15
+ # :valplugin
16
+ # "valplugin"
17
+ # "ValpluginValidator"
18
+ def self.[](klass)
19
+ if klass.is_a?(Symbol)
20
+ klass = validator_class(klass)
21
+ elsif !(klass.match(/.*Validator$/))
22
+ klass = validator_class(klass)
23
+ end
24
+
25
+ const_get(klass)
26
+ end
27
+
28
+ # Allows validation plugins to be called like module methods : Validator.validate()
29
+ def self.method_missing(method, *args, &block)
30
+ if has_validator?(method)
31
+ validator = Validator[method].validate(*args)
32
+ else
33
+ raise ValidatorError, "Unknown validator: '#{method}'."
34
+ end
35
+ end
36
+
37
+ def self.has_validator?(validator)
38
+ const_defined?(validator_class(validator))
39
+ end
40
+
41
+ def self.validator_class(validator)
42
+ "#{validator.to_s.capitalize}Validator"
43
+ end
44
+
45
+ def self.load_validators?
46
+ return true if @last_load.nil?
47
+
48
+ (@last_load - Time.now.to_i) > 300
49
+ end
50
+
51
+ # Generic validate method that will call the correct validator
52
+ # plugin based on the type of the validation parameter
53
+ def self.validate(validator, validation)
54
+ Validator.load_validators
55
+
56
+ begin
57
+ if [:integer, :boolean, :float, :number, :string].include?(validation)
58
+ Validator.typecheck(validator, validation)
59
+
60
+ else
61
+ case validation
62
+ when Regexp,String
63
+ Validator.regex(validator, validation)
64
+
65
+ when Symbol
66
+ Validator.send(validation, validator)
67
+
68
+ when Array
69
+ Validator.array(validator, validation)
70
+
71
+ when Class
72
+ Validator.typecheck(validator, validation)
73
+ end
74
+ end
75
+ rescue => e
76
+ raise ValidatorError, e.to_s
77
+ end
78
+ end
79
+ end
80
+ end