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

Creates a standard options hash

[Source]

     # File lib/mcollective/util.rb, line 149
149:         def self.default_options
150:             {:verbose     => false,
151:              :disctimeout => 2,
152:              :timeout     => 5,
153:              :config      => config_file_for_user,
154:              :collective  => nil,
155:              :filter      => empty_filter}
156:         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:         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

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 218
218:         def self.loadclass(klass)
219:             PluginManager.loadclass(klass)
220:         end

[Source]

     # File lib/mcollective/util.rb, line 179
179:         def self.make_subscriptions(agent, type, collective=nil)
180:             config = Config.instance
181: 
182:             raise("Unknown target type #{type}") unless [:broadcast, :directed, :reply].include?(type)
183: 
184:             if collective.nil?
185:                 config.collectives.map do |c|
186:                     {:agent => agent, :type => type, :collective => c}
187:                 end
188:             else
189:                 raise("Unknown collective '#{collective}' known collectives are '#{config.collectives.join ', '}'") unless config.collectives.include?(collective)
190: 
191:                 [{:agent => agent, :type => type, :collective => collective}]
192:             end
193:         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

[Source]

     # File lib/mcollective/util.rb, line 163
163:         def self.make_target(agent, type, collective=nil)
164:             config = Config.instance
165: 
166:             raise("Unknown target type #{type}") unless type == :command || type == :reply
167: 
168:             if collective.nil?
169:                 config.collectives.map do |c|
170:                     ["#{config.topicprefix}#{c}", agent, type].join(config.topicsep)
171:                 end
172:             else
173:                 raise("Unknown collective '#{collective}' known collectives are '#{config.collectives.join ', '}'") unless config.collectives.include?(collective)
174: 
175:                 ["#{config.topicprefix}#{collective}", agent, type].join(config.topicsep)
176:             end
177:         end

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

[Source]

     # File lib/mcollective/util.rb, line 223
223:         def self.parse_fact_string(fact)
224:             if fact =~ /^([^ ]+?)[ ]*=>[ ]*(.+)/
225:                 return {:fact => $1, :value => $2, :operator => '>=' }
226:             elsif fact =~ /^([^ ]+?)[ ]*=<[ ]*(.+)/
227:                 return {:fact => $1, :value => $2, :operator => '<=' }
228:             elsif fact =~ /^([^ ]+?)[ ]*(<=|>=|<|>|!=|==|=~)[ ]*(.+)/
229:                 return {:fact => $1, :value => $3, :operator => $2 }
230:             elsif fact =~ /^(.+?)[ ]*=[ ]*\/(.+)\/$/
231:                 return {:fact => $1, :value => "/#{$2}/", :operator => '=~' }
232:             elsif fact =~ /^([^= ]+?)[ ]*=[ ]*(.+)/
233:                 return {:fact => $1, :value => $2, :operator => '==' }
234:             end
235: 
236:             return false
237:         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

[Source]

     # File lib/mcollective/util.rb, line 260
260:         def self.parse_msgtarget(target)
261:             sep = Regexp.escape(Config.instance.topicsep)
262:             prefix = Regexp.escape(Config.instance.topicprefix)
263:             regex = "#{prefix}(.+?)#{sep}(.+?)#{sep}command"
264: 
265:             if target.match(regex)
266:                 return {:collective => $1, :agent => $2}
267:             else
268:                 raise "Failed to handle message, could not figure out agent and collective from #{target}"
269:             end
270:         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 242
242:         def self.shellescape(str)
243:             return "''" if str.empty?
244: 
245:             str = str.dup
246: 
247:             # Process as a single byte sequence because not all shell
248:             # implementations are multibyte aware.
249:             str.gsub!(/([^A-Za-z0-9_\-.,:\/@\n])/n, "\\\\\\1")
250: 
251:             # A LF cannot be escaped with a backslash because a backslash + LF
252:             # combo is regarded as line continuation and simply ignored.
253:             str.gsub!(/\n/, "'\n'")
254: 
255:             return str
256:         end

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

[Source]

     # File lib/mcollective/util.rb, line 196
196:         def self.subscribe(targets)
197:             connection = PluginManager["connector_plugin"]
198: 
199:             targets = [targets].flatten
200: 
201:             targets.each do |target|
202:                 connection.subscribe(target[:agent], target[:type], target[:collective])
203:             end
204:         end

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

[Source]

     # File lib/mcollective/util.rb, line 207
207:         def self.unsubscribe(targets)
208:             connection = PluginManager["connector_plugin"]
209: 
210:             targets = [targets].flatten
211: 
212:             targets.each do |target|
213:                 connection.unsubscribe(target[:agent], target[:type], target[:collective])
214:             end
215:         end

[Validate]