Merge branch 'master' of https://github.com/ZirconCode/huginn into ZirconCode-master

Andrew Cantino 11 years ago
parent
commit
317642a9c0
2 changed files with 171 additions and 0 deletions
  1. 101 0
      app/models/agents/command_agent.rb
  2. 70 0
      spec/models/agents/command_agent_spec.rb

+ 101 - 0
app/models/agents/command_agent.rb

@@ -0,0 +1,101 @@
1
+module Agents
2
+  class CommandAgent < Agent
3
+    
4
+    require 'open3'
5
+
6
+
7
+    default_schedule "midnight"
8
+
9
+
10
+    description <<-MD
11
+
12
+      The CommandAgent can execute commands on your local system, returning the output.
13
+
14
+      `command` specifies the command to be executed, and `path` will tell CommandAgent in what directory to run this command.
15
+
16
+      `expected_update_period_in_days` is used to determine if the Agent is working.
17
+
18
+      CommandAgent can also act upon recieveing events. These events may contain their own path and command arguments. If they do not, CommandAgent will use the configured options. For this reason, please specify defaults even if you are planning to have this Agent respond to events.
19
+
20
+      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`. CommandAgent will not log an error if the result implies that something went wrong.
21
+
22
+      *Warning*: Misuse of this Agent can pose a security threat.
23
+
24
+    MD
25
+
26
+    event_description <<-MD
27
+    Events look like this:
28
+
29
+      {
30
+        'command' => 'pwd',
31
+        'path' => '/home/Huginn',
32
+        'exit_status' => '0',
33
+        'errors' => '',
34
+        'output' => '/home/Huginn' 
35
+      }
36
+    MD
37
+
38
+    def default_options
39
+      {
40
+          'path' => "/home",
41
+          'command' => "pwd",
42
+          'expected_update_period_in_days' => 1
43
+      }
44
+    end
45
+
46
+    def validate_options
47
+      unless options['path'].present? && options['command'].present? && options['expected_update_period_in_days'].present?
48
+        errors.add(:base, "The path, command, and expected_update_period_in_days fields are all required.")
49
+      end   
50
+      unless File.directory?(options['path'])
51
+        errors.add(:base, "#{options['path']} is not a real directory.")
52
+      end
53
+    end
54
+
55
+    def working?
56
+      event_created_within?(options['expected_update_period_in_days']) && !recent_error_logs?
57
+    end
58
+
59
+    def exec_command(opts = options)
60
+      command = opts['command'] || options['command']
61
+      path = opts['path'] || options['path']
62
+
63
+      result = nil
64
+      errors = nil
65
+      exit_status = nil
66
+
67
+      Dir.chdir(path){
68
+        begin
69
+          stdin, stdout, stderr, wait_thr = Open3.popen3(command)
70
+          exit_status = wait_thr.value.to_i
71
+          result = stdout.gets(nil)
72
+          errors = stderr.gets(nil)
73
+        rescue Exception => e
74
+          errors = e.to_s
75
+        end
76
+      }
77
+
78
+      result.chomp! if result.is_a?(String)
79
+      result = '' if result.nil?
80
+      errors = '' if errors.nil?
81
+
82
+      vals = {"command" => command, "path" => path, "exit_status" => exit_status, "errors" => errors, "output" => result}
83
+      evnt = create_event :payload => vals
84
+
85
+      log("Ran '#{command}' under '#{path}'", :outbound_event => evnt)
86
+    end
87
+
88
+
89
+    def receive(incoming_events)
90
+      incoming_events.each do |event|
91
+        exec_command(event.payload)
92
+      end
93
+    end
94
+
95
+    def check
96
+      exec_command(options)
97
+    end
98
+
99
+
100
+  end
101
+end

+ 70 - 0
spec/models/agents/command_agent_spec.rb

@@ -0,0 +1,70 @@
1
+require 'spec_helper'
2
+
3
+describe Agents::CommandAgent do
4
+
5
+  before do
6
+    @valid_path = Dir.pwd
7
+    @valid_params = {
8
+        :path  => @valid_path,
9
+        :command  => "pwd",
10
+        :expected_update_period_in_days => "1",
11
+      }
12
+
13
+    @checker = Agents::CommandAgent.new(:name => "somename", :options => @valid_params)
14
+    @checker.user = users(:jane)
15
+    @checker.save!
16
+
17
+    @event = Event.new
18
+    @event.agent = agents(:jane_weather_agent)
19
+    @event.payload = {
20
+      :command => "pwd"
21
+    }
22
+    @event.save!
23
+  end
24
+
25
+  describe "validation" do
26
+    before do
27
+      @checker.should be_valid
28
+    end
29
+
30
+    it "should validate presence of necessary fields" do
31
+      @checker.options[:command] = nil
32
+      @checker.should_not be_valid
33
+    end
34
+
35
+    it "should validate path" do
36
+      @checker.options[:path] = 'notarealpath/itreallyisnt'
37
+      @checker.should_not be_valid
38
+    end
39
+  end
40
+
41
+  describe "#working?" do
42
+    it "checks if its generating events as scheduled" do
43
+      @checker.should_not be_working
44
+      @checker.check
45
+      @checker.reload.should be_working
46
+      three_days_from_now = 3.days.from_now
47
+      stub(Time).now { three_days_from_now }
48
+      @checker.should_not be_working
49
+    end
50
+  end
51
+
52
+  describe "#check" do
53
+    it "should check that initial run creates an event" do
54
+      expect { @checker.check }.to change { Event.count }.by(1)
55
+    end
56
+  end
57
+
58
+  describe "#receive" do
59
+    it "checks if creates events" do
60
+      @checker.receive([@event])
61
+      Event.last.payload[:path].should == @valid_path
62
+    end
63
+    it "checks if options are taken from event" do
64
+      @event.payload[:command] = 'notarealcommand'
65
+      @checker.receive([@event])
66
+      Event.last.payload[:command].should == 'notarealcommand'
67
+    end
68
+  end
69
+
70
+end