Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor ZirconCode's Agent to only run when ENABLE_INSECURE_AGENTS i…
…s enabled in .env
- Loading branch information
Showing
5 changed files
with
214 additions
and
171 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
require 'open3' | ||
|
||
module Agents | ||
class ShellCommandAgent < Agent | ||
default_schedule "never" | ||
|
||
def self.should_run? | ||
ENV['ENABLE_INSECURE_AGENTS'] == "true" | ||
end | ||
|
||
description <<-MD | ||
The ShellCommandAgent can execute commands on your local system, returning the output. | ||
`command` specifies the command to be executed, and `path` will tell ShellCommandAgent in what directory to run this command. | ||
`expected_update_period_in_days` is used to determine if the Agent is working. | ||
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. | ||
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. | ||
*Warning*: This type of Agent runs arbitrary commands on your system, #{Agents::ShellCommandAgent.should_run? ? "but is **currently enabled**" : "and is **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 | ||
|
||
event_description <<-MD | ||
Events look like this: | ||
{ | ||
'command' => 'pwd', | ||
'path' => '/home/Huginn', | ||
'exit_status' => '0', | ||
'errors' => '', | ||
'output' => '/home/Huginn' | ||
} | ||
MD | ||
|
||
def default_options | ||
{ | ||
'path' => "/", | ||
'command' => "pwd", | ||
'expected_update_period_in_days' => 1 | ||
} | ||
end | ||
|
||
def validate_options | ||
unless options['path'].present? && options['command'].present? && options['expected_update_period_in_days'].present? | ||
errors.add(:base, "The path, command, and expected_update_period_in_days fields are all required.") | ||
end | ||
|
||
unless File.directory?(options['path']) | ||
errors.add(:base, "#{options['path']} is not a real directory.") | ||
end | ||
end | ||
|
||
def working? | ||
Agents::ShellCommandAgent.should_run? && event_created_within?(options['expected_update_period_in_days']) && !recent_error_logs? | ||
end | ||
|
||
def receive(incoming_events) | ||
incoming_events.each do |event| | ||
handle(event.payload, event) | ||
end | ||
end | ||
|
||
def check | ||
handle(options) | ||
end | ||
|
||
private | ||
|
||
def handle(opts = options, event = nil) | ||
if Agents::ShellCommandAgent.should_run? | ||
command = opts['command'] || options['command'] | ||
path = opts['path'] || options['path'] | ||
|
||
result, errors, exit_status = run_command(path, command) | ||
|
||
vals = {"command" => command, "path" => path, "exit_status" => exit_status, "errors" => errors, "output" => result} | ||
created_event = create_event :payload => vals | ||
|
||
log("Ran '#{command}' under '#{path}'", :outbound_event => created_event, :inbound_event => event) | ||
else | ||
log("Unable to run because insecure agents are not enabled. Edit ENABLE_INSECURE_AGENTS in the Huginn .env configuration.") | ||
end | ||
end | ||
|
||
def run_command(path, command) | ||
result = nil | ||
errors = nil | ||
exit_status = nil | ||
|
||
Dir.chdir(path){ | ||
begin | ||
stdin, stdout, stderr, wait_thr = Open3.popen3(command) | ||
exit_status = wait_thr.value.to_i | ||
result = stdout.gets(nil) | ||
errors = stderr.gets(nil) | ||
rescue Exception => e | ||
errors = e.to_s | ||
end | ||
} | ||
|
||
result = result.to_s.strip | ||
errors = errors.to_s.strip | ||
|
||
[result, errors, exit_status] | ||
end | ||
end | ||
end |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
require 'spec_helper' | ||
|
||
describe Agents::ShellCommandAgent do | ||
before do | ||
@valid_path = Dir.pwd | ||
|
||
@valid_params = { | ||
:path => @valid_path, | ||
:command => "pwd", | ||
:expected_update_period_in_days => "1", | ||
} | ||
|
||
@checker = Agents::ShellCommandAgent.new(:name => "somename", :options => @valid_params) | ||
@checker.user = users(:jane) | ||
@checker.save! | ||
|
||
@event = Event.new | ||
@event.agent = agents(:jane_weather_agent) | ||
@event.payload = { | ||
:command => "ls" | ||
} | ||
@event.save! | ||
|
||
stub(Agents::ShellCommandAgent).should_run? { true } | ||
end | ||
|
||
describe "validation" do | ||
before do | ||
@checker.should be_valid | ||
end | ||
|
||
it "should validate presence of necessary fields" do | ||
@checker.options[:command] = nil | ||
@checker.should_not be_valid | ||
end | ||
|
||
it "should validate path" do | ||
@checker.options[:path] = 'notarealpath/itreallyisnt' | ||
@checker.should_not be_valid | ||
end | ||
|
||
it "should validate path" do | ||
@checker.options[:path] = '/' | ||
@checker.should be_valid | ||
end | ||
end | ||
|
||
describe "#working?" do | ||
it "generating events as scheduled" do | ||
stub(@checker).run_command(@valid_path, 'pwd') { ["fake pwd output", "", 0] } | ||
|
||
@checker.should_not be_working | ||
@checker.check | ||
@checker.reload.should be_working | ||
three_days_from_now = 3.days.from_now | ||
stub(Time).now { three_days_from_now } | ||
@checker.should_not be_working | ||
end | ||
end | ||
|
||
describe "#check" do | ||
before do | ||
stub(@checker).run_command(@valid_path, 'pwd') { ["fake pwd output", "", 0] } | ||
end | ||
|
||
it "should create an event when checking" do | ||
expect { @checker.check }.to change { Event.count }.by(1) | ||
Event.last.payload[:path].should == @valid_path | ||
Event.last.payload[:command].should == 'pwd' | ||
Event.last.payload[:output].should == "fake pwd output" | ||
end | ||
|
||
it "does not run when should_run? is false" do | ||
stub(Agents::ShellCommandAgent).should_run? { false } | ||
expect { @checker.check }.not_to change { Event.count } | ||
end | ||
end | ||
|
||
describe "#receive" do | ||
before do | ||
stub(@checker).run_command(@valid_path, @event.payload[:command]) { ["fake ls output", "", 0] } | ||
end | ||
|
||
it "creates events" do | ||
@checker.receive([@event]) | ||
Event.last.payload[:path].should == @valid_path | ||
Event.last.payload[:command].should == @event.payload[:command] | ||
Event.last.payload[:output].should == "fake ls output" | ||
end | ||
|
||
it "does not run when should_run? is false" do | ||
stub(Agents::ShellCommandAgent).should_run? { false } | ||
|
||
expect { | ||
@checker.receive([@event]) | ||
}.not_to change { Event.count } | ||
end | ||
end | ||
end |