floe 0.14.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/.codeclimate.yml +21 -1
- data/CHANGELOG.md +26 -1
- data/README.md +8 -0
- data/examples/map.asl +46 -0
- data/examples/parallel.asl +32 -0
- data/lib/floe/cli.rb +46 -13
- 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/branch.rb +8 -0
- data/lib/floe/workflow/choice_rule/data.rb +90 -43
- data/lib/floe/workflow/context.rb +5 -1
- data/lib/floe/workflow/item_processor.rb +14 -0
- data/lib/floe/workflow/state.rb +11 -4
- data/lib/floe/workflow/states/child_workflow_mixin.rb +57 -0
- data/lib/floe/workflow/states/choice.rb +5 -6
- data/lib/floe/workflow/states/map.rb +116 -2
- data/lib/floe/workflow/states/parallel.rb +65 -2
- data/lib/floe/workflow/states/retry_catch_mixin.rb +57 -0
- data/lib/floe/workflow/states/task.rb +3 -49
- data/lib/floe/workflow.rb +12 -35
- data/lib/floe/workflow_base.rb +108 -0
- data/lib/floe.rb +9 -2
- data/tools/step_functions +110 -0
- metadata +10 -3
- data/sig/floe.rbs/floe.rbs +0 -4
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/.codeclimate.yml
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
version: '2'
|
1
2
|
prepare:
|
2
3
|
fetch:
|
3
4
|
- url: https://raw.githubusercontent.com/ManageIQ/manageiq-style/master/.rubocop_base.yml
|
@@ -8,9 +9,28 @@ prepare:
|
|
8
9
|
path: styles/base.yml
|
9
10
|
- url: https://raw.githubusercontent.com/ManageIQ/manageiq-style/master/styles/cc_base.yml
|
10
11
|
path: styles/cc_base.yml
|
12
|
+
checks:
|
13
|
+
argument-count:
|
14
|
+
enabled: false
|
15
|
+
complex-logic:
|
16
|
+
enabled: false
|
17
|
+
file-lines:
|
18
|
+
enabled: false
|
19
|
+
method-complexity:
|
20
|
+
config:
|
21
|
+
threshold: 11
|
22
|
+
method-count:
|
23
|
+
enabled: false
|
24
|
+
method-lines:
|
25
|
+
enabled: false
|
26
|
+
nested-control-flow:
|
27
|
+
enabled: false
|
28
|
+
return-statements:
|
29
|
+
enabled: false
|
11
30
|
plugins:
|
12
31
|
rubocop:
|
13
32
|
enabled: true
|
14
33
|
config: ".rubocop_cc.yml"
|
15
34
|
channel: rubocop-1-56-3
|
16
|
-
|
35
|
+
exclude_patterns:
|
36
|
+
- spec/
|
data/CHANGELOG.md
CHANGED
@@ -4,6 +4,29 @@ 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
|
+
|
12
|
+
## [0.15.0] - 2024-10-28
|
13
|
+
### Added
|
14
|
+
- Add WorkflowBase base class for Workflow ([#279](https://github.com/ManageIQ/floe/pull/279))
|
15
|
+
- Add tool for using the aws stepfunctions simulator ([#244](https://github.com/ManageIQ/floe/pull/244))
|
16
|
+
- Implement Map state ([#184](https://github.com/ManageIQ/floe/pull/184))
|
17
|
+
- Add Map State Tolerated Failure ([#282](https://github.com/ManageIQ/floe/pull/282))
|
18
|
+
- Run Map iterations in parallel up to MaxConcurrency ([#283](https://github.com/ManageIQ/floe/pull/283))
|
19
|
+
- Implement Parallel State ([#291](https://github.com/ManageIQ/floe/pull/291))
|
20
|
+
|
21
|
+
### Changed
|
22
|
+
- More granular compare_key and determine path at initialization time ([#274](https://github.com/ManageIQ/floe/pull/274))
|
23
|
+
- For Choice validation, use instance variables and not payload ([#277](https://github.com/ManageIQ/floe/pull/277))
|
24
|
+
- Return ExceedToleratedFailureThreshold if ToleratedFailureCount/Percentage is present ([#285](https://github.com/ManageIQ/floe/pull/285))
|
25
|
+
|
26
|
+
### Fixed
|
27
|
+
- Fix case on log messages ([#280](https://github.com/ManageIQ/floe/pull/280))
|
28
|
+
- Handle either ToleratedFailureCount or ToleratedFailurePercentage ([#284](https://github.com/ManageIQ/floe/pull/284))
|
29
|
+
|
7
30
|
## [0.14.0] - 2024-08-20
|
8
31
|
### Added
|
9
32
|
- Implement "IsNumeric": false ([#266](https://github.com/ManageIQ/floe/pull/266))
|
@@ -249,7 +272,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|
249
272
|
### Added
|
250
273
|
- Initial release
|
251
274
|
|
252
|
-
[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
|
253
278
|
[0.14.0]: https://github.com/ManageIQ/floe/compare/v0.13.1...v0.14.0
|
254
279
|
[0.13.1]: https://github.com/ManageIQ/floe/compare/v0.13.0...v0.13.1
|
255
280
|
[0.13.0]: https://github.com/ManageIQ/floe/compare/v0.12.0...v0.13.0
|
data/README.md
CHANGED
@@ -197,6 +197,14 @@ Options supported by the kubernetes docker runner are:
|
|
197
197
|
* `ca_file` - Path to a certificate-authority file for the kubernetes API, only valid if server and token are passed. If present `/run/secrets/kubernetes.io/serviceaccount/ca.crt` will be used
|
198
198
|
* `verify_ssl` - Controls if the kubernetes API certificate-authority should be verified, defaults to "true", only vaild if server and token are passed
|
199
199
|
|
200
|
+
## Features Not Yet Supported
|
201
|
+
|
202
|
+
The following are not yet supported:
|
203
|
+
- Map State Fields:
|
204
|
+
- ItemReader
|
205
|
+
- ItemSelector/ItemBatcher
|
206
|
+
- ResultWriter
|
207
|
+
|
200
208
|
## Development
|
201
209
|
|
202
210
|
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/examples/map.asl
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
{
|
2
|
+
"Comment": "Using Map state in Inline mode",
|
3
|
+
"StartAt": "Pass",
|
4
|
+
"States": {
|
5
|
+
"Pass": {
|
6
|
+
"Type": "Pass",
|
7
|
+
"Next": "Map demo",
|
8
|
+
"Result": {
|
9
|
+
"foo": "bar",
|
10
|
+
"colors": [
|
11
|
+
"red",
|
12
|
+
"green",
|
13
|
+
"blue",
|
14
|
+
"yellow",
|
15
|
+
"white"
|
16
|
+
]
|
17
|
+
}
|
18
|
+
},
|
19
|
+
"Map demo": {
|
20
|
+
"Type": "Map",
|
21
|
+
"ItemsPath": "$.colors",
|
22
|
+
"MaxConcurrency": 2,
|
23
|
+
"ItemProcessor": {
|
24
|
+
"ProcessorConfig": {
|
25
|
+
"Mode": "INLINE"
|
26
|
+
},
|
27
|
+
"StartAt": "Generate UUID",
|
28
|
+
"States": {
|
29
|
+
"Generate UUID": {
|
30
|
+
"Type": "Pass",
|
31
|
+
"Next": "Sleep",
|
32
|
+
"Parameters": {
|
33
|
+
"uuid.$": "States.UUID()"
|
34
|
+
}
|
35
|
+
},
|
36
|
+
"Sleep": {
|
37
|
+
"Type": "Task",
|
38
|
+
"Resource": "docker://docker.io/agrare/sleep:latest",
|
39
|
+
"End": true
|
40
|
+
}
|
41
|
+
}
|
42
|
+
},
|
43
|
+
"End": true
|
44
|
+
}
|
45
|
+
}
|
46
|
+
}
|
@@ -0,0 +1,32 @@
|
|
1
|
+
{
|
2
|
+
"Comment": "Parallel Example.",
|
3
|
+
"StartAt": "FunWithMath",
|
4
|
+
"States": {
|
5
|
+
"FunWithMath": {
|
6
|
+
"Type": "Parallel",
|
7
|
+
"End": true,
|
8
|
+
"Branches": [
|
9
|
+
{
|
10
|
+
"StartAt": "Add",
|
11
|
+
"States": {
|
12
|
+
"Add": {
|
13
|
+
"Type": "Task",
|
14
|
+
"Resource": "docker://docker.io/agrare/sleep:latest",
|
15
|
+
"End": true
|
16
|
+
}
|
17
|
+
}
|
18
|
+
},
|
19
|
+
{
|
20
|
+
"StartAt": "Subtract",
|
21
|
+
"States": {
|
22
|
+
"Subtract": {
|
23
|
+
"Type": "Task",
|
24
|
+
"Resource": "docker://docker.io/agrare/sleep:latest",
|
25
|
+
"End": true
|
26
|
+
}
|
27
|
+
}
|
28
|
+
}
|
29
|
+
]
|
30
|
+
}
|
31
|
+
}
|
32
|
+
}
|
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)
|
@@ -13,25 +16,29 @@ module Floe
|
|
13
16
|
def run(args = ARGV)
|
14
17
|
workflows_inputs, opts = parse_options!(args)
|
15
18
|
|
16
|
-
credentials =
|
17
|
-
if opts[:credentials_given]
|
18
|
-
opts[:credentials] == "-" ? $stdin.read : opts[:credentials]
|
19
|
-
elsif opts[:credentials_file_given]
|
20
|
-
File.read(opts[:credentials_file])
|
21
|
-
end
|
19
|
+
credentials = create_credentials(opts)
|
22
20
|
|
23
21
|
workflows =
|
24
22
|
workflows_inputs.each_slice(2).map do |workflow, input|
|
25
|
-
|
26
|
-
Floe::Workflow.load(workflow, context)
|
23
|
+
create_workflow(workflow, opts[:context], input, credentials)
|
27
24
|
end
|
28
25
|
|
29
|
-
|
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")
|
30
31
|
|
31
32
|
# Display status
|
32
33
|
workflows.each do |workflow|
|
33
|
-
|
34
|
-
|
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)
|
35
42
|
end
|
36
43
|
|
37
44
|
workflows.all? { |workflow| workflow.context.success? }
|
@@ -55,6 +62,7 @@ module Floe
|
|
55
62
|
opt :context, "JSON payload of the Context", :type => :string
|
56
63
|
opt :credentials, "JSON payload with Credentials", :type => :string
|
57
64
|
opt :credentials_file, "Path to a file with Credentials", :type => :string
|
65
|
+
opt :segment_output, "Segment output by each worker", :default => false
|
58
66
|
|
59
67
|
Floe::ContainerRunner.cli_options(self)
|
60
68
|
|
@@ -82,5 +90,30 @@ module Floe
|
|
82
90
|
|
83
91
|
return workflows_inputs, opts
|
84
92
|
end
|
93
|
+
|
94
|
+
def create_credentials(opts)
|
95
|
+
if opts[:credentials_given]
|
96
|
+
opts[:credentials] == "-" ? $stdin.read : opts[:credentials]
|
97
|
+
elsif opts[:credentials_file_given]
|
98
|
+
File.read(opts[:credentials_file])
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def create_workflow(workflow, context_payload, input, credentials)
|
103
|
+
context = Floe::Workflow::Context.new(context_payload, :input => input, :credentials => credentials)
|
104
|
+
Floe::Workflow.load(workflow, context)
|
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
|
85
118
|
end
|
86
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
@@ -4,16 +4,20 @@ module Floe
|
|
4
4
|
class Workflow
|
5
5
|
class ChoiceRule
|
6
6
|
class Data < Floe::Workflow::ChoiceRule
|
7
|
-
|
7
|
+
TYPES = ["String", "Numeric", "Boolean", "Timestamp", "Present", "Null"].freeze
|
8
|
+
COMPARES = ["Equals", "LessThan", "GreaterThan", "LessThanEquals", "GreaterThanEquals", "Matches"].freeze
|
9
|
+
# e.g.: (Is)(String), (Is)(Present)
|
10
|
+
TYPE_CHECK = /^(Is)(#{TYPES.join("|")})$/.freeze
|
11
|
+
# e.g.: (String)(LessThan)(Path), (Numeric)(GreaterThanEquals)()
|
12
|
+
OPERATION = /^(#{(TYPES - %w[Null Present]).join("|")})(#{COMPARES.join("|")})(Path)?$/.freeze
|
8
13
|
|
9
|
-
attr_reader :variable, :compare_key, :
|
14
|
+
attr_reader :variable, :compare_key, :operation, :type, :compare_predicate, :path
|
10
15
|
|
11
16
|
def initialize(_workflow, _name, payload)
|
12
17
|
super
|
13
18
|
|
14
|
-
@variable = parse_path("Variable"
|
19
|
+
@variable = parse_path("Variable")
|
15
20
|
parse_compare_key
|
16
|
-
@value = path ? parse_path(compare_key, payload) : payload[compare_key]
|
17
21
|
end
|
18
22
|
|
19
23
|
def true?(context, input)
|
@@ -21,39 +25,7 @@ module Floe
|
|
21
25
|
|
22
26
|
lhs = variable_value(context, input)
|
23
27
|
rhs = compare_value(context, input)
|
24
|
-
|
25
|
-
case compare_key
|
26
|
-
when "IsNull" then is_null?(lhs, rhs)
|
27
|
-
when "IsNumeric" then is_numeric?(lhs, rhs)
|
28
|
-
when "IsString" then is_string?(lhs, rhs)
|
29
|
-
when "IsBoolean" then is_boolean?(lhs, rhs)
|
30
|
-
when "IsTimestamp" then is_timestamp?(lhs, rhs)
|
31
|
-
when "StringEquals", "StringEqualsPath",
|
32
|
-
"NumericEquals", "NumericEqualsPath",
|
33
|
-
"BooleanEquals", "BooleanEqualsPath",
|
34
|
-
"TimestampEquals", "TimestampEqualsPath"
|
35
|
-
lhs == rhs
|
36
|
-
when "StringLessThan", "StringLessThanPath",
|
37
|
-
"NumericLessThan", "NumericLessThanPath",
|
38
|
-
"TimestampLessThan", "TimestampLessThanPath"
|
39
|
-
lhs < rhs
|
40
|
-
when "StringGreaterThan", "StringGreaterThanPath",
|
41
|
-
"NumericGreaterThan", "NumericGreaterThanPath",
|
42
|
-
"TimestampGreaterThan", "TimestampGreaterThanPath"
|
43
|
-
lhs > rhs
|
44
|
-
when "StringLessThanEquals", "StringLessThanEqualsPath",
|
45
|
-
"NumericLessThanEquals", "NumericLessThanEqualsPath",
|
46
|
-
"TimestampLessThanEquals", "TimestampLessThanEqualsPath"
|
47
|
-
lhs <= rhs
|
48
|
-
when "StringGreaterThanEquals", "StringGreaterThanEqualsPath",
|
49
|
-
"NumericGreaterThanEquals", "NumericGreaterThanEqualsPath",
|
50
|
-
"TimestampGreaterThanEquals", "TimestampGreaterThanEqualsPath"
|
51
|
-
lhs >= rhs
|
52
|
-
when "StringMatches"
|
53
|
-
lhs.match?(Regexp.escape(rhs).gsub('\*', '.*?'))
|
54
|
-
else
|
55
|
-
raise Floe::InvalidWorkflowError, "Invalid choice [#{compare_key}]"
|
56
|
-
end
|
28
|
+
send(operation, lhs, rhs)
|
57
29
|
end
|
58
30
|
|
59
31
|
private
|
@@ -108,26 +80,101 @@ module Floe
|
|
108
80
|
# rubocop:enable Naming/PredicateName
|
109
81
|
# rubocop:enable Style/OptionalBooleanParameter
|
110
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
|
+
|
107
|
+
# parse the compare key at initialization time
|
111
108
|
def parse_compare_key
|
112
|
-
|
113
|
-
|
109
|
+
payload.each_key do |key|
|
110
|
+
# e.g. (String)(GreaterThan)(Path)
|
111
|
+
if (match_values = OPERATION.match(key))
|
112
|
+
@compare_key = key
|
113
|
+
@type, operator, @path = match_values.captures
|
114
|
+
@operation = "#{operator.downcase}?".to_sym
|
115
|
+
@compare_predicate = parse_predicate(type)
|
116
|
+
break
|
117
|
+
end
|
118
|
+
# e.g. (Is)(String)
|
119
|
+
if (match_value = TYPE_CHECK.match(key))
|
120
|
+
@compare_key = key
|
121
|
+
_operator, type = match_value.captures
|
122
|
+
# type: nil means no runtime type checking.
|
123
|
+
@type = @path = nil
|
124
|
+
@operation = "is_#{type.downcase}?".to_sym
|
125
|
+
@compare_predicate = parse_predicate("Boolean")
|
126
|
+
break
|
127
|
+
end
|
128
|
+
end
|
129
|
+
parser_error!("requires a compare key") if compare_key.nil? || operation.nil?
|
130
|
+
end
|
114
131
|
|
115
|
-
|
132
|
+
# parse predicate at initilization time
|
133
|
+
# @return the right predicate attached to the compare key
|
134
|
+
def parse_predicate(data_type)
|
135
|
+
path ? parse_path(compare_key) : parse_field(compare_key, data_type)
|
116
136
|
end
|
117
137
|
|
138
|
+
# @return right hand predicate - input path or static payload value)
|
118
139
|
def compare_value(context, input)
|
119
|
-
path ?
|
140
|
+
path ? fetch_path(compare_key, compare_predicate, context, input) : compare_predicate
|
120
141
|
end
|
121
142
|
|
143
|
+
# feth the variable value at runtime
|
144
|
+
# @return variable value (left hand side )
|
122
145
|
def variable_value(context, input)
|
123
|
-
variable
|
146
|
+
fetch_path("Variable", variable, context, input)
|
124
147
|
end
|
125
148
|
|
126
|
-
|
149
|
+
# parse path at initilization time
|
150
|
+
# helper method to parse a path from the payload
|
151
|
+
def parse_path(field_name)
|
127
152
|
value = payload[field_name]
|
128
153
|
missing_field_error!(field_name) unless value
|
129
154
|
wrap_parser_error(field_name, value) { Path.new(value) }
|
130
155
|
end
|
156
|
+
|
157
|
+
# parse predicate field at initialization time
|
158
|
+
def parse_field(field_name, data_type)
|
159
|
+
value = payload[field_name]
|
160
|
+
return value if correct_type?(value, data_type)
|
161
|
+
|
162
|
+
invalid_field_error!(field_name, value, "required to be a #{data_type}")
|
163
|
+
end
|
164
|
+
|
165
|
+
# fetch a path at runtime
|
166
|
+
def fetch_path(field_name, field_path, context, input)
|
167
|
+
value = field_path.value(context, input)
|
168
|
+
return value if type.nil? || correct_type?(value, type)
|
169
|
+
|
170
|
+
runtime_field_error!(field_name, field_path.to_s, "required to point to a #{type}")
|
171
|
+
end
|
172
|
+
|
173
|
+
# if we have runtime checking, check against that type
|
174
|
+
# otherwise assume checking a TYPE_CHECK predicate and check against Boolean
|
175
|
+
def correct_type?(value, data_type)
|
176
|
+
send("is_#{data_type.downcase}?".to_sym, value)
|
177
|
+
end
|
131
178
|
end
|
132
179
|
end
|
133
180
|
end
|
@@ -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
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Floe
|
4
|
+
class Workflow
|
5
|
+
class ItemProcessor < Floe::WorkflowBase
|
6
|
+
attr_reader :processor_config
|
7
|
+
|
8
|
+
def initialize(payload, name = nil)
|
9
|
+
super
|
10
|
+
@processor_config = payload.fetch("ProcessorConfig", "INLINE")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
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)
|