foreman-tasks-core 0.3.6 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 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