foreman-tasks-core 0.3.1 → 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: c46d9f3def1bf8405c45eb6a32331437dede8c68071d0c0c8a0159a2e489f007
4
- data.tar.gz: 24e606e5743d4694c607514b25bbff6492153290d9d3c99546a91b6ce75967d0
3
+ metadata.gz: f5c0486d95d64e7da989f4ab1c99111ea47102935d397f3d8995ddb3d6bd682b
4
+ data.tar.gz: ba8622004e32347d1603fb59698f0626470e39bc977a992241c933fb10579e50
5
5
  SHA512:
6
- metadata.gz: 5a3c8a38e39d80a3f5f9bc1deebdcf7e4b082838e687ae78846a17448302dddff4c00c24e81235ad455133c12bfd7112d657b7ca630210ce79bddd1f4d04e13f
7
- data.tar.gz: c4777a0ca2d8d7562e3b2f0ab02293e7af5039753adcd9c7d0fabe1955726b60cf15e2c9664376410aff9e08de45054459ce9e2ac7a0d4a94ce49e02b3a61936
6
+ metadata.gz: f5f287084e00895124f7da6fe936d0db2d3016311fb7a33333650cf9ddd09d2b11a1c5f097831d46945eb5a586e2ba29be9f1b39811fba9a50b2f3ad57d5030d
7
+ data.tar.gz: 968bdcd5f45add8ab258e79db98e5c0a01fd09d843a1f8f9f0f91cf10184421dcc75e0fd97964433b308556edecb05f93b69b83942910c06430d2637448164ae
@@ -1,26 +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/task_launcher'
9
+ require 'foreman_tasks_core/runner'
8
10
 
9
11
  module ForemanTasksCore
10
12
  def self.dynflow_world
11
- raise 'Dynflow world not set. Call initialize first' unless @dynflow_world
12
- @dynflow_world
13
+ Proxy::Dynflow::Core.world
13
14
  end
14
15
 
15
16
  def self.dynflow_present?
16
- defined? Dynflow
17
+ true
17
18
  end
18
19
 
19
- def self.dynflow_setup(dynflow_world)
20
- @dynflow_world = dynflow_world
21
- end
20
+ def self.dynflow_setup(_dynflow_world); end
22
21
 
23
- def self.silent_dead_letter_matchers
24
- [::Dynflow::DeadLetterSilencer::Matcher.new(ForemanTasksCore::Ticker)]
25
- end
22
+ TaskLauncher = Proxy::Dynflow::TaskLauncher
23
+ SettingsLoader = Proxy::Dynflow::SettingsLoader
24
+ OtpManager = Proxy::Dynflow::OtpManager
26
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,41 +1,2 @@
1
- require 'io/wait'
2
- require 'pty'
3
-
4
- module ForemanTasksCore
5
- module Runner
6
- class CommandRunner < Runner::Base
7
- def initialize_command(*command)
8
- @command_out, @command_in, @command_pid = PTY.spawn(*command)
9
- end
10
-
11
- def refresh
12
- return if @command_out.nil?
13
- ready_outputs, * = IO.select([@command_out], nil, nil, 0.1)
14
- if ready_outputs
15
- if @command_out.nread > 0
16
- lines = @command_out.read_nonblock(@command_out.nread)
17
- else
18
- close_io
19
- Process.wait(@command_pid)
20
- publish_exit_status($CHILD_STATUS.exitstatus)
21
- end
22
- publish_data(lines, 'stdout') if lines && !lines.empty?
23
- end
24
- end
25
-
26
- def close
27
- close_io
28
- end
29
-
30
- private
31
-
32
- def close_io
33
- @command_out.close if @command_out && !@command_out.closed?
34
- @command_out = nil
35
-
36
- @command_in.close if @command_in && !@command_in.closed?
37
- @command_in = nil
38
- end
39
- end
40
- end
41
- end
1
+ require 'foreman_tasks_core/runner'
2
+ require 'smart_proxy_dynflow/runner/command_runner'
@@ -1,195 +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
- updates = @runner.run_refresh
41
-
42
- updates.each { |receiver, update| (receiver || @suspended_action) << update }
43
-
44
- # This is a workaround when the runner does not accept the suspended action
45
- main_key = updates.keys.any?(&:nil?) ? nil : @suspended_action
46
- main_process = updates[main_key]
47
- finish if main_process && main_process.exit_status
48
- ensure
49
- @refresh_planned = false
50
- plan_next_refresh
51
- end
52
-
53
- def timeout_runner
54
- @logger.debug("timeout runner #{@runner.id}")
55
- @runner.timeout
56
- rescue => e
57
- handle_exception(e, false)
58
- end
59
-
60
- def kill
61
- @logger.debug("kill runner #{@runner.id}")
62
- @runner.kill
63
- rescue => e
64
- handle_exception(e, false)
65
- end
66
-
67
- def finish
68
- @logger.debug("finish runner #{@runner.id}")
69
- @finishing = true
70
- @dispatcher.finish(@runner.id)
71
- end
72
-
73
- def start_termination(*args)
74
- @logger.debug("terminate #{@runner.id}")
75
- super
76
- @runner.close
77
- finish_termination
78
- end
79
-
80
- def external_event(_event)
81
- refresh_runner
82
- end
83
-
84
- private
85
-
86
- def set_timeout
87
- timeout_time = Time.now.getlocal + @runner.timeout_interval
88
- @logger.debug("setting timeout for #{@runner.id} to #{timeout_time}")
89
- @clock.ping(reference, timeout_time, :timeout_runner)
90
- end
91
-
92
- def plan_next_refresh
93
- if !@finishing && !@refresh_planned
94
- @logger.debug("planning to refresh #{@runner.id}")
95
- @ticker.tell([:add_event, reference, :refresh_runner])
96
- @refresh_planned = true
97
- end
98
- end
99
-
100
- def handle_exception(exception, fatal = true)
101
- @dispatcher.handle_command_exception(@runner.id, exception, fatal)
102
- end
103
- end
104
-
105
- attr_reader :ticker
106
- def initialize(clock, logger)
107
- @mutex = Mutex.new
108
- @clock = clock
109
- @logger = logger
110
- @ticker = ::ForemanTasksCore::Ticker.spawn('dispatcher-ticker', @clock, @logger, refresh_interval)
111
- @runner_actors = {}
112
- @runner_suspended_actions = {}
113
- end
114
-
115
- def synchronize(&block)
116
- @mutex.synchronize(&block)
117
- end
118
-
119
- def start(suspended_action, runner)
120
- synchronize do
121
- begin
122
- raise "Actor with runner id #{runner.id} already exists" if @runner_actors[runner.id]
123
- runner.logger = @logger
124
- runner_actor = RunnerActor.spawn("runner-actor-#{runner.id}", self, suspended_action, runner, @clock, @logger)
125
- @runner_actors[runner.id] = runner_actor
126
- @runner_suspended_actions[runner.id] = suspended_action
127
- runner_actor.tell(:start_runner)
128
- return runner.id
129
- rescue => exception
130
- _handle_command_exception(runner.id, exception)
131
- return nil
132
- end
133
- end
134
- end
135
-
136
- def kill(runner_id)
137
- synchronize do
138
- begin
139
- runner_actor = @runner_actors[runner_id]
140
- runner_actor.tell(:kill) if runner_actor
141
- rescue => exception
142
- _handle_command_exception(runner_id, exception, false)
143
- end
144
- end
145
- end
146
-
147
- def finish(runner_id)
148
- synchronize do
149
- begin
150
- _finish(runner_id)
151
- rescue => exception
152
- _handle_command_exception(runner_id, exception, false)
153
- end
154
- end
155
- end
156
-
157
- def external_event(runner_id, external_event)
158
- synchronize do
159
- runner_actor = @runner_actors[runner_id]
160
- runner_actor.tell([:external_event, external_event]) if runner_actor
161
- end
162
- end
163
-
164
- def handle_command_exception(*args)
165
- synchronize { _handle_command_exception(*args) }
166
- end
167
-
168
- def refresh_interval
169
- 1
170
- end
171
-
172
- private
173
-
174
- def _finish(runner_id)
175
- runner_actor = @runner_actors.delete(runner_id)
176
- return unless runner_actor
177
- @logger.debug("closing session for command [#{runner_id}]," \
178
- "#{@runner_actors.size} actors left ")
179
- runner_actor.tell([:start_termination, Concurrent::Promises.resolvable_future])
180
- ensure
181
- @runner_suspended_actions.delete(runner_id)
182
- end
183
-
184
- def _handle_command_exception(runner_id, exception, fatal = true)
185
- @logger.error("error while dispatching request to runner #{runner_id}:"\
186
- "#{exception.class} #{exception.message}:\n #{exception.backtrace.join("\n")}")
187
- suspended_action = @runner_suspended_actions[runner_id]
188
- if suspended_action
189
- suspended_action << Runner::Update.encode_exception('Runner error', exception, fatal)
190
- end
191
- _finish(runner_id) if fatal
192
- end
193
- end
194
- end
195
- 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.1'.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.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ivan Nečas
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-03-15 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,9 +24,23 @@ 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
+ '
30
44
  email:
31
45
  - inecas@redhat.com
32
46
  executables: []
@@ -35,27 +49,16 @@ extra_rdoc_files: []
35
49
  files:
36
50
  - LICENSE
37
51
  - lib/foreman_tasks_core.rb
38
- - lib/foreman_tasks_core/continuous_output.rb
39
- - lib/foreman_tasks_core/otp_manager.rb
40
52
  - lib/foreman_tasks_core/runner.rb
41
- - lib/foreman_tasks_core/runner/action.rb
42
- - lib/foreman_tasks_core/runner/base.rb
43
53
  - lib/foreman_tasks_core/runner/command_runner.rb
44
54
  - lib/foreman_tasks_core/runner/dispatcher.rb
45
- - lib/foreman_tasks_core/runner/parent.rb
46
- - lib/foreman_tasks_core/runner/update.rb
47
- - lib/foreman_tasks_core/settings_loader.rb
48
55
  - lib/foreman_tasks_core/shareable_action.rb
49
- - lib/foreman_tasks_core/task_launcher.rb
50
- - lib/foreman_tasks_core/task_launcher/abstract.rb
51
- - lib/foreman_tasks_core/task_launcher/batch.rb
52
- - lib/foreman_tasks_core/task_launcher/single.rb
53
- - lib/foreman_tasks_core/ticker.rb
54
56
  - lib/foreman_tasks_core/version.rb
55
57
  homepage: https://github.com/theforeman/foreman-tasks
56
- licenses: []
58
+ licenses:
59
+ - GPL-3.0
57
60
  metadata: {}
58
- post_install_message:
61
+ post_install_message:
59
62
  rdoc_options: []
60
63
  require_paths:
61
64
  - lib
@@ -70,9 +73,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
70
73
  - !ruby/object:Gem::Version
71
74
  version: '0'
72
75
  requirements: []
73
- rubyforge_project:
74
- rubygems_version: 2.7.6
75
- signing_key:
76
+ rubygems_version: 3.1.2
77
+ signing_key:
76
78
  specification_version: 4
77
79
  summary: Common code used both at Forman and Foreman proxy regarding tasks
78
80
  test_files: []
@@ -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,31 +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)
21
- plain = Base64.decode64(hash)
22
- username, otp = plain.split(':', 2)
23
- drop_otp(username, otp)
24
- end
25
-
26
- def tokenize(username, password)
27
- Base64.strict_encode64("#{username}:#{password}")
28
- end
29
- end
30
- end
31
- 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,83 +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
- def start
26
- raise NotImplementedError
27
- end
28
-
29
- def refresh
30
- raise NotImplementedError
31
- end
32
-
33
- def kill
34
- # Override when you can kill the runner in the middle
35
- end
36
-
37
- def close
38
- # if cleanup is needed
39
- end
40
-
41
- def timeout
42
- # Override when timeouts and regular kills should be handled differently
43
- publish_data('Timeout for execution passed, trying to stop the job', 'debug')
44
- kill
45
- end
46
-
47
- def timeout_interval
48
- # A number of seconds after which the runner should receive a #timeout
49
- # or nil for no timeout
50
- end
51
-
52
- def publish_data(data, type)
53
- @continuous_output.add_output(data, type)
54
- end
55
-
56
- def publish_exception(context, exception, fatal = true)
57
- logger.error("#{context} - #{exception.class} #{exception.message}:\n" + \
58
- exception.backtrace.join("\n"))
59
- dispatch_exception context, exception
60
- publish_exit_status('EXCEPTION') if fatal
61
- end
62
-
63
- def publish_exit_status(status)
64
- @exit_status = status
65
- end
66
-
67
- def dispatch_exception(context, exception)
68
- @continuous_output.add_exception(context, exception)
69
- end
70
-
71
- def generate_updates
72
- return {} if @continuous_output.empty? && @exit_status.nil?
73
- new_data = @continuous_output
74
- @continuous_output = ForemanTasksCore::ContinuousOutput.new
75
- { @suspended_action => Runner::Update.new(new_data, @exit_status) }
76
- end
77
-
78
- def initialize_continuous_outputs
79
- @continuous_output = ::ForemanTasksCore::ContinuousOutput.new
80
- end
81
- end
82
- end
83
- end
@@ -1,48 +0,0 @@
1
- module ForemanTasksCore
2
- module Runner
3
- class Parent < Base
4
- # targets = { hostname => { :execution_plan_id => "...", :run_step_id => id,
5
- # :input => { ... } }
6
- def initialize(targets = {}, suspended_action: nil)
7
- @targets = targets
8
- super suspended_action: suspended_action
9
- end
10
-
11
- def generate_updates
12
- @outputs.reduce({}) do |acc, (key, value)|
13
- if value.empty? && @exit_status.nil?
14
- acc
15
- else
16
- @outputs[key] = ForemanTasksCore::ContinuousOutput.new
17
- key = host_action(key) unless key == @suspended_action
18
- acc.merge(key => Runner::Update.new(value, @exit_status))
19
- end
20
- end
21
- end
22
-
23
- def initialize_continuous_outputs
24
- @outputs = ([@suspended_action] + @targets.keys).reduce({}) do |acc, target|
25
- acc.merge(target => ForemanTasksCore::ContinuousOutput.new)
26
- end
27
- end
28
-
29
- def host_action(hostname)
30
- options = @targets[hostname].slice('execution_plan_id', 'run_step_id')
31
- .merge(:world => ForemanTasksCore.dynflow_world)
32
- Dynflow::Action::Suspended.new OpenStruct.new(options)
33
- end
34
-
35
- def broadcast_data(data, type)
36
- @outputs.each { |_k, output| output.add_output(data, type) }
37
- end
38
-
39
- def publish_data_for(hostname, data, type)
40
- @outputs[hostname].add_output(data, type)
41
- end
42
-
43
- def dispatch_exception(context, exception)
44
- @outputs.values.each { |output| output.add_exception(context, exception) }
45
- end
46
- end
47
- end
48
- 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,8 +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'
@@ -1,42 +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
- private
17
-
18
- def format_result(result)
19
- if result.triggered?
20
- { :result => 'success', :task_id => result.execution_plan_id }
21
- else
22
- plan = world.persistence.load_execution_plan(result.id)
23
- { :result => 'error', :errors => plan.errors }
24
- end
25
- end
26
-
27
- def action_class(input)
28
- ::Dynflow::Utils.constantize(input['action_class'])
29
- end
30
-
31
- def with_callback(input)
32
- input.merge(:callback_host => callback)
33
- end
34
-
35
- def trigger(parent, klass, *input)
36
- world.trigger do
37
- world.plan_with_options(caller_action: parent, action_class: klass, args: input)
38
- end
39
- end
40
- end
41
- end
42
- end
@@ -1,37 +0,0 @@
1
- module ForemanTasksCore
2
- module TaskLauncher
3
- class ParentAction < ::Dynflow::Action
4
- include Dynflow::Action::WithSubPlans
5
- include Dynflow::Action::WithPollingSubPlans
6
-
7
- # { task_id => { :action_class => Klass, :input => input } }
8
- def plan(launcher, input_hash)
9
- launcher.launch_children(self, input_hash)
10
- plan_self
11
- end
12
-
13
- def initiate
14
- ping suspended_action
15
- wait_for_sub_plans sub_plans
16
- end
17
-
18
- def rescue_strategy
19
- Dynflow::Action::Rescue::Fail
20
- end
21
- end
22
-
23
- class Batch < Abstract
24
- def launch!(input)
25
- trigger(nil, ParentAction, self, input)
26
- end
27
-
28
- def launch_children(parent, input_hash)
29
- input_hash.each do |task_id, input|
30
- launcher = Single.new(world, callback, :parent => parent)
31
- launcher.launch!(input)
32
- results[task_id] = launcher.results
33
- end
34
- end
35
- end
36
- end
37
- end
@@ -1,14 +0,0 @@
1
- module ForemanTasksCore
2
- module TaskLauncher
3
- class Single < Abstract
4
- # { :action_class => "MyActionClass", :action_input => {} }
5
- def launch!(input)
6
- triggered = trigger(options[:parent],
7
- action_class(input),
8
- with_callback(input.fetch('action_input', {})))
9
- @results = format_result(triggered)
10
- triggered
11
- end
12
- end
13
- end
14
- end
@@ -1,45 +0,0 @@
1
- module ForemanTasksCore
2
- class Ticker < ::Dynflow::Actor
3
- attr_reader :clock
4
-
5
- def initialize(clock, logger, refresh_interval)
6
- @clock = clock
7
- @logger = logger
8
- @events = []
9
- @refresh_interval = refresh_interval
10
- plan_next_tick
11
- end
12
-
13
- def tick
14
- @logger.debug("Ticker ticking for #{@events.size} events")
15
- @events.each do |(target, args)|
16
- pass_event(target, args)
17
- end
18
- @events = []
19
- ensure
20
- @planned = false
21
- plan_next_tick
22
- end
23
-
24
- def add_event(target, args)
25
- @events << [target, args]
26
- plan_next_tick
27
- end
28
-
29
- private
30
-
31
- def pass_event(target, args)
32
- target.tell(args)
33
- rescue => e
34
- @logger.error("Failed passing event to #{target} with #{args}")
35
- @logger.error(e)
36
- end
37
-
38
- def plan_next_tick
39
- if !@planned && !@events.empty?
40
- @clock.ping(reference, Time.now.getlocal + @refresh_interval, :tick)
41
- @planned = true
42
- end
43
- end
44
- end
45
- end