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

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

Methods

Public Class methods

Return color codes, if the config color= option is false just return a empty string

[Source]

     # File lib/mcollective/util.rb, line 254
254:     def self.color(code)
255:       colorize = Config.instance.color
256: 
257:       colors = {:red => "",
258:                 :green => "",
259:                 :yellow => "",
260:                 :cyan => "",
261:                 :bold => "",
262:                 :reset => ""}
263: 
264:       if colorize
265:         return colors[code] || ""
266:       else
267:         return ""
268:       end
269:     end

Helper to return a string in specific color

[Source]

     # File lib/mcollective/util.rb, line 272
272:     def self.colorize(code, msg)
273:       "%s%s%s" % [ color(code), msg, color(:reset) ]
274:     end

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

[Source]

     # 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

[Source]

     # File lib/mcollective/util.rb, line 158
158:     def self.default_options
159:       {:verbose           => false,
160:        :disctimeout       => nil,
161:        :timeout           => 5,
162:        :config            => config_file_for_user,
163:        :collective        => nil,
164:        :discovery_method  => nil,
165:        :discovery_options => Config.instance.default_discovery_options,
166:        :filter            => empty_filter}
167:     end

Creates an empty filter

[Source]

     # 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

[Source]

     # File lib/mcollective/util.rb, line 125
125:     def self.empty_filter?(filter)
126:       filter == empty_filter || filter == {}
127:     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 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

[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 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

[Source]

     # 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

[Source]

     # 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

[Source]

     # File lib/mcollective/util.rb, line 208
208:     def self.loadclass(klass)
209:       PluginManager.loadclass(klass)
210:     end

[Source]

     # File lib/mcollective/util.rb, line 169
169:     def self.make_subscriptions(agent, type, collective=nil)
170:       config = Config.instance
171: 
172:       raise("Unknown target type #{type}") unless [:broadcast, :directed, :reply].include?(type)
173: 
174:       if collective.nil?
175:         config.collectives.map do |c|
176:           {:agent => agent, :type => type, :collective => c}
177:         end
178:       else
179:         raise("Unknown collective '#{collective}' known collectives are '#{config.collectives.join ', '}'") unless config.collectives.include?(collective)
180: 
181:         [{:agent => agent, :type => type, :collective => collective}]
182:       end
183:     end

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

[Source]

     # File lib/mcollective/util.rb, line 213
213:     def self.parse_fact_string(fact)
214:       if fact =~ /^([^ ]+?)[ ]*=>[ ]*(.+)/
215:         return {:fact => $1, :value => $2, :operator => '>=' }
216:       elsif fact =~ /^([^ ]+?)[ ]*=<[ ]*(.+)/
217:         return {:fact => $1, :value => $2, :operator => '<=' }
218:       elsif fact =~ /^([^ ]+?)[ ]*(<=|>=|<|>|!=|==|=~)[ ]*(.+)/
219:         return {:fact => $1, :value => $3, :operator => $2 }
220:       elsif fact =~ /^(.+?)[ ]*=[ ]*\/(.+)\/$/
221:         return {:fact => $1, :value => "/#{$2}/", :operator => '=~' }
222:       elsif fact =~ /^([^= ]+?)[ ]*=[ ]*(.+)/
223:         return {:fact => $1, :value => $2, :operator => '==' }
224:       else
225:         raise "Could not parse fact #{fact} it does not appear to be in a valid format"
226:       end
227:     end

Returns the current ruby version as per RUBY_VERSION, mostly doing this here to aid testing

[Source]

     # File lib/mcollective/util.rb, line 278
278:     def self.ruby_version
279:       RUBY_VERSION
280:     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

[Source]

    # 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

[Source]

     # File lib/mcollective/util.rb, line 232
232:     def self.shellescape(str)
233:       return "''" if str.empty?
234: 
235:       str = str.dup
236: 
237:       # Process as a single byte sequence because not all shell
238:       # implementations are multibyte aware.
239:       str.gsub!(/([^A-Za-z0-9_\-.,:\/@\n])/n, "\\\\\\1")
240: 
241:       # A LF cannot be escaped with a backslash because a backslash + LF
242:       # combo is regarded as line continuation and simply ignored.
243:       str.gsub!(/\n/, "'\n'")
244: 
245:       return str
246:     end

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

[Source]

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

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

[Source]

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

[Source]

     # File lib/mcollective/util.rb, line 248
248:     def self.windows?
249:       !!(RbConfig::CONFIG['host_os'] =~ /mswin|win32|dos|mingw|cygwin/i)
250:     end

[Validate]