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,11 @@
1
+ require 'bundler/setup'
2
+ require 'legion/extensions/<%= config[:lex] %>'
3
+
4
+ RSpec.configure do |config|
5
+ config.example_status_persistence_file_path = '.rspec_status'
6
+ config.disable_monkey_patching!
7
+
8
+ config.expect_with :rspec do |c|
9
+ c.syntax = :expect
10
+ end
11
+ end
@@ -0,0 +1,7 @@
1
+ module Legion
2
+ module Extensions
3
+ module <%= config[:class_name] %>
4
+ VERSION = '0.1.1'.freeze
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ module Legion::Extensions::<%= config[:lex].split('_').collect(&:capitalize).join %>
2
+ module Transport
3
+ module Exchanges
4
+ class <%= config[:name].split('_').collect(&:capitalize).join %> < Legion::Transport::Exchange
5
+ def exchange_name
6
+ '<%= config[:name].split('_').collect(&:capitalize).join %>'
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
File without changes
@@ -0,0 +1,23 @@
1
+ module Legion::Extensions::<%= config[:lex].split('_').collect(&:capitalize).join %>
2
+ module Transport
3
+ module Messages
4
+ class <%= config[:name].split('_').collect(&:capitalize).join %> < Legion::Transport::Message
5
+ def initialize(payload, status, options = {})
6
+ @payload = payload
7
+ @options = options
8
+ @status = status
9
+ @routing_key = routing_key
10
+ validate
11
+ end
12
+
13
+ def routing_key
14
+ "<%= config[:lex].split('_').collect(&:capitalize).join %>.<%= config[:name].split('_').collect(&:capitalize).join %>"
15
+ end
16
+
17
+ def message(payload = @payload, _options = {})
18
+ Legion::JSON.dump(payload)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
File without changes
@@ -0,0 +1,12 @@
1
+ module Legion
2
+ module Extensions
3
+ module <%= config[:lex].split('_').collect(&:capitalize).join %>
4
+ module Transport
5
+ module Queues
6
+ class <%= config[:name].split('_').collect(&:capitalize).join %> < Legion::Transport::Queue
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,24 @@
1
+ module Legion
2
+ module Transport
3
+ class Queue
4
+ def initialize(**opts)
5
+ end
6
+
7
+ def queue_name
8
+ 'test_queue'
9
+ end
10
+
11
+ def acknowledge(delivery_tag)
12
+ true
13
+ end
14
+
15
+ def reject(delivery_tag, requeue: false, **)
16
+ true
17
+ end
18
+
19
+ def queue_options
20
+ Concurrent::Hash.new
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+ require 'queue_helper'
3
+ require 'legion/extensions/<%= config[:lex] %>/transport/queues/<%= config[:name] %>'
4
+
5
+ RSpec.describe Legion::Extensions::<%= config[:lex].split('_').collect(&:capitalize).join %>::Transport::Queues::<%= config[:name].capitalize %> do
6
+ it { should be_a Legion::Transport::Queue }
7
+ it { should respond_to :queue_name }
8
+ it { should respond_to(:acknowledge).with(1).argument }
9
+ it { should respond_to(:reject).with(1).argument }
10
+ it { should respond_to :queue_options }
11
+ end
@@ -0,0 +1,11 @@
1
+ module Legion
2
+ module Extensions
3
+ module <%= config[:lex].split('_').collect(&:capitalize).join %>
4
+ module Runners
5
+ module <%= config[:name].split('_').collect(&:capitalize).join %>
6
+ extend Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined? 'Helpers::Lex'
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+ require 'legion/extensions/<%= config[:lex] %>/runners/<%= config[:name] %>'
3
+
4
+ RSpec.describe Legion::Extensions::<%= config[:lex].split('_').collect(&:capitalize).join %>::Runners::<%= config[:name].capitalize %> do
5
+ it { should be_a Module }
6
+ Legion::Extensions::<%= config[:lex].capitalize %>::Runners::<%= config[:name].split('_').collect(&:capitalize).join %>.extend Legion::Extensions::<%= config[:lex].split('_').collect(&:capitalize).join %>::Runners::<%= config[:name].split('_').collect(&:capitalize).join %>
7
+
8
+ describe 'Functions should work with arguments' do
9
+ let(:test_class) { Class.new { extend Legion::Extensions::<%= config[:lex].split('_').collect(&:capitalize).join %>::Runners::<%= config[:name].split('_').collect(&:capitalize).join %> } }
10
+ end
11
+ end
@@ -0,0 +1,22 @@
1
+ module Legion
2
+ class Cli
3
+ class Relationship < Thor
4
+ desc 'create', 'creates a new relationship'
5
+ def create(_name, _type)
6
+ trigger_id = invoke('legion:cli:function:find', [], internal: true, capture: true) # rubocop:disable Lint/UselessAssignment
7
+ end
8
+
9
+ desc 'activate', 'actives a relationship'
10
+ def active; end
11
+
12
+ desc 'deactivate', 'deactivates a relationship'
13
+ def deactivate; end
14
+
15
+ desc 'modify', 'modify an existing relationship'
16
+ def modify; end
17
+
18
+ desc 'delete', 'deletes a relationship'
19
+ def delete; end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,49 @@
1
+ module Legion
2
+ class Cli
3
+ class Task < Thor
4
+ package_name 'Legion'
5
+
6
+ option :limit, type: :numeric, required: true, default: 10, desc: 'how many tasks to return'
7
+ desc 'show', 'show'
8
+ option :status, type: :string, required: false, desc: 'search for specific status'
9
+ def show
10
+ Legion::Service.new(cache: false, crypt: false, extensions: false, log_level: 'error')
11
+ rows = [%w[id relationship function status]]
12
+ Legion::Data::Model::Task.limit(options[:limit]).order(:id).reverse_each do |row|
13
+ rows.push([row.values[:id], row.values[:relationship_id], row.values[:function_id], row.values[:status]])
14
+ end
15
+
16
+ print_table rows
17
+ end
18
+
19
+ desc 'test', 'test'
20
+ def status(id)
21
+ Legion::Service.new(cache: false, crypt: false, extensions: false, log_level: 'error')
22
+ say Legion::Data::Model::Task[id].values
23
+ end
24
+
25
+ desc 'logs', 'logs'
26
+ option :limit, type: :numeric, required: true, default: 10, desc: 'how many tasks to return'
27
+ def logs(id)
28
+ Legion::Service.new(cache: false, crypt: false, extensions: false, log_level: 'error')
29
+ rows = [%w[id node_id created entry]]
30
+ Legion::Data::Model::TaskLog.where(task_id: id).limit(options[:limit]).each do |row|
31
+ rows.push([row.values[:id], row.values[:node_id], row.values[:created], row.values[:entry]])
32
+ end
33
+ print_table rows
34
+ end
35
+
36
+ desc 'purge', 'purge'
37
+ def purge
38
+ Legion::Service.new(cache: false, crypt: false, extensions: false, log_level: 'error')
39
+ days = ask 'how many days do you want to keep?', default: 7
40
+ dataset = Legion::Data::Model::Task.where { created < DateTime.now - days.to_i }
41
+ yes? "This will delete #{dataset.count} tasks, continue?", :red
42
+ dataset.delete
43
+ say 'Done!'
44
+ end
45
+
46
+ default_task :show
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,88 @@
1
+ module Legion
2
+ class Cli
3
+ class Trigger < Thor
4
+ desc 'queue', 'used to send a job directly to a worker via Legion::Transport'
5
+ option :extension, type: :string, required: false, desc: 'extension short name'
6
+ option :runner, type: :string, required: false, desc: 'runner short name'
7
+ option :function, type: :string, required: false, desc: 'function short name'
8
+ option :delay, type: :numeric, default: 0, desc: 'how long to wait before running the task'
9
+ def queue(*args) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity,Metrics/MethodLength
10
+ Legion::Service.new(cache: false, crypt: false, extensions: false, log_level: 'error')
11
+ include Legion::Extensions::Helpers::Task
12
+ response = if options['extension'].is_a? String
13
+ options[:extension]
14
+ else
15
+ ask 'trigger extension?', limited_to: Legion::Data::Model::Extension.map(:name)
16
+ end
17
+ trigger_extension = Legion::Data::Model::Extension.where(name: response).first
18
+ runners = Legion::Data::Model::Runner.where(extension_id: trigger_extension.values[:id])
19
+ if runners.count == 1
20
+ trigger_runner = runners.first
21
+ say "Auto selecting #{trigger_runner.values[:name]} since it is the only option for runners"
22
+ else
23
+ response = options[:runner].is_a?(String) ? options[:runner] : ask('trigger runner?', limited_to: runners.map(:name))
24
+ trigger_runner = Legion::Data::Model::Runner.where(name: response).where(extension_id: trigger_extension.values[:id]).first
25
+ end
26
+
27
+ functions = Legion::Data::Model::Function.where(runner_id: trigger_runner.values[:id])
28
+
29
+ if functions.count == 1
30
+ trigger_function = functions.first
31
+ say "Auto selecting #{trigger_function.values[:name]} since it is the only option for functions"
32
+ else
33
+ response = if options[:function].is_a?(String)
34
+ options[:function]
35
+ else
36
+ ask('trigger function?',
37
+ limited_to: Legion::Data::Model::Function.where(runner_id: trigger_runner.values[:id]).map(:name))
38
+ end
39
+ trigger_function = Legion::Data::Model::Function.where(runner_id: trigger_runner.values[:id]).where(name: response).first
40
+ end
41
+ say "#{trigger_runner.values[:namespace]}.#{trigger_function.values[:name]} selected as trigger", :green, :italicized
42
+ payload = {}
43
+ auto_opts = {}
44
+ unless args.count.zero?
45
+ args.each do |arg|
46
+ test = arg.split(':')
47
+ auto_opts[test[0].to_sym] = test[1]
48
+ end
49
+ end
50
+
51
+ Legion::JSON.load(trigger_function.values[:args]).each do |arg, required|
52
+ next if %w[args payload opts options].include? arg.to_s
53
+
54
+ if auto_opts.key? arg
55
+ payload[arg.to_sym] = auto_opts[arg]
56
+ next
57
+ end
58
+ response = ask "#{required == 'keyreq' ? '[required]' : '[optional]'} #{arg} value:"
59
+ if response.empty? && required == 'keyreq'
60
+ say "Error! #{arg} is required and cannot be empty", :red
61
+ redo
62
+ end
63
+ payload[arg.to_sym] = response unless response.empty?
64
+ end
65
+
66
+ status = options[:delay].zero? ? 'task.queued' : 'task.delayed'
67
+ task = generate_task_id(function_id: trigger_function.values[:id], status: status, runner_id: trigger_runner.values[:id], args: payload,
68
+ delay: options[:delay])
69
+
70
+ unless options[:delay].zero?
71
+ say "Task: #{task[:task_id]} is queued and will be run in #{options[:delay]}s"
72
+ return true
73
+ end
74
+
75
+ routing_key = "#{trigger_extension.values[:exchange]}.#{trigger_runner.values[:queue]}.#{trigger_function.values[:name]}"
76
+ exchange = Legion::Transport::Messages::Dynamic.new(function: trigger_function.values[:name], function_id: trigger_function.values[:id],
77
+ routing_key: routing_key, args: payload)
78
+ exchange.options[:task_id] = task[:task_id]
79
+ exchange.publish if options[:delay].zero?
80
+
81
+ say "Task: #{task[:task_id]} was queued"
82
+ end
83
+ remove_command :generate_task_id
84
+
85
+ default_task :queue
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,5 @@
1
+ module Legion
2
+ class Cli
3
+ VERSION = '0.2.0'.freeze
4
+ end
5
+ end
@@ -0,0 +1,229 @@
1
+ require 'legion/extensions/core'
2
+ require 'legion/runner'
3
+
4
+ module Legion
5
+ module Extensions
6
+ class << self
7
+ def setup
8
+ hook_extensions
9
+ end
10
+
11
+ def hook_extensions
12
+ @timer_tasks = []
13
+ @loop_tasks = []
14
+ @once_tasks = []
15
+ @poll_tasks = []
16
+ @subscription_tasks = []
17
+ @actors = []
18
+
19
+ find_extensions
20
+ load_extensions
21
+ end
22
+
23
+ def shutdown
24
+ return nil if @loaded_extensions.nil?
25
+
26
+ @subscription_tasks.each do |task|
27
+ task[:threadpool].shutdown
28
+ task[:threadpool].kill unless task[:threadpool].wait_for_termination(5)
29
+ end
30
+
31
+ @loop_tasks.each { |task| task[:running_class].cancel if task[:running_class].respond_to?(:cancel) }
32
+ @once_tasks.each { |task| task[:running_class].cancel if task[:running_class].respond_to?(:cancel) }
33
+ @timer_tasks.each { |task| task[:running_class].cancel if task[:running_class].respond_to?(:cancel) }
34
+ @poll_tasks.each { |task| task[:running_class].cancel if task[:running_class].respond_to?(:cancel) }
35
+
36
+ Legion::Logging.info 'Successfully shut down all actors'
37
+ end
38
+
39
+ def load_extensions
40
+ @extensions ||= {}
41
+ @loaded_extensions ||= []
42
+ @extensions.each do |extension, values|
43
+ if values.key(:enabled) && !values[:enabled]
44
+ Legion::Logging.info "Skipping #{extension} because it's disabled"
45
+ next
46
+ end
47
+
48
+ if Legion::Settings[:extensions].key?(extension.to_sym) && Legion::Settings[:extensions][extension.to_sym].key?(:enabled) && !Legion::Settings[:extensions][extension.to_sym][:enabled] # rubocop:disable Layout/LineLength
49
+ next
50
+ end
51
+
52
+ unless load_extension(extension, values)
53
+ Legion::Logging.warn("#{extension} failed to load")
54
+ next
55
+ end
56
+ @loaded_extensions.push(extension)
57
+ sleep(0.1)
58
+ end
59
+ Legion::Logging.info "#{@extensions.count} extensions loaded with subscription:#{@subscription_tasks.count},every:#{@timer_tasks.count},poll:#{@poll_tasks.count},once:#{@once_tasks.count},loop:#{@loop_tasks.count}"
60
+ end
61
+
62
+ def load_extension(extension, values) # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity, Metrics/AbcSize
63
+ return unless gem_load(values[:gem_name], extension)
64
+
65
+ extension = Kernel.const_get(values[:extension_class])
66
+ extension.extend Legion::Extensions::Core unless extension.singleton_class.included_modules.include? Legion::Extensions::Core
67
+
68
+ min_version = Legion::Settings[:extensions][values[:extension_name]][:min_version] || nil
69
+ Legion::Logging.fatal values if min_version.is_a?(String) && Gem::Version.new(values[:version]) >= Gem::Version.new(min_version)
70
+
71
+ if extension.data_required? && Legion::Settings[:data][:connected] == false
72
+ Legion::Logging.warn "#{values[:extension_name]} requires Legion::Data but isn't enabled, skipping"
73
+ return false
74
+ end
75
+
76
+ if extension.cache_required? && Legion::Settings[:cache][:connected] == false
77
+ Legion::Logging.warn "#{values[:extension_name]} requires Legion::Cache but isn't enabled, skipping"
78
+ return false
79
+ end
80
+
81
+ if extension.crypt_required? && Legion::Settings[:crypt][:cs].nil?
82
+ Legion::Logging.warn "#{values[:extension_name]} requires Legion::Crypt but isn't ready, skipping"
83
+ return false
84
+ end
85
+
86
+ if extension.vault_required? && Legion::Settings[:crypt][:vault][:connected] == false
87
+ Legion::Logging.warn "#{values[:extension_name]} requires Legion::Crypt::Vault but isn't enabled, skipping"
88
+ return false
89
+ end
90
+
91
+ has_logger = extension.respond_to?(:log)
92
+ extension.autobuild
93
+
94
+ require 'legion/transport/messages/lex_register'
95
+ Legion::Transport::Messages::LexRegister.new(function: 'save', opts: extension.runners).publish
96
+
97
+ if extension.respond_to?(:meta_actors) && extension.meta_actors.is_a?(Array)
98
+ extension.meta_actors.each do |_key, actor|
99
+ extension.log.debug("hooking meta actor: #{actor}") if has_logger
100
+ hook_actor(**actor)
101
+ end
102
+ end
103
+
104
+ extension.actors.each do |_key, actor|
105
+ extension.log.debug("hooking literal actor: #{actor}") if has_logger
106
+ hook_actor(**actor)
107
+ end
108
+ extension.log.info "Loaded v#{extension::VERSION}"
109
+ rescue StandardError => e
110
+ Legion::Logging.error e.message
111
+ Legion::Logging.error e.backtrace
112
+ false
113
+ end
114
+
115
+ def hook_actor(extension:, extension_name:, actor_class:, size: 1, **opts)
116
+ size = if Legion::Settings[:extensions].key?(extension_name.to_sym) && Legion::Settings[:extensions][extension_name.to_sym].key?(:workers)
117
+ Legion::Settings[:extensions][extension_name.to_sym][:workers]
118
+ elsif size.is_a? Integer
119
+ size
120
+ else
121
+ 1
122
+ end
123
+
124
+ extension_hash = {
125
+ extension: extension,
126
+ extension_name: extension_name,
127
+ actor_class: actor_class,
128
+ size: size,
129
+ fallback_policy: :abort,
130
+ **opts
131
+ }
132
+ extension_hash[:running_class] = if actor_class.ancestors.include? Legion::Extensions::Actors::Subscription
133
+ actor_class
134
+ else
135
+ actor_class.new
136
+ end
137
+
138
+ return if extension_hash[:running_class].respond_to?(:enabled?) && !extension_hash[:running_class].enabled?
139
+
140
+ if actor_class.ancestors.include? Legion::Extensions::Actors::Every
141
+ @timer_tasks.push(extension_hash)
142
+ elsif actor_class.ancestors.include? Legion::Extensions::Actors::Once
143
+ @once_tasks.push(extension_hash)
144
+ elsif actor_class.ancestors.include? Legion::Extensions::Actors::Loop
145
+ @loop_tasks.push(extension_hash)
146
+ elsif actor_class.ancestors.include? Legion::Extensions::Actors::Poll
147
+ @poll_tasks.push(extension_hash)
148
+ elsif actor_class.ancestors.include? Legion::Extensions::Actors::Subscription
149
+ extension_hash[:threadpool] = Concurrent::FixedThreadPool.new(size)
150
+ size.times do
151
+ extension_hash[:threadpool].post do
152
+ klass = actor_class.new
153
+ if klass.respond_to?(:async)
154
+ klass.async.subscribe
155
+ else
156
+ klass.subscribe
157
+ end
158
+ end
159
+ end
160
+ @subscription_tasks.push(extension_hash)
161
+ else
162
+ Legion::Logging.fatal 'did not match any actor classes'
163
+ end
164
+ end
165
+
166
+ def gem_load(gem_name, name)
167
+ require "#{Gem::Specification.find_by_name(gem_name).gem_dir}/lib/legion/extensions/#{name}"
168
+ true
169
+ rescue LoadError => e
170
+ Legion::Logging.error e.message
171
+ Legion::Logging.error e.backtrace
172
+ Legion::Logging.error "gem_path: #{gem_path}" unless gem_path.nil?
173
+ false
174
+ end
175
+
176
+ def find_extensions
177
+ @extensions ||= {}
178
+ Gem::Specification.all_names.each do |gem|
179
+ next unless gem[0..3] == 'lex-'
180
+
181
+ lex = gem.split('-')
182
+ @extensions[lex[1]] = { full_gem_name: gem,
183
+ gem_name: "lex-#{lex[1]}",
184
+ extension_name: lex[1],
185
+ version: lex[2],
186
+ extension_class: "Legion::Extensions::#{lex[1].split('_').collect(&:capitalize).join}" }
187
+ end
188
+
189
+ enabled = 0
190
+ requested = 0
191
+
192
+ Legion::Settings[:extensions].each do |extension, values|
193
+ next if @extensions.key? extension.to_s
194
+ next if values[:enabled] == false
195
+
196
+ requested += 1
197
+ next if values[:auto_install] == false
198
+ next if ENV['_'].include? 'bundle'
199
+
200
+ Legion::Logging.warn "#{extension} is missing, attempting to install automatically.."
201
+ install = Gem.install("lex-#{extension}", values[:version])
202
+ Legion::Logging.debug(install)
203
+ lex = Gem::Specification.find_by_name("lex-#{extension}")
204
+
205
+ @extensions[extension.to_s] = {
206
+ full_gem_name: "lex-#{extension}-#{lex.version}",
207
+ gem_name: "lex-#{extension}",
208
+ extension_name: extension.to_s,
209
+ version: lex.version,
210
+ extension_class: "Legion::Extensions::#{extension.to_s.split('_').collect(&:capitalize).join}"
211
+ }
212
+
213
+ enabled += 1
214
+
215
+ rescue StandardError, Gem::MissingSpecError => e
216
+ Legion::Logging.error "Failed to auto install #{extension}, e: #{e.message}"
217
+ end
218
+ return true if requested == enabled
219
+
220
+ Legion::Logging.warn "A total of #{requested - enabled} where skipped"
221
+ if ENV.key?('_') && ENV['_'].include?('bundle')
222
+ Legion::Logging.warn 'Please add them to your Gemfile since you are using bundler'
223
+ else
224
+ Legion::Logging.warn 'You must have auto_install_missing_lex set to true to auto install missing extensions'
225
+ end
226
+ end
227
+ end
228
+ end
229
+ end