floe 0.11.2 → 0.12.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|