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 127 127: def self.config_file_for_user 128: # expand_path is pretty lame, it relies on HOME environment 129: # which isnt't always there so just handling all exceptions 130: # here as cant find reverting to default 131: begin 132: config = File.expand_path("~/.mcollective") 133: 134: unless File.readable?(config) && File.file?(config) 135: config = "/etc/mcollective/client.cfg" 136: end 137: rescue Exception => e 138: config = "/etc/mcollective/client.cfg" 139: end 140: 141: return config 142: end
Creates a standard options hash
# File lib/mcollective/util.rb, line 145 145: def self.default_options 146: {:verbose => false, 147: :disctimeout => 2, 148: :timeout => 5, 149: :config => config_file_for_user, 150: :collective => nil, 151: :filter => empty_filter} 152: end
Creates an empty filter
# File lib/mcollective/util.rb, line 118 118: def self.empty_filter 119: {"fact" => [], 120: "cf_class" => [], 121: "agent" => [], 122: "identity" => []} 123: end
Checks if the passed in filter is an empty one
# File lib/mcollective/util.rb, line 113 113: def self.empty_filter?(filter) 114: filter == empty_filter || filter == {} 115: 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 49 49: def self.get_fact(fact) 50: Facts.get_fact(fact) 51: 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 30 30: def self.has_cf_class?(klass) 31: klass = Regexp.new(klass.gsub("\/", "")) if klass.match("^/") 32: cfile = Config.instance.classesfile 33: 34: Log.debug("Looking for configuration management classes in #{cfile}") 35: 36: File.readlines(cfile).each do |k| 37: if klass.is_a?(Regexp) 38: return true if k.chomp.match(klass) 39: else 40: return true if k.chomp == klass 41: end 42: end 43: 44: false 45: 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 57 57: def self.has_fact?(fact, value, operator) 58: 59: Log.debug("Comparing #{fact} #{operator} #{value}") 60: Log.debug("where :fact = '#{fact}', :operator = '#{operator}', :value = '#{value}'") 61: 62: fact = Facts[fact] 63: return false if fact.nil? 64: 65: fact = fact.clone 66: 67: if operator == '=~' 68: # to maintain backward compat we send the value 69: # as /.../ which is what 1.0.x needed. this strips 70: # off the /'s wich is what we need here 71: if value =~ /^\/(.+)\/$/ 72: value = $1 73: end 74: 75: return true if fact.match(Regexp.new(value)) 76: 77: elsif operator == "==" 78: return true if fact == value 79: 80: elsif ['<=', '>=', '<', '>', '!='].include?(operator) 81: # Yuk - need to type cast, but to_i and to_f are overzealous 82: if value =~ /^[0-9]+$/ && fact =~ /^[0-9]+$/ 83: fact = Integer(fact) 84: value = Integer(value) 85: elsif value =~ /^[0-9]+.[0-9]+$/ && fact =~ /^[0-9]+.[0-9]+$/ 86: fact = Float(fact) 87: value = Float(value) 88: end 89: 90: return true if eval("fact #{operator} value") 91: end 92: 93: false 94: 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 100 100: def self.has_identity?(identity) 101: identity = Regexp.new(identity.gsub("\/", "")) if identity.match("^/") 102: 103: if identity.is_a?(Regexp) 104: return Config.instance.identity.match(identity) 105: else 106: return true if Config.instance.identity == identity 107: end 108: 109: false 110: end
Wrapper around PluginManager.loadclass
# File lib/mcollective/util.rb, line 202 202: def self.loadclass(klass) 203: PluginManager.loadclass(klass) 204: end
Constructs an array of the full target names based on topicprefix, topicsep and collectives config options.
If given a collective name it will return a single target aimed at just the one collective
# File lib/mcollective/util.rb, line 159 159: def self.make_target(agent, type, collective=nil) 160: config = Config.instance 161: 162: raise("Unknown target type #{type}") unless type == :command || type == :reply 163: 164: if collective.nil? 165: config.collectives.map do |c| 166: ["#{config.topicprefix}#{c}", agent, type].join(config.topicsep) 167: end 168: else 169: raise("Unknown collective '#{collective}' known collectives are '#{config.collectives.join ', '}'") unless config.collectives.include?(collective) 170: 171: ["#{config.topicprefix}#{collective}", agent, type].join(config.topicsep) 172: end 173: end
Parse a fact filter string like foo=bar into the tuple hash thats needed
# File lib/mcollective/util.rb, line 207 207: def self.parse_fact_string(fact) 208: if fact =~ /^([^ ]+?)[ ]*=>[ ]*(.+)/ 209: return {:fact => $1, :value => $2, :operator => '>=' } 210: elsif fact =~ /^([^ ]+?)[ ]*=<[ ]*(.+)/ 211: return {:fact => $1, :value => $2, :operator => '<=' } 212: elsif fact =~ /^([^ ]+?)[ ]*(<=|>=|<|>|!=|==|=~)[ ]*(.+)/ 213: return {:fact => $1, :value => $3, :operator => $2 } 214: elsif fact =~ /^(.+?)[ ]*=[ ]*\/(.+)\/$/ 215: return {:fact => $1, :value => "/#{$2}/", :operator => '=~' } 216: elsif fact =~ /^([^= ]+?)[ ]*=[ ]*(.+)/ 217: return {:fact => $1, :value => $2, :operator => '==' } 218: end 219: 220: return false 221: end
Parse the msgtarget as sent in 1.1.4 and newer to figure out the agent and collective that a request is targeted at
# File lib/mcollective/util.rb, line 244 244: def self.parse_msgtarget(target) 245: sep = Regexp.escape(Config.instance.topicsep) 246: prefix = Regexp.escape(Config.instance.topicprefix) 247: regex = "#{prefix}(.+?)#{sep}(.+?)#{sep}command" 248: 249: if target.match(regex) 250: return {:collective => $1, :agent => $2} 251: else 252: raise "Failed to handle message, could not figure out agent and collective from #{target}" 253: end 254: 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 226 226: def self.shellescape(str) 227: return "''" if str.empty? 228: 229: str = str.dup 230: 231: # Process as a single byte sequence because not all shell 232: # implementations are multibyte aware. 233: str.gsub!(/([^A-Za-z0-9_\-.,:\/@\n])/n, "\\\\\\1") 234: 235: # A LF cannot be escaped with a backslash because a backslash + LF 236: # combo is regarded as line continuation and simply ignored. 237: str.gsub!(/\n/, "'\n'") 238: 239: return str 240: end
Helper to subscribe to a topic on multiple collectives or just one
# File lib/mcollective/util.rb, line 176 176: def self.subscribe(topics) 177: connection = PluginManager["connector_plugin"] 178: 179: if topics.is_a?(Array) 180: topics.each do |topic| 181: connection.subscribe(topic) 182: end 183: else 184: connection.subscribe(topics) 185: end 186: end
Helper to unsubscribe to a topic on multiple collectives or just one
# File lib/mcollective/util.rb, line 189 189: def self.unsubscribe(topics) 190: connection = PluginManager["connector_plugin"] 191: 192: if topics.is_a?(Array) 193: topics.each do |topic| 194: connection.unsubscribe(topic) 195: end 196: else 197: connection.unsubscribe(topics) 198: end 199: end