floe 0.4.0 → 0.4.1
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/CHANGELOG.md +13 -1
- data/Gemfile +1 -0
- data/lib/floe/version.rb +1 -1
- data/lib/floe/workflow/catcher.rb +1 -1
- data/lib/floe/workflow/context.rb +1 -0
- data/lib/floe/workflow/runner/docker.rb +1 -1
- data/lib/floe/workflow/runner/podman.rb +1 -1
- data/lib/floe/workflow/runner.rb +7 -5
- data/lib/floe/workflow/state.rb +17 -0
- data/lib/floe/workflow/states/fail.rb +14 -6
- data/lib/floe/workflow/states/task.rb +38 -16
- data/lib/floe/workflow/states/wait.rb +19 -10
- data/lib/floe.rb +1 -0
- 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: b2bab2c07fd71a45bcf896aa219993770c83eedd5c4e3268c318d8b9ba0413ed
|
4
|
+
data.tar.gz: 786064609be7fee8a19855cb97fd160984b5a8a7aedc85344eab01302155a988
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 45a9fd8a85bb5b1059574c0dc63b0434496b6c75634c567aba9f0008400867b4dbf8c33337b3940084c644606dc3fe6065fc4f79ac5732449ca0d9675d0e2531
|
7
|
+
data.tar.gz: f4147cd4e1633d2af02b7cbbd04735a9ffdc733359eb7f349b23a2fb8bada9de18a4f37fad82de543f2eb8060fa6fa52d1864a159f3ab3f780cbdab1824b6ab1
|
data/CHANGELOG.md
CHANGED
@@ -4,6 +4,17 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|
4
4
|
|
5
5
|
## [Unreleased]
|
6
6
|
|
7
|
+
## [0.4.1] - 2023-10-06
|
8
|
+
### Added
|
9
|
+
- Add Fail#CausePath and Fail#ErrorPath ([#110](https://github.com/ManageIQ/floe/pull/110))
|
10
|
+
- Add Task#Retrier incremental backoff and Wait#Timestamp ([#100](https://github.com/ManageIQ/floe/pull/100))
|
11
|
+
|
12
|
+
### Fixed
|
13
|
+
- Combine stdout and stderr for docker and podman runners ([#104](https://github.com/ManageIQ/floe/pull/104))
|
14
|
+
- Don't raise an exception on task failure ([#115](https://github.com/ManageIQ/floe/pull/115))
|
15
|
+
- Fix task output handling ([#112](https://github.com/ManageIQ/floe/pull/112))
|
16
|
+
- Fix Context#input not JSON parsed ([#122](https://github.com/ManageIQ/floe/pull/122))
|
17
|
+
|
7
18
|
## [0.4.0] - 2023-09-26
|
8
19
|
### Added
|
9
20
|
- Add ability to run workflows asynchronously ([#52](https://github.com/ManageIQ/floe/pull/92))
|
@@ -63,7 +74,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|
63
74
|
### Added
|
64
75
|
- Initial release
|
65
76
|
|
66
|
-
[Unreleased]: https://github.com/ManageIQ/floe/compare/v0.4.
|
77
|
+
[Unreleased]: https://github.com/ManageIQ/floe/compare/v0.4.1...HEAD
|
78
|
+
[0.4.1]: https://github.com/ManageIQ/floe/compare/v0.4.0...v0.4.1
|
67
79
|
[0.4.0]: https://github.com/ManageIQ/floe/compare/v0.3.1...v0.4.0
|
68
80
|
[0.3.1]: https://github.com/ManageIQ/floe/compare/v0.3.0...v0.3.1
|
69
81
|
[0.3.0]: https://github.com/ManageIQ/floe/compare/v0.2.3...v0.3.0
|
data/Gemfile
CHANGED
data/lib/floe/version.rb
CHANGED
@@ -73,7 +73,7 @@ module Floe
|
|
73
73
|
end
|
74
74
|
|
75
75
|
def output(runner_context)
|
76
|
-
output = docker!("logs", runner_context["container_ref"]).output
|
76
|
+
output = docker!("logs", runner_context["container_ref"], :combined_output => true).output
|
77
77
|
runner_context["output"] = output
|
78
78
|
end
|
79
79
|
|
@@ -83,7 +83,7 @@ module Floe
|
|
83
83
|
end
|
84
84
|
|
85
85
|
def output(runner_context)
|
86
|
-
output = podman!("logs", runner_context["container_ref"]).output
|
86
|
+
output = podman!("logs", runner_context["container_ref"], :combined_output => true).output
|
87
87
|
runner_context["output"] = output
|
88
88
|
end
|
89
89
|
|
data/lib/floe/workflow/runner.rb
CHANGED
@@ -5,7 +5,8 @@ module Floe
|
|
5
5
|
class Runner
|
6
6
|
include Logging
|
7
7
|
|
8
|
-
TYPES
|
8
|
+
TYPES = %w[docker podman kubernetes].freeze
|
9
|
+
OUTPUT_MARKER = "__FLOE_OUTPUT__\n"
|
9
10
|
|
10
11
|
def initialize(_options = {})
|
11
12
|
end
|
@@ -34,23 +35,24 @@ module Floe
|
|
34
35
|
raise NotImplementedError, "Must be implemented in a subclass"
|
35
36
|
end
|
36
37
|
|
38
|
+
# @return [Hash] runner_context
|
37
39
|
def run_async!(_image, _env = {}, _secrets = {})
|
38
40
|
raise NotImplementedError, "Must be implemented in a subclass"
|
39
41
|
end
|
40
42
|
|
41
|
-
def running?(
|
43
|
+
def running?(_runner_context)
|
42
44
|
raise NotImplementedError, "Must be implemented in a subclass"
|
43
45
|
end
|
44
46
|
|
45
|
-
def success?(
|
47
|
+
def success?(_runner_context)
|
46
48
|
raise NotImplementedError, "Must be implemented in a subclass"
|
47
49
|
end
|
48
50
|
|
49
|
-
def output(
|
51
|
+
def output(_runner_context)
|
50
52
|
raise NotImplementedError, "Must be implemented in a subclass"
|
51
53
|
end
|
52
54
|
|
53
|
-
def cleanup(
|
55
|
+
def cleanup(_runner_context)
|
54
56
|
raise NotImplementedError, "Must be implemented in a subclass"
|
55
57
|
end
|
56
58
|
end
|
data/lib/floe/workflow/state.rb
CHANGED
@@ -92,6 +92,23 @@ module Floe
|
|
92
92
|
def finished?
|
93
93
|
context.state.key?("FinishedTime")
|
94
94
|
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def wait(seconds: nil, time: nil)
|
99
|
+
context.state["WaitUntil"] =
|
100
|
+
if seconds
|
101
|
+
(Time.parse(context.state["EnteredTime"]) + seconds).iso8601
|
102
|
+
elsif time.kind_of?(String)
|
103
|
+
time
|
104
|
+
else
|
105
|
+
time.iso8601
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def waiting?
|
110
|
+
context.state["WaitUntil"] && Time.now.utc <= Time.parse(context.state["WaitUntil"])
|
111
|
+
end
|
95
112
|
end
|
96
113
|
end
|
97
114
|
end
|
@@ -9,16 +9,24 @@ module Floe
|
|
9
9
|
def initialize(workflow, name, payload)
|
10
10
|
super
|
11
11
|
|
12
|
-
@cause
|
13
|
-
@error
|
12
|
+
@cause = payload["Cause"]
|
13
|
+
@error = payload["Error"]
|
14
|
+
@cause_path = Path.new(payload["CausePath"]) if payload["CausePath"]
|
15
|
+
@error_path = Path.new(payload["ErrorPath"]) if payload["ErrorPath"]
|
14
16
|
end
|
15
17
|
|
16
18
|
def start(input)
|
17
19
|
super
|
18
|
-
context.
|
19
|
-
|
20
|
-
|
21
|
-
|
20
|
+
context.next_state = nil
|
21
|
+
# TODO: support intrinsic functions here
|
22
|
+
# see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-fail-state.html
|
23
|
+
# https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html#asl-intrsc-func-generic
|
24
|
+
context.output = {
|
25
|
+
"Error" => @error_path ? @error_path.value(context, input) : error,
|
26
|
+
"Cause" => @cause_path ? @cause_path.value(context, input) : cause
|
27
|
+
}.compact
|
28
|
+
context.state["Error"] = context.output["Error"]
|
29
|
+
context.state["Cause"] = context.output["Cause"]
|
22
30
|
end
|
23
31
|
|
24
32
|
def running?
|
@@ -29,10 +29,10 @@ module Floe
|
|
29
29
|
|
30
30
|
def start(input)
|
31
31
|
super
|
32
|
-
input = input_path.value(context, input)
|
33
|
-
input = parameters.value(context, input) if parameters
|
34
32
|
|
33
|
+
input = process_input(input)
|
35
34
|
runner_context = runner.run_async!(resource, input, credentials&.value({}, workflow.credentials))
|
35
|
+
|
36
36
|
context.state["RunnerContext"] = runner_context
|
37
37
|
end
|
38
38
|
|
@@ -41,13 +41,15 @@ module Floe
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def finish
|
44
|
-
|
44
|
+
output = runner.output(context.state["RunnerContext"])
|
45
45
|
|
46
46
|
if success?
|
47
|
-
|
47
|
+
output = parse_output(output)
|
48
|
+
context.state["Output"] = process_output!(output)
|
48
49
|
context.next_state = next_state
|
49
50
|
else
|
50
|
-
|
51
|
+
error = parse_error(output)
|
52
|
+
retry_state!(error) || catch_error!(error) || fail_workflow!(error)
|
51
53
|
end
|
52
54
|
|
53
55
|
super
|
@@ -56,6 +58,8 @@ module Floe
|
|
56
58
|
end
|
57
59
|
|
58
60
|
def running?
|
61
|
+
return true if waiting?
|
62
|
+
|
59
63
|
runner.status!(context.state["RunnerContext"])
|
60
64
|
runner.running?(context.state["RunnerContext"])
|
61
65
|
end
|
@@ -81,7 +85,7 @@ module Floe
|
|
81
85
|
end
|
82
86
|
|
83
87
|
def retry_state!(error)
|
84
|
-
retrier = find_retrier(error)
|
88
|
+
retrier = find_retrier(error["Error"]) if error
|
85
89
|
return if retrier.nil?
|
86
90
|
|
87
91
|
# If a different retrier is hit reset the context
|
@@ -94,16 +98,24 @@ module Floe
|
|
94
98
|
|
95
99
|
return if context["State"]["RetryCount"] > retrier.max_attempts
|
96
100
|
|
97
|
-
|
101
|
+
wait(:seconds => retrier.sleep_duration(context["State"]["RetryCount"]))
|
98
102
|
context.next_state = context.state_name
|
99
103
|
true
|
100
104
|
end
|
101
105
|
|
102
106
|
def catch_error!(error)
|
103
|
-
catcher = find_catcher(error)
|
104
|
-
|
107
|
+
catcher = find_catcher(error["Error"]) if error
|
108
|
+
return if catcher.nil?
|
105
109
|
|
106
110
|
context.next_state = catcher.next
|
111
|
+
context.output = catcher.result_path.set(context.input, error)
|
112
|
+
true
|
113
|
+
end
|
114
|
+
|
115
|
+
def fail_workflow!(error)
|
116
|
+
context.next_state = nil
|
117
|
+
context.output = {"Error" => error["Error"], "Cause" => error["Cause"]}.compact
|
118
|
+
context.state["Error"] = context.output["Error"]
|
107
119
|
end
|
108
120
|
|
109
121
|
def process_input(input)
|
@@ -112,17 +124,27 @@ module Floe
|
|
112
124
|
input
|
113
125
|
end
|
114
126
|
|
127
|
+
def parse_error(output)
|
128
|
+
return if output.nil?
|
129
|
+
|
130
|
+
JSON.parse(output)
|
131
|
+
rescue JSON::ParserError
|
132
|
+
{"Error" => output}
|
133
|
+
end
|
134
|
+
|
135
|
+
def parse_output(output)
|
136
|
+
return if output.nil?
|
137
|
+
|
138
|
+
JSON.parse(output.split("\n").last)
|
139
|
+
rescue JSON::ParserError
|
140
|
+
nil
|
141
|
+
end
|
142
|
+
|
115
143
|
def process_output!(results)
|
116
|
-
output =
|
144
|
+
output = context.input.dup
|
117
145
|
return output if results.nil?
|
118
146
|
return if output_path.nil?
|
119
147
|
|
120
|
-
begin
|
121
|
-
results = JSON.parse(results)
|
122
|
-
rescue JSON::ParserError
|
123
|
-
results = {"results" => results}
|
124
|
-
end
|
125
|
-
|
126
148
|
results = result_selector.value(context, results) if result_selector
|
127
149
|
output = result_path.set(output, results)
|
128
150
|
output_path.value(context, output)
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'time'
|
4
|
+
|
3
5
|
module Floe
|
4
6
|
class Workflow
|
5
7
|
module States
|
@@ -9,9 +11,12 @@ module Floe
|
|
9
11
|
def initialize(workflow, name, payload)
|
10
12
|
super
|
11
13
|
|
12
|
-
@next
|
13
|
-
@end
|
14
|
-
@seconds
|
14
|
+
@next = payload["Next"]
|
15
|
+
@end = !!payload["End"]
|
16
|
+
@seconds = payload["Seconds"]&.to_i
|
17
|
+
@timestamp = payload["Timestamp"]
|
18
|
+
@timestamp_path = Path.new(payload["TimestampPath"]) if payload.key?("TimestampPath")
|
19
|
+
@seconds_path = Path.new(payload["SecondsPath"]) if payload.key?("SecondsPath")
|
15
20
|
|
16
21
|
@input_path = Path.new(payload.fetch("InputPath", "$"))
|
17
22
|
@output_path = Path.new(payload.fetch("OutputPath", "$"))
|
@@ -23,21 +28,25 @@ module Floe
|
|
23
28
|
|
24
29
|
context.output = output_path.value(context, input)
|
25
30
|
context.next_state = end? ? nil : @next
|
31
|
+
please_hold(input)
|
26
32
|
end
|
27
33
|
|
28
34
|
def running?
|
29
|
-
|
30
|
-
if now > (Time.parse(context.state["EnteredTime"]) + @seconds)
|
31
|
-
context.state["FinishedTime"] = now.iso8601
|
32
|
-
false
|
33
|
-
else
|
34
|
-
true
|
35
|
-
end
|
35
|
+
waiting?
|
36
36
|
end
|
37
37
|
|
38
38
|
def end?
|
39
39
|
@end
|
40
40
|
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def please_hold(input)
|
45
|
+
wait(
|
46
|
+
:seconds => @seconds_path ? @seconds_path.value(context, input).to_i : @seconds,
|
47
|
+
:time => @timestamp_path ? @timestamp_path.value(context, input) : @timestamp
|
48
|
+
)
|
49
|
+
end
|
41
50
|
end
|
42
51
|
end
|
43
52
|
end
|
data/lib/floe.rb
CHANGED
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.4.
|
4
|
+
version: 0.4.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: 2023-
|
11
|
+
date: 2023-10-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: awesome_spawn
|