floe 0.11.2 → 0.12.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/.codeclimate.yml +16 -0
- data/.yamllint +1 -3
- data/CHANGELOG.md +51 -1
- data/Gemfile +1 -1
- data/LICENSE.txt +202 -0
- data/README.md +5 -1
- data/exe/floe +3 -72
- data/floe.gemspec +5 -4
- data/lib/floe/cli.rb +86 -0
- data/lib/floe/container_runner/docker_mixin.rb +3 -0
- data/lib/floe/runner.rb +9 -4
- data/lib/floe/validation_mixin.rb +49 -0
- data/lib/floe/version.rb +1 -1
- data/lib/floe/workflow/catcher.rb +17 -3
- data/lib/floe/workflow/choice_rule.rb +14 -9
- data/lib/floe/workflow/context.rb +27 -5
- data/lib/floe/workflow/error_matcher_mixin.rb +17 -0
- data/lib/floe/workflow/intrinsic_function/parser.rb +100 -0
- data/lib/floe/workflow/intrinsic_function/transformer.rb +196 -0
- data/lib/floe/workflow/intrinsic_function.rb +34 -0
- data/lib/floe/workflow/path.rb +7 -1
- data/lib/floe/workflow/payload_template.rb +7 -4
- data/lib/floe/workflow/reference_path.rb +2 -5
- data/lib/floe/workflow/retrier.rb +11 -4
- data/lib/floe/workflow/state.rb +33 -46
- data/lib/floe/workflow/states/choice.rb +12 -11
- data/lib/floe/workflow/states/fail.rb +3 -3
- data/lib/floe/workflow/states/input_output_mixin.rb +8 -8
- data/lib/floe/workflow/states/non_terminal_mixin.rb +6 -6
- data/lib/floe/workflow/states/pass.rb +7 -6
- data/lib/floe/workflow/states/succeed.rb +12 -3
- data/lib/floe/workflow/states/task.rb +35 -30
- data/lib/floe/workflow/states/wait.rb +8 -7
- data/lib/floe/workflow.rb +75 -23
- data/lib/floe.rb +6 -0
- metadata +31 -22
@@ -4,26 +4,31 @@ module Floe
|
|
4
4
|
class Workflow
|
5
5
|
class ChoiceRule
|
6
6
|
class << self
|
7
|
-
def build(payload)
|
7
|
+
def build(workflow, name, payload)
|
8
8
|
if (sub_payloads = payload["Not"])
|
9
|
-
|
9
|
+
name += ["Not"]
|
10
|
+
Floe::Workflow::ChoiceRule::Not.new(workflow, name, payload, build_children(workflow, name, [sub_payloads]))
|
10
11
|
elsif (sub_payloads = payload["And"])
|
11
|
-
|
12
|
+
name += ["And"]
|
13
|
+
Floe::Workflow::ChoiceRule::And.new(workflow, name, payload, build_children(workflow, name, sub_payloads))
|
12
14
|
elsif (sub_payloads = payload["Or"])
|
13
|
-
|
15
|
+
name += ["Or"]
|
16
|
+
Floe::Workflow::ChoiceRule::Or.new(workflow, name, payload, build_children(workflow, name, sub_payloads))
|
14
17
|
else
|
15
|
-
|
18
|
+
name += ["Data"]
|
19
|
+
Floe::Workflow::ChoiceRule::Data.new(workflow, name, payload)
|
16
20
|
end
|
17
21
|
end
|
18
22
|
|
19
|
-
def build_children(sub_payloads)
|
20
|
-
sub_payloads.map { |payload| build(payload) }
|
23
|
+
def build_children(workflow, name, sub_payloads)
|
24
|
+
sub_payloads.map.with_index { |payload, i| build(workflow, name + [i.to_s], payload) }
|
21
25
|
end
|
22
26
|
end
|
23
27
|
|
24
|
-
attr_reader :next, :payload, :variable, :children
|
28
|
+
attr_reader :next, :payload, :variable, :children, :name
|
25
29
|
|
26
|
-
def initialize(payload, children = nil)
|
30
|
+
def initialize(_workflow, name, payload, children = nil)
|
31
|
+
@name = name
|
27
32
|
@payload = payload
|
28
33
|
@children = children
|
29
34
|
|
@@ -3,13 +3,13 @@
|
|
3
3
|
module Floe
|
4
4
|
class Workflow
|
5
5
|
class Context
|
6
|
+
attr_accessor :credentials
|
7
|
+
|
6
8
|
# @param context [Json|Hash] (default, create another with input and execution params)
|
7
9
|
# @param input [Hash] (default: {})
|
8
|
-
def initialize(context = nil, input: nil)
|
10
|
+
def initialize(context = nil, input: nil, credentials: {})
|
9
11
|
context = JSON.parse(context) if context.kind_of?(String)
|
10
|
-
|
11
|
-
input ||= {}
|
12
|
-
input = JSON.parse(input) if input.kind_of?(String)
|
12
|
+
input = JSON.parse(input || "{}")
|
13
13
|
|
14
14
|
@context = context || {}
|
15
15
|
self["Execution"] ||= {}
|
@@ -18,6 +18,10 @@ module Floe
|
|
18
18
|
self["StateHistory"] ||= []
|
19
19
|
self["StateMachine"] ||= {}
|
20
20
|
self["Task"] ||= {}
|
21
|
+
|
22
|
+
@credentials = credentials || {}
|
23
|
+
rescue JSON::ParserError => err
|
24
|
+
raise Floe::InvalidExecutionInput, "Invalid State Machine Execution Input: #{err}: was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')"
|
21
25
|
end
|
22
26
|
|
23
27
|
def execution
|
@@ -33,7 +37,7 @@ module Floe
|
|
33
37
|
end
|
34
38
|
|
35
39
|
def failed?
|
36
|
-
output
|
40
|
+
(output.kind_of?(Hash) && output.key?("Error")) || false
|
37
41
|
end
|
38
42
|
|
39
43
|
def ended?
|
@@ -48,10 +52,18 @@ module Floe
|
|
48
52
|
state["Input"]
|
49
53
|
end
|
50
54
|
|
55
|
+
def json_input
|
56
|
+
input.to_json
|
57
|
+
end
|
58
|
+
|
51
59
|
def output
|
52
60
|
state["Output"]
|
53
61
|
end
|
54
62
|
|
63
|
+
def json_output
|
64
|
+
output.to_json
|
65
|
+
end
|
66
|
+
|
55
67
|
def output=(val)
|
56
68
|
state["Output"] = val
|
57
69
|
end
|
@@ -84,6 +96,16 @@ module Floe
|
|
84
96
|
status == "success"
|
85
97
|
end
|
86
98
|
|
99
|
+
def state_started?
|
100
|
+
state.key?("EnteredTime")
|
101
|
+
end
|
102
|
+
|
103
|
+
# State#running? also checks docker to see if it is running.
|
104
|
+
# You possibly want to use that instead
|
105
|
+
def state_finished?
|
106
|
+
state.key?("FinishedTime")
|
107
|
+
end
|
108
|
+
|
87
109
|
def state=(val)
|
88
110
|
@context["State"] = val
|
89
111
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Floe
|
4
|
+
class Workflow
|
5
|
+
# Methods for common error handling
|
6
|
+
module ErrorMatcherMixin
|
7
|
+
# @param [String] error the error thrown
|
8
|
+
def match_error?(error)
|
9
|
+
return false if error == "States.Runtime"
|
10
|
+
return true if error_equals.include?("States.ALL")
|
11
|
+
return true if error_equals.include?("States.Timeout") && error == "States.HeartbeatTimeout"
|
12
|
+
|
13
|
+
error_equals.include?(error)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require "parslet"
|
2
|
+
|
3
|
+
module Floe
|
4
|
+
class Workflow
|
5
|
+
class IntrinsicFunction
|
6
|
+
class Parser < Parslet::Parser
|
7
|
+
rule(:spaces) { str(' ').repeat(1) }
|
8
|
+
rule(:spaces?) { spaces.maybe }
|
9
|
+
rule(:digit) { match('[0-9]') }
|
10
|
+
rule(:quote) { str('\'') }
|
11
|
+
|
12
|
+
rule(:comma_sep) { str(',') >> spaces? }
|
13
|
+
|
14
|
+
rule(:true_literal) { str('true').as(:true_literal) }
|
15
|
+
rule(:false_literal) { str('false').as(:false_literal) }
|
16
|
+
rule(:null_literal) { str('null').as(:null_literal) }
|
17
|
+
|
18
|
+
rule(:number) do
|
19
|
+
(
|
20
|
+
str('-').maybe >> (
|
21
|
+
str('0') | (match('[1-9]') >> digit.repeat)
|
22
|
+
) >> (
|
23
|
+
str('.') >> digit.repeat(1)
|
24
|
+
).maybe >> (
|
25
|
+
match('[eE]') >> (str('+') | str('-')).maybe >> digit.repeat(1)
|
26
|
+
).maybe
|
27
|
+
).as(:number)
|
28
|
+
end
|
29
|
+
|
30
|
+
rule(:string) do
|
31
|
+
(
|
32
|
+
quote >> (
|
33
|
+
(str('\\') >> any) | (quote.absent? >> any)
|
34
|
+
).repeat >> quote
|
35
|
+
).as(:string)
|
36
|
+
end
|
37
|
+
|
38
|
+
rule(:jsonpath) do
|
39
|
+
(
|
40
|
+
str('$') >> match('[^,)]').repeat(0)
|
41
|
+
).as(:jsonpath)
|
42
|
+
end
|
43
|
+
|
44
|
+
rule(:arg) do
|
45
|
+
(
|
46
|
+
string | number | jsonpath | true_literal | false_literal | null_literal | expression
|
47
|
+
).as(:arg)
|
48
|
+
end
|
49
|
+
|
50
|
+
rule(:args) do
|
51
|
+
(
|
52
|
+
arg >> (comma_sep >> arg).repeat
|
53
|
+
).maybe.as(:args)
|
54
|
+
end
|
55
|
+
|
56
|
+
[
|
57
|
+
:states_array, "States.Array",
|
58
|
+
:states_array_partition, "States.ArrayPartition",
|
59
|
+
:states_array_contains, "States.ArrayContains",
|
60
|
+
:states_array_range, "States.ArrayRange",
|
61
|
+
:states_array_get_item, "States.ArrayGetItem",
|
62
|
+
:states_array_length, "States.ArrayLength",
|
63
|
+
:states_array_unique, "States.ArrayUnique",
|
64
|
+
:states_base64_encode, "States.Base64Encode",
|
65
|
+
:states_base64_decode, "States.Base64Decode",
|
66
|
+
:states_hash, "States.Hash",
|
67
|
+
:states_math_random, "States.MathRandom",
|
68
|
+
:states_math_add, "States.MathAdd",
|
69
|
+
:states_string_split, "States.StringSplit",
|
70
|
+
:states_uuid, "States.UUID",
|
71
|
+
].each_slice(2) do |function_symbol, function_name|
|
72
|
+
rule(function_symbol) do
|
73
|
+
(
|
74
|
+
str(function_name) >> str('(') >> args >> str(')')
|
75
|
+
).as(function_symbol)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
rule(:expression) do
|
80
|
+
states_array |
|
81
|
+
states_array_partition |
|
82
|
+
states_array_contains |
|
83
|
+
states_array_range |
|
84
|
+
states_array_get_item |
|
85
|
+
states_array_length |
|
86
|
+
states_array_unique |
|
87
|
+
states_base64_encode |
|
88
|
+
states_base64_decode |
|
89
|
+
states_hash |
|
90
|
+
states_math_random |
|
91
|
+
states_math_add |
|
92
|
+
states_string_split |
|
93
|
+
states_uuid
|
94
|
+
end
|
95
|
+
|
96
|
+
root(:expression)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,196 @@
|
|
1
|
+
# Disable rubocops against the `match` method, since this is a Parslet specific
|
2
|
+
# match method and not the typical `Object#match`.
|
3
|
+
# rubocop:disable Performance/RegexpMatch, Performance/RedundantMatch
|
4
|
+
|
5
|
+
require "parslet"
|
6
|
+
|
7
|
+
module Floe
|
8
|
+
class Workflow
|
9
|
+
class IntrinsicFunction
|
10
|
+
class Transformer < Parslet::Transform
|
11
|
+
OptionalArg = Struct.new(:type)
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def process_args(args, function, signature = nil)
|
15
|
+
args = resolve_args(args)
|
16
|
+
if signature
|
17
|
+
check_arity(args, function, signature)
|
18
|
+
check_types(args, function, signature)
|
19
|
+
end
|
20
|
+
args
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def resolve_args(args)
|
26
|
+
if args.nil?
|
27
|
+
# 0 args
|
28
|
+
[]
|
29
|
+
elsif args.kind_of?(Array)
|
30
|
+
# >1 arg
|
31
|
+
args.map { |a| a[:arg] }
|
32
|
+
else
|
33
|
+
# 1 arg
|
34
|
+
[args[:arg]]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def check_arity(args, function, signature)
|
39
|
+
if signature.any?(OptionalArg)
|
40
|
+
signature_without_optional = signature.reject { |a| a.kind_of?(OptionalArg) }
|
41
|
+
signature_size = (signature_without_optional.size..signature.size)
|
42
|
+
|
43
|
+
raise ArgumentError, "wrong number of arguments to #{function} (given #{args.size}, expected #{signature_size})" unless signature_size.include?(args.size)
|
44
|
+
else
|
45
|
+
raise ArgumentError, "wrong number of arguments to #{function} (given #{args.size}, expected #{signature.size})" unless signature.size == args.size
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def check_types(args, function, signature)
|
50
|
+
args.zip(signature).each_with_index do |(arg, type), index|
|
51
|
+
type = type.type if type.kind_of?(OptionalArg)
|
52
|
+
|
53
|
+
raise ArgumentError, "wrong type for argument #{index + 1} to #{function} (given #{arg.class}, expected #{type})" unless arg.kind_of?(type)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
rule(:null_literal => simple(:v)) { nil }
|
59
|
+
rule(:true_literal => simple(:v)) { true }
|
60
|
+
rule(:false_literal => simple(:v)) { false }
|
61
|
+
|
62
|
+
rule(:string => simple(:v)) { v.to_s[1..-2] }
|
63
|
+
rule(:number => simple(:v)) { v.match(/[eE.]/) ? Float(v) : Integer(v) }
|
64
|
+
rule(:jsonpath => simple(:v)) { Floe::Workflow::Path.value(v.to_s, context, input) }
|
65
|
+
|
66
|
+
rule(:states_array => {:args => subtree(:args)}) do
|
67
|
+
Transformer.process_args(args, "States.Array")
|
68
|
+
end
|
69
|
+
|
70
|
+
rule(:states_array_partition => {:args => subtree(:args)}) do
|
71
|
+
args = Transformer.process_args(args(), "States.ArrayPartition", [Array, Integer])
|
72
|
+
array, chunk = *args
|
73
|
+
raise ArgumentError, "invalid value for argument 2 to States.ArrayPartition (given #{chunk}, expected a positive Integer)" unless chunk.positive?
|
74
|
+
|
75
|
+
array.each_slice(chunk).to_a
|
76
|
+
end
|
77
|
+
|
78
|
+
rule(:states_array_contains => {:args => subtree(:args)}) do
|
79
|
+
args = Transformer.process_args(args(), "States.ArrayContains", [Array, Object])
|
80
|
+
array, target = *args
|
81
|
+
|
82
|
+
array.include?(target)
|
83
|
+
end
|
84
|
+
|
85
|
+
rule(:states_array_range => {:args => subtree(:args)}) do
|
86
|
+
args = Transformer.process_args(args(), "States.ArrayRange", [Integer, Integer, Integer])
|
87
|
+
range_begin, range_end, increment = *args
|
88
|
+
raise ArgumentError, "invalid value for argument 3 to States.ArrayRange (given #{increment}, expected a non-zero Integer)" if increment.zero?
|
89
|
+
|
90
|
+
(range_begin..range_end).step(increment).to_a
|
91
|
+
end
|
92
|
+
|
93
|
+
rule(:states_array_get_item => {:args => subtree(:args)}) do
|
94
|
+
args = Transformer.process_args(args(), "States.ArrayGetItem", [Array, Integer])
|
95
|
+
array, index = *args
|
96
|
+
raise ArgumentError, "invalid value for argument 2 to States.ArrayGetItem (given #{index}, expected 0 or a positive Integer)" unless index >= 0
|
97
|
+
|
98
|
+
array[index]
|
99
|
+
end
|
100
|
+
|
101
|
+
rule(:states_array_length => {:args => subtree(:args)}) do
|
102
|
+
args = Transformer.process_args(args(), "States.ArrayLength", [Array])
|
103
|
+
array = args.first
|
104
|
+
|
105
|
+
array.size
|
106
|
+
end
|
107
|
+
|
108
|
+
rule(:states_array_unique => {:args => subtree(:args)}) do
|
109
|
+
args = Transformer.process_args(args(), "States.ArrayUnique", [Array])
|
110
|
+
array = args.first
|
111
|
+
|
112
|
+
array.uniq
|
113
|
+
end
|
114
|
+
|
115
|
+
rule(:states_base64_encode => {:args => subtree(:args)}) do
|
116
|
+
args = Transformer.process_args(args(), "States.Base64Encode", [String])
|
117
|
+
str = args.first
|
118
|
+
|
119
|
+
require "base64"
|
120
|
+
Base64.strict_encode64(str).force_encoding("UTF-8")
|
121
|
+
end
|
122
|
+
|
123
|
+
rule(:states_base64_decode => {:args => subtree(:args)}) do
|
124
|
+
args = Transformer.process_args(args(), "States.Base64Decode", [String])
|
125
|
+
str = args.first
|
126
|
+
|
127
|
+
require "base64"
|
128
|
+
begin
|
129
|
+
Base64.strict_decode64(str)
|
130
|
+
rescue ArgumentError => err
|
131
|
+
raise ArgumentError, "invalid value for argument 1 to States.Base64Decode (#{err})"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
rule(:states_hash => {:args => subtree(:args)}) do
|
136
|
+
args = Transformer.process_args(args(), "States.Hash", [Object, String])
|
137
|
+
data, algorithm = *args
|
138
|
+
raise NotImplementedError if data.kind_of?(Hash)
|
139
|
+
if data.nil?
|
140
|
+
raise ArgumentError, "invalid value for argument 1 to States.Hash (given null, expected non-null)"
|
141
|
+
end
|
142
|
+
|
143
|
+
algorithms = %w[MD5 SHA-1 SHA-256 SHA-384 SHA-512]
|
144
|
+
unless algorithms.include?(algorithm)
|
145
|
+
raise ArgumentError, "invalid value for argument 2 to States.Hash (given #{algorithm.inspect}, expected one of #{algorithms.map(&:inspect).join(", ")})"
|
146
|
+
end
|
147
|
+
|
148
|
+
require "openssl"
|
149
|
+
algorithm = algorithm.sub("-", "")
|
150
|
+
data = data.to_json unless data.kind_of?(String)
|
151
|
+
OpenSSL::Digest.hexdigest(algorithm, data)
|
152
|
+
end
|
153
|
+
|
154
|
+
rule(:states_math_random => {:args => subtree(:args)}) do
|
155
|
+
args = Transformer.process_args(args(), "States.MathRandom", [Integer, Integer, OptionalArg[Integer]])
|
156
|
+
range_start, range_end, seed = *args
|
157
|
+
unless range_start < range_end
|
158
|
+
raise ArgumentError, "invalid values for arguments to States.MathRandom (start must be less than end)"
|
159
|
+
end
|
160
|
+
|
161
|
+
seed ||= Random.new_seed
|
162
|
+
Random.new(seed).rand(range_start..range_end)
|
163
|
+
end
|
164
|
+
|
165
|
+
rule(:states_math_add => {:args => subtree(:args)}) do
|
166
|
+
args = Transformer.process_args(args(), "States.MathAdd", [Integer, Integer])
|
167
|
+
|
168
|
+
args.sum
|
169
|
+
end
|
170
|
+
|
171
|
+
rule(:states_string_split => {:args => subtree(:args)}) do
|
172
|
+
args = Transformer.process_args(args(), "States.StringSplit", [String, String])
|
173
|
+
str, delimeter = *args
|
174
|
+
|
175
|
+
case delimeter.size
|
176
|
+
when 0
|
177
|
+
str.empty? ? [] : [str]
|
178
|
+
when 1
|
179
|
+
str.split(delimeter)
|
180
|
+
else
|
181
|
+
str.split(/[#{Regexp.escape(delimeter)}]+/)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
rule(:states_uuid => {:args => subtree(:args)}) do
|
186
|
+
Transformer.process_args(args, "States.UUID", [])
|
187
|
+
|
188
|
+
require "securerandom"
|
189
|
+
SecureRandom.uuid
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
# rubocop:enable Performance/RegexpMatch, Performance/RedundantMatch
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require "parslet"
|
2
|
+
|
3
|
+
module Floe
|
4
|
+
class Workflow
|
5
|
+
class IntrinsicFunction
|
6
|
+
def self.value(payload, context = {}, input = {})
|
7
|
+
new(payload).value(context, input)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.intrinsic_function?(payload)
|
11
|
+
payload.start_with?("States.")
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :payload
|
15
|
+
|
16
|
+
def initialize(payload)
|
17
|
+
@payload = payload
|
18
|
+
@tree = Parser.new.parse(payload)
|
19
|
+
|
20
|
+
Floe.logger.debug { "Parsed intrinsic function: #{payload.inspect} => #{tree.inspect}" }
|
21
|
+
rescue Parslet::ParseFailed => err
|
22
|
+
raise Floe::InvalidWorkflowError, err.message
|
23
|
+
end
|
24
|
+
|
25
|
+
def value(context = {}, input = {})
|
26
|
+
Transformer.new.apply(tree, :context => context, :input => input)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
attr_reader :tree
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/floe/workflow/path.rb
CHANGED
@@ -4,6 +4,10 @@ module Floe
|
|
4
4
|
class Workflow
|
5
5
|
class Path
|
6
6
|
class << self
|
7
|
+
def path?(payload)
|
8
|
+
payload.start_with?("$")
|
9
|
+
end
|
10
|
+
|
7
11
|
def value(payload, context, input = {})
|
8
12
|
new(payload).value(context, input)
|
9
13
|
end
|
@@ -26,8 +30,10 @@ module Floe
|
|
26
30
|
[input, payload]
|
27
31
|
end
|
28
32
|
|
29
|
-
|
33
|
+
# If path is $ then just return the entire input
|
34
|
+
return obj if path == "$"
|
30
35
|
|
36
|
+
results = JsonPath.on(obj, path)
|
31
37
|
results.count < 2 ? results.first : results
|
32
38
|
end
|
33
39
|
end
|
@@ -42,14 +42,17 @@ module Floe
|
|
42
42
|
end
|
43
43
|
|
44
44
|
def parse_payload_string(value)
|
45
|
-
|
45
|
+
return Path.new(value) if Path.path?(value)
|
46
|
+
return IntrinsicFunction.new(value) if IntrinsicFunction.intrinsic_function?(value)
|
47
|
+
|
48
|
+
value
|
46
49
|
end
|
47
50
|
|
48
51
|
def interpolate_value(value, context, inputs)
|
49
52
|
case value
|
50
|
-
when Array
|
51
|
-
when Hash
|
52
|
-
when Path
|
53
|
+
when Array then interpolate_value_array(value, context, inputs)
|
54
|
+
when Hash then interpolate_value_hash(value, context, inputs)
|
55
|
+
when Path, IntrinsicFunction then value.value(context, inputs)
|
53
56
|
else
|
54
57
|
value
|
55
58
|
end
|
@@ -25,12 +25,9 @@ module Floe
|
|
25
25
|
def set(context, value)
|
26
26
|
result = context.dup
|
27
27
|
|
28
|
-
# If the payload is '$' then
|
29
|
-
# otherwise store the value under the path
|
30
|
-
#
|
31
|
-
# TODO: how to handle non-hash values, raise error if path=$ and value not a hash?
|
28
|
+
# If the payload is '$' then replace the output with the value
|
32
29
|
if path.empty?
|
33
|
-
result.
|
30
|
+
result = value.dup
|
34
31
|
else
|
35
32
|
child = result
|
36
33
|
keys = path.dup
|
@@ -3,19 +3,26 @@
|
|
3
3
|
module Floe
|
4
4
|
class Workflow
|
5
5
|
class Retrier
|
6
|
-
|
6
|
+
include ErrorMatcherMixin
|
7
|
+
include ValidationMixin
|
7
8
|
|
8
|
-
|
9
|
-
|
9
|
+
attr_reader :error_equals, :interval_seconds, :max_attempts, :backoff_rate, :name
|
10
|
+
|
11
|
+
def initialize(_workflow, name, payload)
|
12
|
+
@name = name
|
13
|
+
@payload = payload
|
10
14
|
|
11
15
|
@error_equals = payload["ErrorEquals"]
|
12
16
|
@interval_seconds = payload["IntervalSeconds"] || 1.0
|
13
17
|
@max_attempts = payload["MaxAttempts"] || 3
|
14
18
|
@backoff_rate = payload["BackoffRate"] || 2.0
|
19
|
+
|
20
|
+
missing_field_error!("ErrorEquals") if !@error_equals.kind_of?(Array) || @error_equals.empty?
|
15
21
|
end
|
16
22
|
|
23
|
+
# @param [Integer] attempt 1 for the first attempt
|
17
24
|
def sleep_duration(attempt)
|
18
|
-
interval_seconds * (backoff_rate
|
25
|
+
interval_seconds * (backoff_rate**(attempt - 1))
|
19
26
|
end
|
20
27
|
end
|
21
28
|
end
|