legionio 0.3.5 → 1.2.0

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 (95) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rspec.yml +45 -0
  3. data/.github/workflows/rubocop.yml +28 -0
  4. data/.github/workflows/sourcehawk-scan.yml +20 -0
  5. data/.gitignore +15 -0
  6. data/.rubocop.yml +90 -0
  7. data/CHANGELOG.md +4 -0
  8. data/CODE_OF_CONDUCT.md +75 -0
  9. data/CONTRIBUTING.md +55 -0
  10. data/Dockerfile +9 -0
  11. data/Gemfile +10 -0
  12. data/INDIVIDUAL_CONTRIBUTOR_LICENSE.md +30 -0
  13. data/LICENSE +201 -0
  14. data/NOTICE.txt +9 -0
  15. data/README.md +162 -0
  16. data/SECURITY.md +9 -0
  17. data/attribution.txt +1 -0
  18. data/docker_deploy.rb +10 -0
  19. data/exe/legion +4 -0
  20. data/exe/legionio +53 -0
  21. data/exe/lex_gen +5 -0
  22. data/legionio.gemspec +53 -0
  23. data/lib/legion.rb +24 -0
  24. data/lib/legion/cli.rb +56 -0
  25. data/lib/legion/cli/chain.rb +35 -0
  26. data/lib/legion/cli/cohort.rb +10 -0
  27. data/lib/legion/cli/function.rb +41 -0
  28. data/lib/legion/cli/lex/actor.rb +31 -0
  29. data/lib/legion/cli/lex/exchange.rb +32 -0
  30. data/lib/legion/cli/lex/message.rb +32 -0
  31. data/lib/legion/cli/lex/queue.rb +45 -0
  32. data/lib/legion/cli/lex/runner.rb +70 -0
  33. data/lib/legion/cli/lex/templates/actor.erb +6 -0
  34. data/lib/legion/cli/lex/templates/actor_spec.erb +0 -0
  35. data/lib/legion/cli/lex/templates/base/bitbucket.yml.erb +69 -0
  36. data/lib/legion/cli/lex/templates/base/gemfile.erb +3 -0
  37. data/lib/legion/cli/lex/templates/base/gemspec.erb +26 -0
  38. data/lib/legion/cli/lex/templates/base/gitignore.erb +11 -0
  39. data/lib/legion/cli/lex/templates/base/lex.erb +9 -0
  40. data/lib/legion/cli/lex/templates/base/lex_spec.erb +5 -0
  41. data/lib/legion/cli/lex/templates/base/lic.erb +21 -0
  42. data/lib/legion/cli/lex/templates/base/rakefile.erb +6 -0
  43. data/lib/legion/cli/lex/templates/base/readme.md.erb +2 -0
  44. data/lib/legion/cli/lex/templates/base/rubocop.yml.erb +15 -0
  45. data/lib/legion/cli/lex/templates/base/spec_helper.rb.erb +11 -0
  46. data/lib/legion/cli/lex/templates/base/version.erb +7 -0
  47. data/lib/legion/cli/lex/templates/exchange.erb +11 -0
  48. data/lib/legion/cli/lex/templates/exchange_spec.erb +0 -0
  49. data/lib/legion/cli/lex/templates/message.erb +23 -0
  50. data/lib/legion/cli/lex/templates/message_spec.erb +0 -0
  51. data/lib/legion/cli/lex/templates/queue.erb +12 -0
  52. data/lib/legion/cli/lex/templates/queue_helper.erb +24 -0
  53. data/lib/legion/cli/lex/templates/queue_spec.erb +11 -0
  54. data/lib/legion/cli/lex/templates/runner.erb +11 -0
  55. data/lib/legion/cli/lex/templates/runner_spec.erb +11 -0
  56. data/lib/legion/cli/relationship.rb +22 -0
  57. data/lib/legion/cli/task.rb +49 -0
  58. data/lib/legion/cli/trigger.rb +88 -0
  59. data/lib/legion/cli/version.rb +5 -0
  60. data/lib/legion/extensions.rb +229 -0
  61. data/lib/legion/extensions/actors/base.rb +47 -0
  62. data/lib/legion/extensions/actors/defaults.rb +28 -0
  63. data/lib/legion/extensions/actors/every.rb +48 -0
  64. data/lib/legion/extensions/actors/loop.rb +32 -0
  65. data/lib/legion/extensions/actors/nothing.rb +15 -0
  66. data/lib/legion/extensions/actors/once.rb +40 -0
  67. data/lib/legion/extensions/actors/poll.rb +87 -0
  68. data/lib/legion/extensions/actors/subscription.rb +139 -0
  69. data/lib/legion/extensions/builders/actors.rb +61 -0
  70. data/lib/legion/extensions/builders/base.rb +36 -0
  71. data/lib/legion/extensions/builders/helpers.rb +24 -0
  72. data/lib/legion/extensions/builders/runners.rb +62 -0
  73. data/lib/legion/extensions/core.rb +131 -0
  74. data/lib/legion/extensions/data.rb +61 -0
  75. data/lib/legion/extensions/data/migrator.rb +33 -0
  76. data/lib/legion/extensions/data/model.rb +8 -0
  77. data/lib/legion/extensions/helpers/base.rb +82 -0
  78. data/lib/legion/extensions/helpers/cache.rb +23 -0
  79. data/lib/legion/extensions/helpers/core.rb +41 -0
  80. data/lib/legion/extensions/helpers/data.rb +23 -0
  81. data/lib/legion/extensions/helpers/lex.rb +48 -0
  82. data/lib/legion/extensions/helpers/logger.rb +44 -0
  83. data/lib/legion/extensions/helpers/task.rb +60 -0
  84. data/lib/legion/extensions/helpers/transport.rb +44 -0
  85. data/lib/legion/extensions/transport.rb +159 -0
  86. data/lib/legion/lex.rb +89 -0
  87. data/lib/legion/process.rb +124 -0
  88. data/lib/legion/runner.rb +57 -0
  89. data/lib/legion/runner/log.rb +10 -0
  90. data/lib/legion/runner/status.rb +69 -0
  91. data/lib/legion/service.rb +130 -0
  92. data/lib/legion/supervision.rb +15 -0
  93. data/lib/legion/version.rb +3 -0
  94. data/sourcehawk.yml +4 -0
  95. metadata +142 -168
@@ -0,0 +1,23 @@
1
+ require 'legion/extensions/helpers/base'
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Helpers
6
+ module Cache
7
+ include Legion::Extensions::Helpers::Base
8
+
9
+ def cache_namespace
10
+ @cache_namespace ||= lex_name
11
+ end
12
+
13
+ def cache_set(key, value, ttl: 60, **)
14
+ Legion::Cache.set(cache_namespace + key, value, ttl: ttl)
15
+ end
16
+
17
+ def cache_get(key)
18
+ Legion::Cache.get(cache_namespace + key)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,41 @@
1
+ require_relative 'base'
2
+ module Legion
3
+ module Extensions
4
+ module Helpers
5
+ module Core
6
+ include Legion::Extensions::Helpers::Base
7
+
8
+ def settings
9
+ if Legion::Settings[:extensions].key?(lex_filename.to_sym)
10
+ Legion::Settings[:extensions][lex_filename.to_sym]
11
+ else
12
+ { logger: { level: 'info', extended: false, internal: false } }
13
+ end
14
+ end
15
+
16
+ # looks local, then in crypt, then settings, then cache, then env
17
+ def find_setting(name, **opts)
18
+ log.debug ".find_setting(#{name}) called"
19
+ return opts[name.to_sym] if opts.key? name.to_sym
20
+
21
+ string_name = "#{lex_name}_#{name.to_s.downcase}"
22
+ if Legion::Settings[:crypt][:vault][:connected] && Legion::Crypt.exist?(lex_name)
23
+ log.debug "looking for #{string_name} in Legion::Crypt"
24
+ crypt_result = Legion::Crypt.get(lex_name)
25
+ return crypt_result[name.to_sym] if crypt_result.is_a?(Hash) && crypt_result.key?(name.to_sym)
26
+ end
27
+ return settings[name.to_sym] if settings.key? name.to_sym
28
+
29
+ if Legion::Settings[:cache][:connected]
30
+ log.debug "looking for #{string_name} in Legion::Cache"
31
+ cache_result = Legion::Cache.get(string_name)
32
+ return cache_result unless cache_result.nil?
33
+ end
34
+
35
+ ENV[string_name] if ENV.key? string_name
36
+ nil
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,23 @@
1
+ require 'legion/extensions/helpers/base'
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Helpers
6
+ module Data
7
+ include Legion::Extensions::Helpers::Base
8
+
9
+ def data_path
10
+ @data_path ||= "#{full_path}/data"
11
+ end
12
+
13
+ def data_class
14
+ @data_class ||= lex_class::Data
15
+ end
16
+
17
+ def models_class
18
+ @models_class ||= data_class::Model
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,48 @@
1
+ module Legion
2
+ module Extensions
3
+ module Helpers
4
+ module Lex
5
+ include Legion::Extensions::Helpers::Core
6
+ include Legion::Extensions::Helpers::Logger
7
+
8
+ def function_example(function, example)
9
+ function_set(function, :example, example)
10
+ end
11
+
12
+ def function_options(function, options)
13
+ function_set(function, :options, options)
14
+ end
15
+
16
+ def function_desc(function, desc)
17
+ function_set(function, :desc, desc)
18
+ end
19
+
20
+ def function_set(function, key, value)
21
+ unless respond_to? function
22
+ log.debug "function_#{key} called but function doesn't exist, f: #{function}"
23
+ return nil
24
+ end
25
+ settings[:functions] = {} if settings[:functions].nil?
26
+ settings[:functions][function] = {} if settings[:functions][function].nil?
27
+ settings[:functions][function][key] = value
28
+ end
29
+
30
+ def runner_desc(desc)
31
+ settings[:runners] = {} if settings[:runners].nil?
32
+ settings[:runners][actor_name.to_sym] = {} if settings[:runners][actor_name.to_sym].nil?
33
+ settings[:runners][actor_name.to_sym][:desc] = desc
34
+ end
35
+
36
+ def self.included(base)
37
+ base.send :extend, Legion::Extensions::Helpers::Core if base.instance_of?(Class)
38
+ base.send :extend, Legion::Extensions::Helpers::Logger if base.instance_of?(Class)
39
+ base.extend base if base.instance_of?(Module)
40
+ end
41
+
42
+ def default_settings
43
+ { logger: { level: 'info' }, workers: 1, runners: {}, functions: {} }
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,44 @@
1
+ module Legion
2
+ module Extensions
3
+ module Helpers
4
+ module Logger
5
+ def log
6
+ return @log unless @log.nil?
7
+
8
+ logger_hash = { lex: lex_filename || nil }
9
+ logger_hash[:lex] = lex_filename.first if logger_hash[:lex].is_a? Array
10
+ if respond_to?(:settings) && settings.key?(:logger)
11
+ logger_hash[:level] = settings[:logger].key?(:level) ? settings[:logger][:level] : 'info'
12
+ logger_hash[:log_file] = settings[:logger][:log_file] if settings[:logger].key? :log_file
13
+ logger_hash[:trace] = settings[:logger][:trace] if settings[:logger].key? :trace
14
+ logger_hash[:extended] = settings[:logger][:extended] if settings[:logger].key? :extended
15
+ elsif respond_to?(:settings)
16
+ Legion::Logging.warn Legion::Settings[:extensions][lex_filename.to_sym]
17
+ Legion::Logging.warn "#{lex_name} has settings but no :logger key"
18
+ end
19
+ @log = Legion::Logging::Logger.new(**logger_hash)
20
+ end
21
+
22
+ def handle_exception(exception, task_id: nil, **opts)
23
+ log.error exception.message + " for task_id: #{task_id} but was logged "
24
+ log.error exception.backtrace[0..10]
25
+ log.error opts
26
+
27
+ unless task_id.nil?
28
+ Legion::Transport::Messages::TaskLog.new(
29
+ task_id: task_id,
30
+ runner_class: to_s,
31
+ entry: {
32
+ exception: true,
33
+ message: exception.message,
34
+ **opts
35
+ }
36
+ ).publish
37
+ end
38
+
39
+ raise Legion::Exception::HandledTask
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,60 @@
1
+ require 'legion/transport'
2
+ require 'legion/transport/messages/task_update'
3
+ require 'legion/transport/messages/task_log'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module Helpers
8
+ module Task
9
+ def generate_task_log(task_id:, function:, runner_class: to_s, **payload)
10
+ begin
11
+ if Legion::Settings[:data][:connected]
12
+ runner_id = Legion::Data::Model::Runner[namespace: runner_class].values[:id]
13
+ function_id = Legion::Data::Model::Function.where(runner_id: runner_id, name: function).first.values[:id]
14
+ return true if Legion::Data::Model::TaskLog.insert(task_id: task_id, function_id: function_id, entry: Legion::JSON.dump(payload))
15
+ end
16
+ rescue StandardError => e
17
+ log.warn e.backtrace
18
+ log.warn("generate_task_log failed, reverting to rmq message, e: #{e.message}")
19
+ end
20
+ Legion::Transport::Messages::TaskLog.new(task_id: task_id, runner_class: runner_class, function: function, entry: payload).publish
21
+ end
22
+
23
+ def task_update(task_id, status, use_database: true, **opts)
24
+ return if task_id.nil? || status.nil?
25
+
26
+ begin
27
+ if Legion::Settings[:data][:connected] && use_database
28
+ task = Legion::Data::Model::Task[task_id]
29
+ task.update(status: status)
30
+ return true
31
+ end
32
+ rescue StandardError => e
33
+ log.debug("task_update failed, reverting to rmq message, e: #{e.message}")
34
+ end
35
+
36
+ update_hash = { task_id: task_id, status: status }
37
+ %i[results payload function_args payload results].each do |column|
38
+ update_hash[column] = opts[column] if opts.key? column
39
+ end
40
+ Legion::Transport::Messages::TaskUpdate.new(**update_hash).publish
41
+ rescue StandardError => e
42
+ log.fatal e.message
43
+ log.fatal e.backtrace
44
+ raise e
45
+ end
46
+
47
+ def generate_task_id(function_id:, status: 'task.queued', **opts)
48
+ insert = { status: status, function_id: function_id }
49
+ insert[:payload] = Legion::JSON.dump(opts[:payload]) if opts.key? :payload
50
+ insert[:function_args] = Legion::JSON.dump(opts[:args]) if opts.key? :args
51
+ %i[master_id parent_id relationship_id task_id].each do |column|
52
+ insert[column] = opts[column] if opts.key? column
53
+ end
54
+
55
+ { success: true, task_id: Legion::Data::Model::Task.insert(insert), **insert }
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,44 @@
1
+ require_relative 'base'
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Helpers
6
+ module Transport
7
+ include Legion::Extensions::Helpers::Base
8
+
9
+ def transport_path
10
+ @transport_path ||= "#{full_path}/transport"
11
+ end
12
+
13
+ def transport_class
14
+ @transport_class ||= lex_class::Transport
15
+ end
16
+
17
+ def messages
18
+ @messages ||= transport_class::Messages
19
+ end
20
+
21
+ def queues
22
+ @queues ||= transport_class::Queues
23
+ end
24
+
25
+ def exchanges
26
+ @exchanges ||= transport_class::Exchanges
27
+ end
28
+
29
+ def default_exchange
30
+ @default_exchange ||= build_default_exchange
31
+ end
32
+
33
+ def build_default_exchange
34
+ exchange = "#{transport_class}::Exchanges::#{lex_const}"
35
+ return Object.const_get(exchange) if transport_class::Exchanges.const_defined? lex_const
36
+
37
+ transport_class::Exchanges.const_set(lex_const, Class.new(Legion::Transport::Exchange))
38
+ @default_exchange = Kernel.const_get(exchange)
39
+ @default_exchange
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,159 @@
1
+ module Legion
2
+ module Extensions
3
+ module Transport
4
+ include Legion::Extensions::Helpers::Transport
5
+ include Legion::Extensions::Helpers::Logger
6
+
7
+ attr_accessor :exchanges, :queues, :consumers, :messages
8
+
9
+ def build
10
+ @queues = []
11
+ @exchanges = []
12
+ @messages = []
13
+ @consumers = []
14
+ generate_base_modules
15
+ require_transport_items
16
+
17
+ build_e_to_e
18
+ build_e_to_q(e_to_q)
19
+ build_e_to_q(additional_e_to_q)
20
+ auto_create_dlx_exchange
21
+ auto_create_dlx_queue
22
+ rescue StandardError => e
23
+ Legion::Logging.error e.message
24
+ Legion::Logging.error e.backtrace
25
+ end
26
+
27
+ def generate_base_modules
28
+ lex_class.const_set('Transport', Module.new) unless lex_class.const_defined?('Transport')
29
+ %w[Queues Exchanges Messages Consumers].each do |thing|
30
+ next if transport_class.const_defined? thing
31
+
32
+ transport_class.const_set(thing, Module.new)
33
+ end
34
+ end
35
+
36
+ def require_transport_items
37
+ { exchanges: @exchanges, queues: @queues, consumers: @consumers, messages: @messages }.each do |item, obj|
38
+ Dir[File.expand_path("#{transport_path}/#{item}/*.rb")].sort.each do |file|
39
+ require file
40
+ file_name = file.to_s.split('/').last.split('.').first
41
+ obj.push(file_name) unless obj.include?(file_name)
42
+ end
43
+ end
44
+ end
45
+
46
+ def auto_create_exchange(exchange, default_exchange = false) # rubocop:disable Style/OptionalBooleanParameter
47
+ if Object.const_defined? exchange
48
+ Legion::Logging.warn "#{exchange} is already defined"
49
+ return
50
+ end
51
+ return build_default_exchange if default_exchange
52
+
53
+ transport_class::Exchanges.const_set(exchange.split('::').pop, Class.new(Legion::Transport::Exchange) do
54
+ def exchange_name
55
+ self.class.ancestors.first.to_s.split('::')[5].downcase
56
+ end
57
+ end)
58
+ end
59
+
60
+ def auto_create_queue(queue)
61
+ if Kernel.const_defined?(queue)
62
+ Legion::Logging.warn "#{queue} is already defined"
63
+ return
64
+ end
65
+
66
+ transport_class::Queues.const_set(queue.split('::').last, Class.new(Legion::Transport::Queue))
67
+ end
68
+
69
+ def auto_create_dlx_exchange
70
+ dlx = if transport_class::Exchanges.const_defined? 'Dlx'
71
+ transport_class::Exchanges::Dlx
72
+ else
73
+ transport_class::Exchanges.const_set('Dlx', Class.new(default_exchange) do
74
+ def exchange_name
75
+ "#{super}.dlx"
76
+ end
77
+ end)
78
+ end
79
+
80
+ dlx.new
81
+ end
82
+
83
+ def auto_create_dlx_queue
84
+ return if transport_class::Queues.const_defined?('Dlx')
85
+
86
+ special_name = default_exchange.new.exchange_name
87
+ dlx_queue = Legion::Transport::Queue.new "#{special_name}.dlx", auto_delete: false
88
+ dlx_queue.bind("#{special_name}.dlx", { routing_key: '#' })
89
+ end
90
+
91
+ def build_e_to_q(array)
92
+ array.each do |binding|
93
+ binding[:routing_key] = nil unless binding.key? :routing_key
94
+ binding[:to] = nil unless binding.key?(:to)
95
+ binding[:from] = default_exchange if !binding.key?(:from) || binding[:from].nil?
96
+ bind_e_to_q(**binding)
97
+ end
98
+ end
99
+
100
+ def bind_e_to_q(to:, from: default_exchange, routing_key: nil, **)
101
+ if from.is_a? String
102
+ from = "#{transport_class}::Exchanges::#{from.split('_').collect(&:capitalize).join}" unless from.include?('::')
103
+ auto_create_exchange(from) unless Object.const_defined? from
104
+ end
105
+
106
+ if to.is_a? String
107
+ to = "#{transport_class}::Queues::#{to.split('_').collect(&:capitalize).join}" unless to.include?('::')
108
+ auto_create_queue(to) unless Object.const_defined?(to)
109
+ end
110
+
111
+ routing_key = to.to_s.split('::').last.downcase if routing_key.nil?
112
+ bind(from, to, routing_key: routing_key)
113
+ end
114
+
115
+ def build_e_to_e
116
+ e_to_e.each do |binding|
117
+ if binding[:from].is_a? String
118
+ binding[:from] = "#{transport_class}::Exchanges::#{binding[:from].capitalize}" unless binding[:from].include?('::')
119
+ auto_create_exchange(binding[:from]) unless Object.const_defined? binding[:from]
120
+ end
121
+
122
+ if binding[:to].is_a? String
123
+ binding[:to] = "#{transport_class}::Exchanges::#{binding[:to].capitalize}" unless binding[:to].include?('::')
124
+ auto_create_exchange(binding[:to]) unless Object.const_defined? binding[:to]
125
+ end
126
+
127
+ bind(binding[:from], binding[:to], binding)
128
+ end
129
+ end
130
+
131
+ def bind(from, to, routing_key: nil, **_options)
132
+ from = from.is_a?(String) ? Kernel.const_get(from).new : from.new
133
+ to = to.is_a?(String) ? Kernel.const_get(to).new : to.new
134
+ to.bind(from, routing_key: routing_key)
135
+ rescue StandardError => e
136
+ log.fatal e.message
137
+ log.fatal e.backtrace
138
+ log.fatal({ from: from, to: to, routing_key: routing_key })
139
+ end
140
+
141
+ def e_to_q
142
+ [] if !@exchanges.count != 1
143
+ auto = []
144
+ @queues.each do |queue|
145
+ auto.push(from: @exchanges.first, to: queue, routing_key: queue)
146
+ end
147
+ auto
148
+ end
149
+
150
+ def e_to_e
151
+ []
152
+ end
153
+
154
+ def additional_e_to_q
155
+ []
156
+ end
157
+ end
158
+ end
159
+ end