| 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