foreman-tasks-core 0.3.6 → 0.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 774254a2eeb7a396c3035410552edc56882d54a1b96a9a800c726f6db96951b8
4
- data.tar.gz: 53221a9224ebd106fe67d5c2d51f681d601eb1c1f40a50b88b05b900644edb13
3
+ metadata.gz: f5c0486d95d64e7da989f4ab1c99111ea47102935d397f3d8995ddb3d6bd682b
4
+ data.tar.gz: ba8622004e32347d1603fb59698f0626470e39bc977a992241c933fb10579e50
5
5
  SHA512:
6
- metadata.gz: 186a69b1cae292b820cf97834139ad447b0a8dc75376b2fe9cd1b7c2f70e48b63504e5a2589b2e483cbc7021f18ece002e8995fe80f3892b4b44174a2cbb4999
7
- data.tar.gz: 7d9f89140bfe0000b59c42704ed3b5fb16f5645b5bd5ecb95e0aa446c34b93678a575390cdabdd6afe29bf22edfa8f226d3f1810a8f0a928d22f434562abd44c
6
+ metadata.gz: f5f287084e00895124f7da6fe936d0db2d3016311fb7a33333650cf9ddd09d2b11a1c5f097831d46945eb5a586e2ba29be9f1b39811fba9a50b2f3ad57d5030d
7
+ data.tar.gz: 968bdcd5f45add8ab258e79db98e5c0a01fd09d843a1f8f9f0f91cf10184421dcc75e0fd97964433b308556edecb05f93b69b83942910c06430d2637448164ae
@@ -1,31 +1,25 @@
1
1
  # The goal of ForemanTasksCore is to collect parts of foreman-tasks
2
2
  # that can be shared by the Foreman server and Foreman proxy
3
+ #
4
+ require 'smart_proxy_dynflow'
5
+ require 'smart_proxy_dynflow/task_launcher'
6
+ require 'smart_proxy_dynflow/settings_loader'
7
+ require 'smart_proxy_dynflow/otp_manager'
3
8
 
4
- require 'foreman_tasks_core/settings_loader'
5
- require 'foreman_tasks_core/otp_manager'
6
- require 'foreman_tasks_core/ticker'
7
- require 'foreman_tasks_core/batch_action'
8
- require 'foreman_tasks_core/batch_callback_action'
9
- require 'foreman_tasks_core/batch_runner_action'
10
- require 'foreman_tasks_core/output_collector_action'
11
- require 'foreman_tasks_core/single_runner_batch_action'
12
- require 'foreman_tasks_core/task_launcher'
9
+ require 'foreman_tasks_core/runner'
13
10
 
14
11
  module ForemanTasksCore
15
12
  def self.dynflow_world
16
- raise 'Dynflow world not set. Call initialize first' unless @dynflow_world
17
- @dynflow_world
13
+ Proxy::Dynflow::Core.world
18
14
  end
19
15
 
20
16
  def self.dynflow_present?
21
- defined? Dynflow
17
+ true
22
18
  end
23
19
 
24
- def self.dynflow_setup(dynflow_world)
25
- @dynflow_world = dynflow_world
26
- end
20
+ def self.dynflow_setup(_dynflow_world); end
27
21
 
28
- def self.silent_dead_letter_matchers
29
- [::Dynflow::DeadLetterSilencer::Matcher.new(ForemanTasksCore::Ticker)]
30
- end
22
+ TaskLauncher = Proxy::Dynflow::TaskLauncher
23
+ SettingsLoader = Proxy::Dynflow::SettingsLoader
24
+ OtpManager = Proxy::Dynflow::OtpManager
31
25
  end
@@ -1,10 +1,9 @@
1
+ require 'smart_proxy_dynflow/runner'
2
+
1
3
  module ForemanTasksCore
4
+ Runner = Proxy::Dynflow::Runner
5
+
2
6
  module Runner
7
+ Action = Proxy::Dynflow::Action::Runner
3
8
  end
4
9
  end
5
-
6
- require 'foreman_tasks_core/runner/update'
7
- require 'foreman_tasks_core/runner/base'
8
- require 'foreman_tasks_core/runner/dispatcher'
9
- require 'foreman_tasks_core/runner/action'
10
- require 'foreman_tasks_core/runner/parent'
@@ -1,11 +1,2 @@
1
- require 'io/wait'
2
- require 'pty'
3
- require 'foreman_tasks_core/runner/command'
4
-
5
- module ForemanTasksCore
6
- module Runner
7
- class CommandRunner < Base
8
- include Command
9
- end
10
- end
11
- end
1
+ require 'foreman_tasks_core/runner'
2
+ require 'smart_proxy_dynflow/runner/command_runner'
@@ -1,191 +1,2 @@
1
- require 'foreman_tasks_core/ticker'
2
-
3
- module ForemanTasksCore
4
- module Runner
5
- class Dispatcher
6
- def self.instance
7
- return @instance if @instance
8
- @instance = new(ForemanTasksCore.dynflow_world.clock,
9
- ForemanTasksCore.dynflow_world.logger)
10
- end
11
-
12
- class RunnerActor < ::Dynflow::Actor
13
- def initialize(dispatcher, suspended_action, runner, clock, logger, _options = {})
14
- @dispatcher = dispatcher
15
- @clock = clock
16
- @ticker = dispatcher.ticker
17
- @logger = logger
18
- @suspended_action = suspended_action
19
- @runner = runner
20
- @finishing = false
21
- end
22
-
23
- def on_envelope(*args)
24
- super
25
- rescue => e
26
- handle_exception(e)
27
- end
28
-
29
- def start_runner
30
- @logger.debug("start runner #{@runner.id}")
31
- set_timeout if @runner.timeout_interval
32
- @runner.start
33
- refresh_runner
34
- ensure
35
- plan_next_refresh
36
- end
37
-
38
- def refresh_runner
39
- @logger.debug("refresh runner #{@runner.id}")
40
- dispatch_updates(@runner.run_refresh)
41
- ensure
42
- @refresh_planned = false
43
- plan_next_refresh
44
- end
45
-
46
- def dispatch_updates(updates)
47
- updates.each { |receiver, update| (receiver || @suspended_action) << update }
48
-
49
- # This is a workaround when the runner does not accept the suspended action
50
- main_key = updates.keys.any?(&:nil?) ? nil : @suspended_action
51
- main_process = updates[main_key]
52
- finish if main_process && main_process.exit_status
53
- end
54
-
55
- def timeout_runner
56
- @logger.debug("timeout runner #{@runner.id}")
57
- @runner.timeout
58
- rescue => e
59
- handle_exception(e, false)
60
- end
61
-
62
- def kill
63
- @logger.debug("kill runner #{@runner.id}")
64
- @runner.kill
65
- rescue => e
66
- handle_exception(e, false)
67
- end
68
-
69
- def finish
70
- @logger.debug("finish runner #{@runner.id}")
71
- @finishing = true
72
- @dispatcher.finish(@runner.id)
73
- end
74
-
75
- def start_termination(*args)
76
- @logger.debug("terminate #{@runner.id}")
77
- super
78
- @runner.close
79
- finish_termination
80
- end
81
-
82
- def external_event(event)
83
- dispatch_updates(@runner.external_event(event))
84
- end
85
-
86
- private
87
-
88
- def set_timeout
89
- timeout_time = Time.now.getlocal + @runner.timeout_interval
90
- @logger.debug("setting timeout for #{@runner.id} to #{timeout_time}")
91
- @clock.ping(reference, timeout_time, :timeout_runner)
92
- end
93
-
94
- def plan_next_refresh
95
- if !@finishing && !@refresh_planned
96
- @logger.debug("planning to refresh #{@runner.id}")
97
- @ticker.tell([:add_event, reference, :refresh_runner])
98
- @refresh_planned = true
99
- end
100
- end
101
-
102
- def handle_exception(exception, fatal = true)
103
- @dispatcher.handle_command_exception(@runner.id, exception, fatal)
104
- end
105
- end
106
-
107
- attr_reader :ticker
108
- def initialize(clock, logger)
109
- @mutex = Mutex.new
110
- @clock = clock
111
- @logger = logger
112
- @ticker = ::ForemanTasksCore::Ticker.spawn('dispatcher-ticker', @clock, @logger, refresh_interval)
113
- @runner_actors = {}
114
- @runner_suspended_actions = {}
115
- end
116
-
117
- def synchronize(&block)
118
- @mutex.synchronize(&block)
119
- end
120
-
121
- def start(suspended_action, runner)
122
- synchronize do
123
- raise "Actor with runner id #{runner.id} already exists" if @runner_actors[runner.id]
124
- runner.logger = @logger
125
- runner_actor = RunnerActor.spawn("runner-actor-#{runner.id}", self, suspended_action, runner, @clock, @logger)
126
- @runner_actors[runner.id] = runner_actor
127
- @runner_suspended_actions[runner.id] = suspended_action
128
- runner_actor.tell(:start_runner)
129
- return runner.id
130
- rescue => exception
131
- _handle_command_exception(runner.id, exception)
132
- return nil
133
- end
134
- end
135
-
136
- def kill(runner_id)
137
- synchronize do
138
- runner_actor = @runner_actors[runner_id]
139
- runner_actor.tell(:kill) if runner_actor
140
- rescue => exception
141
- _handle_command_exception(runner_id, exception, false)
142
- end
143
- end
144
-
145
- def finish(runner_id)
146
- synchronize do
147
- _finish(runner_id)
148
- rescue => exception
149
- _handle_command_exception(runner_id, exception, false)
150
- end
151
- end
152
-
153
- def external_event(runner_id, external_event)
154
- synchronize do
155
- runner_actor = @runner_actors[runner_id]
156
- runner_actor.tell([:external_event, external_event]) if runner_actor
157
- end
158
- end
159
-
160
- def handle_command_exception(*args)
161
- synchronize { _handle_command_exception(*args) }
162
- end
163
-
164
- def refresh_interval
165
- 1
166
- end
167
-
168
- private
169
-
170
- def _finish(runner_id)
171
- runner_actor = @runner_actors.delete(runner_id)
172
- return unless runner_actor
173
- @logger.debug("closing session for command [#{runner_id}]," \
174
- "#{@runner_actors.size} actors left ")
175
- runner_actor.tell([:start_termination, Concurrent::Promises.resolvable_future])
176
- ensure
177
- @runner_suspended_actions.delete(runner_id)
178
- end
179
-
180
- def _handle_command_exception(runner_id, exception, fatal = true)
181
- @logger.error("error while dispatching request to runner #{runner_id}:"\
182
- "#{exception.class} #{exception.message}:\n #{exception.backtrace.join("\n")}")
183
- suspended_action = @runner_suspended_actions[runner_id]
184
- if suspended_action
185
- suspended_action << Runner::Update.encode_exception('Runner error', exception, fatal)
186
- end
187
- _finish(runner_id) if fatal
188
- end
189
- end
190
- end
191
- end
1
+ require 'foreman_tasks_core/runner'
2
+ require 'smart_proxy_dynflow/runner/dispatcher'
@@ -1,25 +1,5 @@
1
- module ForemanTasksCore
2
- class ShareableAction < ::Dynflow::Action
3
- def plan(input)
4
- input = input.dup
5
- callback = input.delete('callback')
6
- if callback
7
- input[:task_id] = callback['task_id']
8
- else
9
- input[:task_id] ||= SecureRandom.uuid
10
- end
11
-
12
- planned_action = plan_self(input)
13
- # code only applicable, when run with SmartProxyDynflowCore in place
14
- if on_proxy? && callback
15
- plan_action(SmartProxyDynflowCore::Callback::Action, callback, planned_action.output)
16
- end
17
- end
1
+ require 'smart_proxy_dynflow/action/shareable'
18
2
 
19
- private
20
-
21
- def on_proxy?
22
- defined?(SmartProxyDynflowCore::Callback)
23
- end
24
- end
3
+ module ForemanTasksCore
4
+ ShareableAction = Proxy::Dynflow::Action::Shareable
25
5
  end
@@ -1,3 +1,3 @@
1
1
  module ForemanTasksCore
2
- VERSION = '0.3.6'.freeze
2
+ VERSION = '0.4.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foreman-tasks-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.6
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ivan Nečas
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-07-29 00:00:00.000000000 Z
11
+ date: 2021-06-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dynflow
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: 1.2.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: smart_proxy_dynflow
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.5.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 0.5.0
27
41
  description: 'Common code used both at Forman and Foreman proxy regarding tasks
28
42
 
29
43
  '
@@ -35,29 +49,10 @@ extra_rdoc_files: []
35
49
  files:
36
50
  - LICENSE
37
51
  - lib/foreman_tasks_core.rb
38
- - lib/foreman_tasks_core/batch_action.rb
39
- - lib/foreman_tasks_core/batch_callback_action.rb
40
- - lib/foreman_tasks_core/batch_runner_action.rb
41
- - lib/foreman_tasks_core/continuous_output.rb
42
- - lib/foreman_tasks_core/otp_manager.rb
43
- - lib/foreman_tasks_core/output_collector_action.rb
44
52
  - lib/foreman_tasks_core/runner.rb
45
- - lib/foreman_tasks_core/runner/action.rb
46
- - lib/foreman_tasks_core/runner/base.rb
47
- - lib/foreman_tasks_core/runner/command.rb
48
53
  - lib/foreman_tasks_core/runner/command_runner.rb
49
54
  - lib/foreman_tasks_core/runner/dispatcher.rb
50
- - lib/foreman_tasks_core/runner/parent.rb
51
- - lib/foreman_tasks_core/runner/update.rb
52
- - lib/foreman_tasks_core/settings_loader.rb
53
55
  - lib/foreman_tasks_core/shareable_action.rb
54
- - lib/foreman_tasks_core/single_runner_batch_action.rb
55
- - lib/foreman_tasks_core/task_launcher.rb
56
- - lib/foreman_tasks_core/task_launcher/abstract.rb
57
- - lib/foreman_tasks_core/task_launcher/batch.rb
58
- - lib/foreman_tasks_core/task_launcher/group.rb
59
- - lib/foreman_tasks_core/task_launcher/single.rb
60
- - lib/foreman_tasks_core/ticker.rb
61
56
  - lib/foreman_tasks_core/version.rb
62
57
  homepage: https://github.com/theforeman/foreman-tasks
63
58
  licenses:
@@ -1,21 +0,0 @@
1
- module ForemanTasksCore
2
- class BatchAction < ::Dynflow::Action
3
- include Dynflow::Action::WithSubPlans
4
- include Dynflow::Action::WithPollingSubPlans
5
-
6
- # { task_id => { :action_class => Klass, :input => input } }
7
- def plan(launcher, input_hash)
8
- launcher.launch_children(self, input_hash)
9
- plan_self
10
- end
11
-
12
- def initiate
13
- ping suspended_action
14
- wait_for_sub_plans sub_plans
15
- end
16
-
17
- def rescue_strategy
18
- Dynflow::Action::Rescue::Fail
19
- end
20
- end
21
- end
@@ -1,20 +0,0 @@
1
- module ForemanTasksCore
2
- class BatchCallback < ::Dynflow::Action
3
- def plan(input_hash, results)
4
- plan_self :targets => input_hash, :results => results
5
- end
6
-
7
- def run
8
- payload = format_payload(input['targets'], input['results'])
9
- SmartProxyDynflowCore::Callback::Request.new.callback({ :callbacks => payload }.to_json)
10
- end
11
-
12
- private
13
-
14
- def format_payload(input_hash, results)
15
- input_hash.map do |task_id, callback|
16
- { :callback => callback, :data => results[task_id] }
17
- end
18
- end
19
- end
20
- end
@@ -1,14 +0,0 @@
1
- require 'foreman_tasks_core/runner/action'
2
-
3
- module ForemanTasksCore
4
- class BatchRunnerAction < ::ForemanTasksCore::Runner::Action
5
- def plan(launcher, input)
6
- plan_self :targets => launcher.runner_input(input), :operation => launcher.operation
7
- end
8
-
9
- def initiate_runner
10
- launcher = SmartProxyDynflowCore::TaskLauncherRegistry.fetch(input[:operation])
11
- launcher.runner_class.new(input[:targets], suspended_action: suspended_action)
12
- end
13
- end
14
- end
@@ -1,50 +0,0 @@
1
- module ForemanTasksCore
2
- class ContinuousOutput
3
- attr_accessor :raw_outputs
4
-
5
- def initialize(raw_outputs = [])
6
- @raw_outputs = []
7
- raw_outputs.each { |raw_output| add_raw_output(raw_output) }
8
- end
9
-
10
- def add_raw_output(raw_output)
11
- missing_args = %w[output_type output timestamp] - raw_output.keys
12
- unless missing_args.empty?
13
- raise ArgumentError, "Missing args for raw output: #{missing_args.inspect}"
14
- end
15
- @raw_outputs << raw_output
16
- end
17
-
18
- def empty?
19
- @raw_outputs.empty?
20
- end
21
-
22
- def last_timestamp
23
- return if @raw_outputs.empty?
24
- @raw_outputs.last.fetch('timestamp')
25
- end
26
-
27
- def sort!
28
- @raw_outputs.sort_by! { |record| record['timestamp'].to_f }
29
- end
30
-
31
- def humanize
32
- sort!
33
- raw_outputs.map { |output| output['output'] }.join("\n")
34
- end
35
-
36
- def add_exception(context, exception, timestamp = Time.now.getlocal)
37
- add_output(context + ": #{exception.class} - #{exception.message}", 'debug', timestamp)
38
- end
39
-
40
- def add_output(*args)
41
- add_raw_output(self.class.format_output(*args))
42
- end
43
-
44
- def self.format_output(message, type = 'debug', timestamp = Time.now.getlocal)
45
- { 'output_type' => type,
46
- 'output' => message,
47
- 'timestamp' => timestamp.to_f }
48
- end
49
- end
50
- end
@@ -1,36 +0,0 @@
1
- require 'base64'
2
- require 'securerandom'
3
-
4
- module ForemanTasksCore
5
- class OtpManager
6
- class << self
7
- def generate_otp(username)
8
- otp = SecureRandom.hex
9
- passwords[username] = otp.to_s
10
- end
11
-
12
- def drop_otp(username, password)
13
- passwords.delete(username) if passwords[username] == password
14
- end
15
-
16
- def passwords
17
- @password ||= {}
18
- end
19
-
20
- def authenticate(hash, expected_user: nil, clear: true)
21
- plain = Base64.decode64(hash)
22
- username, otp = plain.split(':', 2)
23
- if expected_user
24
- return false unless expected_user == username
25
- end
26
- password_matches = passwords[username] == otp
27
- passwords.delete(username) if clear && password_matches
28
- password_matches
29
- end
30
-
31
- def tokenize(username, password)
32
- Base64.strict_encode64("#{username}:#{password}")
33
- end
34
- end
35
- end
36
- end
@@ -1,8 +0,0 @@
1
- module ForemanTasksCore
2
- class OutputCollectorAction < ::ForemanTasksCore::Runner::Action
3
- def init_run
4
- output[:result] = []
5
- suspend
6
- end
7
- end
8
- end
@@ -1,76 +0,0 @@
1
- require 'foreman_tasks_core/shareable_action'
2
- module ForemanTasksCore
3
- module Runner
4
- class Action < ::ForemanTasksCore::ShareableAction
5
- include ::Dynflow::Action::Cancellable
6
-
7
- def run(event = nil)
8
- case event
9
- when nil
10
- init_run
11
- when Runner::Update
12
- process_update(event)
13
- when Runner::ExternalEvent
14
- process_external_event(event)
15
- when ::Dynflow::Action::Cancellable::Cancel
16
- kill_run
17
- else
18
- raise "Unexpected event #{event.inspect}"
19
- end
20
- rescue => e
21
- action_logger.error(e)
22
- process_update(Runner::Update.encode_exception('Proxy error', e))
23
- end
24
-
25
- def finalize
26
- # To mark the task as a whole as failed
27
- error! 'Script execution failed' if on_proxy? && failed_run?
28
- end
29
-
30
- def rescue_strategy_for_self
31
- ::Dynflow::Action::Rescue::Fail
32
- end
33
-
34
- def initiate_runner
35
- raise NotImplementedError
36
- end
37
-
38
- def init_run
39
- output[:result] = []
40
- output[:runner_id] = runner_dispatcher.start(suspended_action, initiate_runner)
41
- suspend
42
- end
43
-
44
- def runner_dispatcher
45
- Runner::Dispatcher.instance
46
- end
47
-
48
- def kill_run
49
- runner_dispatcher.kill(output[:runner_id])
50
- suspend
51
- end
52
-
53
- def finish_run(update)
54
- output[:exit_status] = update.exit_status
55
- end
56
-
57
- def process_external_event(event)
58
- runner_dispatcher.external_event(output[:runner_id], event)
59
- suspend
60
- end
61
-
62
- def process_update(update)
63
- output[:result].concat(update.continuous_output.raw_outputs)
64
- if update.exit_status
65
- finish_run(update)
66
- else
67
- suspend
68
- end
69
- end
70
-
71
- def failed_run?
72
- output[:exit_status] != 0
73
- end
74
- end
75
- end
76
- end
@@ -1,98 +0,0 @@
1
- module ForemanTasksCore
2
- module Runner
3
- # Runner is an object that is able to initiate some action and
4
- # provide update data on refresh call.
5
- class Base
6
- attr_reader :id
7
- attr_writer :logger
8
-
9
- def initialize(*_args, suspended_action: nil)
10
- @suspended_action = suspended_action
11
- @id = SecureRandom.uuid
12
- initialize_continuous_outputs
13
- end
14
-
15
- def logger
16
- @logger ||= Logger.new(STDERR)
17
- end
18
-
19
- def run_refresh
20
- logger.debug('refreshing runner')
21
- refresh
22
- generate_updates
23
- end
24
-
25
- # by default, external event just causes the refresh to be triggered: this allows the descendants
26
- # of the Base to add custom logic to process the external events.
27
- # Similarly as `run_refresh`, it's expected to return updates to be dispatched.
28
- def external_event(_event)
29
- run_refresh
30
- end
31
-
32
- def start
33
- raise NotImplementedError
34
- end
35
-
36
- def refresh
37
- raise NotImplementedError
38
- end
39
-
40
- def kill
41
- # Override when you can kill the runner in the middle
42
- end
43
-
44
- def close
45
- # if cleanup is needed
46
- end
47
-
48
- def timeout
49
- # Override when timeouts and regular kills should be handled differently
50
- publish_data('Timeout for execution passed, trying to stop the job', 'debug')
51
- kill
52
- end
53
-
54
- def timeout_interval
55
- # A number of seconds after which the runner should receive a #timeout
56
- # or nil for no timeout
57
- end
58
-
59
- def publish_data(data, type)
60
- @continuous_output.add_output(data, type)
61
- end
62
-
63
- def publish_exception(context, exception, fatal = true)
64
- logger.error("#{context} - #{exception.class} #{exception.message}:\n" + \
65
- exception.backtrace.join("\n"))
66
- dispatch_exception context, exception
67
- publish_exit_status('EXCEPTION') if fatal
68
- end
69
-
70
- def publish_exit_status(status)
71
- @exit_status = status
72
- end
73
-
74
- def dispatch_exception(context, exception)
75
- @continuous_output.add_exception(context, exception)
76
- end
77
-
78
- def generate_updates
79
- return no_update if @continuous_output.empty? && @exit_status.nil?
80
- new_data = @continuous_output
81
- @continuous_output = ForemanTasksCore::ContinuousOutput.new
82
- new_update(new_data, @exit_status)
83
- end
84
-
85
- def no_update
86
- {}
87
- end
88
-
89
- def new_update(data, exit_status)
90
- { @suspended_action => Runner::Update.new(data, exit_status) }
91
- end
92
-
93
- def initialize_continuous_outputs
94
- @continuous_output = ::ForemanTasksCore::ContinuousOutput.new
95
- end
96
- end
97
- end
98
- end
@@ -1,40 +0,0 @@
1
- module ForemanTasksCore
2
- module Runner
3
- module Command
4
- def initialize_command(*command)
5
- @command_out, @command_in, @command_pid = PTY.spawn(*command)
6
- rescue Errno::ENOENT => e
7
- publish_exception("Error running command '#{command.join(' ')}'", e)
8
- end
9
-
10
- def refresh
11
- return if @command_out.nil?
12
- ready_outputs, * = IO.select([@command_out], nil, nil, 0.1)
13
- if ready_outputs
14
- if @command_out.nread > 0
15
- lines = @command_out.read_nonblock(@command_out.nread)
16
- else
17
- close_io
18
- Process.wait(@command_pid)
19
- publish_exit_status($CHILD_STATUS.exitstatus)
20
- end
21
- publish_data(lines, 'stdout') if lines && !lines.empty?
22
- end
23
- end
24
-
25
- def close
26
- close_io
27
- end
28
-
29
- private
30
-
31
- def close_io
32
- @command_out.close if @command_out && !@command_out.closed?
33
- @command_out = nil
34
-
35
- @command_in.close if @command_in && !@command_in.closed?
36
- @command_in = nil
37
- end
38
- end
39
- end
40
- end
@@ -1,57 +0,0 @@
1
- module ForemanTasksCore
2
- module Runner
3
- class Parent < Base
4
- # targets = { identifier => { :execution_plan_id => "...", :run_step_id => id,
5
- # :input => { ... } }
6
- def initialize(targets = {}, suspended_action: nil)
7
- @targets = targets
8
- @exit_statuses = {}
9
- super suspended_action: suspended_action
10
- end
11
-
12
- def generate_updates
13
- base = {}
14
- base[@suspended_action] = Runner::Update.new(ForemanTasksCore::ContinuousOutput.new, @exit_status) if @exit_status
15
- # Operate on all hosts if the main process ended or only on hosts for which we have updates
16
- @outputs.reject { |_, output| @exit_status.nil? && output.empty? }
17
- .reduce(base) do |acc, (identifier, output)|
18
- @outputs[identifier] = ForemanTasksCore::ContinuousOutput.new # Create a new ContinuousOutput for next round of updates
19
- exit_status = @exit_statuses[identifier] || @exit_status if @exit_status
20
- acc.merge(host_action(identifier) => Runner::Update.new(output, exit_status))
21
- end
22
- end
23
-
24
- def initialize_continuous_outputs
25
- @outputs = @targets.keys.reduce({}) do |acc, target|
26
- acc.merge(target => ForemanTasksCore::ContinuousOutput.new)
27
- end
28
- end
29
-
30
- def host_action(identifier)
31
- options = @targets[identifier].slice('execution_plan_id', 'run_step_id')
32
- .merge(:world => ForemanTasksCore.dynflow_world)
33
- Dynflow::Action::Suspended.new OpenStruct.new(options)
34
- end
35
-
36
- def broadcast_data(data, type)
37
- @outputs.each { |_k, output| output.add_output(data, type) }
38
- end
39
-
40
- def publish_data(_data, _type)
41
- true
42
- end
43
-
44
- def publish_data_for(identifier, data, type)
45
- @outputs[identifier].add_output(data, type)
46
- end
47
-
48
- def dispatch_exception(context, exception)
49
- @outputs.values.each { |output| output.add_exception(context, exception) }
50
- end
51
-
52
- def publish_exit_status_for(identifier, exit_status)
53
- @exit_statuses[identifier] = exit_status
54
- end
55
- end
56
- end
57
- end
@@ -1,28 +0,0 @@
1
- require 'foreman_tasks_core/continuous_output'
2
-
3
- module ForemanTasksCore
4
- module Runner
5
- # Runner::Update represents chunk of data produced by runner that
6
- # can be consumed by other components, such as RunnerAction
7
- class Update
8
- attr_reader :continuous_output, :exit_status
9
- def initialize(continuous_output, exit_status)
10
- @continuous_output = continuous_output
11
- @exit_status = exit_status
12
- end
13
-
14
- def self.encode_exception(context, exception, fatal = true)
15
- continuous_output = ::ForemanTasksCore::ContinuousOutput.new
16
- continuous_output.add_exception(context, exception)
17
- new(continuous_output, fatal ? 'EXCEPTION' : nil)
18
- end
19
- end
20
-
21
- class ExternalEvent
22
- attr_reader :data
23
- def initialize(data = {})
24
- @data = data
25
- end
26
- end
27
- end
28
- end
@@ -1,53 +0,0 @@
1
- module ForemanTasksCore
2
- module SettingsLoader
3
- def self.settings_registry
4
- @settings_registry ||= {}
5
- end
6
-
7
- def self.name_to_settings
8
- @name_to_settings ||= {}
9
- end
10
-
11
- def self.settings_keys
12
- @settings_keys ||= []
13
- end
14
-
15
- def self.settings_registered?(name)
16
- name_to_settings.key?(name)
17
- end
18
-
19
- def self.register_settings(names, object)
20
- names = [names] unless names.is_a? Array
21
- names.each do |name|
22
- raise 'settings name has to be a symbol' unless name.is_a? Symbol
23
- raise "settings #{name} already registered" if SettingsLoader.settings_registered?(name)
24
- name_to_settings[name] = object
25
- end
26
- settings_registry[names] = object
27
- end
28
-
29
- def self.setup_settings(name, settings)
30
- raise "Settings for #{name} were not registered" unless settings_registered?(name)
31
- name_to_settings[name].initialize_settings(settings)
32
- end
33
-
34
- def register_settings(names, defaults = {})
35
- SettingsLoader.register_settings(names, self)
36
- @defaults = defaults
37
- end
38
-
39
- def initialize_settings(settings = {})
40
- @settings = @defaults.merge(settings)
41
- validate_settings!
42
- end
43
-
44
- def settings
45
- raise "Settings for #{self} not initalized" unless @settings
46
- @settings
47
- end
48
-
49
- def validate_settings!
50
- raise 'Only symbols expected in keys' unless @settings.keys.all? { |key| key.is_a? Symbol }
51
- end
52
- end
53
- end
@@ -1,39 +0,0 @@
1
- module ForemanTasksCore
2
- class SingleRunnerBatchAction < ForemanTasksCore::BatchAction
3
- def plan(launcher, input_hash)
4
- launcher.launch_children(self, input_hash)
5
- sequence do
6
- results = plan_self
7
- plan_action BatchCallback, launcher.prepare_batch(input_hash), results.output[:results]
8
- end
9
- end
10
-
11
- def run(event = nil)
12
- super unless event == Dynflow::Action::Skip
13
- end
14
-
15
- def initiate
16
- ping suspended_action
17
- wait_for_sub_plans sub_plans
18
- end
19
-
20
- def check_for_errors!(optional = true)
21
- super unless optional
22
- end
23
-
24
- def on_finish
25
- output[:results] = sub_plans.map(&:entry_action).reduce({}) do |acc, cur|
26
- acc.merge(cur.execution_plan_id => cur.output)
27
- end
28
- end
29
-
30
- def finalize
31
- output.delete(:results)
32
- check_for_errors!
33
- end
34
-
35
- def rescue_strategy_for_self
36
- Dynflow::Action::Rescue::Skip
37
- end
38
- end
39
- end
@@ -1,9 +0,0 @@
1
- module ForemanTasksCore
2
- module TaskLauncher
3
- end
4
- end
5
-
6
- require 'foreman_tasks_core/task_launcher/abstract'
7
- require 'foreman_tasks_core/task_launcher/single'
8
- require 'foreman_tasks_core/task_launcher/batch'
9
- require 'foreman_tasks_core/task_launcher/group'
@@ -1,44 +0,0 @@
1
- module ForemanTasksCore
2
- module TaskLauncher
3
- class Abstract
4
- attr_reader :callback, :options, :results, :world
5
- def initialize(world, callback, options = {})
6
- @world = world
7
- @callback = callback
8
- @options = options
9
- @results = {}
10
- end
11
-
12
- def launch!(_input)
13
- raise NotImplementedError
14
- end
15
-
16
- def self.input_format; end
17
-
18
- private
19
-
20
- def format_result(result)
21
- if result.triggered?
22
- { :result => 'success', :task_id => result.execution_plan_id }
23
- else
24
- plan = world.persistence.load_execution_plan(result.id)
25
- { :result => 'error', :errors => plan.errors }
26
- end
27
- end
28
-
29
- def action_class(input)
30
- options[:action_class_override] || ::Dynflow::Utils.constantize(input['action_class'])
31
- end
32
-
33
- def with_callback(input)
34
- input.merge(:callback_host => callback)
35
- end
36
-
37
- def trigger(parent, klass, *input)
38
- world.trigger do
39
- world.plan_with_options(caller_action: parent, action_class: klass, args: input)
40
- end
41
- end
42
- end
43
- end
44
- end
@@ -1,37 +0,0 @@
1
- module ForemanTasksCore
2
- module TaskLauncher
3
- class Batch < Abstract
4
- def launch!(input)
5
- trigger(nil, BatchAction, self, input)
6
- end
7
-
8
- def launch_children(parent, input_hash)
9
- input_hash.each do |task_id, input|
10
- launcher = child_launcher(parent)
11
- launcher.launch!(transform_input(input))
12
- results[task_id] = launcher.results
13
- end
14
- end
15
-
16
- def prepare_batch(input_hash)
17
- success_tasks = input_hash.select do |task_id, _input|
18
- results[task_id][:result] == 'success'
19
- end
20
- success_tasks.reduce({}) do |acc, (key, value)|
21
- acc.merge(results[key][:task_id] => value['action_input']['callback'])
22
- end
23
- end
24
-
25
- private
26
-
27
- def child_launcher(parent)
28
- Single.new(world, callback, :parent => parent)
29
- end
30
-
31
- # Identity by default
32
- def transform_input(input)
33
- input
34
- end
35
- end
36
- end
37
- end
@@ -1,48 +0,0 @@
1
- require 'foreman_tasks_core/runner'
2
-
3
- module ForemanTasksCore
4
- module TaskLauncher
5
- class AbstractGroup < Batch
6
- def self.runner_class
7
- raise NotImplementedError
8
- end
9
-
10
- def launch!(input)
11
- trigger(nil, SingleRunnerBatchAction, self, input)
12
- end
13
-
14
- def launch_children(parent, input_hash)
15
- super(parent, input_hash)
16
- trigger(parent, BatchRunnerAction, self, input_hash)
17
- end
18
-
19
- def operation
20
- raise NotImplementedError
21
- end
22
-
23
- def runner_input(input)
24
- input.reduce({}) do |acc, (id, input)|
25
- input = { :execution_plan_id => results[id][:task_id],
26
- :run_step_id => 2,
27
- :input => input }
28
- acc.merge(id => input)
29
- end
30
- end
31
-
32
- private
33
-
34
- def child_launcher(parent)
35
- Single.new(world, callback, :parent => parent, :action_class_override => OutputCollectorAction)
36
- end
37
-
38
- def transform_input(input)
39
- wipe_callback(input)
40
- end
41
-
42
- def wipe_callback(input)
43
- callback = input['action_input']['callback']
44
- input.merge('action_input' => input['action_input'].merge('callback' => nil, :task_id => callback['task_id']))
45
- end
46
- end
47
- end
48
- end
@@ -1,17 +0,0 @@
1
- module ForemanTasksCore
2
- module TaskLauncher
3
- class Single < Abstract
4
- def self.input_format
5
- { :action_class => "MyActionClass", :action_input => {} }
6
- end
7
-
8
- def launch!(input)
9
- triggered = trigger(options[:parent],
10
- action_class(input),
11
- with_callback(input.fetch('action_input', {})))
12
- @results = format_result(triggered)
13
- triggered
14
- end
15
- end
16
- end
17
- end
@@ -1,47 +0,0 @@
1
- require 'dynflow'
2
-
3
- module ForemanTasksCore
4
- class Ticker < ::Dynflow::Actor
5
- attr_reader :clock
6
-
7
- def initialize(clock, logger, refresh_interval)
8
- @clock = clock
9
- @logger = logger
10
- @events = []
11
- @refresh_interval = refresh_interval
12
- plan_next_tick
13
- end
14
-
15
- def tick
16
- @logger.debug("Ticker ticking for #{@events.size} events")
17
- @events.each do |(target, args)|
18
- pass_event(target, args)
19
- end
20
- @events = []
21
- ensure
22
- @planned = false
23
- plan_next_tick
24
- end
25
-
26
- def add_event(target, args)
27
- @events << [target, args]
28
- plan_next_tick
29
- end
30
-
31
- private
32
-
33
- def pass_event(target, args)
34
- target.tell(args)
35
- rescue => e
36
- @logger.error("Failed passing event to #{target} with #{args}")
37
- @logger.error(e)
38
- end
39
-
40
- def plan_next_tick
41
- if !@planned && !@events.empty?
42
- @clock.ping(reference, Time.now.getlocal + @refresh_interval, :tick)
43
- @planned = true
44
- end
45
- end
46
- end
47
- end