Latest Threat Research:SANDWORM_MODE: Shai-Hulud-Style npm Worm Hijacks CI Workflows and Poisons AI Toolchains.Details
Socket
Book a DemoInstallSign in
Socket

huginn_ruby_agent

Package Overview
Dependencies
Maintainers
1
Versions
3
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

huginn_ruby_agent - npm Package Compare versions

Comparing version
0.2
to
0.3
+142
lib/agents/ruby_agent.rb
# frozen_string_literal: true
require 'huginn_ruby_agent'
require 'huginn_ruby_agent/agent'
module Agents
class RubyAgent < Agent
include FormConfigurable
can_dry_run!
default_schedule "never"
# TODO: remove redundant
gem_dependency_check { defined?(MiniRacer) }
description <<-MD
The Ruby Agent allows you to write code in Ruby that can create and receive events. If other Agents aren't meeting your needs, try this one!
You should put code in the `code` option.
You can implement `Agent.check` and `Agent.receive` as you see fit. The following methods will be available on Agent:
* `@api.create_event(payload)`
* `@api.incoming_vents()` (the returned event objects will each have a `payload` property) # TODO
* `@api.memory()` # TODO
* `@api.memory(key)` # TODO
* `@api.memory(keyToSet, valueToSet)` # TODO
* `@api.set_memory(object)` (replaces the Agent's memory with the provided object) # TODO
* `@api.delete_key(key)` (deletes a key from memory and returns the value) # TODO
* `@api.credential(name)`
* `@api.set_credential(name, valueToSet)`
* `@api.options()` # TODO
* `@api.options(key)` # TODO
* `@api.log(message)`
* `@api.error(message)`
MD
form_configurable :code, type: :text, ace: true
form_configurable :expected_receive_period_in_days
form_configurable :expected_update_period_in_days
def validate_options
errors.add(:base, "The 'code' option is required") unless options['code'].present?
end
def working?
return false if recent_error_logs?
if interpolated['expected_update_period_in_days'].present?
return false unless event_created_within?(interpolated['expected_update_period_in_days'])
end
if interpolated['expected_receive_period_in_days'].present?
return false unless last_receive_at && last_receive_at > interpolated['expected_receive_period_in_days'].to_i.days.ago
end
true
end
def check
running_agent do |agent|
agent.check
end
end
def receive(events)
running_agent do |agent|
agent.receive events
end
end
def default_options
code = <<~CODE
require "bundler/inline"
gemfile do
source "https://rubygems.org"
# gem "mechanize"
end
class Agent
def initialize(api)
@api = api
end
def check
@api.create_event({ message: 'I made an event!' })
end
def receive(incoming_events)
incoming_events.each do |event|
@api.create_event({ message: 'new event', event_was: event[:payload] })
end
end
end
CODE
{
'code' => code,
'expected_receive_period_in_days' => '2',
'expected_update_period_in_days' => '2'
}
end
private
def running_agent
agent = HuginnRubyAgent::Agent.new(code:, credentials: credentials_hash)
yield agent
agent.events.each do |event|
create_event(payload: event)
end
agent.logs.each do |message|
log message
end
agent.errors.each do |message|
error message
end
agent.changed_credentials.each do |name, value|
set_credential(name, value)
end
end
def code
interpolated['code']
end
def credentials_hash
Hash[user.user_credentials.map { |c| [c.credential_name, c.credential_value] }]
end
def set_credential(name, value)
c = user.user_credentials.find_or_initialize_by(credential_name: name)
c.credential_value = value
c.save!
end
end
end
require 'open3'
require 'json'
require 'huginn_ruby_agent/sdk'
require 'base64'
module HuginnRubyAgent
class Agent
attr_reader :events, :errors, :logs, :changed_credentials
def initialize(code:, credentials: {})
@code = code
@events = []
@logs = []
@errors = []
@credentials = credentials
@changed_credentials = {}
end
def check
execute ".check"
end
def receive(events)
execute ".receive(api.deserialize('#{sdk.serialize(events)}'))"
end
def create_event(payload)
@events << payload
end
def log(message)
@logs << message
end
def error(message)
@errors << message
end
def sdk
@sdk ||= SDK.new
end
private
# https://stackoverflow.com/questions/23884526/is-there-a-safe-way-to-eval-in-ruby-or-a-better-way-to-do-this
def execute(command=".check")
Bundler.with_original_env do
Open3.popen3("ruby", chdir: '/') do |input, output, err, thread|
input.write sdk.code
input.write @code
input.write <<~CODE
api = Huginn::API.new(serialized_credentials: '#{sdk.serialize(@credentials)}')
Agent.new(api)#{command}
CODE
input.close
output.readlines.map { |line| sdk.deserialize(line) }.each do |data|
case data[:action]
when 'create_event'
create_event(data[:payload])
when 'log'
log data[:payload]
when 'error'
error data[:payload]
when 'set_credential'
@changed_credentials[data[:payload][:name].to_sym] = data[:payload][:value]
end
end
log_errors(err)
end
end
rescue StandardError => e
error "Runtime error: #{e.message}"
end
def log_errors(err)
err.read.lines.each do |line|
error line.strip
end
end
end
end
require 'json'
require 'base64'
module HuginnRubyAgent
class SDK
def serialize(payload)
Base64.urlsafe_encode64(payload.to_json)
end
def deserialize(serialized_payload)
JSON.parse Base64.urlsafe_decode64(serialized_payload.strip), symbolize_names: true
end
def code
<<~CODE
require 'json'
require 'base64'
module Huginn
class API
attr_reader :changed_credentials
def initialize(serialized_credentials: nil)
@serialized_credentials = serialized_credentials
@changed_credentials = {}
end
def credentials
@credentials ||=
begin
@serialized_credentials.nil? ? {} : deserialize(@serialized_credentials)
end
end
def serialize(payload)
Base64.urlsafe_encode64(payload.to_json)
end
def deserialize(serialized_payload)
JSON.parse Base64.urlsafe_decode64(serialized_payload.strip), symbolize_names: true
end
def credential(name)
credentials[name.to_sym]
end
def set_credential(name, value)
credentials[name.to_sym] = value
changed_credentials[name.to_sym] = value
puts serialize({ action: :set_credential, payload: { name: name, value: value } })
end
def create_event(payload)
puts serialize({ action: :create_event, payload: payload })
end
def log(message)
puts serialize({ action: :log, payload: message })
end
def error(message)
puts serialize({ action: :error, payload: message })
end
end
end
CODE
end
end
end
require 'huginn_ruby_agent'
require 'huginn_ruby_agent/agent'
module HuginnRubyAgent
describe Agent do
describe '#check' do
example 'it produces event' do
code = <<~CODE
class Agent
def initialize(api)
@api = api
end
def check
@api.create_event({ message: 'hello' })
end
end
CODE
agent = described_class.new(code: code)
agent.check
expect(agent.events.size).to eq 1
expect(agent.events[0]).to eq(message: 'hello')
end
example "it captures error" do
code = <<~CODE
class Agent
def initialize(api)
@api = api
end
def check
some error here
end
end
CODE
agent = described_class.new(code: code)
agent.check
expect(agent.events).to be_empty
expect(agent.errors).not_to be_empty
end
end
describe '#receive' do
example 'it produces event' do
code = <<~CODE
class Agent
def initialize(api)
@api = api
end
def receive(events)
events.each do |event|
@api.create_event({ number: event[:number] + 1 })
end
end
end
CODE
agent = described_class.new(code: code)
agent.receive([{ number: 1 }])
expect(agent.events.size).to eq 1
expect(agent.events[0]).to eq(number: 2)
end
end
describe '#logs' do
example 'it produces log' do
code = <<~CODE
class Agent
def initialize(api)
@api = api
end
def check
@api.log "hello"
end
end
CODE
agent = described_class.new(code: code)
agent.check
expect(agent.logs.size).to eq 1
expect(agent.logs[0]).to eq "hello"
end
end
describe '#credentials' do
example 'it gives access to creds' do
code = <<~CODE
class Agent
def initialize(api)
@api = api
end
def check
@api.create_event token_from_credential: @api.credential(:token)
end
end
CODE
agent = described_class.new(code: code, credentials: { token: 'abc123' })
agent.check
expect(agent.events[0]).to eq({ token_from_credential: 'abc123' })
end
example 'it updates creds' do
code = <<~CODE
class Agent
def initialize(api)
@api = api
end
def check
@api.set_credential(:token, 'new_val')
end
end
CODE
agent = described_class.new(code: code, credentials: { token: 'abc123' })
expect(agent.changed_credentials).to be_empty
agent.check
expect(agent.changed_credentials).to eq({ token: 'new_val' })
end
end
end
end
require 'huginn_ruby_agent'
require 'huginn_ruby_agent/sdk'
module HuginnRubyAgent
describe SDK do
subject(:sdk) { described_class.new }
def transfered(data)
sdk.deserialize(sdk.serialize(data))
end
def expect_to_transfer_safe(data)
expect(transfered(data)).to eq data
end
it { expect_to_transfer_safe(1) }
it { expect_to_transfer_safe({}) }
it { expect_to_transfer_safe({ payload: 1 }) }
it { expect_to_transfer_safe({ action: 'create_event', payload: { number: 1 } }) }
it { expect_to_transfer_safe({ action: 'create_event', payload: { message: "hello" }}) }
end
end
+4
-2

@@ -5,3 +5,5 @@ # frozen_string_literal: true

# HuginnAgent.load 'huginn_ruby_agent/concerns/my_agent_concern'
HuginnAgent.register 'huginn_ruby_agent/ruby_agent'
module HuginnRubyAgent
end
HuginnAgent.register 'agents/ruby_agent'
# frozen_string_literal: true
require 'date'
require 'cgi'
require 'tempfile'
require 'base64'
# https://stackoverflow.com/questions/23884526/is-there-a-safe-way-to-eval-in-ruby-or-a-better-way-to-do-this
module Agents
class RubyAgent < Agent
include FormConfigurable
can_dry_run!
default_schedule "never"
gem_dependency_check { defined?(MiniRacer) }
description <<-MD
The Ruby Agent allows you to write code in Ruby that can create and receive events. If other Agents aren't meeting your needs, try this one!
You should put code in the `code` option.
You can implement `Agent.check` and `Agent.receive` as you see fit. The following methods will be available on Agent:
* `createEvent(payload)`
* `incomingEvents()` (the returned event objects will each have a `payload` property)
* `memory()`
* `memory(key)`
* `memory(keyToSet, valueToSet)`
* `setMemory(object)` (replaces the Agent's memory with the provided object)
* `deleteKey(key)` (deletes a key from memory and returns the value)
* `credential(name)`
* `credential(name, valueToSet)`
* `options()`
* `options(key)`
* `log(message)`
* `error(message)`
MD
form_configurable :code, type: :text, ace: true
form_configurable :expected_receive_period_in_days
form_configurable :expected_update_period_in_days
def validate_options
errors.add(:base, "The 'code' option is required") unless options['code'].present?
end
def working?
return false if recent_error_logs?
if interpolated['expected_update_period_in_days'].present?
return false unless event_created_within?(interpolated['expected_update_period_in_days'])
end
if interpolated['expected_receive_period_in_days'].present?
return false unless last_receive_at && last_receive_at > interpolated['expected_receive_period_in_days'].to_i.days.ago
end
true
end
def check
log_errors do
execute_check
end
end
def receive(events)
log_errors do
execute_receive(events)
end
end
def default_options
code = <<~CODE
require "bundler/inline"
gemfile do
source "https://rubygems.org"
# gem "mechanize"
end
class Agent
def initialize(api)
@api = api
end
def check
@api.create_event({ message: 'I made an event!' })
end
def receive(incoming_events)
incoming_events.each do |event|
@api.create_event({ message: 'new event', event_was: event[:payload] })
end
end
end
CODE
{
'code' => code,
'expected_receive_period_in_days' => '2',
'expected_update_period_in_days' => '2'
}
end
private
def execute_check
Bundler.with_original_env do
Open3.popen3("ruby", chdir: '/') do |input, output, err, thread|
input.write sdk_code
input.write code
input.write <<~CODE
Agent.new(Huginn::API.new).check
CODE
input.close
output.readlines.map { |line| JSON.parse(line, symbolize_names: true) }.each do |data|
case data[:action]
when 'create_event'
create_event(payload: data[:payload])
when 'log'
log data[:payload]
when 'error'
error data[:payload]
end
end
errors = err.read
error err.read
log "thread #{thread.value}"
end
end
end
def execute_receive(events)
Bundler.with_original_env do
Open3.popen3("ruby", chdir: '/') do |input, output, err, thread|
input.write sdk_code
input.write code
input.write <<~CODE
api = Huginn::API.new
begin
Agent.new(api).receive(
JSON.parse(
Base64.decode64(
"#{Base64.encode64(events.to_json)}"
),
symbolize_names: true
)
)
rescue StandardError => ex
api.error ex
end
CODE
input.close
output.readlines.map { |line| JSON.parse(line, symbolize_names: true) }.each do |data|
case data[:action]
when 'create_event'
create_event(payload: data[:payload])
when 'log'
log data[:payload]
when 'error'
error data[:payload]
end
end
errors = err.read
error err.read
log "thread #{thread.value}"
end
end
end
def code
interpolated['code']
end
def sdk_code
<<~CODE
require 'json'
require 'base64'
module Huginn
class API
def create_event(payload)
puts(
{
action: :create_event,
payload: payload
}.to_json
)
end
def log(message)
puts(
{
action: :log,
payload: message
}.to_json
)
end
def error(message)
puts(
{
action: :error,
payload: message
}.to_json
)
end
end
end
CODE
end
def log_errors
begin
yield
rescue StandardError => e
error "Runtime error: #{e.message}"
end
end
end
end
# frozen_string_literal: true
require 'rails_helper'
require 'huginn_agent/spec_helper'
describe Agents::RubyAgent do
before(:each) do
@valid_options = Agents::RubyAgent.new.default_options
@checker = Agents::RubyAgent.new(name: 'RubyAgent', options: @valid_options)
@checker.user = users(:bob)
@checker.save!
end
pending 'add specs here'
end