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 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