123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202 |
- module Agents
- class LocalFileAgent < Agent
- include LongRunnable
- include FormConfigurable
- include FileHandling
- emits_file_pointer!
- default_schedule 'every_1h'
- def self.should_run?
- ENV['ENABLE_INSECURE_AGENTS'] == "true"
- end
- description do
- <<~MD
- The LocalFileAgent can watch a file/directory for changes or emit an event for every file in that directory. When receiving an event it writes the received data into a file.
- `mode` determines if the agent is emitting events for (changed) files or writing received event data to disk.
- ### Reading
- When `watch` is set to `true` the LocalFileAgent will watch the specified `path` for changes, the schedule is ignored and the file system is watched continuously. An event will be emitted for every detected change.
- When `watch` is set to `false` the agent will emit an event for every file in the directory on each scheduled run.
- #{emitting_file_handling_agent_description}
- ### Writing
- Every event will be writting into a file at `path`, Liquid interpolation is possible to change the path per event.
- When `append` is true the received data will be appended to the file.
- Use [Liquid](https://github.com/huginn/huginn/wiki/Formatting-Events-using-Liquid) templating in `data` to specify which part of the received event should be written.
- *Warning*: This type of Agent can read and write any file the user that runs the Huginn server has access to, and is #{Agents::LocalFileAgent.should_run? ? "**currently enabled**" : "**currently disabled**"}.
- Only enable this Agent if you trust everyone using your Huginn installation.
- You can enable this Agent in your .env file by setting `ENABLE_INSECURE_AGENTS` to `true`.
- MD
- end
- event_description do
- "Events will looks like this:\n\n " +
- if boolify(interpolated['watch'])
- Utils.pretty_print(
- "file_pointer" => {
- "file" => "/tmp/test/filename",
- "agent_id" => id
- },
- "event_type" => "modified/added/removed"
- )
- else
- Utils.pretty_print(
- "file_pointer" => {
- "file" => "/tmp/test/filename",
- "agent_id" => id
- }
- )
- end
- end
- def default_options
- {
- 'mode' => 'read',
- 'watch' => 'true',
- 'append' => 'false',
- 'path' => "",
- 'data' => '{{ data }}'
- }
- end
- form_configurable :mode, type: :array, values: %w[read write]
- form_configurable :watch, type: :array, values: %w[true false]
- form_configurable :path, type: :string
- form_configurable :append, type: :boolean
- form_configurable :data, type: :string
- def validate_options
- if options['mode'].blank? || !['read', 'write'].include?(options['mode'])
- errors.add(:base, "The 'mode' option is required and must be set to 'read' or 'write'")
- end
- if options['watch'].blank? || ![true, false].include?(boolify(options['watch']))
- errors.add(:base, "The 'watch' option is required and must be set to 'true' or 'false'")
- end
- if options['append'].blank? || ![true, false].include?(boolify(options['append']))
- errors.add(:base, "The 'append' option is required and must be set to 'true' or 'false'")
- end
- if options['path'].blank?
- errors.add(:base, "The 'path' option is required.")
- end
- end
- def working?
- should_run?(false) && ((interpolated['mode'] == 'read' && check_path_existance && checked_without_error?) ||
- (interpolated['mode'] == 'write' && received_event_without_error?))
- end
- def check
- return if interpolated['mode'] != 'read' || boolify(interpolated['watch']) || !should_run?
- return unless check_path_existance(true)
- if File.directory?(expanded_path)
- Dir.glob(File.join(expanded_path, '*')).select { |f| File.file?(f) }
- else
- [expanded_path]
- end.each do |file|
- create_event payload: get_file_pointer(file)
- end
- end
- def receive(incoming_events)
- return if interpolated['mode'] != 'write' || !should_run?
- incoming_events.each do |event|
- mo = interpolated(event)
- expanded_path = File.expand_path(mo['path'])
- File.open(expanded_path, boolify(mo['append']) ? 'a' : 'w') do |file|
- file.write(mo['data'])
- end
- create_event payload: get_file_pointer(expanded_path)
- end
- end
- def start_worker?
- interpolated['mode'] == 'read' && boolify(interpolated['watch']) && should_run? && check_path_existance
- end
- def check_path_existance(log = true)
- if !File.exist?(expanded_path)
- error("File or directory '#{expanded_path}' does not exist") if log
- return false
- end
- true
- end
- def get_io(file)
- File.open(file, 'r')
- end
- def expanded_path
- @expanded_path ||= File.expand_path(interpolated['path'])
- end
- private
- def should_run?(log = true)
- if self.class.should_run?
- true
- else
- error("Unable to run because insecure agents are not enabled. Set ENABLE_INSECURE_AGENTS to true in the Huginn .env configuration.") if log
- false
- end
- end
- class Worker < LongRunnable::Worker
- def setup
- require 'listen'
- path, options = listen_options
- @listener = Listen.to(path, **options, &method(:callback))
- end
- def run
- sleep unless agent.check_path_existance(true)
- @listener.start
- sleep
- end
- def stop
- @listener.stop
- end
- private
- def callback(*changes)
- AgentRunner.with_connection do
- changes.zip([:modified, :added, :removed]).each do |files, event_type|
- files.each do |file|
- agent.create_event payload: agent.get_file_pointer(file).merge(event_type:)
- end
- end
- agent.touch(:last_check_at)
- end
- end
- def listen_options
- if File.directory?(agent.expanded_path)
- [
- agent.expanded_path,
- ignore!: []
- ]
- else
- [
- File.dirname(agent.expanded_path),
- ignore!: [], only: /\A#{Regexp.escape(File.basename(agent.expanded_path))}\z/
- ]
- end
- end
- end
- end
- end
|