smart_proxy_dynflow 0.6.1 → 0.8.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: 3f3493966a6909232bc66c339f43bbf92a5fe816a12fa2ecd3f2a619b6ada735
4
- data.tar.gz: e5c8ef258353c72e1d453c1dce1f97e12e25c2e12a59b2fdb3a317067c179ab9
3
+ metadata.gz: 8852c64e45de97691310f1c9fd17bef228a9ea87db2085377cee1cd404546752
4
+ data.tar.gz: 54afa19849d245f44f8d5e03c69d82e30015ff95978b266cad0db3919eb82195
5
5
  SHA512:
6
- metadata.gz: 25b9acceec9b4ba73f1e93cb8f9e0ad24fdb04d1f404304fa417c3ad84d81904f9cca562350031fbbce964bee076d5451ccde9c4a132546cc3fb7f2c9621737b
7
- data.tar.gz: 6f02401a1eb65179772b0592856acb125cb53197e067d6fcabdcad98581d221052390b0bdb42fa39f1dae1e14de4d8be5b9c6aab28272423756ffeb1f72a82ac
6
+ metadata.gz: 4a1799f87c74aa10807ea642d217686f208fe2de5444b4a87c540a8685e419d4c88b272e03faa83a9756fa7ef4e996f3cd8195f730ae43e97800e4666fecae59
7
+ data.tar.gz: ab1dd31d7555502d566803ba4e87965e2431bb6d32c67cc6e0e39f7ac1c6c5369e22b1b6c7f6e3abb8cfdcccf6b4432029ba10b277171931f6a5fa05ed792d8d
@@ -3,15 +3,16 @@ module Proxy::Dynflow::Action
3
3
  include Dynflow::Action::WithSubPlans
4
4
  include Dynflow::Action::WithPollingSubPlans
5
5
 
6
- # { task_id => { :action_class => Klass, :input => input } }
6
+ # { execution_plan_uuid => { :action_class => Klass, :input => input } }
7
7
  def plan(launcher, input_hash)
8
- launcher.launch_children(self, input_hash)
9
- plan_self
8
+ plan_self :input_hash => input_hash,
9
+ :launcher => launcher.to_hash
10
10
  end
11
11
 
12
- def initiate
13
- ping suspended_action
14
- wait_for_sub_plans sub_plans
12
+ def create_sub_plans
13
+ Proxy::Dynflow::TaskLauncher::Abstract
14
+ .new_from_hash(world, input[:launcher])
15
+ .launch_children(self, input[:input_hash])
15
16
  end
16
17
 
17
18
  def rescue_strategy
@@ -1,7 +1,12 @@
1
1
  module Proxy::Dynflow::Action
2
2
  class BatchCallback < ::Dynflow::Action
3
3
  def plan(input_hash, results)
4
- plan_self :targets => input_hash, :results => results
4
+ # In input_hash there are complete inputs for all the actions for which this is reporting
5
+ # Trim it down to only the bare minimum we actually need
6
+ callbacks = input_hash.reduce({}) do |acc, (key, value)|
7
+ acc.merge(key => value['action_input']['callback'])
8
+ end
9
+ plan_self :targets => callbacks, :results => results
5
10
  end
6
11
 
7
12
  def run
@@ -2,13 +2,13 @@ require 'smart_proxy_dynflow/action/runner'
2
2
 
3
3
  module Proxy::Dynflow::Action
4
4
  class BatchRunner < ::Proxy::Dynflow::Action::Runner
5
- def plan(launcher, input)
6
- plan_self :targets => launcher.runner_input(input), :operation => launcher.operation
5
+ def plan(launcher, input, runner_id)
6
+ plan_self :targets => launcher.runner_input(input), :operation => launcher.operation, :runner_id => runner_id
7
7
  end
8
8
 
9
9
  def initiate_runner
10
10
  launcher = Proxy::Dynflow::TaskLauncherRegistry.fetch(input[:operation])
11
- launcher.runner_class.new(input[:targets], suspended_action: suspended_action)
11
+ launcher.runner_class.new(input[:targets], suspended_action: suspended_action, id: input[:runner_id])
12
12
  end
13
13
  end
14
14
  end
@@ -0,0 +1,16 @@
1
+ module Proxy::Dynflow::Action
2
+ module WithExternalPolling
3
+ Poll = Algebrick.atom
4
+
5
+ def run(event = nil)
6
+ if event.is_a?(Poll)
7
+ poll
8
+ suspend
9
+ else
10
+ super
11
+ end
12
+ end
13
+
14
+ def poll; end
15
+ end
16
+ end
@@ -2,6 +2,7 @@ module Proxy::Dynflow::Action
2
2
  class OutputCollector < ::Proxy::Dynflow::Action::Runner
3
3
  def init_run
4
4
  output[:result] = []
5
+ output[:runner_id] = input[:runner_id]
5
6
  suspend
6
7
  end
7
8
  end
@@ -1,8 +1,10 @@
1
1
  require 'smart_proxy_dynflow/action/shareable'
2
+ require 'smart_proxy_dynflow/action/external_polling'
2
3
  module Proxy::Dynflow
3
4
  module Action
4
5
  class Runner < Shareable
5
6
  include ::Dynflow::Action::Cancellable
7
+ include ::Proxy::Dynflow::Action::WithExternalPolling
6
8
 
7
9
  def run(event = nil)
8
10
  case event
@@ -14,6 +16,9 @@ module Proxy::Dynflow
14
16
  process_external_event(event)
15
17
  when ::Dynflow::Action::Cancellable::Cancel
16
18
  kill_run
19
+ when ::Proxy::Dynflow::Action::WithExternalPolling::Poll
20
+ poll
21
+ suspend
17
22
  else
18
23
  raise "Unexpected event #{event.inspect}"
19
24
  end
@@ -69,12 +74,16 @@ module Proxy::Dynflow
69
74
  end
70
75
  end
71
76
 
77
+ def poll
78
+ runner_dispatcher.refresh_output(output[:runner_id])
79
+ end
80
+
72
81
  def failed_run?
73
82
  output[:exit_status] != 0
74
83
  end
75
84
 
76
85
  def output_result
77
- stored_output_chunks.map { |c| c[:chunk] }.reduce([], &:concat)
86
+ (stored_output_chunks + (@pending_output_chunks || [])).map { |c| c[:chunk] }.reduce([], &:concat)
78
87
  end
79
88
  end
80
89
  end
@@ -1,20 +1,8 @@
1
1
  module Proxy::Dynflow::Action
2
2
  class SingleRunnerBatch < Batch
3
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
4
+ results = super
5
+ plan_action BatchCallback, input_hash, results.output[:results]
18
6
  end
19
7
 
20
8
  def check_for_errors!(optional = true)
@@ -1,3 +1,5 @@
1
+ require 'fileutils'
2
+
1
3
  module Proxy::Dynflow
2
4
  class Core
3
5
  attr_accessor :world, :accepted_cert_serial
@@ -29,6 +31,7 @@ module Proxy::Dynflow
29
31
  Log.instance.warn "Could not open DB for dynflow at '#{db_file}', " \
30
32
  "will keep data in memory. Restart will drop all dynflow data."
31
33
  else
34
+ FileUtils.mkdir_p(File.dirname(db_file))
32
35
  db_conn_string += "/#{db_file}"
33
36
  end
34
37
 
@@ -36,9 +36,8 @@ module Proxy
36
36
  def task_status(task_id)
37
37
  ep = world.persistence.load_execution_plan(task_id)
38
38
  actions = ep.actions.map do |action|
39
- hash = action.to_hash
40
- hash[:output][:result] = action.output_result if action.is_a?(Proxy::Dynflow::Action::Runner)
41
- hash
39
+ refresh_output(ep, action)
40
+ expand_output(action)
42
41
  end
43
42
  ep.to_hash.merge(:actions => actions)
44
43
  rescue KeyError => _e
@@ -58,6 +57,18 @@ module Proxy
58
57
  params['step_id'].to_i,
59
58
  ::Proxy::Dynflow::Runner::ExternalEvent.new(params))
60
59
  end
60
+
61
+ def refresh_output(execution_plan, action)
62
+ if action.is_a?(Proxy::Dynflow::Action::WithExternalPolling) && %i[running suspended].include?(action.run_step&.state)
63
+ world.event(execution_plan.id, action.run_step_id, Proxy::Dynflow::Action::WithExternalPolling::Poll)
64
+ end
65
+ end
66
+
67
+ def expand_output(action)
68
+ hash = action.to_hash
69
+ hash[:output][:result] = action.output_result if action.is_a?(Proxy::Dynflow::Action::Runner)
70
+ hash
71
+ end
61
72
  end
62
73
  end
63
74
  end
@@ -0,0 +1,115 @@
1
+ module Proxy
2
+ module Dynflow
3
+ # A buffer around an IO object providing buffering and convenience methods
4
+ # for non-blocking reads and writes.
5
+ #
6
+ # @note Using a single IOBuffer with a single IO for both reads and writes might not be a good idea. If you need to use a single IO for both reads and writes, wrap it in two separate IOBuffers.
7
+ #
8
+ # @attr_accessor [IO] io The IO which the buffer wraps
9
+ # @attr_reader [String] buffer The buffer where the data read from the underlying IO is buffered
10
+ class IOBuffer
11
+ attr_accessor :io
12
+ attr_reader :buffer
13
+
14
+ # @param [IO] io The IO object to be buffered
15
+ def initialize(io)
16
+ @buffer = ''
17
+ @io = io
18
+ end
19
+
20
+ # Sets a callback to be executed each time data is read from the
21
+ # underlying IO.
22
+ #
23
+ # @note Note that if the callback is provided, the buffer will store the return value of the callback instead of the raw data.
24
+ #
25
+ # @yieldparam [String] data read from the underlying IO
26
+ # @yieldreturn [String] data to be buffered
27
+ # @return [void]
28
+ def on_data(&block)
29
+ @callback = block
30
+ end
31
+
32
+ # Exposes the underlying IO so that the buffer itself can be used in IO.select calls.
33
+ #
34
+ # @return [IO] the underlying IO
35
+ def to_io
36
+ @io
37
+ end
38
+
39
+ # Exposes the contents of the buffer as a String
40
+ #
41
+ # @return [String] the buffered data
42
+ def to_s
43
+ @buffer
44
+ end
45
+
46
+ # Checks whether the buffer is empty
47
+ #
48
+ # @return [true, false] whether the buffer is empty
49
+ def empty?
50
+ @buffer.empty?
51
+ end
52
+
53
+ # Checks whether the underlying IO is empty
54
+ #
55
+ # @return [true, false] whether the underlying IO is empty
56
+ def closed?
57
+ @io.closed?
58
+ end
59
+
60
+ # Closes the underlying IO. Does nothing if the IO is already closed.
61
+ #
62
+ # @return [void]
63
+ def close
64
+ @io.close unless @io.closed?
65
+ end
66
+
67
+ # Reads all the data that is currently waiting in the IO and stores it. If
68
+ # EOFError is encountered during the read, the underlying IO is closed.
69
+ #
70
+ # @return [void]
71
+ def read_available!
72
+ data = ''
73
+ loop { data += @io.read_nonblock(4096) }
74
+ rescue IO::WaitReadable # rubocop:disable Lint/HandleExceptions
75
+ rescue EOFError
76
+ close
77
+ ensure
78
+ @buffer += with_callback(data) unless data.empty?
79
+ end
80
+
81
+ # Writes all the data into the IO that can be written without blocking. It
82
+ # is a no-op if there are no data to be written. If an EOFError is
83
+ # encountered during the write, the underlying IO is closed.
84
+ #
85
+ # @return [void]
86
+ def write_available!
87
+ until @buffer.empty?
88
+ n = @io.write_nonblock(@buffer)
89
+ @buffer = @buffer[n..-1]
90
+ end
91
+ rescue IO::WaitWritable # rubocop:disable Lint/HandleExceptions
92
+ rescue EOFError
93
+ close
94
+ end
95
+
96
+ # Adds data to the buffer. If the buffer is used for writing, then this
97
+ # should be the preferred method of queueing the data to be written.
98
+ #
99
+ # @return [void]
100
+ def add_data(data)
101
+ @buffer += data
102
+ end
103
+
104
+ private
105
+
106
+ def with_callback(data)
107
+ if @callback
108
+ @callback.call(data)
109
+ else
110
+ data
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -11,9 +11,11 @@ module Proxy::Dynflow
11
11
  settings_file "dynflow.yml"
12
12
  requires :foreman_proxy, ">= 1.16.0"
13
13
  default_settings :console_auth => true,
14
- :execution_plan_cleaner_age => 60 * 60 * 24
14
+ :execution_plan_cleaner_age => 60 * 30
15
15
  plugin :dynflow, Proxy::Dynflow::VERSION
16
16
 
17
+ capability(proc { self.available_operations })
18
+
17
19
  after_activation do
18
20
  require 'smart_proxy_dynflow/settings_loader'
19
21
  require 'smart_proxy_dynflow/otp_manager'
@@ -25,5 +27,9 @@ module Proxy::Dynflow
25
27
 
26
28
  Proxy::Dynflow::Core.ensure_initialized
27
29
  end
30
+
31
+ def self.available_operations
32
+ TaskLauncherRegistry.operations
33
+ end
28
34
  end
29
35
  end
@@ -0,0 +1,166 @@
1
+ require 'smart_proxy_dynflow/io_buffer'
2
+
3
+ module Proxy
4
+ module Dynflow
5
+ # An abstraction for managing local processes.
6
+ #
7
+ # It can be used to:
8
+ # - spawn a local process
9
+ # - track its lifecycle
10
+ # - communicate with it through its standard input, output and error
11
+ # - step through the execution one event at a time or start the child process and wait until it finishes
12
+ #
13
+ # @example Run date command and collect its output
14
+ # pm = ProcessManager.new('date')
15
+ # pm.run!
16
+ # pm.status #=> 0
17
+ # pm.stdout.to_s.chomp #=> "Thu Feb 3 04:27:42 PM CET 2022"
18
+ #
19
+ # @example Run a shell loop, outputting all the lines it generates
20
+ # pm = ProcessManager.new(['/bin/sh', '-c', 'for i in 1 2 3; do echo $i; sleep 1; done'])
21
+ # pm.on_stdout { |data| puts data; '' }
22
+ # pm.run!
23
+ # #=> 1
24
+ # #=> 2
25
+ # #=> 3
26
+ #
27
+ # @example Run bc (calculator) interactively and count down from 10 to 0
28
+ # pm = ProcessManager.new('bc')
29
+ # pm.on_stdout do |data|
30
+ # if data.match?(/^\d+/)
31
+ # n = data.to_i
32
+ # if n.zero?
33
+ # pm.stdin.to_io.close
34
+ # else
35
+ # pm.stdin.add_data("#{n} - 1\n")
36
+ # end
37
+ # end
38
+ # data
39
+ # end
40
+ # pm.stdin.add_data("10\n")
41
+ # pm.run!
42
+ # pm.stdout.to_s.lines #=. ["10\n", "9\n", "8\n", "7\n", "6\n", "5\n", "4\n", "3\n", "2\n", "1\n", "0\n"]
43
+ #
44
+ # @attr_reader [Proxy::Dynflow::IOBuffer] stdin IOBuffer buffering writes to child process' standard input
45
+ # @attr_reader [Proxy::Dynflow::IOBuffer] stdout IOBuffer buffering reads from child process' standard output
46
+ # @attr_reader [Proxy::Dynflow::IOBuffer] stderr IOBuffer buffering reads from child process' standard error
47
+ # @attr_reader [nil, Integer] pid Process id of the child process, nil if the process was not started yet, -1 if the process could not be started
48
+ # @attr_reader [nil, Integer] status Exit status of the child process. nil if the child process has not finished yet, 255 if the process could not be started
49
+ class ProcessManager
50
+ attr_reader :stdin, :stdout, :stderr, :pid, :status
51
+
52
+ # @param [String, [String], [Hash, String]] command A command to run in one of the forms accepted by Kernel.spawn
53
+ def initialize(command)
54
+ @command = command
55
+ @stdin = IOBuffer.new(nil)
56
+ @stdout = IOBuffer.new(nil)
57
+ @stderr = IOBuffer.new(nil)
58
+ end
59
+
60
+ # Starts the process manager and runs it until it finishes
61
+ #
62
+ # @return [ProcessManager] the process manager itself to allow method chaining
63
+ def run!
64
+ start! unless started?
65
+ process until done?
66
+ self
67
+ end
68
+
69
+ # Starts the child process. It creates 3 pipes for communicating with the
70
+ # child process and the forks it. The process manager is considered done
71
+ # if the child process cannot be started.
72
+ #
73
+ # @return [void]
74
+ def start!
75
+ in_read, in_write = IO.pipe
76
+ out_read, out_write = IO.pipe
77
+ err_read, err_write = IO.pipe
78
+
79
+ @pid = spawn(*@command, :in => in_read, :out => out_write, :err => err_write)
80
+ [in_read, out_write, err_write].each(&:close)
81
+
82
+ @stdin.io = in_write
83
+ @stdout.io = out_read
84
+ @stderr.io = err_read
85
+ rescue Errno::ENOENT => e
86
+ [in_read, in_write, out_read, out_write, err_read, err_write].each(&:close)
87
+ @pid = -1
88
+ @status = 255
89
+ @stderr.add_data(e.message)
90
+ end
91
+
92
+ # Determines whether the process manager already forked off its child process
93
+ #
94
+ # @return [true, false] whether the process manager already forked off its child process
95
+ def started?
96
+ !pid.nil?
97
+ end
98
+
99
+ # Determines whether the child process of the process manager already finished
100
+ #
101
+ # @return [true, false] whether the child process of the process manager already finished
102
+ def done?
103
+ started? && !status.nil?
104
+ end
105
+
106
+ # Runs a single iteration of the manager's processing loop. It waits until either:
107
+ # - data is available in pipes connected to the child process' standard output or error
108
+ # - there is pending data to be written and the pipe connected to the child process' standard input is writable
109
+ # - a timeout is reached
110
+ #
111
+ # After the wait, all pending data is read and written.
112
+ #
113
+ # If all the pipes connected to the child process are closed, it marks the
114
+ # execution as complete and performs cleanup.
115
+ #
116
+ # @param timeout [nil, Numeric] controls how long this call should wait for data to become available. Waits indefinitely if nil.
117
+ # @return [void]
118
+ def process(timeout: nil)
119
+ raise 'Cannot process until the manager is started' unless started?
120
+ writers = [@stdin].reject { |buf| buf.empty? || buf.closed? }
121
+ readers = [@stdout, @stderr].reject(&:closed?)
122
+
123
+ if readers.empty? && writers.empty?
124
+ finish
125
+ return
126
+ end
127
+
128
+ ready_readers, ready_writers = IO.select(readers, writers, nil, timeout)
129
+ (ready_readers || []).each(&:read_available!)
130
+ (ready_writers || []).each(&:write_available!)
131
+ end
132
+
133
+ # Sets block to be executed each time data is read from child process' standard output
134
+ #
135
+ # @return [void]
136
+ def on_stdout(&block)
137
+ @stdout.on_data(&block)
138
+ end
139
+
140
+ # Sets block to be executed each time data is read from child process' standard error
141
+ #
142
+ # @return [void]
143
+ def on_stderr(&block)
144
+ @stderr.on_data(&block)
145
+ end
146
+
147
+ # Makes the process manager close all the pipes it may have opened to communicate with the child process
148
+ #
149
+ # @return [void]
150
+ def close
151
+ [@stdin, @stdout, @stderr].each(&:close)
152
+ end
153
+
154
+ private
155
+
156
+ # Makes the process manager finish its run, closing opened FDs and reaping the child process
157
+ #
158
+ # @return [void]
159
+ def finish
160
+ close
161
+ _pid, status = Process.wait2(@pid)
162
+ @status = status.exitstatus
163
+ end
164
+ end
165
+ end
166
+ end
@@ -6,9 +6,9 @@ module Proxy::Dynflow
6
6
  attr_reader :id
7
7
  attr_writer :logger
8
8
 
9
- def initialize(*_args, suspended_action: nil)
9
+ def initialize(*_args, suspended_action: nil, id: nil)
10
10
  @suspended_action = suspended_action
11
- @id = SecureRandom.uuid
11
+ @id = id || SecureRandom.uuid
12
12
  initialize_continuous_outputs
13
13
  end
14
14
 
@@ -93,6 +93,12 @@ module Proxy::Dynflow
93
93
  def initialize_continuous_outputs
94
94
  @continuous_output = ::Proxy::Dynflow::ContinuousOutput.new
95
95
  end
96
+
97
+ def run_refresh_output
98
+ logger.debug('refreshing runner on demand')
99
+ refresh
100
+ generate_updates
101
+ end
96
102
  end
97
103
  end
98
104
  end
@@ -43,6 +43,11 @@ module Proxy::Dynflow
43
43
  plan_next_refresh
44
44
  end
45
45
 
46
+ def refresh_output
47
+ @logger.debug("refresh output #{@runner.id}")
48
+ dispatch_updates(@runner.run_refresh_output)
49
+ end
50
+
46
51
  def dispatch_updates(updates)
47
52
  updates.each { |receiver, update| (receiver || @suspended_action) << update }
48
53
 
@@ -157,6 +162,12 @@ module Proxy::Dynflow
157
162
  end
158
163
  end
159
164
 
165
+ def refresh_output(runner_id)
166
+ synchronize do
167
+ @runner_actors[runner_id]&.tell([:refresh_output])
168
+ end
169
+ end
170
+
160
171
  def handle_command_exception(*args)
161
172
  synchronize { _handle_command_exception(*args) }
162
173
  end
@@ -3,10 +3,10 @@ module Proxy::Dynflow
3
3
  class Parent < Base
4
4
  # targets = { identifier => { :execution_plan_id => "...", :run_step_id => id,
5
5
  # :input => { ... } }
6
- def initialize(targets = {}, suspended_action: nil)
6
+ def initialize(targets = {}, suspended_action: nil, id: nil)
7
7
  @targets = targets
8
8
  @exit_statuses = {}
9
- super suspended_action: suspended_action
9
+ super suspended_action: suspended_action, id: id
10
10
  end
11
11
 
12
12
  def generate_updates
@@ -0,0 +1,36 @@
1
+ require 'smart_proxy_dynflow/process_manager'
2
+
3
+ module Proxy::Dynflow
4
+ module Runner
5
+ module ProcessManagerCommand
6
+ def initialize_command(*command)
7
+ @process_manager = ProcessManager.new(command)
8
+ set_process_manager_callbacks(@process_manager)
9
+ @process_manager.start!
10
+ if @process_manager.done? && @process_manager.status == 255
11
+ publish_exception("Error running command '#{command.join(' ')}'", @process_manager.stderr.to_s)
12
+ end
13
+ end
14
+
15
+ def set_process_manager_callbacks(pm)
16
+ pm.on_stdout do |data|
17
+ publish_data(data, 'stdout')
18
+ ''
19
+ end
20
+ pm.on_stderr do |data|
21
+ publish_data(data, 'stderr')
22
+ ''
23
+ end
24
+ end
25
+
26
+ def refresh
27
+ @process_manager.process(timeout: 0.1)
28
+ publish_exit_status(@process_manager.status) if @process_manager.done?
29
+ end
30
+
31
+ def close
32
+ @process_manager&.close
33
+ end
34
+ end
35
+ end
36
+ end
@@ -15,6 +15,14 @@ module Proxy::Dynflow
15
15
 
16
16
  def self.input_format; end
17
17
 
18
+ def to_hash
19
+ { :class => self.class.to_s, :callback => callback, :options => options }
20
+ end
21
+
22
+ def self.new_from_hash(world, hash)
23
+ ::Dynflow::Utils.constantize(hash[:class]).new(world, hash[:callback], hash[:options])
24
+ end
25
+
18
26
  private
19
27
 
20
28
  def format_result(result)
@@ -34,9 +42,9 @@ module Proxy::Dynflow
34
42
  input.merge(:callback_host => callback)
35
43
  end
36
44
 
37
- def trigger(parent, klass, *input)
45
+ def trigger(parent, klass, *input, id: nil)
38
46
  world.trigger do
39
- world.plan_with_options(caller_action: parent, action_class: klass, args: input)
47
+ world.plan_with_options(caller_action: parent, action_class: klass, args: input, id: id)
40
48
  end
41
49
  end
42
50
  end
@@ -2,36 +2,37 @@ module Proxy::Dynflow
2
2
  module TaskLauncher
3
3
  class Batch < Abstract
4
4
  def launch!(input)
5
- trigger(nil, Proxy::Dynflow::Action::Batch, self, input)
5
+ plan = trigger(nil, action_class, self, input)
6
+ results[:parent] = format_result(plan)
6
7
  end
7
8
 
8
9
  def launch_children(parent, input_hash)
9
- input_hash.each do |task_id, input|
10
+ input_hash.map do |task_id, input|
10
11
  launcher = child_launcher(parent)
11
- launcher.launch!(transform_input(input))
12
+ triggered = launcher.launch!(transform_input(input), id: task_id)
12
13
  results[task_id] = launcher.results
14
+ triggered
13
15
  end
14
16
  end
15
17
 
16
18
  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
19
+ input_hash
23
20
  end
24
21
 
25
- private
26
-
27
22
  def child_launcher(parent)
28
23
  Single.new(world, callback, :parent => parent)
29
24
  end
30
25
 
26
+ private
27
+
31
28
  # Identity by default
32
29
  def transform_input(input)
33
30
  input
34
31
  end
32
+
33
+ def action_class
34
+ Proxy::Dynflow::Action::Batch
35
+ end
35
36
  end
36
37
  end
37
38
  end
@@ -3,17 +3,22 @@ require 'smart_proxy_dynflow/runner'
3
3
  module Proxy::Dynflow
4
4
  module TaskLauncher
5
5
  class AbstractGroup < Batch
6
+ def initialize(*args)
7
+ super
8
+ @runner_id = SecureRandom.uuid
9
+ end
10
+
6
11
  def self.runner_class
7
12
  raise NotImplementedError
8
13
  end
9
14
 
10
- def launch!(input)
11
- trigger(nil, Action::SingleRunnerBatch, self, input)
15
+ def action_class
16
+ Action::SingleRunnerBatch
12
17
  end
13
18
 
14
19
  def launch_children(parent, input_hash)
15
20
  super(parent, input_hash)
16
- trigger(parent, Action::BatchRunner, self, input_hash)
21
+ trigger(parent, Action::BatchRunner, self, input_hash, @runner_id)
17
22
  end
18
23
 
19
24
  def operation
@@ -36,7 +41,8 @@ module Proxy::Dynflow
36
41
  end
37
42
 
38
43
  def transform_input(input)
39
- wipe_callback(input)
44
+ tmp = wipe_callback(input)
45
+ input.merge('action_input' => tmp['action_input'].merge(:runner_id => @runner_id))
40
46
  end
41
47
 
42
48
  def wipe_callback(input)
@@ -5,10 +5,11 @@ module Proxy::Dynflow
5
5
  { :action_class => "MyActionClass", :action_input => {} }
6
6
  end
7
7
 
8
- def launch!(input)
8
+ def launch!(input, id: nil)
9
9
  triggered = trigger(options[:parent],
10
10
  action_class(input),
11
- with_callback(input.fetch('action_input', {})))
11
+ with_callback(input.fetch('action_input', {})),
12
+ id: id)
12
13
  @results = format_result(triggered)
13
14
  triggered
14
15
  end
@@ -1,5 +1,5 @@
1
1
  module Proxy
2
2
  module Dynflow
3
- VERSION = '0.6.1'.freeze
3
+ VERSION = '0.8.0'.freeze
4
4
  end
5
5
  end
@@ -1,10 +1,10 @@
1
1
  ---
2
2
  :enabled: true
3
- :database: /var/lib/foreman-proxy/dynflow/dynflow.sqlite
3
+ :database:
4
4
 
5
5
  # Require a valid cert to access Dynflow console
6
6
  # :console_auth: true
7
7
 
8
8
  # Maximum age of execution plans to keep before having them cleaned
9
- # by the execution plan cleaner (in seconds), defaults to 24 hours
10
- # :execution_plan_cleaner_age: 86400
9
+ # by the execution plan cleaner (in seconds), defaults to 30 minutes
10
+ # :execution_plan_cleaner_age: 1800
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smart_proxy_dynflow
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.8.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-12-03 00:00:00.000000000 Z
11
+ date: 1980-01-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dynflow
@@ -146,13 +146,12 @@ files:
146
146
  - Gemfile
147
147
  - LICENSE
148
148
  - bundler.d/dynflow.rb
149
- - bundler.d/inspect_anything.rb
150
- - bundler.d/x.local.rb
151
149
  - lib/smart_proxy_dynflow.rb
152
150
  - lib/smart_proxy_dynflow/action.rb
153
151
  - lib/smart_proxy_dynflow/action/batch.rb
154
152
  - lib/smart_proxy_dynflow/action/batch_callback.rb
155
153
  - lib/smart_proxy_dynflow/action/batch_runner.rb
154
+ - lib/smart_proxy_dynflow/action/external_polling.rb
156
155
  - lib/smart_proxy_dynflow/action/output_collector.rb
157
156
  - lib/smart_proxy_dynflow/action/runner.rb
158
157
  - lib/smart_proxy_dynflow/action/shareable.rb
@@ -163,10 +162,12 @@ files:
163
162
  - lib/smart_proxy_dynflow/core.rb
164
163
  - lib/smart_proxy_dynflow/helpers.rb
165
164
  - lib/smart_proxy_dynflow/http_config.ru
165
+ - lib/smart_proxy_dynflow/io_buffer.rb
166
166
  - lib/smart_proxy_dynflow/log.rb
167
167
  - lib/smart_proxy_dynflow/middleware/keep_current_request_id.rb
168
168
  - lib/smart_proxy_dynflow/otp_manager.rb
169
169
  - lib/smart_proxy_dynflow/plugin.rb
170
+ - lib/smart_proxy_dynflow/process_manager.rb
170
171
  - lib/smart_proxy_dynflow/proxy_adapter.rb
171
172
  - lib/smart_proxy_dynflow/runner.rb
172
173
  - lib/smart_proxy_dynflow/runner/base.rb
@@ -174,6 +175,7 @@ files:
174
175
  - lib/smart_proxy_dynflow/runner/command_runner.rb
175
176
  - lib/smart_proxy_dynflow/runner/dispatcher.rb
176
177
  - lib/smart_proxy_dynflow/runner/parent.rb
178
+ - lib/smart_proxy_dynflow/runner/process_manager_command.rb
177
179
  - lib/smart_proxy_dynflow/runner/update.rb
178
180
  - lib/smart_proxy_dynflow/settings.rb
179
181
  - lib/smart_proxy_dynflow/settings_loader.rb
@@ -206,7 +208,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
206
208
  - !ruby/object:Gem::Version
207
209
  version: '0'
208
210
  requirements: []
209
- rubygems_version: 3.1.2
211
+ rubygems_version: 3.2.26
210
212
  signing_key:
211
213
  specification_version: 4
212
214
  summary: Dynflow runtime for Foreman smart proxy
@@ -1 +0,0 @@
1
- # gem 'inspect_anything'
data/bundler.d/x.local.rb DELETED
@@ -1 +0,0 @@
1
- # gem 'dynflow', :path => '../dynflow'