Module: MCollective::Util

Defined in:
lib/mcollective/util.rb

Overview

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

Class Method Summary collapse

Class Method Details

.config_file_for_userObject

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



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/mcollective/util.rb', line 140

def self.config_file_for_user
  # expand_path is pretty lame, it relies on HOME environment
  # which isnt't always there so just handling all exceptions
  # here as cant find reverting to default
  begin
    config = File.expand_path("~/.mcollective")

    unless File.readable?(config) && File.file?(config)
      config = "/etc/mcollective/client.cfg"
    end
  rescue Exception => e
    config = "/etc/mcollective/client.cfg"
  end

  return config
end

.default_optionsObject

Creates a standard options hash



158
159
160
161
162
163
164
165
# File 'lib/mcollective/util.rb', line 158

def self.default_options
  {:verbose     => false,
    :disctimeout => 2,
    :timeout     => 5,
    :config      => config_file_for_user,
    :collective  => nil,
    :filter      => empty_filter}
end

.empty_filterObject

Creates an empty filter



130
131
132
133
134
135
136
# File 'lib/mcollective/util.rb', line 130

def self.empty_filter
  {"fact"     => [],
    "cf_class" => [],
    "agent"    => [],
    "identity" => [],
    "compound" => []}
end

.empty_filter?(filter) ⇒ Boolean

Checks if the passed in filter is an empty one

Returns:

  • (Boolean)


125
126
127
# File 'lib/mcollective/util.rb', line 125

def self.empty_filter?(filter)
  filter == empty_filter || filter == {}
end

.eval_compound_statement(expression) ⇒ Object



250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/mcollective/util.rb', line 250

def self.eval_compound_statement(expression)
  if expression.values.first =~ /^\//
    return Util.has_cf_class?(expression.values.first)
  elsif expression.values.first =~ />=|<=|=|<|>/
    optype = expression.values.first.match(/>=|<=|=|<|>/)
    name, value = expression.values.first.split(optype[0])
    unless value.split("")[0] == "/"
      optype[0] == "=" ? optype = "==" : optype = optype[0]
    else
      optype = "=~"
    end

    return Util.has_fact?(name,value, optype).to_s
  else
    return Util.has_cf_class?(expression.values.first)
  end
end

.get_fact(fact) ⇒ Object

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



61
62
63
# File 'lib/mcollective/util.rb', line 61

def self.get_fact(fact)
  Facts.get_fact(fact)
end

.has_agent?(agent) ⇒ Boolean

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

Returns:

  • (Boolean)


8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# File 'lib/mcollective/util.rb', line 8

def self.has_agent?(agent)
  agent = Regexp.new(agent.gsub("\/", "")) if agent.match("^/")

  if agent.is_a?(Regexp)
    if Agents.agentlist.grep(agent).size > 0
      return true
    else
      return false
    end
  else
    return Agents.agentlist.include?(agent)
  end

  false
end

.has_cf_class?(klass) ⇒ Boolean

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

Returns:

  • (Boolean)


38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/mcollective/util.rb', line 38

def self.has_cf_class?(klass)
  klass = Regexp.new(klass.gsub("\/", "")) if klass.match("^/")
  cfile = Config.instance.classesfile

  Log.debug("Looking for configuration management classes in #{cfile}")

  begin
    File.readlines(cfile).each do |k|
      if klass.is_a?(Regexp)
        return true if k.chomp.match(klass)
      else
        return true if k.chomp == klass
      end
    end
  rescue Exception => e
    Log.warn("Parsing classes file '#{cfile}' failed: #{e.class}: #{e}")
  end

  false
end

.has_fact?(fact, value, operator) ⇒ Boolean

Compares fact == value,

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

Returns:

  • (Boolean)


69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/mcollective/util.rb', line 69

def self.has_fact?(fact, value, operator)

  Log.debug("Comparing #{fact} #{operator} #{value}")
  Log.debug("where :fact = '#{fact}', :operator = '#{operator}', :value = '#{value}'")

  fact = Facts[fact]
  return false if fact.nil?

  fact = fact.clone

  if operator == '=~'
    # to maintain backward compat we send the value
    # as /.../ which is what 1.0.x needed.  this strips
    # off the /'s wich is what we need here
    if value =~ /^\/(.+)\/$/
      value = $1
    end

    return true if fact.match(Regexp.new(value))

  elsif operator == "=="
    return true if fact == value

  elsif ['<=', '>=', '<', '>', '!='].include?(operator)
    # Yuk - need to type cast, but to_i and to_f are overzealous
    if value =~ /^[0-9]+$/ && fact =~ /^[0-9]+$/
      fact = Integer(fact)
      value = Integer(value)
    elsif value =~ /^[0-9]+.[0-9]+$/ && fact =~ /^[0-9]+.[0-9]+$/
      fact = Float(fact)
      value = Float(value)
    end

    return true if eval("fact #{operator} value")
  end

  false
end

.has_identity?(identity) ⇒ Boolean

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

Returns:

  • (Boolean)


112
113
114
115
116
117
118
119
120
121
122
# File 'lib/mcollective/util.rb', line 112

def self.has_identity?(identity)
  identity = Regexp.new(identity.gsub("\/", "")) if identity.match("^/")

  if identity.is_a?(Regexp)
    return Config.instance.identity.match(identity)
  else
    return true if Config.instance.identity == identity
  end

  false
end

.loadclass(klass) ⇒ Object

Wrapper around PluginManager.loadclass



206
207
208
# File 'lib/mcollective/util.rb', line 206

def self.loadclass(klass)
  PluginManager.loadclass(klass)
end

.make_subscriptions(agent, type, collective = nil) ⇒ Object



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/mcollective/util.rb', line 167

def self.make_subscriptions(agent, type, collective=nil)
  config = Config.instance

  raise("Unknown target type #{type}") unless [:broadcast, :directed, :reply].include?(type)

  if collective.nil?
    config.collectives.map do |c|
      {:agent => agent, :type => type, :collective => c}
    end
  else
    raise("Unknown collective '#{collective}' known collectives are '#{config.collectives.join ', '}'") unless config.collectives.include?(collective)

    [{:agent => agent, :type => type, :collective => collective}]
  end
end

.parse_fact_string(fact) ⇒ Object

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



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/mcollective/util.rb', line 211

def self.parse_fact_string(fact)
  if fact =~ /^([^ ]+?)[ ]*=>[ ]*(.+)/
    return {:fact => $1, :value => $2, :operator => '>=' }
  elsif fact =~ /^([^ ]+?)[ ]*=<[ ]*(.+)/
    return {:fact => $1, :value => $2, :operator => '<=' }
  elsif fact =~ /^([^ ]+?)[ ]*(<=|>=|<|>|!=|==|=~)[ ]*(.+)/
    return {:fact => $1, :value => $3, :operator => $2 }
  elsif fact =~ /^(.+?)[ ]*=[ ]*\/(.+)\/$/
    return {:fact => $1, :value => "/#{$2}/", :operator => '=~' }
  elsif fact =~ /^([^= ]+?)[ ]*=[ ]*(.+)/
    return {:fact => $1, :value => $2, :operator => '==' }
  else
    raise "Could not parse fact #{fact} it does not appear to be in a valid format"
  end
end

.ruby_versionObject

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



270
271
272
# File 'lib/mcollective/util.rb', line 270

def self.ruby_version
  RUBY_VERSION
end

.setup_windows_sleeperObject

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



28
29
30
# File 'lib/mcollective/util.rb', line 28

def self.setup_windows_sleeper
  Thread.new { loop { sleep 1 } } if Util.windows?
end

.shellescape(str) ⇒ Object

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



230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/mcollective/util.rb', line 230

def self.shellescape(str)
  return "''" if str.empty?

  str = str.dup

  # Process as a single byte sequence because not all shell
  # implementations are multibyte aware.
  str.gsub!(/([^A-Za-z0-9_\-.,:\/@\n])/n, "\\\\\\1")

  # A LF cannot be escaped with a backslash because a backslash + LF
  # combo is regarded as line continuation and simply ignored.
  str.gsub!(/\n/, "'\n'")

  return str
end

.subscribe(targets) ⇒ Object

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



184
185
186
187
188
189
190
191
192
# File 'lib/mcollective/util.rb', line 184

def self.subscribe(targets)
  connection = PluginManager["connector_plugin"]

  targets = [targets].flatten

  targets.each do |target|
    connection.subscribe(target[:agent], target[:type], target[:collective])
  end
end

.unsubscribe(targets) ⇒ Object

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



195
196
197
198
199
200
201
202
203
# File 'lib/mcollective/util.rb', line 195

def self.unsubscribe(targets)
  connection = PluginManager["connector_plugin"]

  targets = [targets].flatten

  targets.each do |target|
    connection.unsubscribe(target[:agent], target[:type], target[:collective])
  end
end

.windows?Boolean

Returns:

  • (Boolean)


246
247
248
# File 'lib/mcollective/util.rb', line 246

def self.windows?
  !!(RbConfig::CONFIG['host_os'] =~ /mswin|win32|dos|mingw|cygwin/i)
end