floe 0.15.0 → 0.15.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -1
- data/lib/floe/cli.rb +31 -5
- data/lib/floe/container_runner/docker.rb +4 -4
- data/lib/floe/container_runner/kubernetes.rb +4 -1
- data/lib/floe/logging.rb +5 -1
- data/lib/floe/runner.rb +0 -2
- data/lib/floe/version.rb +1 -1
- data/lib/floe/workflow/choice_rule/data.rb +32 -37
- data/lib/floe/workflow/context.rb +5 -1
- data/lib/floe/workflow/state.rb +11 -4
- data/lib/floe/workflow/states/child_workflow_mixin.rb +1 -2
- data/lib/floe/workflow/states/retry_catch_mixin.rb +3 -3
- data/lib/floe/workflow/states/task.rb +2 -1
- data/lib/floe/workflow.rb +0 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6f21cf2fcc6a7fa9f7536e0cca328d101a867c389f53f768e4746b6e69f643a5
|
4
|
+
data.tar.gz: f3d82401842f66504afdeaa4b051db01d5fcb9e79b704af368712d68b4950251
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3e73dbdd868ec57b8138a9e4b0b529c0f2c44cdb21d43c0ae29ca9094512f4044cc621a38e4ac217bf6b17e13f1ef3a361cf5917023eab87d30c7b4cc90eccfc
|
7
|
+
data.tar.gz: e174fabde62d6348f8517f7c2d019600b34612f5b8d822eb83c2c5579f5e224596b6edaec383bfe9cce75c3260e50a4a0b744764119cd96e2a973f92b49b7d5a
|
data/CHANGELOG.md
CHANGED
@@ -4,6 +4,11 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|
4
4
|
|
5
5
|
## [Unreleased]
|
6
6
|
|
7
|
+
## [0.15.1] - 2024-11-21
|
8
|
+
### Fixed
|
9
|
+
- Fix Map/Parallel States checking container_runner#status! of finished states ([#296](https://github.com/ManageIQ/floe/pull/296))
|
10
|
+
- Fix child workflow mixin tight loop ([#297](https://github.com/ManageIQ/floe/pull/297))
|
11
|
+
|
7
12
|
## [0.15.0] - 2024-10-28
|
8
13
|
### Added
|
9
14
|
- Add WorkflowBase base class for Workflow ([#279](https://github.com/ManageIQ/floe/pull/279))
|
@@ -267,7 +272,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|
267
272
|
### Added
|
268
273
|
- Initial release
|
269
274
|
|
270
|
-
[Unreleased]: https://github.com/ManageIQ/floe/compare/v0.
|
275
|
+
[Unreleased]: https://github.com/ManageIQ/floe/compare/v0.15.1...HEAD
|
276
|
+
[0.15.1]: https://github.com/ManageIQ/floe/compare/v0.15.0...v0.15.1
|
277
|
+
[0.15.0]: https://github.com/ManageIQ/floe/compare/v0.14.0...v0.15.0
|
271
278
|
[0.14.0]: https://github.com/ManageIQ/floe/compare/v0.13.1...v0.14.0
|
272
279
|
[0.13.1]: https://github.com/ManageIQ/floe/compare/v0.13.0...v0.13.1
|
273
280
|
[0.13.0]: https://github.com/ManageIQ/floe/compare/v0.12.0...v0.13.0
|
data/lib/floe/cli.rb
CHANGED
@@ -1,9 +1,12 @@
|
|
1
|
+
require "floe"
|
2
|
+
require "floe/container_runner"
|
3
|
+
|
1
4
|
module Floe
|
2
5
|
class CLI
|
6
|
+
include Logging
|
7
|
+
|
3
8
|
def initialize
|
4
9
|
require "optimist"
|
5
|
-
require "floe"
|
6
|
-
require "floe/container_runner"
|
7
10
|
require "logger"
|
8
11
|
|
9
12
|
Floe.logger = Logger.new($stdout)
|
@@ -20,12 +23,22 @@ module Floe
|
|
20
23
|
create_workflow(workflow, opts[:context], input, credentials)
|
21
24
|
end
|
22
25
|
|
23
|
-
|
26
|
+
output_streams = create_loggers(workflows, opts[:segment_output])
|
27
|
+
|
28
|
+
logger.info("Checking #{workflows.count} workflows...")
|
29
|
+
ready = Floe::Workflow.wait(workflows, &:run_nonblock)
|
30
|
+
logger.info("Checking #{workflows.count} workflows...Complete - #{ready.count} ready")
|
24
31
|
|
25
32
|
# Display status
|
26
33
|
workflows.each do |workflow|
|
27
|
-
|
28
|
-
|
34
|
+
if workflows.size > 1
|
35
|
+
logger.info("")
|
36
|
+
logger.info("#{workflow.name}#{" (#{workflow.status})" unless workflow.context.success?}")
|
37
|
+
logger.info("===")
|
38
|
+
end
|
39
|
+
|
40
|
+
logger.info(output_streams[workflow].string) if output_streams[workflow]
|
41
|
+
logger.info(workflow.output)
|
29
42
|
end
|
30
43
|
|
31
44
|
workflows.all? { |workflow| workflow.context.success? }
|
@@ -49,6 +62,7 @@ module Floe
|
|
49
62
|
opt :context, "JSON payload of the Context", :type => :string
|
50
63
|
opt :credentials, "JSON payload with Credentials", :type => :string
|
51
64
|
opt :credentials_file, "Path to a file with Credentials", :type => :string
|
65
|
+
opt :segment_output, "Segment output by each worker", :default => false
|
52
66
|
|
53
67
|
Floe::ContainerRunner.cli_options(self)
|
54
68
|
|
@@ -89,5 +103,17 @@ module Floe
|
|
89
103
|
context = Floe::Workflow::Context.new(context_payload, :input => input, :credentials => credentials)
|
90
104
|
Floe::Workflow.load(workflow, context)
|
91
105
|
end
|
106
|
+
|
107
|
+
def create_loggers(workflows, segment_output)
|
108
|
+
if workflows.size == 1 || !segment_output
|
109
|
+
# no extra work necessary
|
110
|
+
{}
|
111
|
+
else
|
112
|
+
workflows.each_with_object({}) do |workflow, h|
|
113
|
+
workflow.context.logger = Logger.new(output = StringIO.new)
|
114
|
+
h[workflow] = output
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
92
118
|
end
|
93
119
|
end
|
@@ -30,7 +30,7 @@ module Floe
|
|
30
30
|
end
|
31
31
|
|
32
32
|
begin
|
33
|
-
runner_context["container_ref"] = run_container(image, env, execution_id, runner_context["secrets_ref"])
|
33
|
+
runner_context["container_ref"] = run_container(image, env, execution_id, runner_context["secrets_ref"], context.logger)
|
34
34
|
runner_context
|
35
35
|
rescue AwesomeSpawn::CommandResultError => err
|
36
36
|
cleanup(runner_context)
|
@@ -123,7 +123,7 @@ module Floe
|
|
123
123
|
|
124
124
|
attr_reader :network
|
125
125
|
|
126
|
-
def run_container(image, env, execution_id, secrets_file)
|
126
|
+
def run_container(image, env, execution_id, secrets_file, logger)
|
127
127
|
params = run_container_params(image, env, execution_id, secrets_file)
|
128
128
|
|
129
129
|
logger.debug("Running #{AwesomeSpawn.build_command_line(self.class::DOCKER_COMMAND, params)}")
|
@@ -182,8 +182,8 @@ module Floe
|
|
182
182
|
|
183
183
|
def inspect_container(container_id)
|
184
184
|
JSON.parse(docker!("inspect", container_id).output).first
|
185
|
-
rescue
|
186
|
-
|
185
|
+
rescue AwesomeSpawn::CommandResultError => err
|
186
|
+
raise Floe::ExecutionError.new("Failed to get status for container #{container_id}: #{err}")
|
187
187
|
end
|
188
188
|
|
189
189
|
def delete_container(container_id)
|
@@ -155,6 +155,8 @@ module Floe
|
|
155
155
|
|
156
156
|
def pod_info(pod_name)
|
157
157
|
kubeclient.get_pod(pod_name, namespace)
|
158
|
+
rescue Kubeclient::HttpError => err
|
159
|
+
raise Floe::ExecutionError.new("Failed to get status for pod #{namespace}/#{pod_name}: #{err}")
|
158
160
|
end
|
159
161
|
|
160
162
|
def pod_running?(context)
|
@@ -285,7 +287,8 @@ module Floe
|
|
285
287
|
code = notice.object&.code
|
286
288
|
reason = notice.object&.reason
|
287
289
|
|
288
|
-
|
290
|
+
# This feels like a global concern and not an end user's concern
|
291
|
+
Floe.logger.warn("Received [#{code} #{reason}], [#{message}]")
|
289
292
|
|
290
293
|
true
|
291
294
|
end
|
data/lib/floe/logging.rb
CHANGED
data/lib/floe/runner.rb
CHANGED
data/lib/floe/version.rb
CHANGED
@@ -11,7 +11,7 @@ module Floe
|
|
11
11
|
# e.g.: (String)(LessThan)(Path), (Numeric)(GreaterThanEquals)()
|
12
12
|
OPERATION = /^(#{(TYPES - %w[Null Present]).join("|")})(#{COMPARES.join("|")})(Path)?$/.freeze
|
13
13
|
|
14
|
-
attr_reader :variable, :compare_key, :type, :compare_predicate, :path
|
14
|
+
attr_reader :variable, :compare_key, :operation, :type, :compare_predicate, :path
|
15
15
|
|
16
16
|
def initialize(_workflow, _name, payload)
|
17
17
|
super
|
@@ -25,39 +25,7 @@ module Floe
|
|
25
25
|
|
26
26
|
lhs = variable_value(context, input)
|
27
27
|
rhs = compare_value(context, input)
|
28
|
-
|
29
|
-
case compare_key
|
30
|
-
when "IsNull" then is_null?(lhs, rhs)
|
31
|
-
when "IsNumeric" then is_numeric?(lhs, rhs)
|
32
|
-
when "IsString" then is_string?(lhs, rhs)
|
33
|
-
when "IsBoolean" then is_boolean?(lhs, rhs)
|
34
|
-
when "IsTimestamp" then is_timestamp?(lhs, rhs)
|
35
|
-
when "StringEquals", "StringEqualsPath",
|
36
|
-
"NumericEquals", "NumericEqualsPath",
|
37
|
-
"BooleanEquals", "BooleanEqualsPath",
|
38
|
-
"TimestampEquals", "TimestampEqualsPath"
|
39
|
-
lhs == rhs
|
40
|
-
when "StringLessThan", "StringLessThanPath",
|
41
|
-
"NumericLessThan", "NumericLessThanPath",
|
42
|
-
"TimestampLessThan", "TimestampLessThanPath"
|
43
|
-
lhs < rhs
|
44
|
-
when "StringGreaterThan", "StringGreaterThanPath",
|
45
|
-
"NumericGreaterThan", "NumericGreaterThanPath",
|
46
|
-
"TimestampGreaterThan", "TimestampGreaterThanPath"
|
47
|
-
lhs > rhs
|
48
|
-
when "StringLessThanEquals", "StringLessThanEqualsPath",
|
49
|
-
"NumericLessThanEquals", "NumericLessThanEqualsPath",
|
50
|
-
"TimestampLessThanEquals", "TimestampLessThanEqualsPath"
|
51
|
-
lhs <= rhs
|
52
|
-
when "StringGreaterThanEquals", "StringGreaterThanEqualsPath",
|
53
|
-
"NumericGreaterThanEquals", "NumericGreaterThanEqualsPath",
|
54
|
-
"TimestampGreaterThanEquals", "TimestampGreaterThanEqualsPath"
|
55
|
-
lhs >= rhs
|
56
|
-
when "StringMatches"
|
57
|
-
lhs.match?(Regexp.escape(rhs).gsub('\*', '.*?'))
|
58
|
-
else
|
59
|
-
raise Floe::InvalidWorkflowError, "Invalid choice [#{compare_key}]"
|
60
|
-
end
|
28
|
+
send(operation, lhs, rhs)
|
61
29
|
end
|
62
30
|
|
63
31
|
private
|
@@ -112,26 +80,53 @@ module Floe
|
|
112
80
|
# rubocop:enable Naming/PredicateName
|
113
81
|
# rubocop:enable Style/OptionalBooleanParameter
|
114
82
|
|
83
|
+
def equals?(lhs, rhs)
|
84
|
+
lhs == rhs
|
85
|
+
end
|
86
|
+
|
87
|
+
def lessthan?(lhs, rhs)
|
88
|
+
lhs < rhs
|
89
|
+
end
|
90
|
+
|
91
|
+
def greaterthan?(lhs, rhs)
|
92
|
+
lhs > rhs
|
93
|
+
end
|
94
|
+
|
95
|
+
def lessthanequals?(lhs, rhs)
|
96
|
+
lhs <= rhs
|
97
|
+
end
|
98
|
+
|
99
|
+
def greaterthanequals?(lhs, rhs)
|
100
|
+
lhs >= rhs
|
101
|
+
end
|
102
|
+
|
103
|
+
def matches?(lhs, rhs)
|
104
|
+
lhs.match?(Regexp.escape(rhs).gsub('\*', '.*?'))
|
105
|
+
end
|
106
|
+
|
115
107
|
# parse the compare key at initialization time
|
116
108
|
def parse_compare_key
|
117
109
|
payload.each_key do |key|
|
118
110
|
# e.g. (String)(GreaterThan)(Path)
|
119
111
|
if (match_values = OPERATION.match(key))
|
120
112
|
@compare_key = key
|
121
|
-
@type,
|
113
|
+
@type, operator, @path = match_values.captures
|
114
|
+
@operation = "#{operator.downcase}?".to_sym
|
122
115
|
@compare_predicate = parse_predicate(type)
|
123
116
|
break
|
124
117
|
end
|
125
118
|
# e.g. (Is)(String)
|
126
|
-
if TYPE_CHECK.match
|
119
|
+
if (match_value = TYPE_CHECK.match(key))
|
127
120
|
@compare_key = key
|
121
|
+
_operator, type = match_value.captures
|
128
122
|
# type: nil means no runtime type checking.
|
129
123
|
@type = @path = nil
|
124
|
+
@operation = "is_#{type.downcase}?".to_sym
|
130
125
|
@compare_predicate = parse_predicate("Boolean")
|
131
126
|
break
|
132
127
|
end
|
133
128
|
end
|
134
|
-
parser_error!("requires a compare key")
|
129
|
+
parser_error!("requires a compare key") if compare_key.nil? || operation.nil?
|
135
130
|
end
|
136
131
|
|
137
132
|
# parse predicate at initilization time
|
@@ -3,11 +3,13 @@
|
|
3
3
|
module Floe
|
4
4
|
class Workflow
|
5
5
|
class Context
|
6
|
+
include Logging
|
7
|
+
|
6
8
|
attr_accessor :credentials
|
7
9
|
|
8
10
|
# @param context [Json|Hash] (default, create another with input and execution params)
|
9
11
|
# @param input [Hash] (default: {})
|
10
|
-
def initialize(context = nil, input: nil, credentials: {})
|
12
|
+
def initialize(context = nil, input: nil, credentials: {}, logger: nil)
|
11
13
|
context = JSON.parse(context) if context.kind_of?(String)
|
12
14
|
input = JSON.parse(input || "{}")
|
13
15
|
|
@@ -20,6 +22,8 @@ module Floe
|
|
20
22
|
self["Task"] ||= {}
|
21
23
|
|
22
24
|
@credentials = credentials || {}
|
25
|
+
|
26
|
+
self.logger = logger if logger
|
23
27
|
rescue JSON::ParserError => err
|
24
28
|
raise Floe::InvalidExecutionInput, "Invalid State Machine Execution Input: #{err}: was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')"
|
25
29
|
end
|
data/lib/floe/workflow/state.rb
CHANGED
@@ -3,7 +3,6 @@
|
|
3
3
|
module Floe
|
4
4
|
class Workflow
|
5
5
|
class State
|
6
|
-
include Logging
|
7
6
|
include ValidationMixin
|
8
7
|
|
9
8
|
class << self
|
@@ -56,14 +55,22 @@ module Floe
|
|
56
55
|
mark_started(context)
|
57
56
|
end
|
58
57
|
|
58
|
+
def started?(context)
|
59
|
+
context.state_started?
|
60
|
+
end
|
61
|
+
|
59
62
|
def finish(context)
|
60
63
|
mark_finished(context)
|
61
64
|
end
|
62
65
|
|
66
|
+
def finished?(context)
|
67
|
+
context.state_finished?
|
68
|
+
end
|
69
|
+
|
63
70
|
def mark_started(context)
|
64
71
|
context.state["EnteredTime"] = Time.now.utc.iso8601
|
65
72
|
|
66
|
-
logger.info("Running state: [#{long_name}] with input [#{context.json_input}]...")
|
73
|
+
context.logger.info("Running state: [#{long_name}] with input [#{context.json_input}]...")
|
67
74
|
end
|
68
75
|
|
69
76
|
def mark_finished(context)
|
@@ -74,7 +81,7 @@ module Floe
|
|
74
81
|
context.state["Duration"] = finished_time - entered_time
|
75
82
|
|
76
83
|
level = context.failed? ? :error : :info
|
77
|
-
logger.public_send(level, "Running state: [#{long_name}] with input [#{context.json_input}]...Complete #{context.next_state ? "- next state [#{context.next_state}]" : "workflow -"} output: [#{context.json_output}]")
|
84
|
+
context.logger.public_send(level, "Running state: [#{long_name}] with input [#{context.json_input}]...Complete #{context.next_state ? "- next state [#{context.next_state}]" : "workflow -"} output: [#{context.json_output}]")
|
78
85
|
|
79
86
|
0
|
80
87
|
end
|
@@ -88,7 +95,7 @@ module Floe
|
|
88
95
|
end
|
89
96
|
|
90
97
|
def ready?(context)
|
91
|
-
!context
|
98
|
+
!started?(context) || !running?(context)
|
92
99
|
end
|
93
100
|
|
94
101
|
def running?(context)
|
@@ -6,8 +6,7 @@ module Floe
|
|
6
6
|
module ChildWorkflowMixin
|
7
7
|
def run_nonblock!(context)
|
8
8
|
start(context) unless context.state_started?
|
9
|
-
|
10
|
-
step_nonblock!(context) while running?(context)
|
9
|
+
step_nonblock!(context)
|
11
10
|
return Errno::EAGAIN unless ready?(context)
|
12
11
|
|
13
12
|
finish(context) if ended?(context)
|
@@ -29,7 +29,7 @@ module Floe
|
|
29
29
|
wait_until!(context, :seconds => retrier.sleep_duration(context["State"]["RetryCount"]))
|
30
30
|
context.next_state = context.state_name
|
31
31
|
context.output = error
|
32
|
-
logger.info("Running state: [#{long_name}] with input [#{context.json_input}] got error[#{context.json_output}]...Retry - delay: #{wait_until(context)}")
|
32
|
+
context.logger.info("Running state: [#{long_name}] with input [#{context.json_input}] got error[#{context.json_output}]...Retry - delay: #{wait_until(context)}")
|
33
33
|
true
|
34
34
|
end
|
35
35
|
|
@@ -39,7 +39,7 @@ module Floe
|
|
39
39
|
|
40
40
|
context.next_state = catcher.next
|
41
41
|
context.output = catcher.result_path.set(context.input, error)
|
42
|
-
logger.info("Running state: [#{long_name}] with input [#{context.json_input}]...CatchError - next state: [#{context.next_state}] output: [#{context.json_output}]")
|
42
|
+
context.logger.info("Running state: [#{long_name}] with input [#{context.json_input}]...CatchError - next state: [#{context.next_state}] output: [#{context.json_output}]")
|
43
43
|
|
44
44
|
true
|
45
45
|
end
|
@@ -49,7 +49,7 @@ module Floe
|
|
49
49
|
# keeping in here for completeness
|
50
50
|
context.next_state = nil
|
51
51
|
context.output = error
|
52
|
-
logger.error("Running state: [#{long_name}] with input [#{context.json_input}]...Complete workflow - output: [#{context.json_output}]")
|
52
|
+
context.logger.error("Running state: [#{long_name}] with input [#{context.json_input}]...Complete workflow - output: [#{context.json_output}]")
|
53
53
|
end
|
54
54
|
end
|
55
55
|
end
|
@@ -61,7 +61,8 @@ module Floe
|
|
61
61
|
end
|
62
62
|
|
63
63
|
def running?(context)
|
64
|
-
return true
|
64
|
+
return true if waiting?(context)
|
65
|
+
return false if finished?(context)
|
65
66
|
|
66
67
|
runner.status!(context.state["RunnerContext"])
|
67
68
|
runner.running?(context.state["RunnerContext"])
|
data/lib/floe/workflow.rb
CHANGED
@@ -18,7 +18,6 @@ module Floe
|
|
18
18
|
|
19
19
|
def wait(workflows, timeout: nil, &block)
|
20
20
|
workflows = [workflows] if workflows.kind_of?(self)
|
21
|
-
logger.info("Checking #{workflows.count} workflows...")
|
22
21
|
|
23
22
|
run_until = Time.now.utc + timeout if timeout.to_i > 0
|
24
23
|
ready = []
|
@@ -72,7 +71,6 @@ module Floe
|
|
72
71
|
sleep_thread&.kill
|
73
72
|
end
|
74
73
|
|
75
|
-
logger.info("Checking #{workflows.count} workflows...Complete - #{ready.count} ready")
|
76
74
|
ready
|
77
75
|
ensure
|
78
76
|
wait_thread&.kill
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: floe
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.15.
|
4
|
+
version: 0.15.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- ManageIQ Developers
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-11-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: awesome_spawn
|