shell_command_agent.rb 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. require 'open3'
  2. module Agents
  3. class ShellCommandAgent < Agent
  4. default_schedule "never"
  5. def self.should_run?
  6. ENV['ENABLE_INSECURE_AGENTS'] == "true"
  7. end
  8. description <<-MD
  9. The ShellCommandAgent can execute commands on your local system, returning the output.
  10. `command` specifies the command to be executed, and `path` will tell ShellCommandAgent in what directory to run this command.
  11. `expected_update_period_in_days` is used to determine if the Agent is working.
  12. ShellCommandAgent can also act upon received events. When receiving an event, this Agent's options can interpolate values from the incoming event.
  13. For example, your command could be defined as `{{cmd}}`, in which case the event's `cmd` property would be used.
  14. The resulting event will contain the `command` which was executed, the `path` it was executed under, the `exit_status` of the command, the `errors`, and the actual `output`. ShellCommandAgent will not log an error if the result implies that something went wrong.
  15. *Warning*: This type of Agent runs arbitrary commands on your system, #{Agents::ShellCommandAgent.should_run? ? "but is **currently enabled**" : "and is **currently disabled**"}.
  16. Only enable this Agent if you trust everyone using your Huginn installation.
  17. You can enable this Agent in your .env file by setting `ENABLE_INSECURE_AGENTS` to `true`.
  18. MD
  19. event_description <<-MD
  20. Events look like this:
  21. {
  22. "command": "pwd",
  23. "path": "/home/Huginn",
  24. "exit_status": "0",
  25. "errors": "",
  26. "output": "/home/Huginn"
  27. }
  28. MD
  29. def default_options
  30. {
  31. 'path' => "/",
  32. 'command' => "pwd",
  33. 'expected_update_period_in_days' => 1
  34. }
  35. end
  36. def validate_options
  37. unless options['path'].present? && options['command'].present? && options['expected_update_period_in_days'].present?
  38. errors.add(:base, "The path, command, and expected_update_period_in_days fields are all required.")
  39. end
  40. unless File.directory?(options['path'])
  41. errors.add(:base, "#{options['path']} is not a real directory.")
  42. end
  43. end
  44. def working?
  45. Agents::ShellCommandAgent.should_run? && event_created_within?(interpolated['expected_update_period_in_days']) && !recent_error_logs?
  46. end
  47. def receive(incoming_events)
  48. incoming_events.each do |event|
  49. handle(interpolated(event), event)
  50. end
  51. end
  52. def check
  53. handle(interpolated)
  54. end
  55. private
  56. def handle(opts, event = nil)
  57. if Agents::ShellCommandAgent.should_run?
  58. command = opts['command']
  59. path = opts['path']
  60. result, errors, exit_status = run_command(path, command)
  61. vals = {"command" => command, "path" => path, "exit_status" => exit_status, "errors" => errors, "output" => result}
  62. created_event = create_event :payload => vals
  63. log("Ran '#{command}' under '#{path}'", :outbound_event => created_event, :inbound_event => event)
  64. else
  65. log("Unable to run because insecure agents are not enabled. Edit ENABLE_INSECURE_AGENTS in the Huginn .env configuration.")
  66. end
  67. end
  68. def run_command(path, command)
  69. result = nil
  70. errors = nil
  71. exit_status = nil
  72. Dir.chdir(path){
  73. begin
  74. stdin, stdout, stderr, wait_thr = Open3.popen3(command)
  75. exit_status = wait_thr.value.to_i
  76. result = stdout.gets(nil)
  77. errors = stderr.gets(nil)
  78. rescue Exception => e
  79. errors = e.to_s
  80. end
  81. }
  82. result = result.to_s.strip
  83. errors = errors.to_s.strip
  84. [result, errors, exit_status]
  85. end
  86. end
  87. end