huginn_ruby_agent 0.2 → 0.3

Sign up to get free protection for your applications and to get access to all the features.
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