Class Index [+]

Quicksearch

MCollective::Util

Some basic utility helper methods useful to clients, agents, runner etc.

Public Class Methods

absolute_path?(path, separator=File::SEPARATOR, alt_separator=File::ALT_SEPARATOR) click to toggle source

we should really use Pathname#absolute? but it’s not in all the ruby versions we support and it comes down to roughly this

     # File lib/mcollective/util.rb, line 464
464:     def self.absolute_path?(path, separator=File::SEPARATOR, alt_separator=File::ALT_SEPARATOR)
465:       if alt_separator
466:         path_matcher = /^([a-zA-Z]:){0,1}[#{Regexp.quote alt_separator}#{Regexp.quote separator}]/
467:       else
468:         path_matcher = /^#{Regexp.quote separator}/
469:       end
470: 
471:       !!path.match(path_matcher)
472:     end
align_text(text, console_cols = nil, preamble = 5) click to toggle source

Returns an aligned_string of text relative to the size of the terminal window. If a line in the string exceeds the width of the terminal window the line will be chopped off at the whitespace chacter closest to the end of the line and prepended to the next line, keeping all indentation.

The terminal size is detected by default, but custom line widths can passed. All strings will also be left aligned with 5 whitespace characters by default.

     # File lib/mcollective/util.rb, line 310
310:     def self.align_text(text, console_cols = nil, preamble = 5)
311:       unless console_cols
312:         console_cols = terminal_dimensions[0]
313: 
314:         # if unknown size we default to the typical unix default
315:         console_cols = 80 if console_cols == 0
316:       end
317: 
318:       console_cols -= preamble
319: 
320:       # Return unaligned text if console window is too small
321:       return text if console_cols <= 0
322: 
323:       # If console is 0 this implies unknown so we assume the common
324:       # minimal unix configuration of 80 characters
325:       console_cols = 80 if console_cols <= 0
326: 
327:       text = text.split("\n")
328:       piece = ''
329:       whitespace = 0
330: 
331:       text.each_with_index do |line, i|
332:         whitespace = 0
333: 
334:         while whitespace < line.length && line[whitespace].chr == ' '
335:           whitespace += 1
336:         end
337: 
338:         # If the current line is empty, indent it so that a snippet
339:         # from the previous line is aligned correctly.
340:         if line == ""
341:           line = (" " * whitespace)
342:         end
343: 
344:         # If text was snipped from the previous line, prepend it to the
345:         # current line after any current indentation.
346:         if piece != ''
347:           # Reset whitespaces to 0 if there are more whitespaces than there are
348:           # console columns
349:           whitespace = 0 if whitespace >= console_cols
350: 
351:           # If the current line is empty and being prepended to, create a new
352:           # empty line in the text so that formatting is preserved.
353:           if text[i + 1] && line == (" " * whitespace)
354:             text.insert(i + 1, "")
355:           end
356: 
357:           # Add the snipped text to the current line
358:           line.insert(whitespace, "#{piece} ")
359:         end
360: 
361:         piece = ''
362: 
363:         # Compare the line length to the allowed line length.
364:         # If it exceeds it, snip the offending text from the line
365:         # and store it so that it can be prepended to the next line.
366:         if line.length > (console_cols + preamble)
367:           reverse = console_cols
368: 
369:           while line[reverse].chr != ' '
370:             reverse -= 1
371:           end
372: 
373:           piece = line.slice!(reverse, (line.length - 1)).lstrip
374:         end
375: 
376:         # If a snippet exists when all the columns in the text have been
377:         # updated, create a new line and append the snippet to it, using
378:         # the same left alignment as the last line in the text.
379:         if piece != '' && text[i+1].nil?
380:           text[i+1] = "#{' ' * (whitespace)}#{piece}"
381:           piece = ''
382:         end
383: 
384:         # Add the preamble to the line and add it to the text
385:         line = ((' ' * preamble) + line)
386:         text[i] = line
387:       end
388: 
389:       text.join("\n")
390:     end
color(code) click to toggle source

Return color codes, if the config color= option is false just return a empty string

     # File lib/mcollective/util.rb, line 270
270:     def self.color(code)
271:       colorize = Config.instance.color
272: 
273:       colors = {:red => "",
274:                 :green => "",
275:                 :yellow => "",
276:                 :cyan => "",
277:                 :bold => "",
278:                 :reset => ""}
279: 
280:       if colorize
281:         return colors[code] || ""
282:       else
283:         return ""
284:       end
285:     end
colorize(code, msg) click to toggle source

Helper to return a string in specific color

     # File lib/mcollective/util.rb, line 288
288:     def self.colorize(code, msg)
289:       "%s%s%s" % [ color(code), msg, color(:reset) ]
290:     end
command_in_path?(command) click to toggle source

Checks in PATH returns true if the command is found

     # File lib/mcollective/util.rb, line 417
417:     def self.command_in_path?(command)
418:       found = ENV["PATH"].split(File::PATH_SEPARATOR).map do |p|
419:         File.exist?(File.join(p, command))
420:       end
421: 
422:       found.include?(true)
423:     end
config_file_for_user() click to toggle source

Picks a config file defaults to ~/.mcollective else /etc/mcollective/client.cfg

     # File lib/mcollective/util.rb, line 148
148:     def self.config_file_for_user
149:       # expand_path is pretty lame, it relies on HOME environment
150:       # which isnt't always there so just handling all exceptions
151:       # here as cant find reverting to default
152:       begin
153:         config = File.expand_path("~/.mcollective")
154: 
155:         unless File.readable?(config) && File.file?(config)
156:           if self.windows?
157:             config = File.join(self.windows_prefix, "etc", "client.cfg")
158:           else
159:             config = "/etc/mcollective/client.cfg"
160:           end
161:         end
162:       rescue Exception => e
163:         if self.windows?
164:           config = File.join(self.windows_prefix, "etc", "client.cfg")
165:         else
166:           config = "/etc/mcollective/client.cfg"
167:         end
168:       end
169: 
170:       return config
171:     end
default_options() click to toggle source

Creates a standard options hash

     # File lib/mcollective/util.rb, line 174
174:     def self.default_options
175:       {:verbose           => false,
176:        :disctimeout       => nil,
177:        :timeout           => 5,
178:        :config            => config_file_for_user,
179:        :collective        => nil,
180:        :discovery_method  => nil,
181:        :discovery_options => Config.instance.default_discovery_options,
182:        :filter            => empty_filter}
183:     end
empty_filter() click to toggle source

Creates an empty filter

     # File lib/mcollective/util.rb, line 132
132:     def self.empty_filter
133:       {"fact"     => [],
134:        "cf_class" => [],
135:        "agent"    => [],
136:        "identity" => [],
137:        "compound" => []}
138:     end
empty_filter?(filter) click to toggle source

Checks if the passed in filter is an empty one

     # File lib/mcollective/util.rb, line 127
127:     def self.empty_filter?(filter)
128:       filter == empty_filter || filter == {}
129:     end
get_fact(fact) click to toggle source

Gets the value of a specific fact, mostly just a duplicate of MCollective::Facts.get_fact but it kind of goes with the other classes here

    # File lib/mcollective/util.rb, line 63
63:     def self.get_fact(fact)
64:       Facts.get_fact(fact)
65:     end
has_agent?(agent) click to toggle source

Finds out if this MCollective has an agent by the name passed

If the passed name starts with a / it’s assumed to be regex and will use regex to match

    # File lib/mcollective/util.rb, line 10
10:     def self.has_agent?(agent)
11:       agent = Regexp.new(agent.gsub("\/", "")) if agent.match("^/")
12: 
13:       if agent.is_a?(Regexp)
14:         if Agents.agentlist.grep(agent).size > 0
15:           return true
16:         else
17:           return false
18:         end
19:       else
20:         return Agents.agentlist.include?(agent)
21:       end
22: 
23:       false
24:     end
has_cf_class?(klass) click to toggle source

Checks if this node has a configuration management class by parsing the a text file with just a list of classes, recipes, roles etc. This is ala the classes.txt from puppet.

If the passed name starts with a / it’s assumed to be regex and will use regex to match

    # File lib/mcollective/util.rb, line 40
40:     def self.has_cf_class?(klass)
41:       klass = Regexp.new(klass.gsub("\/", "")) if klass.match("^/")
42:       cfile = Config.instance.classesfile
43: 
44:       Log.debug("Looking for configuration management classes in #{cfile}")
45: 
46:       begin
47:         File.readlines(cfile).each do |k|
48:           if klass.is_a?(Regexp)
49:             return true if k.chomp.match(klass)
50:           else
51:             return true if k.chomp == klass
52:           end
53:         end
54:       rescue Exception => e
55:         Log.warn("Parsing classes file '#{cfile}' failed: #{e.class}: #{e}")
56:       end
57: 
58:       false
59:     end
has_fact?(fact, value, operator) click to toggle source

Compares fact == value,

If the passed value starts with a / it’s assumed to be regex and will use regex to match

     # File lib/mcollective/util.rb, line 71
 71:     def self.has_fact?(fact, value, operator)
 72: 
 73:       Log.debug("Comparing #{fact} #{operator} #{value}")
 74:       Log.debug("where :fact = '#{fact}', :operator = '#{operator}', :value = '#{value}'")
 75: 
 76:       fact = Facts[fact]
 77:       return false if fact.nil?
 78: 
 79:       fact = fact.clone
 80: 
 81:       if operator == '=~'
 82:         # to maintain backward compat we send the value
 83:         # as /.../ which is what 1.0.x needed.  this strips
 84:         # off the /'s wich is what we need here
 85:         if value =~ /^\/(.+)\/$/
 86:           value = $1
 87:         end
 88: 
 89:         return true if fact.match(Regexp.new(value))
 90: 
 91:       elsif operator == "=="
 92:         return true if fact == value
 93: 
 94:       elsif ['<=', '>=', '<', '>', '!='].include?(operator)
 95:         # Yuk - need to type cast, but to_i and to_f are overzealous
 96:         if value =~ /^[0-9]+$/ && fact =~ /^[0-9]+$/
 97:           fact = Integer(fact)
 98:           value = Integer(value)
 99:         elsif value =~ /^[0-9]+.[0-9]+$/ && fact =~ /^[0-9]+.[0-9]+$/
100:           fact = Float(fact)
101:           value = Float(value)
102:         end
103: 
104:         return true if eval("fact #{operator} value")
105:       end
106: 
107:       false
108:     end
has_identity?(identity) click to toggle source

Checks if the configured identity matches the one supplied

If the passed name starts with a / it’s assumed to be regex and will use regex to match

     # File lib/mcollective/util.rb, line 114
114:     def self.has_identity?(identity)
115:       identity = Regexp.new(identity.gsub("\/", "")) if identity.match("^/")
116: 
117:       if identity.is_a?(Regexp)
118:         return Config.instance.identity.match(identity)
119:       else
120:         return true if Config.instance.identity == identity
121:       end
122: 
123:       false
124:     end
loadclass(klass) click to toggle source

Wrapper around PluginManager.loadclass

     # File lib/mcollective/util.rb, line 224
224:     def self.loadclass(klass)
225:       PluginManager.loadclass(klass)
226:     end
make_subscriptions(agent, type, collective=nil) click to toggle source

(Not documented)

     # File lib/mcollective/util.rb, line 185
185:     def self.make_subscriptions(agent, type, collective=nil)
186:       config = Config.instance
187: 
188:       raise("Unknown target type #{type}") unless [:broadcast, :directed, :reply].include?(type)
189: 
190:       if collective.nil?
191:         config.collectives.map do |c|
192:           {:agent => agent, :type => type, :collective => c}
193:         end
194:       else
195:         raise("Unknown collective '#{collective}' known collectives are '#{config.collectives.join ', '}'") unless config.collectives.include?(collective)
196: 
197:         [{:agent => agent, :type => type, :collective => collective}]
198:       end
199:     end
mcollective_version() click to toggle source

(Not documented)

     # File lib/mcollective/util.rb, line 298
298:     def self.mcollective_version
299:       MCollective::VERSION
300:     end
parse_fact_string(fact) click to toggle source

Parse a fact filter string like foo=bar into the tuple hash thats needed

     # File lib/mcollective/util.rb, line 229
229:     def self.parse_fact_string(fact)
230:       if fact =~ /^([^ ]+?)[ ]*=>[ ]*(.+)/
231:         return {:fact => $1, :value => $2, :operator => '>=' }
232:       elsif fact =~ /^([^ ]+?)[ ]*=<[ ]*(.+)/
233:         return {:fact => $1, :value => $2, :operator => '<=' }
234:       elsif fact =~ /^([^ ]+?)[ ]*(<=|>=|<|>|!=|==|=~)[ ]*(.+)/
235:         return {:fact => $1, :value => $3, :operator => $2 }
236:       elsif fact =~ /^(.+?)[ ]*=[ ]*\/(.+)\/$/
237:         return {:fact => $1, :value => "/#{$2}/", :operator => '=~' }
238:       elsif fact =~ /^([^= ]+?)[ ]*=[ ]*(.+)/
239:         return {:fact => $1, :value => $2, :operator => '==' }
240:       else
241:         raise "Could not parse fact #{fact} it does not appear to be in a valid format"
242:       end
243:     end
ruby_version() click to toggle source

Returns the current ruby version as per RUBY_VERSION, mostly doing this here to aid testing

     # File lib/mcollective/util.rb, line 294
294:     def self.ruby_version
295:       RUBY_VERSION
296:     end
setup_windows_sleeper() click to toggle source

On windows ^c can’t interrupt the VM if its blocking on IO, so this sets up a dummy thread that sleeps and this will have the end result of being interruptable at least once a second. This is a common pattern found in Rails etc

    # File lib/mcollective/util.rb, line 30
30:     def self.setup_windows_sleeper
31:       Thread.new { loop { sleep 1 } } if Util.windows?
32:     end
shellescape(str) click to toggle source

Escapes a string so it’s safe to use in system() or backticks

Taken from Shellwords#shellescape since it’s only in a few ruby versions

     # File lib/mcollective/util.rb, line 248
248:     def self.shellescape(str)
249:       return "''" if str.empty?
250: 
251:       str = str.dup
252: 
253:       # Process as a single byte sequence because not all shell
254:       # implementations are multibyte aware.
255:       str.gsub!(/([^A-Za-z0-9_\-.,:\/@\n])/n, "\\\\\\1")
256: 
257:       # A LF cannot be escaped with a backslash because a backslash + LF
258:       # combo is regarded as line continuation and simply ignored.
259:       str.gsub!(/\n/, "'\n'")
260: 
261:       return str
262:     end
str_to_bool(val) click to toggle source

Converts a string into a boolean value Strings matching 1,y,yes,true or t will return TrueClass Any other value will return FalseClass

     # File lib/mcollective/util.rb, line 477
477:     def self.str_to_bool(val)
478:       clean_val = val.to_s.strip
479:       if clean_val =~ /^(1|yes|true|y|t)$/i
480:         return  true
481:       elsif clean_val =~ /^(0|no|false|n|f)$/i
482:         return false
483:       else
484:         raise_code(:PLMC42, "Cannot convert string value '%{value}' into a boolean.", :error, :value => clean_val)
485:       end
486:     end
subscribe(targets) click to toggle source

Helper to subscribe to a topic on multiple collectives or just one

     # File lib/mcollective/util.rb, line 202
202:     def self.subscribe(targets)
203:       connection = PluginManager["connector_plugin"]
204: 
205:       targets = [targets].flatten
206: 
207:       targets.each do |target|
208:         connection.subscribe(target[:agent], target[:type], target[:collective])
209:       end
210:     end
t(msgid, args={}) click to toggle source

Looks up and interprolate the hash values into a i18n string

     # File lib/mcollective/util.rb, line 489
489:     def self.t(msgid, args={})
490:       if msgid.is_a?(Symbol)
491:         I18n.t("%s.pattern" % msgid, args)
492:       else
493:         I18n.t(msgid, args)
494:       end
495:     end
templatepath(template_file) click to toggle source

Looks up the template directory and returns its full path

     # File lib/mcollective/util.rb, line 498
498:     def self.templatepath(template_file)
499:       config_dir = File.dirname(Config.instance.configfile)
500:       template_path = File.join(config_dir, template_file)
501:       return template_path if File.exists?(template_path)
502: 
503:       template_path = File.join("/etc/mcollective", template_file)
504:       return template_path
505:     end
terminal_dimensions(stdout = STDOUT, environment = ENV) click to toggle source

Figures out the columns and lines of the current tty

Returns [0, 0] if it can’t figure it out or if you’re not running on a tty

     # File lib/mcollective/util.rb, line 396
396:     def self.terminal_dimensions(stdout = STDOUT, environment = ENV)
397:       return [0, 0] unless stdout.tty?
398: 
399:       return [80, 40] if Util.windows?
400: 
401:       if environment["COLUMNS"] && environment["LINES"]
402:         return [environment["COLUMNS"].to_i, environment["LINES"].to_i]
403: 
404:       elsif environment["TERM"] && command_in_path?("tput")
405:         return [`tput cols`.to_i, `tput lines`.to_i]
406: 
407:       elsif command_in_path?('stty')
408:         return `stty size`.scan(/\d+/).map {|s| s.to_i }
409:       else
410:         return [0, 0]
411:       end
412:     rescue
413:       [0, 0]
414:     end
unsubscribe(targets) click to toggle source

Helper to unsubscribe to a topic on multiple collectives or just one

     # File lib/mcollective/util.rb, line 213
213:     def self.unsubscribe(targets)
214:       connection = PluginManager["connector_plugin"]
215: 
216:       targets = [targets].flatten
217: 
218:       targets.each do |target|
219:         connection.unsubscribe(target[:agent], target[:type], target[:collective])
220:       end
221:     end
versioncmp(version_a, version_b) click to toggle source

compare two software versions as commonly found in package versions.

returns 0 if a == b returns -1 if a < b returns 1 if a > b

Code originally from Puppet

     # File lib/mcollective/util.rb, line 433
433:     def self.versioncmp(version_a, version_b)
434:       vre = /[-.]|\d+|[^-.\d]+/
435:       ax = version_a.scan(vre)
436:       bx = version_b.scan(vre)
437: 
438:       while (ax.length>0 && bx.length>0)
439:         a = ax.shift
440:         b = bx.shift
441: 
442:         if( a == b )                 then next
443:         elsif (a == '-' && b == '-') then next
444:         elsif (a == '-')             then return -1
445:         elsif (b == '-')             then return 1
446:         elsif (a == '.' && b == '.') then next
447:         elsif (a == '.' )            then return -1
448:         elsif (b == '.' )            then return 1
449:         elsif (a =~ /^\d+$/ && b =~ /^\d+$/) then
450:           if( a =~ /^0/ or b =~ /^0/ ) then
451:             return a.to_s.upcase <=> b.to_s.upcase
452:           end
453:           return a.to_i <=> b.to_i
454:         else
455:           return a.upcase <=> b.upcase
456:         end
457:       end
458: 
459:       version_a <=> version_b;
460:     end
windows?() click to toggle source

(Not documented)

     # File lib/mcollective/util.rb, line 264
264:     def self.windows?
265:       !!(RbConfig::CONFIG['host_os'] =~ /mswin|win32|dos|mingw|cygwin/i)
266:     end
windows_prefix() click to toggle source

Returns the PuppetLabs mcollective path for windows

     # File lib/mcollective/util.rb, line 141
141:     def self.windows_prefix
142:       require 'win32/dir'
143:       prefix = File.join(Dir::COMMON_APPDATA, "PuppetLabs", "mcollective")
144:     end

Disabled; run with --debug to generate this.

[Validate]

Generated with the Darkfish Rdoc Generator 1.1.6.