huginn_ruby_agent 0.1 → 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: 46786fbef6ebd0f956499b17915fa8970f4285795073e3f55a9f6d026e8f9597
4
- data.tar.gz: 21cf1e531eb5652e34eecf0a8b0151458902f4bee65fe69b5c4110cfe5b2c0f5
3
+ metadata.gz: 2dcca4b6d669c71c00b5797822e4dfe24c09a0b8344dcd6ad49b9076b7cfe050
4
+ data.tar.gz: e2580eb01f896a539e98096856a8b79c2416ca04abfa53433f65659cda454cdd
5
5
  SHA512:
6
- metadata.gz: 2a5c0ebee329f780bdc08637a03803de430cf16753f27f14456380c4cedd598b85a8398a4eb94c1c4355e75a9fc095279e0be90c4032872d8c3159ba934f879b
7
- data.tar.gz: 2ecc2518b29ef5f6edfc432325670f8ad09d7f29bb70cd25bc0d09329a87dae0cf9f8053714fd1a335772ce06fd3e106533d9d273ac29034f607aa6399ed433d
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.1'
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
@@ -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/ruby_agent.rb
78
- - 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
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.3.7
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/ruby_agent_spec.rb
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
@@ -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