floe 0.4.0 → 0.5.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/CHANGELOG.md +23 -1
- data/Gemfile +1 -0
- data/examples/workflow.asl +4 -4
- data/exe/floe +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 +5 -23
- data/lib/floe/workflow/runner/kubernetes.rb +4 -28
- data/lib/floe/workflow/runner/podman.rb +5 -22
- 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 +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0aac9121f1d393173b2fb11f9422f9e689693318a97dada576f054d827901024
|
4
|
+
data.tar.gz: 8118d4632e54c6fa3beb94384be517d5f3bf494890855901ce36af7ea94ee1c2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3872e70c2bb41e46570bcba95ef63716cdf3ea93bed77e9654db6614f8225982e13e3a83256b97854b6aa4376799e803f2edd5066dc3e304e75e2970c55d60f2
|
7
|
+
data.tar.gz: fac7e8815bcff8d93e443653759a6cdf8371c78f9114e3fef133212c0a1ef49d76bb35a0f491ef5abfeb134a21dc11cbbc00a6694d1f05d0e90f5bdae3471c41
|
data/CHANGELOG.md
CHANGED
@@ -4,6 +4,27 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|
4
4
|
|
5
5
|
## [Unreleased]
|
6
6
|
|
7
|
+
## [0.5.0] - 2023-10-12
|
8
|
+
### Added
|
9
|
+
- For task errors, use the json on the last line ([#128](https://github.com/ManageIQ/floe/pull/128))
|
10
|
+
- Add ability to pass task service account to kube runner ([#131](https://github.com/ManageIQ/floe/pull/131))
|
11
|
+
|
12
|
+
### Fixed
|
13
|
+
- Don't put credentials file into input ([#124](https://github.com/ManageIQ/floe/pull/124))
|
14
|
+
- exe/floe return success status if the workflow was successful ([#129](https://github.com/ManageIQ/floe/pull/129))
|
15
|
+
- For error output, drop trailing newline ([#126](https://github.com/ManageIQ/floe/pull/126))
|
16
|
+
|
17
|
+
## [0.4.1] - 2023-10-06
|
18
|
+
### Added
|
19
|
+
- Add Fail#CausePath and Fail#ErrorPath ([#110](https://github.com/ManageIQ/floe/pull/110))
|
20
|
+
- Add Task#Retrier incremental backoff and Wait#Timestamp ([#100](https://github.com/ManageIQ/floe/pull/100))
|
21
|
+
|
22
|
+
### Fixed
|
23
|
+
- Combine stdout and stderr for docker and podman runners ([#104](https://github.com/ManageIQ/floe/pull/104))
|
24
|
+
- Don't raise an exception on task failure ([#115](https://github.com/ManageIQ/floe/pull/115))
|
25
|
+
- Fix task output handling ([#112](https://github.com/ManageIQ/floe/pull/112))
|
26
|
+
- Fix Context#input not JSON parsed ([#122](https://github.com/ManageIQ/floe/pull/122))
|
27
|
+
|
7
28
|
## [0.4.0] - 2023-09-26
|
8
29
|
### Added
|
9
30
|
- Add ability to run workflows asynchronously ([#52](https://github.com/ManageIQ/floe/pull/92))
|
@@ -63,7 +84,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|
63
84
|
### Added
|
64
85
|
- Initial release
|
65
86
|
|
66
|
-
[Unreleased]: https://github.com/ManageIQ/floe/compare/v0.4.
|
87
|
+
[Unreleased]: https://github.com/ManageIQ/floe/compare/v0.4.1...HEAD
|
88
|
+
[0.4.1]: https://github.com/ManageIQ/floe/compare/v0.4.0...v0.4.1
|
67
89
|
[0.4.0]: https://github.com/ManageIQ/floe/compare/v0.3.1...v0.4.0
|
68
90
|
[0.3.1]: https://github.com/ManageIQ/floe/compare/v0.3.0...v0.3.1
|
69
91
|
[0.3.0]: https://github.com/ManageIQ/floe/compare/v0.2.3...v0.3.0
|
data/Gemfile
CHANGED
data/examples/workflow.asl
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
"States": {
|
5
5
|
"FirstState": {
|
6
6
|
"Type": "Task",
|
7
|
-
"Resource": "docker://agrare/hello-world:latest",
|
7
|
+
"Resource": "docker://docker.io/agrare/hello-world:latest",
|
8
8
|
"Credentials": {
|
9
9
|
"mysecret": "dont tell anyone"
|
10
10
|
},
|
@@ -49,13 +49,13 @@
|
|
49
49
|
|
50
50
|
"FirstMatchState": {
|
51
51
|
"Type" : "Task",
|
52
|
-
"Resource": "docker://agrare/hello-world:latest",
|
52
|
+
"Resource": "docker://docker.io/agrare/hello-world:latest",
|
53
53
|
"Next": "PassState"
|
54
54
|
},
|
55
55
|
|
56
56
|
"SecondMatchState": {
|
57
57
|
"Type" : "Task",
|
58
|
-
"Resource": "docker://agrare/hello-world:latest",
|
58
|
+
"Resource": "docker://docker.io/agrare/hello-world:latest",
|
59
59
|
"Next": "NextState"
|
60
60
|
},
|
61
61
|
|
@@ -87,7 +87,7 @@
|
|
87
87
|
|
88
88
|
"NextState": {
|
89
89
|
"Type": "Task",
|
90
|
-
"Resource": "docker://agrare/hello-world:latest",
|
90
|
+
"Resource": "docker://docker.io/agrare/hello-world:latest",
|
91
91
|
"Secrets": ["vmdb:aaa-bbb-ccc"],
|
92
92
|
"End": true
|
93
93
|
}
|
data/exe/floe
CHANGED
data/lib/floe/version.rb
CHANGED
@@ -13,24 +13,6 @@ module Floe
|
|
13
13
|
@network = options.fetch("network", "bridge")
|
14
14
|
end
|
15
15
|
|
16
|
-
def run!(resource, env = {}, secrets = {})
|
17
|
-
raise ArgumentError, "Invalid resource" unless resource&.start_with?("docker://")
|
18
|
-
|
19
|
-
image = resource.sub("docker://", "")
|
20
|
-
|
21
|
-
secrets_file = nil
|
22
|
-
if secrets && !secrets.empty?
|
23
|
-
secrets_file = create_secret(secrets)
|
24
|
-
env["_CREDENTIALS"] = "/run/secrets"
|
25
|
-
end
|
26
|
-
|
27
|
-
output = run_container(image, env, secrets_file)
|
28
|
-
|
29
|
-
{"exit_code" => 0, "output" => output}
|
30
|
-
ensure
|
31
|
-
cleanup({"secrets_ref" => secrets_file})
|
32
|
-
end
|
33
|
-
|
34
16
|
def run_async!(resource, env = {}, secrets = {})
|
35
17
|
raise ArgumentError, "Invalid resource" unless resource&.start_with?("docker://")
|
36
18
|
|
@@ -40,11 +22,10 @@ module Floe
|
|
40
22
|
|
41
23
|
if secrets && !secrets.empty?
|
42
24
|
runner_context["secrets_ref"] = create_secret(secrets)
|
43
|
-
env["_CREDENTIALS"] = "/run/secrets"
|
44
25
|
end
|
45
26
|
|
46
27
|
begin
|
47
|
-
runner_context["container_ref"] = run_container(image, env, runner_context["secrets_ref"]
|
28
|
+
runner_context["container_ref"] = run_container(image, env, runner_context["secrets_ref"])
|
48
29
|
rescue
|
49
30
|
cleanup(runner_context)
|
50
31
|
raise
|
@@ -73,7 +54,7 @@ module Floe
|
|
73
54
|
end
|
74
55
|
|
75
56
|
def output(runner_context)
|
76
|
-
output = docker!("logs", runner_context["container_ref"]).output
|
57
|
+
output = docker!("logs", runner_context["container_ref"], :combined_output => true).output
|
77
58
|
runner_context["output"] = output
|
78
59
|
end
|
79
60
|
|
@@ -81,10 +62,11 @@ module Floe
|
|
81
62
|
|
82
63
|
attr_reader :network
|
83
64
|
|
84
|
-
def run_container(image, env, secrets_file
|
65
|
+
def run_container(image, env, secrets_file)
|
85
66
|
params = ["run"]
|
86
|
-
params <<
|
67
|
+
params << :detach
|
87
68
|
params += env.map { |k, v| [:e, "#{k}=#{v}"] }
|
69
|
+
params << [:e, "_CREDENTIALS=/run/secrets"] if secrets_file
|
88
70
|
params << [:net, "host"] if @network == "host"
|
89
71
|
params << [:v, "#{secrets_file}:/run/secrets:z"] if secrets_file
|
90
72
|
params << image
|
@@ -35,35 +35,9 @@ module Floe
|
|
35
35
|
|
36
36
|
@namespace = options.fetch("namespace", "default")
|
37
37
|
|
38
|
-
|
39
|
-
end
|
40
|
-
|
41
|
-
def run!(resource, env = {}, secrets = {})
|
42
|
-
raise ArgumentError, "Invalid resource" unless resource&.start_with?("docker://")
|
43
|
-
|
44
|
-
image = resource.sub("docker://", "")
|
45
|
-
name = pod_name(image)
|
46
|
-
secret = create_secret!(secrets) if secrets && !secrets.empty?
|
47
|
-
|
48
|
-
begin
|
49
|
-
runner_context = {"container_ref" => name}
|
38
|
+
@task_service_account = options["task_service_account"]
|
50
39
|
|
51
|
-
|
52
|
-
loop do
|
53
|
-
case pod_info(name).dig("status", "phase")
|
54
|
-
when "Pending", "Running"
|
55
|
-
sleep(1)
|
56
|
-
else # also "Succeeded"
|
57
|
-
runner_context["exit_code"] = 0
|
58
|
-
output(runner_context)
|
59
|
-
break
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
runner_context
|
64
|
-
ensure
|
65
|
-
cleanup({"container_ref" => name, "secrets_ref" => secret})
|
66
|
-
end
|
40
|
+
super
|
67
41
|
end
|
68
42
|
|
69
43
|
def run_async!(resource, env = {}, secrets = {})
|
@@ -148,6 +122,8 @@ module Floe
|
|
148
122
|
}
|
149
123
|
}
|
150
124
|
|
125
|
+
spec[:spec][:serviceAccountName] = @task_service_account if @task_service_account
|
126
|
+
|
151
127
|
if secret
|
152
128
|
spec[:spec][:volumes] = [
|
153
129
|
{
|
@@ -26,23 +26,6 @@ module Floe
|
|
26
26
|
@volumepath = options["volumepath"]
|
27
27
|
end
|
28
28
|
|
29
|
-
def run!(resource, env = {}, secrets = {})
|
30
|
-
raise ArgumentError, "Invalid resource" unless resource&.start_with?("docker://")
|
31
|
-
|
32
|
-
image = resource.sub("docker://", "")
|
33
|
-
|
34
|
-
if secrets && !secrets.empty?
|
35
|
-
secret = create_secret(secrets)
|
36
|
-
env["_CREDENTIALS"] = "/run/secrets/#{secret}"
|
37
|
-
end
|
38
|
-
|
39
|
-
output = run_container(image, env, secret)
|
40
|
-
|
41
|
-
{"exit_code" => 0, :output => output}
|
42
|
-
ensure
|
43
|
-
delete_secret(secret) if secret
|
44
|
-
end
|
45
|
-
|
46
29
|
def run_async!(resource, env = {}, secrets = {})
|
47
30
|
raise ArgumentError, "Invalid resource" unless resource&.start_with?("docker://")
|
48
31
|
|
@@ -50,11 +33,10 @@ module Floe
|
|
50
33
|
|
51
34
|
if secrets && !secrets.empty?
|
52
35
|
secret_guid = create_secret(secrets)
|
53
|
-
env["_CREDENTIALS"] = "/run/secrets/#{secret_guid}"
|
54
36
|
end
|
55
37
|
|
56
38
|
begin
|
57
|
-
container_id = run_container(image, env, secret_guid
|
39
|
+
container_id = run_container(image, env, secret_guid)
|
58
40
|
rescue
|
59
41
|
cleanup({"container_ref" => container_id, "secrets_ref" => secret_guid})
|
60
42
|
raise
|
@@ -83,16 +65,17 @@ module Floe
|
|
83
65
|
end
|
84
66
|
|
85
67
|
def output(runner_context)
|
86
|
-
output = podman!("logs", runner_context["container_ref"]).output
|
68
|
+
output = podman!("logs", runner_context["container_ref"], :combined_output => true).output
|
87
69
|
runner_context["output"] = output
|
88
70
|
end
|
89
71
|
|
90
72
|
private
|
91
73
|
|
92
|
-
def run_container(image, env, secret
|
74
|
+
def run_container(image, env, secret)
|
93
75
|
params = ["run"]
|
94
|
-
params <<
|
76
|
+
params << :detach
|
95
77
|
params += env.map { |k, v| [:e, "#{k}=#{v}"] }
|
78
|
+
params << [:e, "_CREDENTIALS=/run/secrets/#{secret}"] if secret
|
96
79
|
params << [:net, "host"] if @network == "host"
|
97
80
|
params << [:secret, secret] if secret
|
98
81
|
params << image
|
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.split("\n").last)
|
131
|
+
rescue JSON::ParserError
|
132
|
+
{"Error" => output.chomp}
|
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
|
+
version: 0.5.0
|
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-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: awesome_spawn
|
@@ -152,7 +152,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
152
152
|
- !ruby/object:Gem::Version
|
153
153
|
version: '0'
|
154
154
|
requirements: []
|
155
|
-
rubygems_version: 3.
|
155
|
+
rubygems_version: 3.2.33
|
156
156
|
signing_key:
|
157
157
|
specification_version: 4
|
158
158
|
summary: Simple Workflow Runner.
|