floe 0.14.0 → 0.15.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/.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)
|