huginn_ruby_agent 0.1 → 0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/agents/ruby_agent.rb +142 -0
- data/lib/huginn_ruby_agent/agent.rb +85 -0
- data/lib/huginn_ruby_agent/sdk.rb +70 -0
- data/lib/huginn_ruby_agent.rb +4 -2
- data/spec/agent_spec.rb +135 -0
- data/spec/sdk_spec.rb +22 -0
- metadata +24 -6
- data/lib/huginn_ruby_agent/ruby_agent.rb +0 -29
- data/spec/ruby_agent_spec.rb +0 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2dcca4b6d669c71c00b5797822e4dfe24c09a0b8344dcd6ad49b9076b7cfe050
|
4
|
+
data.tar.gz: e2580eb01f896a539e98096856a8b79c2416ca04abfa53433f65659cda454cdd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d4ad3b1d8bf620b5184cdcc9d84d312bf2fdbb5949f5e9b4311d5ae757855ae329b86745da593ace0c8d8d18434a62bb303abb85756246344944748db9a0b79d
|
7
|
+
data.tar.gz: 0512e8b2944b8e2e542e2529673ed1473062d45e8f8b409f852f31dd78b8d963c215c84dc92366593c77a4b60a14292958daa7ad653dc0da5edabe3fecc64197
|
@@ -0,0 +1,142 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'huginn_ruby_agent'
|
3
|
+
require 'huginn_ruby_agent/agent'
|
4
|
+
|
5
|
+
module Agents
|
6
|
+
class RubyAgent < Agent
|
7
|
+
include FormConfigurable
|
8
|
+
|
9
|
+
can_dry_run!
|
10
|
+
|
11
|
+
default_schedule "never"
|
12
|
+
|
13
|
+
# TODO: remove redundant
|
14
|
+
gem_dependency_check { defined?(MiniRacer) }
|
15
|
+
|
16
|
+
description <<-MD
|
17
|
+
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!
|
18
|
+
|
19
|
+
You should put code in the `code` option.
|
20
|
+
|
21
|
+
You can implement `Agent.check` and `Agent.receive` as you see fit. The following methods will be available on Agent:
|
22
|
+
|
23
|
+
* `@api.create_event(payload)`
|
24
|
+
* `@api.incoming_vents()` (the returned event objects will each have a `payload` property) # TODO
|
25
|
+
* `@api.memory()` # TODO
|
26
|
+
* `@api.memory(key)` # TODO
|
27
|
+
* `@api.memory(keyToSet, valueToSet)` # TODO
|
28
|
+
* `@api.set_memory(object)` (replaces the Agent's memory with the provided object) # TODO
|
29
|
+
* `@api.delete_key(key)` (deletes a key from memory and returns the value) # TODO
|
30
|
+
* `@api.credential(name)`
|
31
|
+
* `@api.set_credential(name, valueToSet)`
|
32
|
+
* `@api.options()` # TODO
|
33
|
+
* `@api.options(key)` # TODO
|
34
|
+
* `@api.log(message)`
|
35
|
+
* `@api.error(message)`
|
36
|
+
MD
|
37
|
+
|
38
|
+
form_configurable :code, type: :text, ace: true
|
39
|
+
form_configurable :expected_receive_period_in_days
|
40
|
+
form_configurable :expected_update_period_in_days
|
41
|
+
|
42
|
+
def validate_options
|
43
|
+
errors.add(:base, "The 'code' option is required") unless options['code'].present?
|
44
|
+
end
|
45
|
+
|
46
|
+
def working?
|
47
|
+
return false if recent_error_logs?
|
48
|
+
|
49
|
+
if interpolated['expected_update_period_in_days'].present?
|
50
|
+
return false unless event_created_within?(interpolated['expected_update_period_in_days'])
|
51
|
+
end
|
52
|
+
|
53
|
+
if interpolated['expected_receive_period_in_days'].present?
|
54
|
+
return false unless last_receive_at && last_receive_at > interpolated['expected_receive_period_in_days'].to_i.days.ago
|
55
|
+
end
|
56
|
+
|
57
|
+
true
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
def check
|
62
|
+
running_agent do |agent|
|
63
|
+
agent.check
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def receive(events)
|
68
|
+
running_agent do |agent|
|
69
|
+
agent.receive events
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def default_options
|
74
|
+
code = <<~CODE
|
75
|
+
|
76
|
+
require "bundler/inline"
|
77
|
+
|
78
|
+
gemfile do
|
79
|
+
source "https://rubygems.org"
|
80
|
+
|
81
|
+
# gem "mechanize"
|
82
|
+
end
|
83
|
+
|
84
|
+
class Agent
|
85
|
+
def initialize(api)
|
86
|
+
@api = api
|
87
|
+
end
|
88
|
+
|
89
|
+
def check
|
90
|
+
@api.create_event({ message: 'I made an event!' })
|
91
|
+
end
|
92
|
+
|
93
|
+
def receive(incoming_events)
|
94
|
+
incoming_events.each do |event|
|
95
|
+
@api.create_event({ message: 'new event', event_was: event[:payload] })
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
CODE
|
100
|
+
|
101
|
+
{
|
102
|
+
'code' => code,
|
103
|
+
'expected_receive_period_in_days' => '2',
|
104
|
+
'expected_update_period_in_days' => '2'
|
105
|
+
}
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def running_agent
|
111
|
+
agent = HuginnRubyAgent::Agent.new(code:, credentials: credentials_hash)
|
112
|
+
yield agent
|
113
|
+
|
114
|
+
agent.events.each do |event|
|
115
|
+
create_event(payload: event)
|
116
|
+
end
|
117
|
+
agent.logs.each do |message|
|
118
|
+
log message
|
119
|
+
end
|
120
|
+
agent.errors.each do |message|
|
121
|
+
error message
|
122
|
+
end
|
123
|
+
agent.changed_credentials.each do |name, value|
|
124
|
+
set_credential(name, value)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def code
|
129
|
+
interpolated['code']
|
130
|
+
end
|
131
|
+
|
132
|
+
def credentials_hash
|
133
|
+
Hash[user.user_credentials.map { |c| [c.credential_name, c.credential_value] }]
|
134
|
+
end
|
135
|
+
|
136
|
+
def set_credential(name, value)
|
137
|
+
c = user.user_credentials.find_or_initialize_by(credential_name: name)
|
138
|
+
c.credential_value = value
|
139
|
+
c.save!
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'open3'
|
2
|
+
require 'json'
|
3
|
+
require 'huginn_ruby_agent/sdk'
|
4
|
+
require 'base64'
|
5
|
+
|
6
|
+
module HuginnRubyAgent
|
7
|
+
class Agent
|
8
|
+
attr_reader :events, :errors, :logs, :changed_credentials
|
9
|
+
|
10
|
+
def initialize(code:, credentials: {})
|
11
|
+
@code = code
|
12
|
+
@events = []
|
13
|
+
@logs = []
|
14
|
+
@errors = []
|
15
|
+
@credentials = credentials
|
16
|
+
@changed_credentials = {}
|
17
|
+
end
|
18
|
+
|
19
|
+
def check
|
20
|
+
execute ".check"
|
21
|
+
end
|
22
|
+
|
23
|
+
def receive(events)
|
24
|
+
execute ".receive(api.deserialize('#{sdk.serialize(events)}'))"
|
25
|
+
end
|
26
|
+
|
27
|
+
def create_event(payload)
|
28
|
+
@events << payload
|
29
|
+
end
|
30
|
+
|
31
|
+
def log(message)
|
32
|
+
@logs << message
|
33
|
+
end
|
34
|
+
|
35
|
+
def error(message)
|
36
|
+
@errors << message
|
37
|
+
end
|
38
|
+
|
39
|
+
def sdk
|
40
|
+
@sdk ||= SDK.new
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
# https://stackoverflow.com/questions/23884526/is-there-a-safe-way-to-eval-in-ruby-or-a-better-way-to-do-this
|
46
|
+
def execute(command=".check")
|
47
|
+
Bundler.with_original_env do
|
48
|
+
Open3.popen3("ruby", chdir: '/') do |input, output, err, thread|
|
49
|
+
input.write sdk.code
|
50
|
+
input.write @code
|
51
|
+
input.write <<~CODE
|
52
|
+
|
53
|
+
api = Huginn::API.new(serialized_credentials: '#{sdk.serialize(@credentials)}')
|
54
|
+
Agent.new(api)#{command}
|
55
|
+
|
56
|
+
CODE
|
57
|
+
input.close
|
58
|
+
|
59
|
+
output.readlines.map { |line| sdk.deserialize(line) }.each do |data|
|
60
|
+
case data[:action]
|
61
|
+
when 'create_event'
|
62
|
+
create_event(data[:payload])
|
63
|
+
when 'log'
|
64
|
+
log data[:payload]
|
65
|
+
when 'error'
|
66
|
+
error data[:payload]
|
67
|
+
when 'set_credential'
|
68
|
+
@changed_credentials[data[:payload][:name].to_sym] = data[:payload][:value]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
log_errors(err)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
rescue StandardError => e
|
76
|
+
error "Runtime error: #{e.message}"
|
77
|
+
end
|
78
|
+
|
79
|
+
def log_errors(err)
|
80
|
+
err.read.lines.each do |line|
|
81
|
+
error line.strip
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'base64'
|
3
|
+
|
4
|
+
module HuginnRubyAgent
|
5
|
+
class SDK
|
6
|
+
def serialize(payload)
|
7
|
+
Base64.urlsafe_encode64(payload.to_json)
|
8
|
+
end
|
9
|
+
|
10
|
+
def deserialize(serialized_payload)
|
11
|
+
JSON.parse Base64.urlsafe_decode64(serialized_payload.strip), symbolize_names: true
|
12
|
+
end
|
13
|
+
|
14
|
+
def code
|
15
|
+
<<~CODE
|
16
|
+
require 'json'
|
17
|
+
require 'base64'
|
18
|
+
|
19
|
+
module Huginn
|
20
|
+
class API
|
21
|
+
attr_reader :changed_credentials
|
22
|
+
|
23
|
+
def initialize(serialized_credentials: nil)
|
24
|
+
@serialized_credentials = serialized_credentials
|
25
|
+
@changed_credentials = {}
|
26
|
+
end
|
27
|
+
|
28
|
+
def credentials
|
29
|
+
@credentials ||=
|
30
|
+
begin
|
31
|
+
@serialized_credentials.nil? ? {} : deserialize(@serialized_credentials)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def serialize(payload)
|
36
|
+
Base64.urlsafe_encode64(payload.to_json)
|
37
|
+
end
|
38
|
+
|
39
|
+
def deserialize(serialized_payload)
|
40
|
+
JSON.parse Base64.urlsafe_decode64(serialized_payload.strip), symbolize_names: true
|
41
|
+
end
|
42
|
+
|
43
|
+
def credential(name)
|
44
|
+
credentials[name.to_sym]
|
45
|
+
end
|
46
|
+
|
47
|
+
def set_credential(name, value)
|
48
|
+
credentials[name.to_sym] = value
|
49
|
+
changed_credentials[name.to_sym] = value
|
50
|
+
|
51
|
+
puts serialize({ action: :set_credential, payload: { name: name, value: value } })
|
52
|
+
end
|
53
|
+
|
54
|
+
def create_event(payload)
|
55
|
+
puts serialize({ action: :create_event, payload: payload })
|
56
|
+
end
|
57
|
+
|
58
|
+
def log(message)
|
59
|
+
puts serialize({ action: :log, payload: message })
|
60
|
+
end
|
61
|
+
|
62
|
+
def error(message)
|
63
|
+
puts serialize({ action: :error, payload: message })
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
CODE
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
data/lib/huginn_ruby_agent.rb
CHANGED
data/spec/agent_spec.rb
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'huginn_ruby_agent'
|
2
|
+
require 'huginn_ruby_agent/agent'
|
3
|
+
|
4
|
+
module HuginnRubyAgent
|
5
|
+
describe Agent do
|
6
|
+
describe '#check' do
|
7
|
+
example 'it produces event' do
|
8
|
+
code = <<~CODE
|
9
|
+
class Agent
|
10
|
+
def initialize(api)
|
11
|
+
@api = api
|
12
|
+
end
|
13
|
+
|
14
|
+
def check
|
15
|
+
@api.create_event({ message: 'hello' })
|
16
|
+
end
|
17
|
+
end
|
18
|
+
CODE
|
19
|
+
|
20
|
+
agent = described_class.new(code: code)
|
21
|
+
agent.check
|
22
|
+
|
23
|
+
expect(agent.events.size).to eq 1
|
24
|
+
expect(agent.events[0]).to eq(message: 'hello')
|
25
|
+
end
|
26
|
+
|
27
|
+
example "it captures error" do
|
28
|
+
code = <<~CODE
|
29
|
+
class Agent
|
30
|
+
def initialize(api)
|
31
|
+
@api = api
|
32
|
+
end
|
33
|
+
|
34
|
+
def check
|
35
|
+
some error here
|
36
|
+
end
|
37
|
+
end
|
38
|
+
CODE
|
39
|
+
|
40
|
+
agent = described_class.new(code: code)
|
41
|
+
agent.check
|
42
|
+
|
43
|
+
expect(agent.events).to be_empty
|
44
|
+
expect(agent.errors).not_to be_empty
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe '#receive' do
|
49
|
+
example 'it produces event' do
|
50
|
+
code = <<~CODE
|
51
|
+
class Agent
|
52
|
+
def initialize(api)
|
53
|
+
@api = api
|
54
|
+
end
|
55
|
+
|
56
|
+
def receive(events)
|
57
|
+
events.each do |event|
|
58
|
+
@api.create_event({ number: event[:number] + 1 })
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
CODE
|
63
|
+
|
64
|
+
agent = described_class.new(code: code)
|
65
|
+
agent.receive([{ number: 1 }])
|
66
|
+
|
67
|
+
expect(agent.events.size).to eq 1
|
68
|
+
expect(agent.events[0]).to eq(number: 2)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe '#logs' do
|
73
|
+
example 'it produces log' do
|
74
|
+
code = <<~CODE
|
75
|
+
class Agent
|
76
|
+
def initialize(api)
|
77
|
+
@api = api
|
78
|
+
end
|
79
|
+
|
80
|
+
def check
|
81
|
+
@api.log "hello"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
CODE
|
85
|
+
|
86
|
+
agent = described_class.new(code: code)
|
87
|
+
agent.check
|
88
|
+
|
89
|
+
expect(agent.logs.size).to eq 1
|
90
|
+
expect(agent.logs[0]).to eq "hello"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe '#credentials' do
|
95
|
+
example 'it gives access to creds' do
|
96
|
+
code = <<~CODE
|
97
|
+
class Agent
|
98
|
+
def initialize(api)
|
99
|
+
@api = api
|
100
|
+
end
|
101
|
+
|
102
|
+
def check
|
103
|
+
@api.create_event token_from_credential: @api.credential(:token)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
CODE
|
107
|
+
|
108
|
+
agent = described_class.new(code: code, credentials: { token: 'abc123' })
|
109
|
+
agent.check
|
110
|
+
|
111
|
+
expect(agent.events[0]).to eq({ token_from_credential: 'abc123' })
|
112
|
+
end
|
113
|
+
|
114
|
+
example 'it updates creds' do
|
115
|
+
code = <<~CODE
|
116
|
+
class Agent
|
117
|
+
def initialize(api)
|
118
|
+
@api = api
|
119
|
+
end
|
120
|
+
|
121
|
+
def check
|
122
|
+
@api.set_credential(:token, 'new_val')
|
123
|
+
end
|
124
|
+
end
|
125
|
+
CODE
|
126
|
+
|
127
|
+
agent = described_class.new(code: code, credentials: { token: 'abc123' })
|
128
|
+
|
129
|
+
expect(agent.changed_credentials).to be_empty
|
130
|
+
agent.check
|
131
|
+
expect(agent.changed_credentials).to eq({ token: 'new_val' })
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
data/spec/sdk_spec.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'huginn_ruby_agent'
|
2
|
+
require 'huginn_ruby_agent/sdk'
|
3
|
+
|
4
|
+
module HuginnRubyAgent
|
5
|
+
describe SDK do
|
6
|
+
subject(:sdk) { described_class.new }
|
7
|
+
|
8
|
+
def transfered(data)
|
9
|
+
sdk.deserialize(sdk.serialize(data))
|
10
|
+
end
|
11
|
+
|
12
|
+
def expect_to_transfer_safe(data)
|
13
|
+
expect(transfered(data)).to eq data
|
14
|
+
end
|
15
|
+
|
16
|
+
it { expect_to_transfer_safe(1) }
|
17
|
+
it { expect_to_transfer_safe({}) }
|
18
|
+
it { expect_to_transfer_safe({ payload: 1 }) }
|
19
|
+
it { expect_to_transfer_safe({ action: 'create_event', payload: { number: 1 } }) }
|
20
|
+
it { expect_to_transfer_safe({ action: 'create_event', payload: { message: "hello" }}) }
|
21
|
+
end
|
22
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: huginn_ruby_agent
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0.
|
4
|
+
version: '0.3'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sergei O. Udalov
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-01-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
70
|
name: huginn_agent
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -73,9 +87,12 @@ executables: []
|
|
73
87
|
extensions: []
|
74
88
|
extra_rdoc_files: []
|
75
89
|
files:
|
90
|
+
- lib/agents/ruby_agent.rb
|
76
91
|
- lib/huginn_ruby_agent.rb
|
77
|
-
- lib/huginn_ruby_agent/
|
78
|
-
-
|
92
|
+
- lib/huginn_ruby_agent/agent.rb
|
93
|
+
- lib/huginn_ruby_agent/sdk.rb
|
94
|
+
- spec/agent_spec.rb
|
95
|
+
- spec/sdk_spec.rb
|
79
96
|
homepage: https://github.com/sergio-fry/huginn_ruby_agent
|
80
97
|
licenses: []
|
81
98
|
metadata: {}
|
@@ -94,9 +111,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
94
111
|
- !ruby/object:Gem::Version
|
95
112
|
version: '0'
|
96
113
|
requirements: []
|
97
|
-
rubygems_version: 3.
|
114
|
+
rubygems_version: 3.1.6
|
98
115
|
signing_key:
|
99
116
|
specification_version: 4
|
100
117
|
summary: Ruby Agent for Huginn automation platform
|
101
118
|
test_files:
|
102
|
-
- spec/
|
119
|
+
- spec/sdk_spec.rb
|
120
|
+
- spec/agent_spec.rb
|
@@ -1,29 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Agents
|
4
|
-
class RubyAgent < Agent
|
5
|
-
default_schedule '12h'
|
6
|
-
|
7
|
-
description <<-MD
|
8
|
-
Add a Agent description here
|
9
|
-
MD
|
10
|
-
|
11
|
-
def default_options
|
12
|
-
{}
|
13
|
-
end
|
14
|
-
|
15
|
-
def validate_options; end
|
16
|
-
|
17
|
-
def working?
|
18
|
-
# Implement me! Maybe one of these next two lines would be a good fit?
|
19
|
-
# checked_without_error?
|
20
|
-
# received_event_without_error?
|
21
|
-
end
|
22
|
-
|
23
|
-
# def check
|
24
|
-
# end
|
25
|
-
|
26
|
-
# def receive(incoming_events)
|
27
|
-
# end
|
28
|
-
end
|
29
|
-
end
|
data/spec/ruby_agent_spec.rb
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'rails_helper'
|
4
|
-
require 'huginn_agent/spec_helper'
|
5
|
-
|
6
|
-
describe Agents::RubyAgent do
|
7
|
-
before(:each) do
|
8
|
-
@valid_options = Agents::RubyAgent.new.default_options
|
9
|
-
@checker = Agents::RubyAgent.new(name: 'RubyAgent', options: @valid_options)
|
10
|
-
@checker.user = users(:bob)
|
11
|
-
@checker.save!
|
12
|
-
end
|
13
|
-
|
14
|
-
pending 'add specs here'
|
15
|
-
end
|