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 +4 -4
- data/lib/smart_proxy_dynflow/action/batch.rb +7 -6
- data/lib/smart_proxy_dynflow/action/batch_callback.rb +6 -1
- data/lib/smart_proxy_dynflow/action/batch_runner.rb +3 -3
- data/lib/smart_proxy_dynflow/action/external_polling.rb +16 -0
- data/lib/smart_proxy_dynflow/action/output_collector.rb +1 -0
- data/lib/smart_proxy_dynflow/action/runner.rb +10 -1
- data/lib/smart_proxy_dynflow/action/single_runner_batch.rb +2 -14
- data/lib/smart_proxy_dynflow/core.rb +3 -0
- data/lib/smart_proxy_dynflow/helpers.rb +14 -3
- data/lib/smart_proxy_dynflow/io_buffer.rb +115 -0
- data/lib/smart_proxy_dynflow/plugin.rb +7 -1
- data/lib/smart_proxy_dynflow/process_manager.rb +166 -0
- data/lib/smart_proxy_dynflow/runner/base.rb +8 -2
- data/lib/smart_proxy_dynflow/runner/dispatcher.rb +11 -0
- data/lib/smart_proxy_dynflow/runner/parent.rb +2 -2
- data/lib/smart_proxy_dynflow/runner/process_manager_command.rb +36 -0
- data/lib/smart_proxy_dynflow/task_launcher/abstract.rb +10 -2
- data/lib/smart_proxy_dynflow/task_launcher/batch.rb +12 -11
- data/lib/smart_proxy_dynflow/task_launcher/group.rb +10 -4
- data/lib/smart_proxy_dynflow/task_launcher/single.rb +3 -2
- data/lib/smart_proxy_dynflow/version.rb +1 -1
- data/settings.d/dynflow.yml.example +3 -3
- metadata +7 -5
- data/bundler.d/inspect_anything.rb +0 -1
- data/bundler.d/x.local.rb +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8852c64e45de97691310f1c9fd17bef228a9ea87db2085377cee1cd404546752
|
4
|
+
data.tar.gz: 54afa19849d245f44f8d5e03c69d82e30015ff95978b266cad0db3919eb82195
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
# {
|
6
|
+
# { execution_plan_uuid => { :action_class => Klass, :input => input } }
|
7
7
|
def plan(launcher, input_hash)
|
8
|
-
|
9
|
-
|
8
|
+
plan_self :input_hash => input_hash,
|
9
|
+
:launcher => launcher.to_hash
|
10
10
|
end
|
11
11
|
|
12
|
-
def
|
13
|
-
|
14
|
-
|
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
|
-
|
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
|
@@ -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
|
-
|
5
|
-
|
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
|
-
|
40
|
-
|
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 *
|
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,
|
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.
|
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
|
-
|
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
|
11
|
-
|
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,10 +1,10 @@
|
|
1
1
|
---
|
2
2
|
:enabled: true
|
3
|
-
:database:
|
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
|
10
|
-
# :execution_plan_cleaner_age:
|
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.
|
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:
|
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.
|
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'
|