legionio 0.3.3 → 0.4.2

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.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +136 -0
  3. data/.gitignore +15 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +94 -0
  6. data/CHANGELOG.md +43 -0
  7. data/Dockerfile +9 -0
  8. data/Gemfile +3 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +161 -0
  11. data/Rakefile +32 -0
  12. data/bitbucket-pipelines.yml +19 -0
  13. data/docker_deploy.rb +10 -0
  14. data/exe/legion +4 -0
  15. data/exe/legionio +53 -0
  16. data/exe/lex_gen +5 -0
  17. data/legionio.gemspec +64 -0
  18. data/lib/legion.rb +25 -0
  19. data/lib/legion/cli.rb +56 -0
  20. data/lib/legion/cli/chain.rb +35 -0
  21. data/lib/legion/cli/cohort.rb +10 -0
  22. data/lib/legion/cli/function.rb +41 -0
  23. data/lib/legion/cli/lex/actor.rb +31 -0
  24. data/lib/legion/cli/lex/exchange.rb +32 -0
  25. data/lib/legion/cli/lex/message.rb +32 -0
  26. data/lib/legion/cli/lex/queue.rb +45 -0
  27. data/lib/legion/cli/lex/runner.rb +70 -0
  28. data/lib/legion/cli/lex/templates/actor.erb +6 -0
  29. data/lib/legion/cli/lex/templates/actor_spec.erb +0 -0
  30. data/lib/legion/cli/lex/templates/base/bitbucket.yml.erb +69 -0
  31. data/lib/legion/cli/lex/templates/base/gemfile.erb +3 -0
  32. data/lib/legion/cli/lex/templates/base/gemspec.erb +26 -0
  33. data/lib/legion/cli/lex/templates/base/gitignore.erb +11 -0
  34. data/lib/legion/cli/lex/templates/base/lex.erb +9 -0
  35. data/lib/legion/cli/lex/templates/base/lex_spec.erb +5 -0
  36. data/lib/legion/cli/lex/templates/base/lic.erb +21 -0
  37. data/lib/legion/cli/lex/templates/base/rakefile.erb +6 -0
  38. data/lib/legion/cli/lex/templates/base/readme.md.erb +2 -0
  39. data/lib/legion/cli/lex/templates/base/rubocop.yml.erb +15 -0
  40. data/lib/legion/cli/lex/templates/base/spec_helper.rb.erb +11 -0
  41. data/lib/legion/cli/lex/templates/base/version.erb +7 -0
  42. data/lib/legion/cli/lex/templates/exchange.erb +11 -0
  43. data/lib/legion/cli/lex/templates/exchange_spec.erb +0 -0
  44. data/lib/legion/cli/lex/templates/message.erb +23 -0
  45. data/lib/legion/cli/lex/templates/message_spec.erb +0 -0
  46. data/lib/legion/cli/lex/templates/queue.erb +12 -0
  47. data/lib/legion/cli/lex/templates/queue_helper.erb +24 -0
  48. data/lib/legion/cli/lex/templates/queue_spec.erb +11 -0
  49. data/lib/legion/cli/lex/templates/runner.erb +11 -0
  50. data/lib/legion/cli/lex/templates/runner_spec.erb +11 -0
  51. data/lib/legion/cli/relationship.rb +22 -0
  52. data/lib/legion/cli/task.rb +49 -0
  53. data/lib/legion/cli/trigger.rb +88 -0
  54. data/lib/legion/cli/version.rb +5 -0
  55. data/lib/legion/extensions.rb +219 -0
  56. data/lib/legion/extensions/actors/base.rb +47 -0
  57. data/lib/legion/extensions/actors/every.rb +48 -0
  58. data/lib/legion/extensions/actors/loop.rb +32 -0
  59. data/lib/legion/extensions/actors/nothing.rb +15 -0
  60. data/lib/legion/extensions/actors/once.rb +40 -0
  61. data/lib/legion/extensions/actors/poll.rb +87 -0
  62. data/lib/legion/extensions/actors/subscription.rb +139 -0
  63. data/lib/legion/extensions/builders/actors.rb +61 -0
  64. data/lib/legion/extensions/builders/base.rb +36 -0
  65. data/lib/legion/extensions/builders/helpers.rb +24 -0
  66. data/lib/legion/extensions/builders/runners.rb +58 -0
  67. data/lib/legion/extensions/core.rb +131 -0
  68. data/lib/legion/extensions/data.rb +58 -0
  69. data/lib/legion/extensions/data/migrator.rb +28 -0
  70. data/lib/legion/extensions/data/model.rb +8 -0
  71. data/lib/legion/extensions/helpers/base.rb +82 -0
  72. data/lib/legion/extensions/helpers/cache.rb +23 -0
  73. data/lib/legion/extensions/helpers/core.rb +41 -0
  74. data/lib/legion/extensions/helpers/data.rb +23 -0
  75. data/lib/legion/extensions/helpers/lex.rb +48 -0
  76. data/lib/legion/extensions/helpers/logger.rb +44 -0
  77. data/lib/legion/extensions/helpers/task.rb +60 -0
  78. data/lib/legion/extensions/helpers/transport.rb +44 -0
  79. data/lib/legion/extensions/transport.rb +159 -0
  80. data/lib/legion/lex.rb +89 -0
  81. data/lib/legion/process.rb +124 -0
  82. data/lib/legion/runner.rb +55 -0
  83. data/lib/legion/runner/log.rb +10 -0
  84. data/lib/legion/runner/status.rb +69 -0
  85. data/lib/legion/service.rb +130 -0
  86. data/lib/legion/supervision.rb +15 -0
  87. data/lib/legion/version.rb +3 -0
  88. metadata +243 -39
@@ -0,0 +1,32 @@
1
+ require_relative 'base'
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Actors
6
+ class Loop
7
+ include Concurrent::Async
8
+ include Legion::Extensions::Actors::Base
9
+
10
+ def initialize
11
+ @loop = true
12
+ async.run
13
+ rescue StandardError => e
14
+ Legion::Logging.error e
15
+ Legion::Logging.error e.backtrace
16
+ end
17
+
18
+ def run
19
+ action while @loop
20
+ end
21
+
22
+ def action(**_opts)
23
+ Legion::Logging.warn 'An extension is using the default action for a loop'
24
+ end
25
+
26
+ def cancel
27
+ @loop = false
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,15 @@
1
+ require_relative 'base'
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Actors
6
+ class Nothing
7
+ include Legion::Extensions::Actors::Base
8
+
9
+ def initialize; end
10
+
11
+ def cancel; end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,40 @@
1
+ require_relative 'base'
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Actors
6
+ class Once
7
+ include Legion::Extensions::Actors::Base
8
+
9
+ def initialize
10
+ return if disabled?
11
+
12
+ if respond_to? :functions
13
+ functions.each do
14
+ function
15
+ @task = Concurrent::ScheduledTask.execute(delay) do
16
+ use_runner ? runner : manual
17
+ end
18
+ end
19
+ else
20
+ @task = Concurrent::ScheduledTask.execute(delay) do
21
+ use_runner ? runner : manual
22
+ end
23
+ end
24
+ rescue StandardError => e
25
+ Legion::Logging.error e
26
+ end
27
+
28
+ def delay
29
+ 1.0
30
+ end
31
+
32
+ def cancel
33
+ return if disabled?
34
+
35
+ @task.cancel unless @task.cancelled?
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,87 @@
1
+ require_relative 'base'
2
+ require 'time'
3
+
4
+ module Legion
5
+ module Extensions
6
+ module Actors
7
+ class Poll
8
+ include Legion::Extensions::Actors::Base
9
+
10
+ def initialize # rubocop:disable Metrics/AbcSize
11
+ log.debug "Starting timer for #{self.class} with #{{ execution_interval: time, timeout_interval: timeout, run_now: run_now?, check_subtask: check_subtask? }}"
12
+ @timer = Concurrent::TimerTask.new(execution_interval: time, timeout_interval: timeout, run_now: run_now?) do
13
+ t1 = Time.now
14
+ log.debug "Running #{self.class}"
15
+ old_result = Legion::Cache.get(cache_name)
16
+ log.debug "Cached value for #{self.class}: #{old_result}"
17
+ results = Legion::JSON.load(Legion::JSON.dump(manual))
18
+ Legion::Cache.set(cache_name, results, time * 2)
19
+
20
+ unless old_result.nil?
21
+ results[:diff] = Hashdiff.diff(results, old_result, numeric_tolerance: 0.0, array_path: false) do |_path, obj1, obj2|
22
+ if int_percentage_normalize.positive? && obj1.is_a?(Integer) && obj2.is_a?(Integer)
23
+ obj1.between?(obj2 * (1 - int_percentage_normalize), obj2 * (1 + int_percentage_normalize))
24
+ end
25
+ end
26
+ results[:changed] = results[:diff].count.positive?
27
+
28
+ Legion::Logging.info results[:diff] if results[:changed]
29
+ Legion::Transport::Messages::CheckSubtask.new(runner_class: runner_class.to_s,
30
+ function: runner_function,
31
+ result: results,
32
+ type: 'poll_result',
33
+ polling: true).publish
34
+ end
35
+
36
+ sleep_time = 1 - (Time.now - t1)
37
+ sleep(sleep_time) if sleep_time.positive?
38
+ log.debug("#{self.class} result: #{results}")
39
+ results
40
+ rescue StandardError => e
41
+ Legion::Logging.fatal e.message
42
+ Legion::Logging.fatal e.backtrace
43
+ end
44
+ @timer.execute
45
+ rescue StandardError => e
46
+ Legion::Logging.error e.message
47
+ Legion::Logging.error e.backtrace
48
+ end
49
+
50
+ def cache_name
51
+ "#{lex_name}_#{runner_name}"
52
+ end
53
+
54
+ def int_percentage_normalize
55
+ 0.00
56
+ end
57
+
58
+ def time
59
+ 9
60
+ end
61
+
62
+ def run_now?
63
+ true
64
+ end
65
+
66
+ def check_subtask?
67
+ true
68
+ end
69
+
70
+ def timeout
71
+ 5
72
+ end
73
+
74
+ def action(_payload = {})
75
+ Legion::Logging.warn 'An extension is using the default block from Legion::Extensions::Runners::Every'
76
+ end
77
+
78
+ def cancel
79
+ Legion::Logging.debug 'Cancelling Legion Poller'
80
+ @timer.shutdown
81
+ rescue StandardError => e
82
+ Legion::Logging.error e.message
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,139 @@
1
+ require_relative 'base'
2
+ require 'date'
3
+
4
+ module Legion
5
+ module Extensions
6
+ module Actors
7
+ class Subscription
8
+ include Concurrent::Async
9
+ include Legion::Extensions::Actors::Base
10
+ include Legion::Extensions::Helpers::Transport
11
+
12
+ def initialize(**_options)
13
+ super()
14
+ @queue = queue.new
15
+ @queue.channel.prefetch(prefetch) if defined? prefetch
16
+ rescue StandardError => e
17
+ log.fatal e.message
18
+ log.fatal e.backtrace
19
+ end
20
+
21
+ def create_queue
22
+ queues.const_set(actor_const, Class.new(Legion::Transport::Queue))
23
+ exchange_object = default_exchange.new
24
+ queue_object = Kernel.const_get(queue_string).new
25
+
26
+ queue_object.bind(exchange_object, routing_key: actor_name)
27
+ queue_object.bind(exchange_object, routing_key: "#{lex_name}.#{actor_name}.#")
28
+ end
29
+
30
+ def queue
31
+ create_queue unless queues.const_defined?(actor_const)
32
+ Kernel.const_get queue_string
33
+ end
34
+
35
+ def queue_string
36
+ @queue_string ||= "#{queues}::#{actor_const}"
37
+ end
38
+
39
+ def cancel
40
+ return true unless @queue.channel.active
41
+
42
+ log.debug "Closing subscription to #{@queue.name}"
43
+ @consumer.cancel
44
+ @queue.channel.close
45
+ true
46
+ end
47
+
48
+ def block
49
+ false
50
+ end
51
+
52
+ def consumers
53
+ 1
54
+ end
55
+
56
+ def manual_ack
57
+ true
58
+ end
59
+
60
+ def delay_start
61
+ 0
62
+ end
63
+
64
+ def include_metadata_in_message?
65
+ true
66
+ end
67
+
68
+ def process_message(message, metadata, delivery_info)
69
+ payload = if metadata.content_encoding && metadata.content_encoding == 'encrypted/cs'
70
+ Legion::Crypt.decrypt(message, metadata.headers['iv'])
71
+ elsif metadata.content_encoding && metadata.content_encoding == 'encrypted/pk'
72
+ Legion::Crypt.decrypt_from_keypair(metadata.headers[:public_key], message)
73
+ else
74
+ message
75
+ end
76
+
77
+ message = if metadata.content_type == 'application/json'
78
+ Legion::JSON.load(payload)
79
+ else
80
+ { value: payload }
81
+ end
82
+ if include_metadata_in_message?
83
+ message = message.merge(metadata.headers.transform_keys(&:to_sym)) unless metadata.headers.nil?
84
+ message[:routing_key] = if Legion::Transport::TYPE == 'march_hare'
85
+ metadata.routing_key
86
+ else
87
+ delivery_info[:routing_key]
88
+ end
89
+ end
90
+
91
+ message[:timestamp] = (message[:timestamp_in_ms] / 1000).round if message.key?(:timestamp_in_ms) && !message.key?(:timestamp)
92
+ message[:datetime] = Time.at(message[:timestamp].to_i).to_datetime.to_s if message.key?(:timestamp)
93
+ message
94
+ end
95
+
96
+ def find_function(message = {})
97
+ return runner_function if actor_class.instance_methods(false).include?(:runner_function)
98
+ return function if actor_class.instance_methods(false).include?(:function)
99
+ return action if actor_class.instance_methods(false).include?(:action)
100
+ return message[:function] if message.key? :function
101
+
102
+ function
103
+ end
104
+
105
+ def subscribe
106
+ sleep(delay_start)
107
+ consumer_tag = "#{Legion::Settings[:client][:name]}_#{lex_name}_#{runner_name}_#{Thread.current.object_id}"
108
+ on_cancellation = block { cancel }
109
+
110
+ @consumer = @queue.subscribe(manual_ack: manual_ack, block: false, consumer_tag: consumer_tag, on_cancellation: on_cancellation) do |*rmq_message|
111
+ payload = rmq_message.pop
112
+ metadata = rmq_message.last
113
+ delivery_info = rmq_message.first
114
+
115
+ message = process_message(payload, metadata, delivery_info)
116
+ if use_runner?
117
+ Legion::Runner.run(**message,
118
+ runner_class: runner_class,
119
+ function: find_function(message),
120
+ check_subtask: check_subtask?,
121
+ generate_task: generate_task?)
122
+ else
123
+ runner_class.send(find_function(message), **message)
124
+ end
125
+ @queue.acknowledge(delivery_info.delivery_tag) if manual_ack
126
+
127
+ cancel if Legion::Settings[:client][:shutting_down]
128
+ rescue StandardError => e
129
+ Legion::Logging.error e.message
130
+ Legion::Logging.error e.backtrace
131
+ Legion::Logging.error message
132
+ Legion::Logging.error function
133
+ @queue.reject(delivery_info.delivery_tag) if manual_ack
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,61 @@
1
+ require_relative 'base'
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Builder
6
+ module Actors
7
+ include Legion::Extensions::Builder::Base
8
+
9
+ attr_reader :actors
10
+
11
+ def build_actors
12
+ @actors = {}
13
+ require_files(actor_files)
14
+ build_actor_list
15
+ build_meta_actor_list
16
+ end
17
+
18
+ def build_actor_list
19
+ actor_files.each do |file|
20
+ actor_name = file.split('/').last.sub('.rb', '')
21
+ actor_class = "#{lex_class}::Actor::#{actor_name.split('_').collect(&:capitalize).join}"
22
+ @actors[actor_name.to_sym] = {
23
+ extension: lex_class.to_s.downcase,
24
+ extension_name: extension_name,
25
+ actor_name: actor_name,
26
+ actor_class: Kernel.const_get(actor_class),
27
+ type: 'literal'
28
+ }
29
+ end
30
+ end
31
+
32
+ def build_meta_actor_list
33
+ @runners.each do |runner, attr|
34
+ next if @actors[runner.to_sym].is_a? Hash
35
+
36
+ actor_class = "#{attr[:extension_class]}::Actor::#{runner.to_s.split('_').collect(&:capitalize).join}"
37
+ build_meta_actor(runner, attr) unless Kernel.const_defined? actor_class
38
+ @actors[runner.to_sym] = {
39
+ extension: attr[:extension],
40
+ extension_name: attr[:extension_name],
41
+ actor_name: attr[:runner_name],
42
+ actor_class: Kernel.const_get(actor_class),
43
+ type: 'meta'
44
+ }
45
+ end
46
+ end
47
+
48
+ def build_meta_actor(runner, attr)
49
+ define_constant_two('Actor', root: lex_class)
50
+
51
+ Kernel.const_get("#{attr[:extension_class]}::Actor")
52
+ .const_set(runner.to_s.split('_').collect(&:capitalize).join, Class.new(Legion::Extensions::Actors::Subscription))
53
+ end
54
+
55
+ def actor_files
56
+ @actor_files ||= find_files('actors')
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,36 @@
1
+ module Legion
2
+ module Extensions
3
+ module Builder
4
+ module Base
5
+ def find_files(name, path = extension_path)
6
+ files = []
7
+ return files unless Dir.exist? "#{path}/#{name}"
8
+
9
+ Dir["#{path}/#{name}/*.rb"].each do |file|
10
+ files.push(file)
11
+ end
12
+ files
13
+ end
14
+
15
+ def require_files(files)
16
+ files.each { |file| require file }
17
+ end
18
+
19
+ def const_defined_two?(item, root = Kernel)
20
+ root.const_defined?(item.to_s)
21
+ end
22
+
23
+ def define_constant_two(item, root: Kernel, type: Module)
24
+ return true if root.const_defined?(item)
25
+
26
+ root.const_set(item.to_s, type.new)
27
+ end
28
+
29
+ def define_get(item, root: Kernel, type: Module)
30
+ define_constant_two(item, root: root, type: type) if const_defined_two?(item, root: root)
31
+ root.const_get(item)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,24 @@
1
+ require_relative 'base'
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Builder
6
+ module Helpers
7
+ include Legion::Extensions::Builder::Base
8
+
9
+ def build_helpers
10
+ @helpers ||= []
11
+ @helpers.push(require_files(helper_files))
12
+ end
13
+
14
+ def helper_files
15
+ @helper_files ||= find_files('helpers')
16
+ end
17
+
18
+ def helpers
19
+ @helpers
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,58 @@
1
+ require_relative 'base'
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Builder
6
+ module Runners
7
+ include Legion::Extensions::Builder::Base
8
+
9
+ attr_reader :runners
10
+
11
+ def build_runners
12
+ @runners = {}
13
+ lex_class.const_set('Runners', Module.new) unless lex_class.const_defined?('Runners')
14
+ require_files(runner_files)
15
+ build_runner_list
16
+ end
17
+
18
+ def build_runner_list
19
+ runner_files.each do |file|
20
+ runner_name = file.split('/').last.sub('.rb', '')
21
+ runner_class = "#{lex_class}::Runners::#{runner_name.split('_').collect(&:capitalize).join}"
22
+ loaded_runner = Kernel.const_get(runner_class)
23
+
24
+ @runners[runner_name.to_sym] = {
25
+ extension: lex_class.to_s.downcase,
26
+ extension_name: extension_name,
27
+ extension_class: lex_class,
28
+ runner_name: runner_name,
29
+ runner_class: runner_class,
30
+ runner_path: file,
31
+ class_methods: {}
32
+ }
33
+
34
+ if settings.key?(:runners) && settings[:runners].key?(runner_name.to_sym)
35
+ @runners[runner_name.to_sym][:desc] = settings[:runners][runner_name.to_sym][:desc]
36
+ end
37
+
38
+ loaded_runner.public_instance_methods(false).each do |runner_method|
39
+ @runners[runner_name.to_sym][:class_methods][runner_method] = {
40
+ args: loaded_runner.instance_method(runner_method).parameters
41
+ }
42
+ end
43
+
44
+ loaded_runner.methods(false).each do |runner_method|
45
+ @runners[runner_name.to_sym][:class_methods][runner_method] = {
46
+ args: loaded_runner.method(runner_method).parameters
47
+ }
48
+ end
49
+ end
50
+ end
51
+
52
+ def runner_files
53
+ @runner_files ||= find_files('runners')
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end