floe 0.16.0 → 0.17.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 +17 -1
- data/README.md +75 -3
- data/examples/everything.asl +146 -0
- data/examples/http.asl +40 -0
- data/examples/set-credential.asl +1 -1
- data/floe.gemspec +3 -1
- data/lib/floe/builtin_runner/methods.rb +82 -0
- data/lib/floe/builtin_runner/runner.rb +53 -0
- data/lib/floe/builtin_runner.rb +25 -0
- data/lib/floe/cli.rb +8 -5
- data/lib/floe/version.rb +1 -1
- data/lib/floe/workflow/context.rb +17 -6
- data/lib/floe/workflow/payload_template.rb +29 -21
- data/lib/floe/workflow/reference_path.rb +5 -2
- data/lib/floe/workflow/states/child_workflow_mixin.rb +1 -1
- data/lib/floe/workflow/states/input_output_mixin.rb +2 -3
- data/lib/floe/workflow/states/task.rb +2 -1
- data/lib/floe/workflow.rb +1 -1
- data/lib/floe.rb +1 -0
- metadata +37 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2b11d410b8a7518f7030bac6a5a29142167f54239e7cf1f966c6a82c2a191207
|
4
|
+
data.tar.gz: 793dd3f55b6d89b22d335831ca0a8663015ad520e8cf92603884f9168a64c8f6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c495de319815ea25d470262472e31675d016d5aa8d6ca761e64eb94556617809e7311b4dcce2242f1ccf0910c893679e44729357dca99b6b9b5299dff2fee0db
|
7
|
+
data.tar.gz: bcfdf7696caeaf80d6464dcc375747a40127c8327d1a75e486ad56e5d9618ef4f6ea73a3a5c35bb11f0f0a8c1e844b208b4a676f4555142cd7047c021d23dccf
|
data/CHANGELOG.md
CHANGED
@@ -4,6 +4,21 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|
4
4
|
|
5
5
|
## [Unreleased]
|
6
6
|
|
7
|
+
## [0.17.0] - 2025-08-19
|
8
|
+
### Fixed
|
9
|
+
- Fix credentials passed via CLI ([#307](https://github.com/ManageIQ/floe/pull/307))
|
10
|
+
- Fix nested PayloadTemplate interpolation ([#311](https://github.com/ManageIQ/floe/pull/311))
|
11
|
+
|
12
|
+
NOTE: Using `".$"` for hash keys is no longer required and will result in an error
|
13
|
+
|
14
|
+
### Changed
|
15
|
+
- Allow credentials to be referenced from parameters ([#308](https://github.com/ManageIQ/floe/pull/308))
|
16
|
+
|
17
|
+
NOTE: Setting credentials via ResultPath now uses `$$.Credentials`
|
18
|
+
|
19
|
+
### Added
|
20
|
+
- Add floe:// builtin runner and floe://http method ([#306](https://github.com/ManageIQ/floe/pull/306))
|
21
|
+
|
7
22
|
## [0.16.0] - 2025-04-08
|
8
23
|
### Added
|
9
24
|
- Add Map state ItemBatcher/ItemSelector support ([#294](https://github.com/ManageIQ/floe/pull/294))
|
@@ -279,7 +294,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|
279
294
|
### Added
|
280
295
|
- Initial release
|
281
296
|
|
282
|
-
[Unreleased]: https://github.com/ManageIQ/floe/compare/v0.
|
297
|
+
[Unreleased]: https://github.com/ManageIQ/floe/compare/v0.17.0...HEAD
|
298
|
+
[0.16.0]: https://github.com/ManageIQ/floe/compare/v0.16.0...v0.17.0
|
283
299
|
[0.16.0]: https://github.com/ManageIQ/floe/compare/v0.15.1...v0.16.0
|
284
300
|
[0.15.1]: https://github.com/ManageIQ/floe/compare/v0.15.0...v0.15.1
|
285
301
|
[0.15.0]: https://github.com/ManageIQ/floe/compare/v0.14.0...v0.15.0
|
data/README.md
CHANGED
@@ -61,7 +61,7 @@ Or you can pass a file path with the `--credentials-file` parameter:
|
|
61
61
|
bundle exec ruby exe/floe --workflow my-workflow.asl --credentials-file /tmp/20231218-80537-kj494t
|
62
62
|
```
|
63
63
|
|
64
|
-
If you need to set a credential at runtime you can do that by using the `"ResultPath": "
|
64
|
+
If you need to set a credential at runtime you can do that by using the `"ResultPath": "$$.Credentials"` directive, for example to user a username/password to login and get a Bearer token:
|
65
65
|
|
66
66
|
```
|
67
67
|
bundle exec ruby exe/floe --workflow my-workflow.asl --credentials='{"username": "user", "password": "pass"}'
|
@@ -78,14 +78,14 @@ bundle exec ruby exe/floe --workflow my-workflow.asl --credentials='{"username":
|
|
78
78
|
"username.$": "$.username",
|
79
79
|
"password.$": "$.password"
|
80
80
|
},
|
81
|
-
"ResultPath": "
|
81
|
+
"ResultPath": "$$.Credentials",
|
82
82
|
"Next": "DoSomething"
|
83
83
|
},
|
84
84
|
"DoSomething": {
|
85
85
|
"Type": "Task",
|
86
86
|
"Resource": "docker://do-something:latest",
|
87
87
|
"Credentials": {
|
88
|
-
"token.$": "
|
88
|
+
"token.$": "$$.Credentials.bearer_token"
|
89
89
|
},
|
90
90
|
"End": true
|
91
91
|
}
|
@@ -155,6 +155,78 @@ until running_workflows.empty?
|
|
155
155
|
end
|
156
156
|
```
|
157
157
|
|
158
|
+
### Task Runners
|
159
|
+
|
160
|
+
Floe provides a number of options for the Task `"Resource"` parameter. The `"Resource"` runner is declared by the author based on the `URI`.
|
161
|
+
|
162
|
+
Task Runner Types:
|
163
|
+
* `floe://`
|
164
|
+
* `docker://`
|
165
|
+
|
166
|
+
#### Floe resource
|
167
|
+
|
168
|
+
This is the "builtin" runner and exposes methods that are executed internally without having to call out to a docker image.
|
169
|
+
|
170
|
+
##### HTTP builtin method
|
171
|
+
|
172
|
+
`floe://http` allows you to execute HTTP method calls.
|
173
|
+
|
174
|
+
Example:
|
175
|
+
```json
|
176
|
+
{
|
177
|
+
"Comment": "Execute a HTTP call",
|
178
|
+
"StartAt": "HTTP",
|
179
|
+
"States": {
|
180
|
+
"HTTP": {
|
181
|
+
"Type": "Task",
|
182
|
+
"Resource": "floe://http",
|
183
|
+
"Parameters": {
|
184
|
+
"Method": "POST",
|
185
|
+
"Url": "http://localhost:3000/api/login",
|
186
|
+
"Headers": {"ContentType": "application/json"},
|
187
|
+
"Body.$": {"username.$": "$$.Credentials.username", "password.$": "$$.Credentials.password"},
|
188
|
+
"Options": {"Encoding": "JSON"}
|
189
|
+
},
|
190
|
+
"ResultSelector": {"auth_token.$": "$.Body.auth_token"},
|
191
|
+
"ResultPath": "$$.Credentials",
|
192
|
+
"End": true
|
193
|
+
}
|
194
|
+
```
|
195
|
+
|
196
|
+
HTTP Parameters:
|
197
|
+
* `Method` (required) - HTTP method name. Permitted values: `GET`, `POST`, `PUT`, `DELETE`, `HEAD`, `PATCH`, `OPTIONS`, or `TRACE`
|
198
|
+
* `Url` (required) - URL to execute the HTTP call to
|
199
|
+
* `Headers` - Hash of unencoded HTTP request header key/value pairs.
|
200
|
+
* `QueryParameters` - URI query unencoded key/value pairs.
|
201
|
+
* `Body` - HTTP request body. Depending on Encoding this can be a String or a Hash of key/value pairs.
|
202
|
+
* `Ssl` - SSL options
|
203
|
+
* `Verify` - Boolean - Verify SSL certificate.
|
204
|
+
* `VerifyHostname` - Boolean - Verify SSL certificate hostname.
|
205
|
+
* `Hostname` - String - Server hostname for SNI.
|
206
|
+
* `CaFile` - String - Path to a CA file in PEM format.
|
207
|
+
* `CaPath` - String - Path to a CA directory.
|
208
|
+
* `VerifyMode` - Integer - OpenSSL constant.
|
209
|
+
* `VerifyDepth` - Integer - Maximum depth for the certificate chain validation.
|
210
|
+
* `Version` - Integer - SSL Version.
|
211
|
+
* `MinVersion` - Integer - Minimum SSL Version.
|
212
|
+
* `MaxVersion` - Integer - Maximum SSL Version.
|
213
|
+
* `Ciphers` - String - Ciphers supported.
|
214
|
+
* `Options`
|
215
|
+
* `Timeout`
|
216
|
+
* `ReadTimeout`
|
217
|
+
* `OpenTimeout`
|
218
|
+
* `WriteTimeout`
|
219
|
+
* `Encoding` - String
|
220
|
+
* `JSON` - JSON encodes the request and decodes the response
|
221
|
+
* `Proxy`
|
222
|
+
* `Uri` - String - URI of the proxy.
|
223
|
+
* `User` - String - User for the proxy.
|
224
|
+
* `Password` - String - Pasword for the proxy
|
225
|
+
|
226
|
+
#### Docker resource
|
227
|
+
|
228
|
+
The docker resource runner takes a docker image URI, including the registry, name, and tag.
|
229
|
+
|
158
230
|
### Docker Runner Options
|
159
231
|
|
160
232
|
#### Docker
|
@@ -0,0 +1,146 @@
|
|
1
|
+
{
|
2
|
+
"Comment": "An example of the Amazon States Language with all states.",
|
3
|
+
"StartAt": "FirstState",
|
4
|
+
"States": {
|
5
|
+
"FirstState": {
|
6
|
+
"Type": "Task",
|
7
|
+
"Resource": "docker://docker.io/agrare/hello-world:latest",
|
8
|
+
"Credentials": {
|
9
|
+
"mysecret": "dont tell anyone"
|
10
|
+
},
|
11
|
+
"Retry": [
|
12
|
+
{
|
13
|
+
"ErrorEquals": [ "States.Timeout" ],
|
14
|
+
"IntervalSeconds": 3,
|
15
|
+
"MaxAttempts": 2,
|
16
|
+
"BackoffRate": 1.5
|
17
|
+
}
|
18
|
+
],
|
19
|
+
"Catch": [
|
20
|
+
{
|
21
|
+
"ErrorEquals": [ "States.ALL" ],
|
22
|
+
"Next": "FailState"
|
23
|
+
}
|
24
|
+
],
|
25
|
+
"Next": "ChoiceState"
|
26
|
+
},
|
27
|
+
|
28
|
+
"ChoiceState": {
|
29
|
+
"Type" : "Choice",
|
30
|
+
"Choices": [
|
31
|
+
{
|
32
|
+
"Variable": "$.foo",
|
33
|
+
"NumericEquals": 1,
|
34
|
+
"Next": "FirstMatchState"
|
35
|
+
},
|
36
|
+
{
|
37
|
+
"Variable": "$.foo",
|
38
|
+
"NumericEquals": 2,
|
39
|
+
"Next": "SecondMatchState"
|
40
|
+
},
|
41
|
+
{
|
42
|
+
"Variable": "$.foo",
|
43
|
+
"NumericEquals": 3,
|
44
|
+
"Next": "SuccessState"
|
45
|
+
}
|
46
|
+
],
|
47
|
+
"Default": "FailState"
|
48
|
+
},
|
49
|
+
|
50
|
+
"FirstMatchState": {
|
51
|
+
"Type" : "Task",
|
52
|
+
"Resource": "docker://docker.io/agrare/hello-world:latest",
|
53
|
+
"Next": "PassState"
|
54
|
+
},
|
55
|
+
|
56
|
+
"SecondMatchState": {
|
57
|
+
"Type" : "Task",
|
58
|
+
"Resource": "docker://docker.io/agrare/hello-world:latest",
|
59
|
+
"Next": "WaitState"
|
60
|
+
},
|
61
|
+
|
62
|
+
"WaitState": {
|
63
|
+
"Type": "Wait",
|
64
|
+
"Seconds": 1,
|
65
|
+
"Next": "PassState"
|
66
|
+
},
|
67
|
+
|
68
|
+
"PassState": {
|
69
|
+
"Type": "Pass",
|
70
|
+
"Next": "MapState",
|
71
|
+
"Result": {
|
72
|
+
"foo": "bar",
|
73
|
+
"colors": [
|
74
|
+
"red",
|
75
|
+
"green",
|
76
|
+
"blue",
|
77
|
+
"yellow",
|
78
|
+
"white"
|
79
|
+
]
|
80
|
+
}
|
81
|
+
},
|
82
|
+
|
83
|
+
"FailState": {
|
84
|
+
"Type": "Fail",
|
85
|
+
"Error": "FailStateError",
|
86
|
+
"Cause": "No Matches!"
|
87
|
+
},
|
88
|
+
|
89
|
+
"MapState": {
|
90
|
+
"Type": "Map",
|
91
|
+
"ItemsPath": "$.colors",
|
92
|
+
"MaxConcurrency": 2,
|
93
|
+
"ItemProcessor": {
|
94
|
+
"ProcessorConfig": {
|
95
|
+
"Mode": "INLINE"
|
96
|
+
},
|
97
|
+
"StartAt": "Generate UUID",
|
98
|
+
"States": {
|
99
|
+
"Generate UUID": {
|
100
|
+
"Type": "Pass",
|
101
|
+
"Next": "PassState",
|
102
|
+
"Parameters": {
|
103
|
+
"uuid.$": "States.UUID()"
|
104
|
+
}
|
105
|
+
},
|
106
|
+
"PassState": {
|
107
|
+
"Type": "Pass",
|
108
|
+
"End": true
|
109
|
+
}
|
110
|
+
}
|
111
|
+
},
|
112
|
+
"Next": "ParallelState"
|
113
|
+
},
|
114
|
+
|
115
|
+
"ParallelState": {
|
116
|
+
"Type": "Parallel",
|
117
|
+
"Next": "SuccessState",
|
118
|
+
"Branches": [
|
119
|
+
{
|
120
|
+
"StartAt": "Add",
|
121
|
+
"States": {
|
122
|
+
"Add": {
|
123
|
+
"Type": "Task",
|
124
|
+
"Resource": "docker://docker.io/agrare/sleep:latest",
|
125
|
+
"End": true
|
126
|
+
}
|
127
|
+
}
|
128
|
+
},
|
129
|
+
{
|
130
|
+
"StartAt": "Subtract",
|
131
|
+
"States": {
|
132
|
+
"Subtract": {
|
133
|
+
"Type": "Task",
|
134
|
+
"Resource": "docker://docker.io/agrare/sleep:latest",
|
135
|
+
"End": true
|
136
|
+
}
|
137
|
+
}
|
138
|
+
}
|
139
|
+
]
|
140
|
+
},
|
141
|
+
|
142
|
+
"SuccessState": {
|
143
|
+
"Type": "Succeed"
|
144
|
+
}
|
145
|
+
}
|
146
|
+
}
|
data/examples/http.asl
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
{
|
2
|
+
"Comment": "Execute a REST API call",
|
3
|
+
"StartAt": "Login",
|
4
|
+
"States": {
|
5
|
+
"Login": {
|
6
|
+
"Type": "Task",
|
7
|
+
"Resource": "floe://http",
|
8
|
+
"Parameters": {
|
9
|
+
"Method": "GET",
|
10
|
+
"Url": "http://localhost:3000/api/auth",
|
11
|
+
"Headers": {
|
12
|
+
"ContentType": "application/json",
|
13
|
+
"Authorization.$": "States.Format('Basic {}', States.Base64Encode(States.Format('{}:{}', $$.Credentials.username, $$.Credentials.password)))"
|
14
|
+
},
|
15
|
+
"Options": {"Encoding": "JSON"}
|
16
|
+
},
|
17
|
+
"ResultSelector": {"auth_token.$": "$.Body.auth_token"},
|
18
|
+
"ResultPath": "$$.Credentials",
|
19
|
+
"Next": "List VMs"
|
20
|
+
},
|
21
|
+
"List VMs": {
|
22
|
+
"Type": "Task",
|
23
|
+
"Resource": "floe://http",
|
24
|
+
"Parameters": {
|
25
|
+
"Method": "GET",
|
26
|
+
"Url": "http://localhost:3000/api/vms",
|
27
|
+
"Headers": {
|
28
|
+
"ContentType": "application/json",
|
29
|
+
"X-Auth-Token.$": "$$.Credentials.auth_token"
|
30
|
+
},
|
31
|
+
"Options": {"Encoding": "JSON"}
|
32
|
+
},
|
33
|
+
"ResultSelector": {
|
34
|
+
"Status.$": "$.Status",
|
35
|
+
"Resources.$": "$.Body.resources"
|
36
|
+
},
|
37
|
+
"End": true
|
38
|
+
}
|
39
|
+
}
|
40
|
+
}
|
data/examples/set-credential.asl
CHANGED
data/floe.gemspec
CHANGED
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
|
|
11
11
|
spec.description = spec.summary
|
12
12
|
spec.homepage = "https://github.com/ManageIQ/floe"
|
13
13
|
spec.licenses = ["Apache-2.0"]
|
14
|
-
spec.required_ruby_version = ">=
|
14
|
+
spec.required_ruby_version = ">= 3.0.0"
|
15
15
|
|
16
16
|
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
17
17
|
spec.metadata['rubygems_mfa_required'] = "true"
|
@@ -38,6 +38,8 @@ Gem::Specification.new do |spec|
|
|
38
38
|
spec.add_dependency "optimist", "~>3.0"
|
39
39
|
spec.add_dependency "parslet", "~>2.0"
|
40
40
|
spec.add_dependency "json", "~>2.10"
|
41
|
+
spec.add_dependency "faraday"
|
42
|
+
spec.add_dependency "faraday-follow_redirects"
|
41
43
|
|
42
44
|
spec.add_development_dependency "manageiq-style", ">= 1.5.2"
|
43
45
|
spec.add_development_dependency "rake", "~> 13.0"
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Floe
|
2
|
+
module BuiltinRunner
|
3
|
+
class Methods < BasicObject
|
4
|
+
def self.http(params, _secrets, _context)
|
5
|
+
error = http_verify_params(params)
|
6
|
+
return BuiltinRunner.error!({}, :cause => error) if error
|
7
|
+
|
8
|
+
method, url, headers, query, body =
|
9
|
+
params.values_at("Method", "Url", "Headers", "QueryParameters", "Body")
|
10
|
+
|
11
|
+
ssl = {
|
12
|
+
"verify" => params.dig("Ssl", "Verify"),
|
13
|
+
"verify_hostname" => params.dig("Ssl", "VerifyHostname"),
|
14
|
+
"hostname" => params.dig("Ssl", "Hostname"),
|
15
|
+
"ca_file" => params.dig("Ssl", "CaFile"),
|
16
|
+
"ca_path" => params.dig("Ssl", "CaPath"),
|
17
|
+
"verify_mode" => params.dig("Ssl", "VerifyMode"),
|
18
|
+
"verify_depth" => params.dig("Ssl", "VerifyDepth"),
|
19
|
+
"version" => params.dig("Ssl", "Version"),
|
20
|
+
"min_version" => params.dig("Ssl", "MinVersion"),
|
21
|
+
"max_version" => params.dig("Ssl", "MaxVersion"),
|
22
|
+
"ciphers" => params.dig("Ssl", "Ciphers")
|
23
|
+
}.compact
|
24
|
+
|
25
|
+
request = {
|
26
|
+
"timeout" => params.dig("Options", "Timeout"),
|
27
|
+
"read_timeout" => params.dig("Options", "ReadTimeout"),
|
28
|
+
"open_timeout" => params.dig("Options", "OpenTimeout"),
|
29
|
+
"write_timeout" => params.dig("Options", "WriteTimeout")
|
30
|
+
}.compact
|
31
|
+
|
32
|
+
proxy = {
|
33
|
+
"uri" => params.dig("Proxy", "Uri"),
|
34
|
+
"user" => params.dig("Proxy", "User"),
|
35
|
+
"password" => params.dig("Proxy", "Password")
|
36
|
+
}.compact
|
37
|
+
|
38
|
+
connection_options = {
|
39
|
+
:url => url,
|
40
|
+
:params => query,
|
41
|
+
:headers => headers,
|
42
|
+
:request => (request unless request.empty?),
|
43
|
+
:proxy => (proxy unless proxy.empty?),
|
44
|
+
:ssl => (ssl unless ssl.empty?)
|
45
|
+
}
|
46
|
+
|
47
|
+
require "faraday"
|
48
|
+
connection = ::Faraday.new(connection_options)
|
49
|
+
|
50
|
+
if params.dig("Options", "Encoding") == "JSON"
|
51
|
+
connection.request(:json)
|
52
|
+
connection.response(:json)
|
53
|
+
end
|
54
|
+
|
55
|
+
if params.dig("Options", "FollowRedirects") != false
|
56
|
+
require "faraday/follow_redirects"
|
57
|
+
connection.response(:follow_redirects)
|
58
|
+
end
|
59
|
+
|
60
|
+
response = connection.send(method.downcase) do |request|
|
61
|
+
request.body = body if body
|
62
|
+
end
|
63
|
+
|
64
|
+
output = {"Status" => response.status, "Body" => response.body, "Headers" => response.headers}
|
65
|
+
|
66
|
+
BuiltinRunner.success!({}, :output => output)
|
67
|
+
end
|
68
|
+
|
69
|
+
private_class_method def self.http_verify_params(params)
|
70
|
+
return "Missing Parameter: Url" if params["Url"].nil?
|
71
|
+
return "Missing Parameter: Method" if params["Method"].nil?
|
72
|
+
return "Invalid Parameter: Method: [#{params["Method"]}], must be GET, POST, PUT, DELETE, HEAD, PATCH, OPTIONS, or TRACE" unless %w[GET POST PUT DELETE HEAD PATCH OPTIONS TRACE].include?(params["Method"])
|
73
|
+
|
74
|
+
nil
|
75
|
+
end
|
76
|
+
|
77
|
+
private_class_method def self.http_status!(runner_context)
|
78
|
+
runner_context
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Floe
|
2
|
+
module BuiltinRunner
|
3
|
+
class Runner < Floe::Runner
|
4
|
+
def run_async!(resource, params, secrets, context)
|
5
|
+
raise ArgumentError, "Invalid resource" unless resource&.start_with?(SCHEME_PREFIX)
|
6
|
+
|
7
|
+
method_name = resource.sub(SCHEME_PREFIX, "")
|
8
|
+
|
9
|
+
begin
|
10
|
+
runner_context = {"method" => method_name}
|
11
|
+
method_result = Methods.public_send(method_name, params, secrets, context)
|
12
|
+
method_result.merge(runner_context)
|
13
|
+
rescue NoMethodError
|
14
|
+
Floe::BuiltinRunner.error!(runner_context, :cause => "undefined method [#{method_name}]")
|
15
|
+
rescue => err
|
16
|
+
Floe::BuiltinRunner.error!(runner_context, :cause => err.to_s)
|
17
|
+
ensure
|
18
|
+
cleanup(runner_context)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def cleanup(runner_context)
|
23
|
+
method_name = runner_context["method"]
|
24
|
+
raise ArgumentError if method_name.nil?
|
25
|
+
|
26
|
+
cleanup_method = "#{method_name}_cleanup"
|
27
|
+
return unless Methods.respond_to?(cleanup_method, true)
|
28
|
+
|
29
|
+
Methods.send(cleanup_method, runner_context)
|
30
|
+
end
|
31
|
+
|
32
|
+
def status!(runner_context)
|
33
|
+
method_name = runner_context["method"]
|
34
|
+
raise ArgumentError if method_name.nil?
|
35
|
+
return if runner_context["running"] == false
|
36
|
+
|
37
|
+
Methods.send("#{method_name}_status!", runner_context)
|
38
|
+
end
|
39
|
+
|
40
|
+
def running?(runner_context)
|
41
|
+
runner_context["running"]
|
42
|
+
end
|
43
|
+
|
44
|
+
def success?(runner_context)
|
45
|
+
runner_context["success"]
|
46
|
+
end
|
47
|
+
|
48
|
+
def output(runner_context)
|
49
|
+
runner_context["output"]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "floe/builtin_runner/runner"
|
2
|
+
require "floe/builtin_runner/methods"
|
3
|
+
|
4
|
+
module Floe
|
5
|
+
module BuiltinRunner
|
6
|
+
SCHEME = "floe".freeze
|
7
|
+
SCHEME_PREFIX = "#{SCHEME}://".freeze
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def error!(runner_context = {}, cause:, error: "States.TaskFailed")
|
11
|
+
runner_context.merge!(
|
12
|
+
"running" => false, "success" => false, "output" => {"Error" => error, "Cause" => cause}
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
def success!(runner_context = {}, output:)
|
17
|
+
runner_context.merge!(
|
18
|
+
"running" => false, "success" => true, "output" => output
|
19
|
+
)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
Floe::Runner.register_scheme(Floe::BuiltinRunner::SCHEME, -> { Floe::BuiltinRunner::Runner.new })
|
data/lib/floe/cli.rb
CHANGED
@@ -92,11 +92,14 @@ module Floe
|
|
92
92
|
end
|
93
93
|
|
94
94
|
def create_credentials(opts)
|
95
|
-
if opts[:credentials_given]
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
95
|
+
credentials_str = if opts[:credentials_given]
|
96
|
+
opts[:credentials] == "-" ? $stdin.read : opts[:credentials]
|
97
|
+
elsif opts[:credentials_file_given]
|
98
|
+
File.read(opts[:credentials_file])
|
99
|
+
else
|
100
|
+
return
|
101
|
+
end
|
102
|
+
JSON.parse(credentials_str)
|
100
103
|
end
|
101
104
|
|
102
105
|
def create_workflow(workflow, context_payload, input, credentials)
|
data/lib/floe/version.rb
CHANGED
@@ -5,15 +5,14 @@ module Floe
|
|
5
5
|
class Context
|
6
6
|
include Logging
|
7
7
|
|
8
|
-
attr_accessor :credentials
|
9
|
-
|
10
8
|
# @param context [Json|Hash] (default, create another with input and execution params)
|
11
9
|
# @param input [Hash] (default: {})
|
12
|
-
def initialize(context = nil, input: nil, credentials:
|
10
|
+
def initialize(context = nil, input: nil, credentials: nil, logger: nil)
|
13
11
|
context = JSON.parse(context) if context.kind_of?(String)
|
14
12
|
input = JSON.parse(input || "{}")
|
15
13
|
|
16
14
|
@context = context || {}
|
15
|
+
self["Credentials"] ||= credentials || {}
|
17
16
|
self["Execution"] ||= {}
|
18
17
|
self["Execution"]["Input"] ||= input
|
19
18
|
self["State"] ||= {}
|
@@ -21,8 +20,6 @@ module Floe
|
|
21
20
|
self["StateMachine"] ||= {}
|
22
21
|
self["Task"] ||= {}
|
23
22
|
|
24
|
-
@credentials = credentials || {}
|
25
|
-
|
26
23
|
self.logger = logger if logger
|
27
24
|
rescue JSON::ParserError => err
|
28
25
|
raise Floe::InvalidExecutionInput, "Invalid State Machine Execution Input: #{err}: was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')"
|
@@ -32,6 +29,10 @@ module Floe
|
|
32
29
|
@context["Execution"]
|
33
30
|
end
|
34
31
|
|
32
|
+
def credentials
|
33
|
+
@context["Credentials"]
|
34
|
+
end
|
35
|
+
|
35
36
|
def started?
|
36
37
|
execution.key?("StartTime")
|
37
38
|
end
|
@@ -138,8 +139,18 @@ module Floe
|
|
138
139
|
@context.dig(*args)
|
139
140
|
end
|
140
141
|
|
142
|
+
def inspect
|
143
|
+
format("#<%s: %s>", self.class.name, safe_context.inspect)
|
144
|
+
end
|
145
|
+
|
141
146
|
def to_h
|
142
|
-
|
147
|
+
safe_context
|
148
|
+
end
|
149
|
+
|
150
|
+
private
|
151
|
+
|
152
|
+
def safe_context
|
153
|
+
@context.except("Credentials")
|
143
154
|
end
|
144
155
|
end
|
145
156
|
end
|
@@ -17,11 +17,9 @@ module Floe
|
|
17
17
|
|
18
18
|
def parse_payload(value)
|
19
19
|
case value
|
20
|
-
when Array
|
21
|
-
when Hash
|
22
|
-
|
23
|
-
else
|
24
|
-
value
|
20
|
+
when Array then parse_payload_array(value)
|
21
|
+
when Hash then parse_payload_hash(value)
|
22
|
+
else value
|
25
23
|
end
|
26
24
|
end
|
27
25
|
|
@@ -32,29 +30,40 @@ module Floe
|
|
32
30
|
def parse_payload_hash(value)
|
33
31
|
value.to_h do |key, val|
|
34
32
|
if key.end_with?(".$")
|
35
|
-
|
33
|
+
check_dynamic_datatype(key, val)
|
34
|
+
check_dynamic_key_conflicts(key, value)
|
36
35
|
|
37
|
-
[key,
|
36
|
+
[key, parse_dynamic_payload_string(key, val)]
|
38
37
|
else
|
39
|
-
[key, val]
|
38
|
+
[key, parse_payload(val)]
|
40
39
|
end
|
41
40
|
end
|
42
41
|
end
|
43
42
|
|
44
|
-
def
|
43
|
+
def parse_dynamic_payload_string(key, value)
|
45
44
|
return Path.new(value) if Path.path?(value)
|
46
45
|
return IntrinsicFunction.new(value) if IntrinsicFunction.intrinsic_function?(value)
|
47
46
|
|
48
|
-
value
|
47
|
+
raise Floe::InvalidWorkflowError, "The value for the field \"#{key}\" must be a String that contains a valid Reference Path or Intrinsic Function expression"
|
48
|
+
end
|
49
|
+
|
50
|
+
def check_dynamic_datatype(key, value)
|
51
|
+
unless value.is_a?(String)
|
52
|
+
raise Floe::InvalidWorkflowError, "The value for the field \"#{key}\" must be a String that contains a valid Reference Path or Intrinsic Function expression"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def check_dynamic_key_conflicts(key, value)
|
57
|
+
if value.key?(key.chomp(".$"))
|
58
|
+
raise Floe::InvalidWorkflowError, "both #{key} and #{key.chomp(".$")} present"
|
59
|
+
end
|
49
60
|
end
|
50
61
|
|
51
62
|
def interpolate_value(value, context, inputs)
|
52
63
|
case value
|
53
|
-
when Array
|
54
|
-
when Hash
|
55
|
-
|
56
|
-
else
|
57
|
-
value
|
64
|
+
when Array then interpolate_value_array(value, context, inputs)
|
65
|
+
when Hash then interpolate_value_hash(value, context, inputs)
|
66
|
+
else value
|
58
67
|
end
|
59
68
|
end
|
60
69
|
|
@@ -65,17 +74,16 @@ module Floe
|
|
65
74
|
def interpolate_value_hash(value, context, inputs)
|
66
75
|
value.to_h do |key, val|
|
67
76
|
if key.end_with?(".$")
|
68
|
-
[key.chomp(".$"),
|
77
|
+
[key.chomp(".$"), interpolate_dynamic_value(val, context, inputs)]
|
69
78
|
else
|
70
|
-
[key, val]
|
79
|
+
[key, interpolate_value(val, context, inputs)]
|
71
80
|
end
|
72
81
|
end
|
73
82
|
end
|
74
83
|
|
75
|
-
def
|
76
|
-
|
77
|
-
|
78
|
-
end
|
84
|
+
def interpolate_dynamic_value(value, context, inputs)
|
85
|
+
# value will be a Path or IntrinsicFunction
|
86
|
+
value.value(context, inputs)
|
79
87
|
end
|
80
88
|
end
|
81
89
|
end
|
@@ -8,10 +8,13 @@ module Floe
|
|
8
8
|
def initialize(*)
|
9
9
|
super
|
10
10
|
|
11
|
-
raise Floe::InvalidWorkflowError, "Invalid Reference Path"
|
11
|
+
raise Floe::InvalidWorkflowError, "Invalid Reference Path" if payload.match?(/@|,|:|\?/)
|
12
|
+
raise Floe::InvalidWorkflowError, "Reference Path cannot start with $$" if payload.start_with?("$$") && !payload.start_with?("$$.Credentials")
|
13
|
+
|
14
|
+
path_shift = payload.start_with?("$$.Credentials") ? 3 : 1
|
12
15
|
|
13
16
|
@path = JsonPath.new(payload)
|
14
|
-
.path[
|
17
|
+
.path[path_shift..]
|
15
18
|
.map { |v| v.match(/\[(?<name>.+)\]/)["name"] }
|
16
19
|
.filter_map { |v| v[0] == "'" ? v.delete("'") : v.to_i }
|
17
20
|
end
|
@@ -15,9 +15,8 @@ module Floe
|
|
15
15
|
return if output_path.nil?
|
16
16
|
|
17
17
|
results = result_selector.value(context, results) if @result_selector
|
18
|
-
if result_path.payload.
|
19
|
-
credentials
|
20
|
-
context.credentials.merge!(credentials)
|
18
|
+
if result_path.payload.match?(/^\$\$\.Credentials\b/)
|
19
|
+
context.credentials.merge!(result_path.set(context.credentials, results))
|
21
20
|
output = context.input.dup
|
22
21
|
else
|
23
22
|
output = result_path.set(context.input.dup, results)
|
@@ -40,7 +40,8 @@ module Floe
|
|
40
40
|
super
|
41
41
|
|
42
42
|
input = process_input(context)
|
43
|
-
|
43
|
+
secrets = credentials&.value(context, context.input)
|
44
|
+
runner_context = runner.run_async!(resource, input, secrets, context)
|
44
45
|
|
45
46
|
context.state["RunnerContext"] = runner_context
|
46
47
|
end
|
data/lib/floe/workflow.rb
CHANGED
@@ -8,7 +8,7 @@ module Floe
|
|
8
8
|
include Logging
|
9
9
|
|
10
10
|
class << self
|
11
|
-
def load(path_or_io, context = nil, credentials =
|
11
|
+
def load(path_or_io, context = nil, credentials = nil, name = nil)
|
12
12
|
payload = path_or_io.respond_to?(:read) ? path_or_io.read : File.read(path_or_io)
|
13
13
|
# default the name if it is a filename and none was passed in
|
14
14
|
name ||= path_or_io.respond_to?(:read) ? "stream" : path_or_io.split("/").last.split(".").first
|
data/lib/floe.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: floe
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.17.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- ManageIQ Developers
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: awesome_spawn
|
@@ -107,6 +107,34 @@ dependencies:
|
|
107
107
|
- - "~>"
|
108
108
|
- !ruby/object:Gem::Version
|
109
109
|
version: '2.10'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: faraday
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
type: :runtime
|
118
|
+
prerelease: false
|
119
|
+
version_requirements: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - ">="
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '0'
|
124
|
+
- !ruby/object:Gem::Dependency
|
125
|
+
name: faraday-follow_redirects
|
126
|
+
requirement: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - ">="
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '0'
|
131
|
+
type: :runtime
|
132
|
+
prerelease: false
|
133
|
+
version_requirements: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - ">="
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '0'
|
110
138
|
- !ruby/object:Gem::Dependency
|
111
139
|
name: manageiq-style
|
112
140
|
requirement: !ruby/object:Gem::Requirement
|
@@ -194,6 +222,8 @@ files:
|
|
194
222
|
- LICENSE.txt
|
195
223
|
- README.md
|
196
224
|
- Rakefile
|
225
|
+
- examples/everything.asl
|
226
|
+
- examples/http.asl
|
197
227
|
- examples/map.asl
|
198
228
|
- examples/parallel.asl
|
199
229
|
- examples/set-credential.asl
|
@@ -201,6 +231,9 @@ files:
|
|
201
231
|
- exe/floe
|
202
232
|
- floe.gemspec
|
203
233
|
- lib/floe.rb
|
234
|
+
- lib/floe/builtin_runner.rb
|
235
|
+
- lib/floe/builtin_runner/methods.rb
|
236
|
+
- lib/floe/builtin_runner/runner.rb
|
204
237
|
- lib/floe/cli.rb
|
205
238
|
- lib/floe/container_runner.rb
|
206
239
|
- lib/floe/container_runner/docker.rb
|
@@ -263,14 +296,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
263
296
|
requirements:
|
264
297
|
- - ">="
|
265
298
|
- !ruby/object:Gem::Version
|
266
|
-
version:
|
299
|
+
version: 3.0.0
|
267
300
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
268
301
|
requirements:
|
269
302
|
- - ">="
|
270
303
|
- !ruby/object:Gem::Version
|
271
304
|
version: '0'
|
272
305
|
requirements: []
|
273
|
-
rubygems_version: 3.6.
|
306
|
+
rubygems_version: 3.6.7
|
274
307
|
specification_version: 4
|
275
308
|
summary: Floe is a runner for Amazon States Language workflows.
|
276
309
|
test_files: []
|