Module | MCollective::Util |
In: |
lib/mcollective/util.rb
|
Some basic utility helper methods useful to clients, agents, runner etc.
Picks a config file defaults to ~/.mcollective else /etc/mcollective/client.cfg
# File lib/mcollective/util.rb, line 140 140: def self.config_file_for_user 141: # expand_path is pretty lame, it relies on HOME environment 142: # which isnt't always there so just handling all exceptions 143: # here as cant find reverting to default 144: begin 145: config = File.expand_path("~/.mcollective") 146: 147: unless File.readable?(config) && File.file?(config) 148: config = "/etc/mcollective/client.cfg" 149: end 150: rescue Exception => e 151: config = "/etc/mcollective/client.cfg" 152: end 153: 154: return config 155: end
Creates a standard options hash
# File lib/mcollective/util.rb, line 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} 165: end
Creates an empty filter
# File lib/mcollective/util.rb, line 130 130: def self.empty_filter 131: {"fact" => [], 132: "cf_class" => [], 133: "agent" => [], 134: "identity" => [], 135: "compound" => []} 136: end
Checks if the passed in filter is an empty one
# File lib/mcollective/util.rb, line 125 125: def self.empty_filter?(filter) 126: filter == empty_filter || filter == {} 127: end
# File lib/mcollective/util.rb, line 250 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 261: 262: return Util.has_fact?(name,value, optype).to_s 263: else 264: return Util.has_cf_class?(expression.values.first) 265: end 266: 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 61 61: def self.get_fact(fact) 62: Facts.get_fact(fact) 63: 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 8 8: def self.has_agent?(agent) 9: agent = Regexp.new(agent.gsub("\/", "")) if agent.match("^/") 10: 11: if agent.is_a?(Regexp) 12: if Agents.agentlist.grep(agent).size > 0 13: return true 14: else 15: return false 16: end 17: else 18: return Agents.agentlist.include?(agent) 19: end 20: 21: false 22: 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 38 38: def self.has_cf_class?(klass) 39: klass = Regexp.new(klass.gsub("\/", "")) if klass.match("^/") 40: cfile = Config.instance.classesfile 41: 42: Log.debug("Looking for configuration management classes in #{cfile}") 43: 44: begin 45: File.readlines(cfile).each do |k| 46: if klass.is_a?(Regexp) 47: return true if k.chomp.match(klass) 48: else 49: return true if k.chomp == klass 50: end 51: end 52: rescue Exception => e 53: Log.warn("Parsing classes file '#{cfile}' failed: #{e.class}: #{e}") 54: end 55: 56: false 57: 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 69 69: def self.has_fact?(fact, value, operator) 70: 71: Log.debug("Comparing #{fact} #{operator} #{value}") 72: Log.debug("where :fact = '#{fact}', :operator = '#{operator}', :value = '#{value}'") 73: 74: fact = Facts[fact] 75: return false if fact.nil? 76: 77: fact = fact.clone 78: 79: if operator == '=~' 80: # to maintain backward compat we send the value 81: # as /.../ which is what 1.0.x needed. this strips 82: # off the /'s wich is what we need here 83: if value =~ /^\/(.+)\/$/ 84: value = $1 85: end 86: 87: return true if fact.match(Regexp.new(value)) 88: 89: elsif operator == "==" 90: return true if fact == value 91: 92: elsif ['<=', '>=', '<', '>', '!='].include?(operator) 93: # Yuk - need to type cast, but to_i and to_f are overzealous 94: if value =~ /^[0-9]+$/ && fact =~ /^[0-9]+$/ 95: fact = Integer(fact) 96: value = Integer(value) 97: elsif value =~ /^[0-9]+.[0-9]+$/ && fact =~ /^[0-9]+.[0-9]+$/ 98: fact = Float(fact) 99: value = Float(value) 100: end 101: 102: return true if eval("fact #{operator} value") 103: end 104: 105: false 106: 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 112 112: def self.has_identity?(identity) 113: identity = Regexp.new(identity.gsub("\/", "")) if identity.match("^/") 114: 115: if identity.is_a?(Regexp) 116: return Config.instance.identity.match(identity) 117: else 118: return true if Config.instance.identity == identity 119: end 120: 121: false 122: end
Wrapper around PluginManager.loadclass
# File lib/mcollective/util.rb, line 206 206: def self.loadclass(klass) 207: PluginManager.loadclass(klass) 208: end
# File lib/mcollective/util.rb, line 167 167: def self.make_subscriptions(agent, type, collective=nil) 168: config = Config.instance 169: 170: raise("Unknown target type #{type}") unless [:broadcast, :directed, :reply].include?(type) 171: 172: if collective.nil? 173: config.collectives.map do |c| 174: {:agent => agent, :type => type, :collective => c} 175: end 176: else 177: raise("Unknown collective '#{collective}' known collectives are '#{config.collectives.join ', '}'") unless config.collectives.include?(collective) 178: 179: [{:agent => agent, :type => type, :collective => collective}] 180: end 181: end
Parse a fact filter string like foo=bar into the tuple hash thats needed
# File lib/mcollective/util.rb, line 211 211: def self.parse_fact_string(fact) 212: if fact =~ /^([^ ]+?)[ ]*=>[ ]*(.+)/ 213: return {:fact => $1, :value => $2, :operator => '>=' } 214: elsif fact =~ /^([^ ]+?)[ ]*=<[ ]*(.+)/ 215: return {:fact => $1, :value => $2, :operator => '<=' } 216: elsif fact =~ /^([^ ]+?)[ ]*(<=|>=|<|>|!=|==|=~)[ ]*(.+)/ 217: return {:fact => $1, :value => $3, :operator => $2 } 218: elsif fact =~ /^(.+?)[ ]*=[ ]*\/(.+)\/$/ 219: return {:fact => $1, :value => "/#{$2}/", :operator => '=~' } 220: elsif fact =~ /^([^= ]+?)[ ]*=[ ]*(.+)/ 221: return {:fact => $1, :value => $2, :operator => '==' } 222: else 223: raise "Could not parse fact #{fact} it does not appear to be in a valid format" 224: end 225: end
Returns the current ruby version as per RUBY_VERSION, mostly doing this here to aid testing
# File lib/mcollective/util.rb, line 270 270: def self.ruby_version 271: RUBY_VERSION 272: 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 28 28: def self.setup_windows_sleeper 29: Thread.new { loop { sleep 1 } } if Util.windows? 30: 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 230 230: def self.shellescape(str) 231: return "''" if str.empty? 232: 233: str = str.dup 234: 235: # Process as a single byte sequence because not all shell 236: # implementations are multibyte aware. 237: str.gsub!(/([^A-Za-z0-9_\-.,:\/@\n])/n, "\\\\\\1") 238: 239: # A LF cannot be escaped with a backslash because a backslash + LF 240: # combo is regarded as line continuation and simply ignored. 241: str.gsub!(/\n/, "'\n'") 242: 243: return str 244: end
Helper to subscribe to a topic on multiple collectives or just one
# File lib/mcollective/util.rb, line 184 184: def self.subscribe(targets) 185: connection = PluginManager["connector_plugin"] 186: 187: targets = [targets].flatten 188: 189: targets.each do |target| 190: connection.subscribe(target[:agent], target[:type], target[:collective]) 191: end 192: end
Helper to unsubscribe to a topic on multiple collectives or just one
# File lib/mcollective/util.rb, line 195 195: def self.unsubscribe(targets) 196: connection = PluginManager["connector_plugin"] 197: 198: targets = [targets].flatten 199: 200: targets.each do |target| 201: connection.unsubscribe(target[:agent], target[:type], target[:collective]) 202: end 203: end