Module MCollective::Util
In: lib/mcollective/util.rb

Some basic utility helper methods useful to clients, agents, runner etc.

Methods

Public Class methods

Picks a config file defaults to ~/.mcollective else /etc/mcollective/client.cfg

[Source]

     # File lib/mcollective/util.rb, line 132
132:     def self.config_file_for_user
133:       # expand_path is pretty lame, it relies on HOME environment
134:       # which isnt't always there so just handling all exceptions
135:       # here as cant find reverting to default
136:       begin
137:         config = File.expand_path("~/.mcollective")
138: 
139:         unless File.readable?(config) && File.file?(config)
140:           config = "/etc/mcollective/client.cfg"
141:         end
142:       rescue Exception => e
143:         config = "/etc/mcollective/client.cfg"
144:       end
145: 
146:       return config
147:     end

Creates a standard options hash

[Source]

     # File lib/mcollective/util.rb, line 150
150:     def self.default_options
151:       {:verbose     => false,
152:         :disctimeout => 2,
153:         :timeout     => 5,
154:         :config      => config_file_for_user,
155:         :collective  => nil,
156:         :filter      => empty_filter}
157:     end

Creates an empty filter

[Source]

     # File lib/mcollective/util.rb, line 122
122:     def self.empty_filter
123:       {"fact"     => [],
124:         "cf_class" => [],
125:         "agent"    => [],
126:         "identity" => [],
127:         "compound" => []}
128:     end

Checks if the passed in filter is an empty one

[Source]

     # File lib/mcollective/util.rb, line 117
117:     def self.empty_filter?(filter)
118:       filter == empty_filter || filter == {}
119:     end

[Source]

     # File lib/mcollective/util.rb, line 238
238:     def self.eval_compound_statement(expression)
239:       if expression.values.first =~ /^\//
240:         return Util.has_cf_class?(expression.values.first)
241:       elsif expression.values.first =~ />=|<=|=|<|>/
242:         optype = expression.values.first.match(/>=|<=|=|<|>/)
243:         name, value = expression.values.first.split(optype[0])
244:         unless value.split("")[0] == "/"
245:           optype[0] == "=" ? optype = "==" : optype = optype[0]
246:         else
247:           optype = "=~"
248:         end
249: 
250:         return Util.has_fact?(name,value, optype).to_s
251:       else
252:         return Util.has_cf_class?(expression.values.first)
253:       end
254:     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

[Source]

    # File lib/mcollective/util.rb, line 53
53:     def self.get_fact(fact)
54:       Facts.get_fact(fact)
55:     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

[Source]

    # 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

[Source]

    # 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:       begin
37:         File.readlines(cfile).each do |k|
38:           if klass.is_a?(Regexp)
39:             return true if k.chomp.match(klass)
40:           else
41:             return true if k.chomp == klass
42:           end
43:         end
44:       rescue Exception => e
45:         Log.warn("Parsing classes file '#{cfile}' failed: #{e.class}: #{e}")
46:       end
47: 
48:       false
49:     end

Compares fact == value,

If the passed value starts with a / it‘s assumed to be regex and will use regex to match

[Source]

    # File lib/mcollective/util.rb, line 61
61:     def self.has_fact?(fact, value, operator)
62: 
63:       Log.debug("Comparing #{fact} #{operator} #{value}")
64:       Log.debug("where :fact = '#{fact}', :operator = '#{operator}', :value = '#{value}'")
65: 
66:       fact = Facts[fact]
67:       return false if fact.nil?
68: 
69:       fact = fact.clone
70: 
71:       if operator == '=~'
72:         # to maintain backward compat we send the value
73:         # as /.../ which is what 1.0.x needed.  this strips
74:         # off the /'s wich is what we need here
75:         if value =~ /^\/(.+)\/$/
76:           value = $1
77:         end
78: 
79:         return true if fact.match(Regexp.new(value))
80: 
81:       elsif operator == "=="
82:         return true if fact == value
83: 
84:       elsif ['<=', '>=', '<', '>', '!='].include?(operator)
85:         # Yuk - need to type cast, but to_i and to_f are overzealous
86:         if value =~ /^[0-9]+$/ && fact =~ /^[0-9]+$/
87:           fact = Integer(fact)
88:           value = Integer(value)
89:         elsif value =~ /^[0-9]+.[0-9]+$/ && fact =~ /^[0-9]+.[0-9]+$/
90:           fact = Float(fact)
91:           value = Float(value)
92:         end
93: 
94:         return true if eval("fact #{operator} value")
95:       end
96: 
97:       false
98:     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

[Source]

     # File lib/mcollective/util.rb, line 104
104:     def self.has_identity?(identity)
105:       identity = Regexp.new(identity.gsub("\/", "")) if identity.match("^/")
106: 
107:       if identity.is_a?(Regexp)
108:         return Config.instance.identity.match(identity)
109:       else
110:         return true if Config.instance.identity == identity
111:       end
112: 
113:       false
114:     end

Wrapper around PluginManager.loadclass

[Source]

     # File lib/mcollective/util.rb, line 198
198:     def self.loadclass(klass)
199:       PluginManager.loadclass(klass)
200:     end

[Source]

     # File lib/mcollective/util.rb, line 159
159:     def self.make_subscriptions(agent, type, collective=nil)
160:       config = Config.instance
161: 
162:       raise("Unknown target type #{type}") unless [:broadcast, :directed, :reply].include?(type)
163: 
164:       if collective.nil?
165:         config.collectives.map do |c|
166:           {:agent => agent, :type => type, :collective => c}
167:         end
168:       else
169:         raise("Unknown collective '#{collective}' known collectives are '#{config.collectives.join ', '}'") unless config.collectives.include?(collective)
170: 
171:         [{:agent => agent, :type => type, :collective => collective}]
172:       end
173:     end

Parse a fact filter string like foo=bar into the tuple hash thats needed

[Source]

     # File lib/mcollective/util.rb, line 203
203:     def self.parse_fact_string(fact)
204:       if fact =~ /^([^ ]+?)[ ]*=>[ ]*(.+)/
205:         return {:fact => $1, :value => $2, :operator => '>=' }
206:       elsif fact =~ /^([^ ]+?)[ ]*=<[ ]*(.+)/
207:         return {:fact => $1, :value => $2, :operator => '<=' }
208:       elsif fact =~ /^([^ ]+?)[ ]*(<=|>=|<|>|!=|==|=~)[ ]*(.+)/
209:         return {:fact => $1, :value => $3, :operator => $2 }
210:       elsif fact =~ /^(.+?)[ ]*=[ ]*\/(.+)\/$/
211:         return {:fact => $1, :value => "/#{$2}/", :operator => '=~' }
212:       elsif fact =~ /^([^= ]+?)[ ]*=[ ]*(.+)/
213:         return {:fact => $1, :value => $2, :operator => '==' }
214:       else
215:         raise "Could not parse fact #{fact} it does not appear to be in a valid format"
216:       end
217:     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

[Source]

     # File lib/mcollective/util.rb, line 222
222:     def self.shellescape(str)
223:       return "''" if str.empty?
224: 
225:       str = str.dup
226: 
227:       # Process as a single byte sequence because not all shell
228:       # implementations are multibyte aware.
229:       str.gsub!(/([^A-Za-z0-9_\-.,:\/@\n])/n, "\\\\\\1")
230: 
231:       # A LF cannot be escaped with a backslash because a backslash + LF
232:       # combo is regarded as line continuation and simply ignored.
233:       str.gsub!(/\n/, "'\n'")
234: 
235:       return str
236:     end

Helper to subscribe to a topic on multiple collectives or just one

[Source]

     # File lib/mcollective/util.rb, line 176
176:     def self.subscribe(targets)
177:       connection = PluginManager["connector_plugin"]
178: 
179:       targets = [targets].flatten
180: 
181:       targets.each do |target|
182:         connection.subscribe(target[:agent], target[:type], target[:collective])
183:       end
184:     end

Helper to unsubscribe to a topic on multiple collectives or just one

[Source]

     # File lib/mcollective/util.rb, line 187
187:     def self.unsubscribe(targets)
188:       connection = PluginManager["connector_plugin"]
189: 
190:       targets = [targets].flatten
191: 
192:       targets.each do |target|
193:         connection.unsubscribe(target[:agent], target[:type], target[:collective])
194:       end
195:     end

[Validate]