Module: Sensu::Utilities

Constant Summary collapse

EVAL_PREFIX =
"eval:".freeze

Instance Method Summary collapse

Instance Method Details

#attributes_match?(object, match_attributes, support_eval = true, object_attributes = nil) ⇒ TrueClass, FalseClass

Determine if all attribute values match those of the corresponding object attributes. Attributes match if the value objects are equivalent, are both hashes with matching key/value pairs (recursive), have equal string values, or evaluate to true (Ruby eval).

Parameters:

  • object (Hash)
  • match_attributes (Object)
  • support_eval (TrueClass, FalseClass) (defaults to: true)
  • object_attributes (Object) (defaults to: nil)

Returns:

  • (TrueClass, FalseClass)


265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
# File 'lib/sensu/utilities.rb', line 265

def attributes_match?(object, match_attributes, support_eval=true, object_attributes=nil)
  object_attributes ||= object
  match_attributes.all? do |key, value_one|
    value_two = object_attributes[key]
    case
    when value_one == value_two
      true
    when value_one.is_a?(Hash) && value_two.is_a?(Hash)
      attributes_match?(object, value_one, support_eval, value_two)
    when value_one.to_s == value_two.to_s
      true
    when value_one.is_a?(String) && value_one.start_with?(EVAL_PREFIX) && support_eval
      eval_attribute_value(object, value_one, value_two)
    else
      false
    end
  end
end

#check_subdued?(check) ⇒ TrueClass, FalseClass

Determine if a check is subdued, by conditions set in the check definition. If any of the conditions are true, without an exception, the check is subdued.

Parameters:

  • check (Hash)

    definition.

Returns:

  • (TrueClass, FalseClass)


340
341
342
343
344
345
346
# File 'lib/sensu/utilities.rb', line 340

def check_subdued?(check)
  if check[:subdue]
    in_time_windows?(check[:subdue])
  else
    false
  end
end

#deep_merge(hash_one, hash_two) ⇒ Hash

Deep merge two hashes. Nested hashes are deep merged, arrays are concatenated and duplicate array items are removed.

Parameters:

  • hash_one (Hash)
  • hash_two (Hash)

Returns:

  • (Hash)

    deep merged hash.



39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/sensu/utilities.rb', line 39

def deep_merge(hash_one, hash_two)
  merged = hash_one.dup
  hash_two.each do |key, value|
    merged[key] = case
    when hash_one[key].is_a?(Hash) && value.is_a?(Hash)
      deep_merge(hash_one[key], value)
    when hash_one[key].is_a?(Array) && value.is_a?(Array)
      (hash_one[key] + value).uniq
    else
      value
    end
  end
  merged
end

#determine_check_cron_time(check) ⇒ Object

Determine the next check cron time.

Parameters:

  • check (Hash)

    definition.



351
352
353
354
355
356
# File 'lib/sensu/utilities.rb', line 351

def determine_check_cron_time(check)
  cron_parser = CronParser.new(check[:cron])
  current_time = Time.now
  next_cron_time = cron_parser.next(current_time)
  next_cron_time - current_time
end

#eval_attribute_value(object, raw_eval_string, raw_value) ⇒ TrueClass, FalseClass

Ruby ‘eval()` a string containing an expression, within the scope/context of a sandbox. This method is for attribute values starting with “eval:”, with the Ruby expression following the colon. A single variable is provided to the expression, `value`, equal to the corresponding object attribute value. Dot notation tokens in the expression, e.g. `:::mysql.user:::`, are substituted with the corresponding object attribute values prior to evaluation. The expression is expected to return a boolean value.

Parameters:

  • object (Hash)
  • raw_eval_string (String)

    containing the Ruby expression to be evaluated.

  • raw_value (Object)

    of the corresponding object attribute value.

Returns:

  • (TrueClass, FalseClass)


234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# File 'lib/sensu/utilities.rb', line 234

def eval_attribute_value(object, raw_eval_string, raw_value)
  eval_string = process_eval_string(object, raw_eval_string)
  unless eval_string.nil?
    begin
      value = Marshal.load(Marshal.dump(raw_value))
      !!Sandbox.eval(eval_string, value)
    rescue StandardError, SyntaxError => error
      @logger.error("attribute value eval error", {
        :object => object,
        :raw_eval_string => raw_eval_string,
        :raw_value => raw_value,
        :error => error.to_s
      })
      false
    end
  else
    false
  end
end

#find_attribute_value(tree, path, default) ⇒ Object

Traverse a hash for an attribute value, with a fallback default value if nil.

Parameters:

  • tree (Hash)

    to traverse.

  • path (Array)

    of attribute keys.

  • default (Object)

    value if attribute value is nil.

Returns:

  • (Object)

    attribute or fallback default value.



129
130
131
132
133
134
135
136
# File 'lib/sensu/utilities.rb', line 129

def find_attribute_value(tree, path, default)
  attribute = tree[path.shift]
  if attribute.is_a?(Hash)
    find_attribute_value(attribute, path, default)
  else
    attribute.nil? ? default : attribute
  end
end

#in_time_window?(condition) ⇒ TrueClass, FalseClass

Determine if the current time falls within a time window. The provided condition must have a ‘:begin` and `:end` time, eg. “11:30:00 PM”, or `false` will be returned.

Parameters:

  • condition (Hash)

Options Hash (condition):

  • :begin (String)

    time.

  • :end (String)

    time.

Returns:

  • (TrueClass, FalseClass)


292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
# File 'lib/sensu/utilities.rb', line 292

def in_time_window?(condition)
  if condition.has_key?(:begin) && condition.has_key?(:end)
    begin_time = Time.parse(condition[:begin])
    end_time = Time.parse(condition[:end])
    if end_time < begin_time
      if Time.now < end_time
        begin_time = Time.new(*begin_time.strftime("%Y %m %d 00 00 00 %:z").split("\s"))
      else
        end_time = Time.new(*end_time.strftime("%Y %m %d 23 59 59 %:z").split("\s"))
      end
    end
    Time.now >= begin_time && Time.now <= end_time
  else
    false
  end
end

#in_time_windows?(conditions) ⇒ TrueClass, FalseClass

Determine if time window conditions for one or more days of the week are met. If a day of the week is provided, it can provide one or more conditions, each with a ‘:begin` and `:end` time, eg. “11:30:00 PM”, or `false` will be returned.

Parameters:

  • conditions (Hash)

Options Hash (conditions):

  • :days (String)

    of the week.

Returns:

  • (TrueClass, FalseClass)


317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
# File 'lib/sensu/utilities.rb', line 317

def in_time_windows?(conditions)
  in_window = false
  window_days = conditions[:days] || {}
  if window_days[:all]
    in_window = window_days[:all].any? do |condition|
      in_time_window?(condition)
    end
  end
  current_day = Time.now.strftime("%A").downcase.to_sym
  if !in_window && window_days[current_day]
    in_window = window_days[current_day].any? do |condition|
      in_time_window?(condition)
    end
  end
  in_window
end

#object_substitute_tokens(object, attributes) ⇒ Array

Perform token substitution for an object. String values are passed to ‘substitute_tokens()`, arrays and sub-hashes are processed recursively. Numeric values are ignored.

Parameters:

  • object (Object)
  • attributes (Hash)

Returns:

  • (Array)

    containing the updated object with substituted values and an array of unmatched tokens.



173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/sensu/utilities.rb', line 173

def object_substitute_tokens(object, attributes)
  unmatched_tokens = []
  case object
  when Hash
    object.each do |key, value|
      object[key], unmatched = object_substitute_tokens(value, attributes)
      unmatched_tokens.push(*unmatched)
    end
  when Array
    object.map! do |value|
      value, unmatched = object_substitute_tokens(value, attributes)
      unmatched_tokens.push(*unmatched)
      value
    end
  when String
    object, unmatched_tokens = substitute_tokens(object, attributes)
  end
  [object, unmatched_tokens.uniq]
end

#process_cpu_times(&callback) ⇒ Array

Retrieve the process CPU times. If the cpu times cannot be determined and an error is thrown, ‘[nil, nil, nil, nil]` will be returned.

Returns:

  • (Array)

    CPU times: utime, stime, cutime, cstime



78
79
80
81
82
83
# File 'lib/sensu/utilities.rb', line 78

def process_cpu_times(&callback)
  determine_cpu_times = Proc.new do
    ::Process.times.to_a rescue [nil, nil, nil, nil]
  end
  EM::defer(determine_cpu_times, callback)
end

#process_eval_string(object, raw_eval_string) ⇒ String

Process an eval attribute value, a Ruby ‘eval()` string containing an expression to be evaluated within the scope/context of a sandbox. This methods strips away the expression prefix, `eval:`, and substitues any dot notation tokens with the corresponding event data values. If there are unmatched tokens, this method will return `nil`.

Returns:

  • (String)

    processed eval string.



203
204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/sensu/utilities.rb', line 203

def process_eval_string(object, raw_eval_string)
  eval_string = raw_eval_string.slice(5..-1)
  eval_string, unmatched_tokens = substitute_tokens(eval_string, object)
  if unmatched_tokens.empty?
    eval_string
  else
    @logger.error("attribute value eval unmatched tokens", {
      :object => object,
      :raw_eval_string => raw_eval_string,
      :unmatched_tokens => unmatched_tokens
    })
    nil
  end
end

#random_uuidString

Generate a random universally unique identifier.

Returns:

  • (String)

    random UUID.



88
89
90
# File 'lib/sensu/utilities.rb', line 88

def random_uuid
  ::SecureRandom.uuid
end

#redact_sensitive(hash, keys = nil) ⇒ Hash

Remove sensitive information from a hash (eg. passwords). By default, hash values will be redacted for the following keys: password, passwd, pass, api_key, api_token, access_key, secret_key, private_key, secret

Parameters:

  • hash (Hash)

    to redact sensitive value from.

  • keys (Array) (defaults to: nil)

    that indicate sensitive values.

Returns:

  • (Hash)

    hash with redacted sensitive values.



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/sensu/utilities.rb', line 100

def redact_sensitive(hash, keys=nil)
  keys ||= %w[
    password passwd pass
    api_key api_token
    access_key secret_key private_key
    secret
  ]
  hash = hash.dup
  hash.each do |key, value|
    if keys.include?(key.to_s)
      hash[key] = "REDACTED"
    elsif value.is_a?(Hash)
      hash[key] = redact_sensitive(value, keys)
    elsif value.is_a?(Array)
      hash[key] = value.map do |item|
        item.is_a?(Hash) ? redact_sensitive(item, keys) : item
      end
    end
  end
  hash
end

#retry_until_true(wait = 0.5, &block) ⇒ Object

Retry a code block until it retures true. The first attempt and following retries are delayed.

Parameters:

  • wait (Numeric) (defaults to: 0.5)

    time to delay block calls.

  • block (Proc)

    to call that needs to return true.



25
26
27
28
29
30
31
# File 'lib/sensu/utilities.rb', line 25

def retry_until_true(wait=0.5, &block)
  EM::Timer.new(wait) do
    unless block.call
      retry_until_true(wait, &block)
    end
  end
end

#substitute_tokens(tokens, attributes) ⇒ Array

Substitute dot notation tokens (eg. :::db.name|production:::) with the associated definition attribute value. Tokens can provide a fallback default value, following a pipe.

Parameters:

  • tokens (String)
  • attributes (Hash)

Returns:

  • (Array)

    containing the string with tokens substituted and an array of unmatched tokens.



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/sensu/utilities.rb', line 146

def substitute_tokens(tokens, attributes)
  unmatched_tokens = []
  encoded_tokens = tokens.encode("UTF-8", "binary", {
    :invalid => :replace,
    :undef => :replace,
    :replace => ""
  })
  substituted = encoded_tokens.gsub(/:::([^:].*?):::/) do
    token, default = $1.to_s.split("|", -1)
    path = token.split(".").map(&:to_sym)
    matched = find_attribute_value(attributes, path, default)
    if matched.nil?
      unmatched_tokens << token
    end
    matched
  end
  [substituted, unmatched_tokens]
end

#system_addressString

Retrieve the system IP address. If a valid non-loopback IPv4 address cannot be found and an error is thrown, ‘nil` will be returned.

Returns:

  • (String)

    system ip address



67
68
69
70
71
# File 'lib/sensu/utilities.rb', line 67

def system_address
  ::Socket.ip_address_list.find { |address|
    address.ipv4? && !address.ipv4_loopback?
  }.ip_address rescue nil
end

#system_hostnameString

Retrieve the system hostname. If the hostname cannot be determined and an error is thrown, ‘nil` will be returned.

Returns:

  • (String)

    system hostname.



58
59
60
# File 'lib/sensu/utilities.rb', line 58

def system_hostname
  ::Socket.gethostname rescue nil
end

#testing?TrueClass, FalseClass

Determine if Sensu is being tested, using the process name. Sensu is being test if the process name is “rspec”,

Returns:

  • (TrueClass, FalseClass)


16
17
18
# File 'lib/sensu/utilities.rb', line 16

def testing?
  File.basename($0) == "rspec"
end