| 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