huginn_ruby_agent 0.2 → 0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 602117200b2aadebec8c4c6e959c9ab19c4bb585df0a9c72b7926f8b0adc8638
4
- data.tar.gz: 65d559d7e90b569f74c8cd890786ac830d469fd58923c75f50399af64c914b1e
3
+ metadata.gz: 2dcca4b6d669c71c00b5797822e4dfe24c09a0b8344dcd6ad49b9076b7cfe050
4
+ data.tar.gz: e2580eb01f896a539e98096856a8b79c2416ca04abfa53433f65659cda454cdd
5
5
  SHA512:
6
- metadata.gz: 540dd8062b8fed871c15a5f784de8eece0317480a6e59025d4345a86267409660b187f37ca4f03ce48e89b0d8b685b196fc9c095e7ca6c20cb7335b15d42960c
7
- data.tar.gz: c68d5ef8c376d7af0121378ffc4323cae0bd51a78802716990c0f9b62dd6d11f7646697d74b73934735cc182a6ba12032682b00caaf61a652cd2dcf6dd4f175e
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
@@ -2,5 +2,7 @@
2
2
 
3
3
  require 'huginn_agent'
4
4
 
5
- # HuginnAgent.load 'huginn_ruby_agent/concerns/my_agent_concern'
6
- HuginnAgent.register 'huginn_ruby_agent/ruby_agent'
5
+ module HuginnRubyAgent
6
+ end
7
+
8
+ HuginnAgent.register 'agents/ruby_agent'
@@ -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.2'
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: 2022-12-26 00:00:00.000000000 Z
11
+ date: 2024-01-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -87,9 +87,12 @@ executables: []
87
87
  extensions: []
88
88
  extra_rdoc_files: []
89
89
  files:
90
+ - lib/agents/ruby_agent.rb
90
91
  - lib/huginn_ruby_agent.rb
91
- - lib/huginn_ruby_agent/ruby_agent.rb
92
- - spec/ruby_agent_spec.rb
92
+ - lib/huginn_ruby_agent/agent.rb
93
+ - lib/huginn_ruby_agent/sdk.rb
94
+ - spec/agent_spec.rb
95
+ - spec/sdk_spec.rb
93
96
  homepage: https://github.com/sergio-fry/huginn_ruby_agent
94
97
  licenses: []
95
98
  metadata: {}
@@ -113,4 +116,5 @@ signing_key:
113
116
  specification_version: 4
114
117
  summary: Ruby Agent for Huginn automation platform
115
118
  test_files:
116
- - spec/ruby_agent_spec.rb
119
+ - spec/sdk_spec.rb
120
+ - spec/agent_spec.rb
@@ -1,238 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'date'
4
- require 'cgi'
5
- require 'tempfile'
6
- require 'base64'
7
-
8
- # https://stackoverflow.com/questions/23884526/is-there-a-safe-way-to-eval-in-ruby-or-a-better-way-to-do-this
9
- module Agents
10
- class RubyAgent < Agent
11
- include FormConfigurable
12
-
13
- can_dry_run!
14
-
15
- default_schedule "never"
16
-
17
- gem_dependency_check { defined?(MiniRacer) }
18
-
19
- description <<-MD
20
- 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!
21
-
22
- You should put code in the `code` option.
23
-
24
- You can implement `Agent.check` and `Agent.receive` as you see fit. The following methods will be available on Agent:
25
-
26
- * `createEvent(payload)`
27
- * `incomingEvents()` (the returned event objects will each have a `payload` property)
28
- * `memory()`
29
- * `memory(key)`
30
- * `memory(keyToSet, valueToSet)`
31
- * `setMemory(object)` (replaces the Agent's memory with the provided object)
32
- * `deleteKey(key)` (deletes a key from memory and returns the value)
33
- * `credential(name)`
34
- * `credential(name, valueToSet)`
35
- * `options()`
36
- * `options(key)`
37
- * `log(message)`
38
- * `error(message)`
39
- MD
40
-
41
- form_configurable :code, type: :text, ace: true
42
- form_configurable :expected_receive_period_in_days
43
- form_configurable :expected_update_period_in_days
44
-
45
- def validate_options
46
- errors.add(:base, "The 'code' option is required") unless options['code'].present?
47
- end
48
-
49
- def working?
50
- return false if recent_error_logs?
51
-
52
- if interpolated['expected_update_period_in_days'].present?
53
- return false unless event_created_within?(interpolated['expected_update_period_in_days'])
54
- end
55
-
56
- if interpolated['expected_receive_period_in_days'].present?
57
- return false unless last_receive_at && last_receive_at > interpolated['expected_receive_period_in_days'].to_i.days.ago
58
- end
59
-
60
- true
61
- end
62
-
63
- def check
64
- log_errors do
65
- execute_check
66
- end
67
- end
68
-
69
- def receive(events)
70
- log_errors do
71
- execute_receive(events)
72
- end
73
- end
74
-
75
- def default_options
76
- code = <<~CODE
77
-
78
- require "bundler/inline"
79
-
80
- gemfile do
81
- source "https://rubygems.org"
82
-
83
- # gem "mechanize"
84
- end
85
-
86
- class Agent
87
- def initialize(api)
88
- @api = api
89
- end
90
-
91
- def check
92
- @api.create_event({ message: 'I made an event!' })
93
- end
94
-
95
- def receive(incoming_events)
96
- incoming_events.each do |event|
97
- @api.create_event({ message: 'new event', event_was: event[:payload] })
98
- end
99
- end
100
- end
101
- CODE
102
-
103
- {
104
- 'code' => code,
105
- 'expected_receive_period_in_days' => '2',
106
- 'expected_update_period_in_days' => '2'
107
- }
108
- end
109
-
110
- private
111
-
112
- def execute_check
113
- Bundler.with_original_env do
114
- Open3.popen3("ruby", chdir: '/') do |input, output, err, thread|
115
- input.write sdk_code
116
- input.write code
117
- input.write <<~CODE
118
-
119
- Agent.new(Huginn::API.new).check
120
-
121
- CODE
122
- input.close
123
-
124
-
125
- output.readlines.map { |line| JSON.parse(line, symbolize_names: true) }.each do |data|
126
- case data[:action]
127
- when 'create_event'
128
- create_event(payload: data[:payload])
129
- when 'log'
130
- log data[:payload]
131
- when 'error'
132
- error data[:payload]
133
- end
134
- end
135
-
136
- errors = err.read
137
-
138
- error err.read
139
- log "thread #{thread.value}"
140
- end
141
- end
142
- end
143
-
144
- def execute_receive(events)
145
- Bundler.with_original_env do
146
- Open3.popen3("ruby", chdir: '/') do |input, output, err, thread|
147
- input.write sdk_code
148
- input.write code
149
- input.write <<~CODE
150
-
151
- api = Huginn::API.new
152
- begin
153
- Agent.new(api).receive(
154
- JSON.parse(
155
- Base64.decode64(
156
- "#{Base64.encode64(events.to_json)}"
157
- ),
158
- symbolize_names: true
159
- )
160
- )
161
- rescue StandardError => ex
162
- api.error ex
163
- end
164
-
165
- CODE
166
- input.close
167
-
168
-
169
- output.readlines.map { |line| JSON.parse(line, symbolize_names: true) }.each do |data|
170
- case data[:action]
171
- when 'create_event'
172
- create_event(payload: data[:payload])
173
- when 'log'
174
- log data[:payload]
175
- when 'error'
176
- error data[:payload]
177
- end
178
- end
179
-
180
- errors = err.read
181
-
182
- error err.read
183
- log "thread #{thread.value}"
184
- end
185
- end
186
- end
187
-
188
- def code
189
- interpolated['code']
190
- end
191
-
192
- def sdk_code
193
- <<~CODE
194
- require 'json'
195
- require 'base64'
196
-
197
- module Huginn
198
- class API
199
- def create_event(payload)
200
- puts(
201
- {
202
- action: :create_event,
203
- payload: payload
204
- }.to_json
205
- )
206
- end
207
-
208
- def log(message)
209
- puts(
210
- {
211
- action: :log,
212
- payload: message
213
- }.to_json
214
- )
215
- end
216
-
217
- def error(message)
218
- puts(
219
- {
220
- action: :error,
221
- payload: message
222
- }.to_json
223
- )
224
- end
225
- end
226
- end
227
- CODE
228
- end
229
-
230
- def log_errors
231
- begin
232
- yield
233
- rescue StandardError => e
234
- error "Runtime error: #{e.message}"
235
- end
236
- end
237
- end
238
- end
@@ -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