trigger_agent.rb 4.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. module Agents
  2. class TriggerAgent < Agent
  3. cannot_be_scheduled!
  4. VALID_COMPARISON_TYPES = %w[regex !regex field<value field<=value field==value field!=value field>=value field>value]
  5. description <<-MD
  6. Use a TriggerAgent to watch for a specific value in an Event payload.
  7. The `rules` array contains hashes of `path`, `value`, and `type`. The `path` value is a dotted path through a hash in [JSONPaths](http://goessner.net/articles/JsonPath/) syntax.
  8. The `type` can be one of #{VALID_COMPARISON_TYPES.map { |t| "`#{t}`" }.to_sentence} and compares with the `value`.
  9. The `value` can be a single value or an array of values. In the case of an array, if one or more values match then the rule matches.
  10. All rules must match for the Agent to match. The resulting Event will have a payload message of `message`. You can include extractions in the message, for example: `I saw a bar of: <foo.bar>`
  11. Set `keep_event` to `true` if you'd like to re-emit the incoming event, optionally merged with 'message' when provided.
  12. Set `expected_receive_period_in_days` to the maximum amount of time that you'd expect to pass between Events being received by this Agent.
  13. MD
  14. event_description <<-MD
  15. Events look like this:
  16. { "message": "Your message" }
  17. MD
  18. def validate_options
  19. unless options['expected_receive_period_in_days'].present? && options['rules'].present? &&
  20. options['rules'].all? { |rule| rule['type'].present? && VALID_COMPARISON_TYPES.include?(rule['type']) && rule['value'].present? && rule['path'].present? }
  21. errors.add(:base, "expected_receive_period_in_days, message, and rules, with a type, value, and path for every rule, are required")
  22. end
  23. errors.add(:base, "message is required unless 'keep_event' is 'true'") unless options['message'].present? || keep_event?
  24. errors.add(:base, "keep_event, when present, must be 'true' or 'false'") unless options['keep_event'].blank? || %w[true false].include?(options['keep_event'])
  25. end
  26. def default_options
  27. {
  28. 'expected_receive_period_in_days' => "2",
  29. 'keep_event' => 'false',
  30. 'rules' => [{
  31. 'type' => "regex",
  32. 'value' => "foo\\d+bar",
  33. 'path' => "topkey.subkey.subkey.goal",
  34. }],
  35. 'message' => "Looks like your pattern matched in '<value>'!"
  36. }
  37. end
  38. def working?
  39. last_receive_at && last_receive_at > options['expected_receive_period_in_days'].to_i.days.ago && !recent_error_logs?
  40. end
  41. def receive(incoming_events)
  42. incoming_events.each do |event|
  43. match = options['rules'].all? do |rule|
  44. value_at_path = Utils.value_at(event['payload'], rule['path'])
  45. rule_values = rule['value']
  46. rule_values = [rule_values] unless rule_values.is_a?(Array)
  47. match_found = rule_values.any? do |rule_value|
  48. case rule['type']
  49. when "regex"
  50. value_at_path.to_s =~ Regexp.new(rule_value, Regexp::IGNORECASE)
  51. when "!regex"
  52. value_at_path.to_s !~ Regexp.new(rule_value, Regexp::IGNORECASE)
  53. when "field>value"
  54. value_at_path.to_f > rule_value.to_f
  55. when "field>=value"
  56. value_at_path.to_f >= rule_value.to_f
  57. when "field<value"
  58. value_at_path.to_f < rule_value.to_f
  59. when "field<=value"
  60. value_at_path.to_f <= rule_value.to_f
  61. when "field==value"
  62. value_at_path.to_s == rule_value.to_s
  63. when "field!=value"
  64. value_at_path.to_s != rule_value.to_s
  65. else
  66. raise "Invalid type of #{rule['type']} in TriggerAgent##{id}"
  67. end
  68. end
  69. end
  70. if match
  71. if keep_event?
  72. payload = event.payload.dup
  73. payload['message'] = make_message(event[:payload]) if options['message'].present?
  74. else
  75. payload = { 'message' => make_message(event[:payload]) }
  76. end
  77. create_event :payload => payload
  78. end
  79. end
  80. end
  81. def keep_event?
  82. options['keep_event'] == 'true'
  83. end
  84. end
  85. end