mcollective-client 2.2.4 → 2.4.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.

Potentially problematic release.


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

Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/lib/mcollective/application.rb +25 -34
  3. data/lib/mcollective/client.rb +91 -33
  4. data/lib/mcollective/config.rb +42 -43
  5. data/lib/mcollective/data/base.rb +1 -1
  6. data/lib/mcollective/data/result.rb +6 -2
  7. data/lib/mcollective/ddl/agentddl.rb +28 -1
  8. data/lib/mcollective/ddl/base.rb +8 -6
  9. data/lib/mcollective/log.rb +11 -3
  10. data/lib/mcollective/logger/file_logger.rb +4 -4
  11. data/lib/mcollective/matcher.rb +9 -1
  12. data/lib/mcollective/message.rb +14 -23
  13. data/lib/mcollective/optionparser.rb +9 -1
  14. data/lib/mcollective/pluginpackager.rb +24 -3
  15. data/lib/mcollective/pluginpackager/agent_definition.rb +12 -12
  16. data/lib/mcollective/pluginpackager/standard_definition.rb +12 -12
  17. data/lib/mcollective/rpc/agent.rb +15 -12
  18. data/lib/mcollective/rpc/client.rb +67 -31
  19. data/lib/mcollective/rpc/helpers.rb +7 -1
  20. data/lib/mcollective/rpc/reply.rb +3 -1
  21. data/lib/mcollective/shell.rb +45 -8
  22. data/lib/mcollective/util.rb +37 -1
  23. data/lib/mcollective/windows_daemon.rb +14 -3
  24. data/spec/spec_helper.rb +2 -0
  25. data/spec/unit/application_spec.rb +45 -26
  26. data/spec/unit/cache_spec.rb +3 -3
  27. data/spec/unit/client_spec.rb +269 -24
  28. data/spec/unit/config_spec.rb +89 -26
  29. data/spec/unit/data/base_spec.rb +1 -0
  30. data/spec/unit/data/result_spec.rb +19 -1
  31. data/spec/unit/data_spec.rb +3 -0
  32. data/spec/unit/ddl/agentddl_spec.rb +32 -0
  33. data/spec/unit/ddl/base_spec.rb +4 -0
  34. data/spec/unit/ddl/dataddl_spec.rb +1 -1
  35. data/spec/unit/log_spec.rb +44 -27
  36. data/spec/unit/logger/base_spec.rb +1 -1
  37. data/spec/unit/matcher_spec.rb +14 -0
  38. data/spec/unit/message_spec.rb +24 -0
  39. data/spec/unit/optionparser_spec.rb +99 -0
  40. data/spec/unit/pluginpackager/agent_definition_spec.rb +48 -17
  41. data/spec/unit/pluginpackager/standard_definition_spec.rb +44 -20
  42. data/spec/unit/pluginpackager_spec.rb +31 -7
  43. data/spec/unit/plugins/mcollective/agent/rpcutil_spec.rb +176 -0
  44. data/spec/unit/plugins/mcollective/application/plugin_spec.rb +81 -0
  45. data/spec/unit/plugins/mcollective/audit/logfile_spec.rb +44 -0
  46. data/spec/unit/plugins/mcollective/connector/activemq_spec.rb +118 -27
  47. data/spec/unit/plugins/mcollective/connector/rabbitmq_spec.rb +168 -34
  48. data/spec/unit/plugins/mcollective/data/agent_data_spec.rb +1 -0
  49. data/spec/unit/plugins/mcollective/data/fstat_data_spec.rb +1 -0
  50. data/spec/unit/plugins/mcollective/discovery/flatfile_spec.rb +10 -0
  51. data/spec/unit/plugins/mcollective/discovery/stdin_spec.rb +65 -0
  52. data/spec/unit/plugins/mcollective/facts/yaml_facts_spec.rb +65 -0
  53. data/spec/unit/plugins/mcollective/packagers/debpackage_packager_spec.rb +240 -219
  54. data/spec/unit/plugins/mcollective/packagers/modulepackage_packager_spec.rb +209 -0
  55. data/spec/unit/plugins/mcollective/packagers/rpmpackage_packager_spec.rb +223 -109
  56. data/spec/unit/rpc/actionrunner_spec.rb +2 -1
  57. data/spec/unit/rpc/agent_spec.rb +130 -1
  58. data/spec/unit/rpc/client_spec.rb +169 -3
  59. data/spec/unit/security/base_spec.rb +0 -1
  60. data/spec/unit/shell_spec.rb +76 -3
  61. data/spec/unit/util_spec.rb +69 -1
  62. data/spec/unit/windows_daemon_spec.rb +30 -9
  63. metadata +104 -90
  64. data/spec/unit/plugins/mcollective/connector/stomp/eventlogger_spec.rb +0 -34
  65. data/spec/unit/plugins/mcollective/connector/stomp_spec.rb +0 -424
  66. data/spec/unit/plugins/mcollective/validator/any_validator_spec.rb +0 -15
@@ -65,7 +65,7 @@ module MCollective
65
65
 
66
66
  begin
67
67
  # Incoming requests need to be validated against the DDL thus reusing
68
- # all the work users put into creating DDLs and creating a consistant
68
+ # all the work users put into creating DDLs and creating a consistent
69
69
  # quality of input validation everywhere with the a simple once off
70
70
  # investment of writing a DDL
71
71
  @request.validate!
@@ -85,7 +85,7 @@ module MCollective
85
85
  if respond_to?("#{@request.action}_action")
86
86
  send("#{@request.action}_action")
87
87
  else
88
- raise UnknownRPCAction, "Unknown action: #{@request.action}"
88
+ raise UnknownRPCAction, "Unknown action '#{@request.action}' for agent '#{@request.agent}'"
89
89
  end
90
90
  rescue RPCAborted => e
91
91
  @reply.fail e.to_s, 1
@@ -96,13 +96,17 @@ module MCollective
96
96
  rescue MissingRPCData => e
97
97
  @reply.fail e.to_s, 3
98
98
 
99
- rescue InvalidRPCData => e
99
+ rescue InvalidRPCData, DDLValidationError => e
100
100
  @reply.fail e.to_s, 4
101
101
 
102
102
  rescue UnknownRPCError => e
103
+ Log.error("%s#%s failed: %s: %s" % [@agent_name, @request.action, e.class, e.to_s])
104
+ Log.error(e.backtrace.join("\n\t"))
103
105
  @reply.fail e.to_s, 5
104
106
 
105
107
  rescue Exception => e
108
+ Log.error("%s#%s failed: %s: %s" % [@agent_name, @request.action, e.class, e.to_s])
109
+ Log.error(e.backtrace.join("\n\t"))
106
110
  @reply.fail e.to_s, 5
107
111
 
108
112
  end
@@ -137,16 +141,13 @@ module MCollective
137
141
 
138
142
  Log.debug("Starting default activation checks for #{agent_name}")
139
143
 
140
- should_activate = Config.instance.pluginconf["#{agent_name}.activate_agent"]
144
+ should_activate = Util.str_to_bool(Config.instance.pluginconf.fetch("#{agent_name}.activate_agent", true))
141
145
 
142
- if should_activate
143
- Log.debug("Found plugin config #{agent_name}.activate_agent with value #{should_activate}")
144
- unless should_activate =~ /^1|y|true$/
145
- return false
146
- end
146
+ unless should_activate
147
+ Log.debug("Found plugin configuration '#{agent_name}.activate_agent' with value '#{should_activate}'")
147
148
  end
148
149
 
149
- return true
150
+ return should_activate
150
151
  end
151
152
 
152
153
  # Returns an array of actions this agent support
@@ -211,7 +212,7 @@ module MCollective
211
212
  end
212
213
  end
213
214
 
214
- [:stdin, :cwd, :environment].each do |k|
215
+ [:stdin, :cwd, :environment, :timeout].each do |k|
215
216
  if options.include?(k)
216
217
  shellopts[k] = options[k]
217
218
  end
@@ -231,7 +232,9 @@ module MCollective
231
232
 
232
233
  # Registers meta data for the introspection hash
233
234
  def self.metadata(data)
234
- Log.warn("%s: setting meta data in agents have been deprecated, DDL files are now being used for this information." % File.basename(caller.first))
235
+ agent = File.basename(caller.first).split(":").first
236
+
237
+ Log.warn("Setting metadata in agents has been deprecated, DDL files are now being used for this information. Please update the '#{agent}' agent")
235
238
  end
236
239
 
237
240
  # Creates the needed activate? class in a manner similar to the other
@@ -5,7 +5,7 @@ module MCollective
5
5
  class Client
6
6
  attr_accessor :timeout, :verbose, :filter, :config, :progress, :ttl, :reply_to
7
7
  attr_reader :client, :stats, :ddl, :agent, :limit_targets, :limit_method, :output_format, :batch_size, :batch_sleep_time, :batch_mode
8
- attr_reader :discovery_options, :discovery_method, :limit_seed
8
+ attr_reader :discovery_options, :discovery_method, :default_discovery_method, :limit_seed
9
9
 
10
10
  @@initial_options = nil
11
11
 
@@ -57,7 +57,13 @@ module MCollective
57
57
  @output_format = initial_options[:output_format] || :console
58
58
  @force_direct_request = false
59
59
  @reply_to = initial_options[:reply_to]
60
- @discovery_method = initial_options[:discovery_method] || Config.instance.default_discovery_method
60
+ @discovery_method = initial_options[:discovery_method]
61
+ if !@discovery_method
62
+ @discovery_method = Config.instance.default_discovery_method
63
+ @default_discovery_method = true
64
+ else
65
+ @default_discovery_method = false
66
+ end
61
67
  @discovery_options = initial_options[:discovery_options] || []
62
68
  @force_display_mode = initial_options[:force_display_mode] || false
63
69
 
@@ -71,6 +77,8 @@ module MCollective
71
77
 
72
78
  @collective = @client.collective
73
79
  @ttl = initial_options[:ttl] || Config.instance.ttl
80
+ @publish_timeout = initial_options[:publish_timeout] || Config.instance.publish_timeout
81
+ @threaded = initial_options[:threaded] || Config.instance.threaded
74
82
 
75
83
  # if we can find a DDL for the service override
76
84
  # the timeout of the client so we always magically
@@ -149,6 +157,18 @@ module MCollective
149
157
  :data => data}
150
158
  end
151
159
 
160
+ # For the provided arguments and action the input arguments get
161
+ # modified by supplying any defaults provided in the DDL for arguments
162
+ # that were not supplied in the request
163
+ #
164
+ # We then pass the modified arguments to the DDL for validation
165
+ def validate_request(action, args)
166
+ raise "No DDL found for agent %s cannot validate inputs" % @agent unless @ddl
167
+
168
+ @ddl.set_default_input_arguments(action, args)
169
+ @ddl.validate_rpc_request(action, args)
170
+ end
171
+
152
172
  # Magic handler to invoke remote methods
153
173
  #
154
174
  # Once the stub is created using the constructor or the RPC#rpcclient helper you can
@@ -217,7 +237,7 @@ module MCollective
217
237
 
218
238
  @stats.reset
219
239
 
220
- @ddl.validate_rpc_request(action, args) if @ddl
240
+ validate_request(action, args)
221
241
 
222
242
  # if a global batch size is set just use that else set it
223
243
  # in the case that it was passed as an argument
@@ -274,7 +294,7 @@ module MCollective
274
294
  # request which technically does not need filters. If you try to use this
275
295
  # mode with direct addressing disabled an exception will be raise
276
296
  def custom_request(action, args, expected_agents, filter = {}, &block)
277
- @ddl.validate_rpc_request(action, args) if @ddl
297
+ validate_request(action, args)
278
298
 
279
299
  if filter == {} && !Config.instance.direct_addressing
280
300
  raise "Attempted to do a filterless custom_request without direct_addressing enabled, preventing unexpected call to all nodes"
@@ -355,6 +375,7 @@ module MCollective
355
375
  # since that is the user supplied timeout either via initial options
356
376
  # or via specifically setting it on the client.
357
377
  def discovery_method=(method)
378
+ @default_discovery_method = false
358
379
  @discovery_method = method
359
380
 
360
381
  if @initial_options[:discovery_options]
@@ -375,7 +396,7 @@ module MCollective
375
396
 
376
397
  # Sets the class filter
377
398
  def class_filter(klass)
378
- @filter["cf_class"] << klass
399
+ @filter["cf_class"] = @filter["cf_class"] | [klass]
379
400
  @filter["cf_class"].compact!
380
401
  reset
381
402
  end
@@ -387,10 +408,10 @@ module MCollective
387
408
 
388
409
  if value.nil?
389
410
  parsed = Util.parse_fact_string(fact)
390
- @filter["fact"] << parsed unless parsed == false
411
+ @filter["fact"] = @filter["fact"] | [parsed] unless parsed == false
391
412
  else
392
413
  parsed = Util.parse_fact_string("#{fact}#{operator}#{value}")
393
- @filter["fact"] << parsed unless parsed == false
414
+ @filter["fact"] = @filter["fact"] | [parsed] unless parsed == false
394
415
  end
395
416
 
396
417
  @filter["fact"].compact!
@@ -399,21 +420,21 @@ module MCollective
399
420
 
400
421
  # Sets the agent filter
401
422
  def agent_filter(agent)
402
- @filter["agent"] << agent
423
+ @filter["agent"] = @filter["agent"] | [agent]
403
424
  @filter["agent"].compact!
404
425
  reset
405
426
  end
406
427
 
407
428
  # Sets the identity filter
408
429
  def identity_filter(identity)
409
- @filter["identity"] << identity
430
+ @filter["identity"] = @filter["identity"] | [identity]
410
431
  @filter["identity"].compact!
411
432
  reset
412
433
  end
413
434
 
414
435
  # Set a compound filter
415
436
  def compound_filter(filter)
416
- @filter["compound"] << Matcher.create_compound_callstack(filter)
437
+ @filter["compound"] = @filter["compound"] | [Matcher.create_compound_callstack(filter)]
417
438
  reset
418
439
  end
419
440
 
@@ -479,24 +500,8 @@ module MCollective
479
500
  @discovered_agents = hosts
480
501
  @force_direct_request = true
481
502
 
482
- # if an identity filter is supplied and it is all strings no regex we can use that
483
- # as discovery data, technically the identity filter is then redundant if we are
484
- # in direct addressing mode and we could empty it out but this use case should
485
- # only really be for a few -I's on the CLI
486
- #
487
- # For safety we leave the filter in place for now, that way we can support this
488
- # enhancement also in broadcast mode.
489
- #
490
- # This is only needed for the 'mc' discovery method, other methods might change
491
- # the concept of identity to mean something else so we should pass the full
492
- # identity filter to them
493
- elsif options[:filter]["identity"].size > 0 && @discovery_method == "mc"
494
- regex_filters = options[:filter]["identity"].select{|i| i.match("^\/")}.size
495
-
496
- if regex_filters == 0
497
- @discovered_agents = options[:filter]["identity"].clone
498
- @force_direct_request = true if Config.instance.direct_addressing
499
- end
503
+ else
504
+ identity_filter_discovery_optimization
500
505
  end
501
506
  end
502
507
 
@@ -559,7 +564,9 @@ module MCollective
559
564
  :discovery_method => @discovery_method,
560
565
  :discovery_options => @discovery_options,
561
566
  :force_display_mode => @force_display_mode,
562
- :config => @config}
567
+ :config => @config,
568
+ :publish_timeout => @publish_timeout,
569
+ :threaded => @threaded}
563
570
  end
564
571
 
565
572
  # Sets the collective we are communicating with
@@ -701,7 +708,9 @@ module MCollective
701
708
  #
702
709
  # Should only be called via method_missing
703
710
  def fire_and_forget_request(action, args, filter=nil)
704
- @ddl.validate_rpc_request(action, args) if @ddl
711
+ validate_request(action, args)
712
+
713
+ identity_filter_discovery_optimization
705
714
 
706
715
  req = new_request(action.to_s, args)
707
716
 
@@ -710,7 +719,34 @@ module MCollective
710
719
  message = Message.new(req, nil, {:agent => @agent, :type => :request, :collective => @collective, :filter => filter, :options => options})
711
720
  message.reply_to = @reply_to if @reply_to
712
721
 
713
- return @client.sendreq(message, nil)
722
+ if @force_direct_request || @client.discoverer.force_direct_mode?
723
+ message.discovered_hosts = discover.clone
724
+ message.type = :direct_request
725
+ end
726
+
727
+ client.sendreq(message, nil)
728
+ end
729
+
730
+ # if an identity filter is supplied and it is all strings no regex we can use that
731
+ # as discovery data, technically the identity filter is then redundant if we are
732
+ # in direct addressing mode and we could empty it out but this use case should
733
+ # only really be for a few -I's on the CLI
734
+ #
735
+ # For safety we leave the filter in place for now, that way we can support this
736
+ # enhancement also in broadcast mode.
737
+ #
738
+ # This is only needed for the 'mc' discovery method, other methods might change
739
+ # the concept of identity to mean something else so we should pass the full
740
+ # identity filter to them
741
+ def identity_filter_discovery_optimization
742
+ if options[:filter]["identity"].size > 0 && @discovery_method == "mc"
743
+ regex_filters = options[:filter]["identity"].select{|i| i.match("^\/")}.size
744
+
745
+ if regex_filters == 0
746
+ @discovered_agents = options[:filter]["identity"].clone
747
+ @force_direct_request = true if Config.instance.direct_addressing
748
+ end
749
+ end
714
750
  end
715
751
 
716
752
  # Calls an agent in a way very similar to call_agent but it supports batching
@@ -100,6 +100,7 @@ module MCollective
100
100
  result_text << text_for_result(sender, status, message, result, ddl)
101
101
 
102
102
  when :flatten
103
+ Log.warn("The display option :flatten is being deprecated and will be removed in the next minor release")
103
104
  result_text << text_for_flattened_result(status, result)
104
105
 
105
106
  end
@@ -128,7 +129,7 @@ module MCollective
128
129
  result_text << " %s\n" % [Util.colorize(:yellow, msg)] unless msg == "OK"
129
130
 
130
131
  # only print good data, ignore data that results from failure
131
- if [0, 1].include?(status)
132
+ if status == 0
132
133
  if result.is_a?(Hash)
133
134
  # figure out the lengths of the display as strings, we'll use
134
135
  # it later to correctly justify the output
@@ -172,6 +173,10 @@ module MCollective
172
173
  result_text << " " << result[k].pretty_inspect.split("\n").join("\n" << padding) << "\n"
173
174
  end
174
175
  end
176
+ elsif status == 1
177
+ # for status 1 we dont want to show half baked
178
+ # data by default since the DDL will supply all the defaults
179
+ # it just doesnt look right
175
180
  else
176
181
  result_text << "\n\t" + result.pretty_inspect.split("\n").join("\n\t")
177
182
  end
@@ -249,6 +254,7 @@ module MCollective
249
254
  # Add SimpleRPC common options
250
255
  def self.add_simplerpc_options(parser, options)
251
256
  parser.separator ""
257
+ parser.separator "RPC Options"
252
258
 
253
259
  # add SimpleRPC specific options to all clients that use our library
254
260
  parser.on('--np', '--no-progress', 'Do not show the progress bar') do |v|
@@ -26,7 +26,9 @@ module MCollective
26
26
  interface = @ddl.action_interface(@action)
27
27
 
28
28
  interface[:output].keys.each do |output|
29
- @data[output] = interface[:output][output][:default]
29
+ # must deep clone this data to avoid accidental updates of the DDL in cases where the
30
+ # default is for example a string and someone does << on it
31
+ @data[output] = Marshal.load(Marshal.dump(interface[:output][output].fetch(:default, nil)))
30
32
  end
31
33
  end
32
34
 
@@ -17,9 +17,12 @@ module MCollective
17
17
  # stderr - a variable that will receive stdin, must support <<
18
18
  # environment - the shell environment, defaults to include LC_ALL=C
19
19
  # set to nil to clear the environment even of LC_ALL
20
+ # timeout - a timeout in seconds after which the subprocess is killed,
21
+ # the special value :on_thread_exit kills the subprocess
22
+ # when the invoking thread (typically the agent) has ended
20
23
  #
21
24
  class Shell
22
- attr_reader :environment, :command, :status, :stdout, :stderr, :stdin, :cwd
25
+ attr_reader :environment, :command, :status, :stdout, :stderr, :stdin, :cwd, :timeout
23
26
 
24
27
  def initialize(command, options={})
25
28
  @environment = {"LC_ALL" => "C"}
@@ -29,6 +32,7 @@ module MCollective
29
32
  @stderr = ""
30
33
  @stdin = nil
31
34
  @cwd = Dir.tmpdir
35
+ @timeout = nil
32
36
 
33
37
  options.each do |opt, val|
34
38
  case opt.to_s
@@ -54,6 +58,9 @@ module MCollective
54
58
  else
55
59
  @environment.merge!(val.dup)
56
60
  end
61
+ when "timeout"
62
+ raise "timeout should be a positive integer or the symbol :on_thread_exit symbol" unless val.eql?(:on_thread_exit) || ( val.is_a?(Fixnum) && val>0 )
63
+ @timeout = val
57
64
  end
58
65
  end
59
66
  end
@@ -67,21 +74,51 @@ module MCollective
67
74
 
68
75
  opts["stdin"] = @stdin if @stdin
69
76
 
70
- # Running waitpid on the cid here will start a thread
71
- # with the waitpid in it, this way even if the thread
72
- # that started this process gets killed due to agent
73
- # timeout or such there will still be a waitpid waiting
74
- # for the child to exit and not leave zombies.
77
+
78
+ thread = Thread.current
79
+ # Start a double fork and exec with systemu which implies a guard thread.
80
+ # If a valid timeout is configured the guard thread will terminate the
81
+ # executing process and reap the pid.
82
+ # If no timeout is specified the process will run to completion with the
83
+ # guard thread reaping the pid on completion.
75
84
  @status = systemu(@command, opts) do |cid|
76
85
  begin
77
- sleep 1
78
- Process::waitpid(cid)
86
+ if timeout.is_a?(Fixnum)
87
+ # wait for the specified timeout
88
+ sleep timeout
89
+ else
90
+ # sleep while the agent thread is still alive
91
+ while(thread.alive?)
92
+ sleep 0.1
93
+ end
94
+ end
95
+
96
+ # if the process is still running
97
+ if (Process.kill(0, cid))
98
+ # and a timeout was specified
99
+ if timeout
100
+ if Util.windows?
101
+ Process.kill('KILL', cid)
102
+ else
103
+ # Kill the process
104
+ Process.kill('TERM', cid)
105
+ sleep 2
106
+ Process.kill('KILL', cid) if (Process.kill(0, cid))
107
+ end
108
+ end
109
+ # only wait if the parent thread is dead
110
+ Process.waitpid(cid) unless thread.alive?
111
+ end
79
112
  rescue SystemExit
113
+ rescue Errno::ESRCH
80
114
  rescue Errno::ECHILD
115
+ Log.warn("Could not reap process '#{cid}'.")
81
116
  rescue Exception => e
82
117
  Log.info("Unexpected exception received while waiting for child process: #{e.class}: #{e}")
83
118
  end
84
119
  end
120
+ @status.thread.kill
121
+ @status
85
122
  end
86
123
  end
87
124
  end
@@ -430,7 +430,7 @@ module MCollective
430
430
  # Code originally from Puppet
431
431
  def self.versioncmp(version_a, version_b)
432
432
  vre = /[-.]|\d+|[^-.\d]+/
433
- ax = version_a.scan(vre)
433
+ ax = version_a.scan(vre)
434
434
  bx = version_b.scan(vre)
435
435
 
436
436
  while (ax.length>0 && bx.length>0)
@@ -456,5 +456,41 @@ module MCollective
456
456
 
457
457
  version_a <=> version_b;
458
458
  end
459
+
460
+ # we should really use Pathname#absolute? but it's not in all the
461
+ # ruby versions we support and it comes down to roughly this
462
+ def self.absolute_path?(path, separator=File::SEPARATOR, alt_separator=File::ALT_SEPARATOR)
463
+ if alt_separator
464
+ path_matcher = /^([a-zA-Z]:){0,1}[#{Regexp.quote alt_separator}#{Regexp.quote separator}]/
465
+ else
466
+ path_matcher = /^#{Regexp.quote separator}/
467
+ end
468
+
469
+ !!path.match(path_matcher)
470
+ end
471
+
472
+ # Converts a string into a boolean value
473
+ # Strings matching 1,y,yes,true or t will return TrueClass
474
+ # Any other value will return FalseClass
475
+ def self.str_to_bool(val)
476
+ clean_val = val.to_s.strip
477
+ if clean_val =~ /^(1|yes|true|y|t)$/i
478
+ return true
479
+ elsif clean_val =~ /^(0|no|false|n|f)$/i
480
+ return false
481
+ else
482
+ raise("Cannot convert string value '#{clean_val}' into a boolean.")
483
+ end
484
+ end
485
+
486
+ # Looks up the template directory and returns its full path
487
+ def self.templatepath(template_file)
488
+ config_dir = File.dirname(Config.instance.configfile)
489
+ template_path = File.join(config_dir, template_file)
490
+ return template_path if File.exists?(template_path)
491
+
492
+ template_path = File.join("/etc/mcollective", template_file)
493
+ return template_path
494
+ end
459
495
  end
460
496
  end