shell_command_agent.rb 3.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  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. These events may contain their own `path` and `command` values. If they do not, ShellCommandAgent will use the configured options. For this reason, please specify defaults even if you are planning to have this Agent to respond to events.
  13. 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.
  14. *Warning*: This type of Agent runs arbitrary commands on your system, #{Agents::ShellCommandAgent.should_run? ? "but is **currently enabled**" : "and is **currently disabled**"}.
  15. Only enable this Agent if you trust everyone using your Huginn installation.
  16. You can enable this Agent in your .env file by setting `ENABLE_INSECURE_AGENTS` to `true`.
  17. MD
  18. event_description <<-MD
  19. Events look like this:
  20. {
  21. 'command' => 'pwd',
  22. 'path' => '/home/Huginn',
  23. 'exit_status' => '0',
  24. 'errors' => '',
  25. 'output' => '/home/Huginn'
  26. }
  27. MD
  28. def default_options
  29. {
  30. 'path' => "/",
  31. 'command' => "pwd",
  32. 'expected_update_period_in_days' => 1
  33. }
  34. end
  35. def validate_options
  36. unless options['path'].present? && options['command'].present? && options['expected_update_period_in_days'].present?
  37. errors.add(:base, "The path, command, and expected_update_period_in_days fields are all required.")
  38. end
  39. unless File.directory?(options['path'])
  40. errors.add(:base, "#{options['path']} is not a real directory.")
  41. end
  42. end
  43. def working?
  44. Agents::ShellCommandAgent.should_run? && event_created_within?(options['expected_update_period_in_days']) && !recent_error_logs?
  45. end
  46. def receive(incoming_events)
  47. incoming_events.each do |event|
  48. handle(event.payload, event)
  49. end
  50. end
  51. def check
  52. handle(options)
  53. end
  54. private
  55. def handle(opts = options, event = nil)
  56. if Agents::ShellCommandAgent.should_run?
  57. command = opts['command'] || options['command']
  58. path = opts['path'] || options['path']
  59. result, errors, exit_status = run_command(path, command)
  60. vals = {"command" => command, "path" => path, "exit_status" => exit_status, "errors" => errors, "output" => result}
  61. created_event = create_event :payload => vals
  62. log("Ran '#{command}' under '#{path}'", :outbound_event => created_event, :inbound_event => event)
  63. else
  64. log("Unable to run because insecure agents are not enabled. Edit ENABLE_INSECURE_AGENTS in the Huginn .env configuration.")
  65. end
  66. end
  67. def run_command(path, command)
  68. result = nil
  69. errors = nil
  70. exit_status = nil
  71. Dir.chdir(path){
  72. begin
  73. stdin, stdout, stderr, wait_thr = Open3.popen3(command)
  74. exit_status = wait_thr.value.to_i
  75. result = stdout.gets(nil)
  76. errors = stderr.gets(nil)
  77. rescue Exception => e
  78. errors = e.to_s
  79. end
  80. }
  81. result = result.to_s.strip
  82. errors = errors.to_s.strip
  83. [result, errors, exit_status]
  84. end
  85. end
  86. end