X-Git-Url: https://review.fuel-infra.org/gitweb?a=blobdiff_plain;ds=sidebyside;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 | +
In: | +
+
+ lib/mcollective/util.rb
+
+ + |
+
+Some basic utility helper methods useful to clients, agents, runner etc. +
+ ++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 ++
+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 ++
+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 => "[31m", +274: :green => "[32m", +275: :yellow => "[33m", +276: :cyan => "[36m", +277: :bold => "[1m", +278: :reset => "[0m"} +279: +280: if colorize +281: return colors[code] || "" +282: else +283: return "" +284: end +285: end ++
+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 ++
+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 ++
+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 ++
+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 ++
+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 ++
+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 ++
+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 ++
+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 ++
+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 ++
+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 ++
+Wrapper around PluginManager.loadclass +
+ ++ # File lib/mcollective/util.rb, line 224 +224: def self.loadclass(klass) +225: PluginManager.loadclass(klass) +226: end ++
+ # 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 ++
+ # 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 +
+ ++ # 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 +
+ ++ # 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 +
+ ++ # 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 +
+ ++ # 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 +
+ ++ # 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 +
+ ++ # 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 +
+ ++ # 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 +
+ ++ # 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 +
+ ++ # 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 +
+ ++ # 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 +
+ ++ # 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 ++
+ # File lib/mcollective/util.rb, line 264 +264: def self.windows? +265: !!(RbConfig::CONFIG['host_os'] =~ /mswin|win32|dos|mingw|cygwin/i) +266: end ++