X-Git-Url: https://review.fuel-infra.org/gitweb?a=blobdiff_plain;f=doc%2Fclasses%2FMCollective%2FUtil.html;fp=doc%2Fclasses%2FMCollective%2FUtil.html;h=a7a701895307ba4adfd7c63c72d3f0fd3cdfcf08;hb=d1f1649ba43c5cbc43c4beb2380096ba051d646a;hp=0000000000000000000000000000000000000000;hpb=8a3fe7daeecccf43dd71c59371c5005400d35101;p=packages%2Fprecise%2Fmcollective.git diff --git a/doc/classes/MCollective/Util.html b/doc/classes/MCollective/Util.html new file mode 100644 index 0000000..a7a7018 --- /dev/null +++ b/doc/classes/MCollective/Util.html @@ -0,0 +1,1289 @@ + + + + + + Module: MCollective::Util + + + + + + + + + + +
+ + + + + + + + + + +
ModuleMCollective::Util
In: + + lib/mcollective/util.rb + +
+
+
+ + +
+ + + +
+ +
+

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

+ +
+ + +
+ +
+

Methods

+ + +
+ +
+ + + + +
+ + + + + + + + + +
+

Public Class methods

+ +
+ + + + +
+

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

+

[Source]

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

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

+

[Source]

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

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

+

[Source]

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

+Helper to return a string in specific color +

+

[Source]

+
+
+     # File lib/mcollective/util.rb, line 288
+288:     def self.colorize(code, msg)
+289:       "%s%s%s" % [ color(code), msg, color(:reset) ]
+290:     end
+
+
+
+
+ +
+ + + + +
+

+Checks in PATH returns true if the command is found +

+

[Source]

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

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

+

[Source]

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

+Creates a standard options hash +

+

[Source]

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

+Creates an empty filter +

+

[Source]

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

+Checks if the passed in filter is an empty one +

+

[Source]

+
+
+     # File lib/mcollective/util.rb, line 127
+127:     def self.empty_filter?(filter)
+128:       filter == empty_filter || filter == {}
+129:     end
+
+
+
+
+ +
+ + + + +
+

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

+

[Source]

+
+
+    # File lib/mcollective/util.rb, line 63
+63:     def self.get_fact(fact)
+64:       Facts.get_fact(fact)
+65:     end
+
+
+
+
+ +
+ + + + +
+

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

+

[Source]

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

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

+

[Source]

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

+Compares fact == value, +

+

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

+

[Source]

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

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

+

[Source]

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

+Wrapper around PluginManager.loadclass +

+

[Source]

+
+
+     # File lib/mcollective/util.rb, line 224
+224:     def self.loadclass(klass)
+225:       PluginManager.loadclass(klass)
+226:     end
+
+
+
+
+ +
+ + + + +
+

[Source]

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

[Source]

+
+
+     # File lib/mcollective/util.rb, line 298
+298:     def self.mcollective_version
+299:       MCollective::VERSION
+300:     end
+
+
+
+
+ +
+ + + + +
+

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

+

[Source]

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

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

+

[Source]

+
+
+     # File lib/mcollective/util.rb, line 294
+294:     def self.ruby_version
+295:       RUBY_VERSION
+296:     end
+
+
+
+
+ +
+ + + + +
+

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

+

[Source]

+
+
+    # File lib/mcollective/util.rb, line 30
+30:     def self.setup_windows_sleeper
+31:       Thread.new { loop { sleep 1 } } if Util.windows?
+32:     end
+
+
+
+
+ +
+ + + + +
+

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

+

[Source]

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

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

+

[Source]

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

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

+

[Source]

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

+Looks up and interprolate the hash values into a i18n string +

+

[Source]

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

+Looks up the template directory and returns its full path +

+

[Source]

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

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

+

[Source]

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

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

+

[Source]

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

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

+

[Source]

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

[Source]

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

+Returns the PuppetLabs mcollective path for windows +

+

[Source]

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

[Validate]

+
+ + + \ No newline at end of file