foreman-tasks-core 0.3.1 → 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: 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