key_value_store_agent.rb 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. # frozen_string_literal: true
  2. module Agents
  3. class KeyValueStoreAgent < Agent
  4. can_control_other_agents!
  5. cannot_be_scheduled!
  6. cannot_create_events!
  7. description <<~MD
  8. The Key-Value Store Agent is a data storage that keeps an associative array in its memory. It receives events to store values and provides the data to other agents as an object via Liquid Templating.
  9. Liquid templates specified in the `key` and `value` options are evaluated for each received event to be stored in the memory.
  10. The `variable` option specifies the name by which agents that use the storage refers to the data in Liquid templating.
  11. The `max_keys` option specifies up to how many keys to keep in the storage. When the number of keys goes beyond this, the oldest key-value pair gets removed. (default: 100)
  12. ### Storing data
  13. For example, say your agent receives these incoming events:
  14. {
  15. "city": "Tokyo",
  16. "weather": "cloudy"
  17. }
  18. {
  19. "city": "Osaka",
  20. "weather": "sunny"
  21. }
  22. Then you could configure the agent with `{ "key": "{{ city }}", "value": "{{ weather }}" }` to get the following data stored:
  23. {
  24. "Tokyo": "cloudy",
  25. "Osaka": "sunny"
  26. }
  27. Here are some specifications:
  28. - Keys are always stringified as mandated by the JSON format.
  29. - Values are stringified by default. Use the `as_object` filter to store non-string values.
  30. - If the key is evaluated to an empty string, the event is ignored.
  31. - If the value is evaluated to either `null` or empty (`""`, `[]`, `{}`) the key gets deleted.
  32. - In the `value` template, the existing value (if any) can be accessed via the variable `_value_`.
  33. - In the `key` and `value` templates, the whole event payload can be accessed via the variable `_event_`.
  34. ### Extracting data
  35. To allow other agents to use the data of a Key-Value Store Agent, designate the agent as a controller.
  36. You can do that by adding those agents to the "Controller targets" of the agent.
  37. The target agents can refer to the storage via the variable specified by the `variable` option value. So, if the store agent in the above example had an option `"variable": "weather"`, they can say something like `{{ weather[city] | default: "unknown" }}` in their templates to get the weather of a city stored in the variable `city`.
  38. MD
  39. def validate_options
  40. options[:key].is_a?(String) or
  41. errors.add(:base, "key is required and must be a string.")
  42. options[:value] or
  43. errors.add(:base, "value is required.")
  44. /\A(?!\d)\w+\z/ === options[:variable] or
  45. errors.add(:base, "variable is required and must be valid as a variable name.")
  46. max_keys > 0 or
  47. errors.add(:base, "max_keys must be a positive number.")
  48. end
  49. def default_options
  50. {
  51. 'key' => '{{ id }}',
  52. 'value' => '{{ _event_ | as_object }}',
  53. 'variable' => 'var',
  54. }
  55. end
  56. def working?
  57. !recent_error_logs?
  58. end
  59. def control_action
  60. 'provide'
  61. end
  62. def max_keys
  63. if value = options[:max_keys].presence
  64. value.to_i
  65. else
  66. 100
  67. end
  68. end
  69. def receive(incoming_events)
  70. max_keys = max_keys()
  71. incoming_events.each do |event|
  72. interpolate_with(event) do
  73. interpolation_context.stack do
  74. interpolation_context['_event_'] = event.payload
  75. key = interpolate_options(options)['key'].to_s
  76. next if key.empty?
  77. storage = memory
  78. interpolation_context['_value_'] = storage.delete(key)
  79. value = interpolate_options(options)['value']
  80. if value.nil? || value.try(:empty?)
  81. storage.delete(key)
  82. else
  83. storage[key] = value
  84. storage.shift while storage.size > max_keys
  85. end
  86. update!(memory: storage)
  87. end
  88. end
  89. end
  90. end
  91. end
  92. end