floe 0.6.0 → 0.7.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 +18 -1
- data/README.md +32 -0
- data/examples/set-credential.asl +26 -0
- data/floe.gemspec +0 -1
- data/lib/floe/version.rb +1 -1
- data/lib/floe/workflow/path.rb +2 -4
- data/lib/floe/workflow/reference_path.rb +28 -11
- data/lib/floe/workflow/runner/docker.rb +17 -10
- data/lib/floe/workflow/runner/kubernetes.rb +13 -11
- data/lib/floe/workflow/runner/podman.rb +1 -6
- data/lib/floe/workflow/states/input_output_mixin.rb +31 -0
- data/lib/floe/workflow/states/pass.rb +4 -4
- data/lib/floe/workflow/states/task.rb +2 -17
- data/lib/floe.rb +1 -0
- metadata +4 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8e3870ee415e08857191014d61d92dac1b84be70aeff69bed7dbb6dd6f5f7af3
|
4
|
+
data.tar.gz: f6695006e7043837a078a8092ba1c544e23750a9179018ed29e84520e78424eb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ada0c662fa9f4c300ca977029b3c522a5ba36adf37485260dfccba5961064adfc513853beb1b992ca9199856b457bb6f66c8f4d60918157cb4579cf0655d7a7b
|
7
|
+
data.tar.gz: f6918df717d7e5c29d7298c1f9076d77515a3033247fefde072cdfd59797227defcec0087189d58945bbbbd528da3b5d876af612479bd1d2754fc7dc78db8c81
|
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.7.0] - 2023-12-18
|
8
|
+
### Changed
|
9
|
+
- Remove the dependency on more_core_extensions in ReferencePath ([#144](https://github.com/ManageIQ/floe/pull/144))
|
10
|
+
|
11
|
+
### Added
|
12
|
+
- Implement `ReferencePath#get` ([#144](https://github.com/ManageIQ/floe/pull/144))
|
13
|
+
- Allow a State to set a value in Credentials for subsequent states ([#145](https://github.com/ManageIQ/floe/pull/145))
|
14
|
+
|
15
|
+
## [0.6.1] - 2023-11-21
|
16
|
+
### Fixed
|
17
|
+
- Return an error payload if run_async! fails ([#143](https://github.com/ManageIQ/floe/pull/143))
|
18
|
+
|
19
|
+
### Changed
|
20
|
+
- Extract run_container_params for docker/podman ([#142](https://github.com/ManageIQ/floe/pull/142))
|
21
|
+
|
7
22
|
## [0.6.0] - 2023-11-09
|
8
23
|
### Added
|
9
24
|
- Prefix pod names with 'floe-' ([#132](https://github.com/ManageIQ/floe/pull/132))
|
@@ -99,7 +114,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|
99
114
|
### Added
|
100
115
|
- Initial release
|
101
116
|
|
102
|
-
[Unreleased]: https://github.com/ManageIQ/floe/compare/v0.
|
117
|
+
[Unreleased]: https://github.com/ManageIQ/floe/compare/v0.7.0...HEAD
|
118
|
+
[0.7.0]: https://github.com/ManageIQ/floe/compare/v0.6.1...v0.7.0
|
119
|
+
[0.6.1]: https://github.com/ManageIQ/floe/compare/v0.6.0...v0.6.1
|
103
120
|
[0.6.0]: https://github.com/ManageIQ/floe/compare/v0.5.0...v0.6.0
|
104
121
|
[0.5.0]: https://github.com/ManageIQ/floe/compare/v0.4.1...v0.5.0
|
105
122
|
[0.4.1]: https://github.com/ManageIQ/floe/compare/v0.4.0...v0.4.1
|
data/README.md
CHANGED
@@ -51,6 +51,38 @@ You can provide that at runtime via the `--credentials` parameter:
|
|
51
51
|
bundle exec ruby exe/floe --workflow my-workflow.asl --credentials='{"roleArn": "arn:aws:iam::111122223333:role/LambdaRole"}'
|
52
52
|
```
|
53
53
|
|
54
|
+
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:
|
55
|
+
|
56
|
+
```
|
57
|
+
bundle exec ruby exe/floe --workflow my-workflow.asl --credentials='{"username": "user", "password": "pass"}'
|
58
|
+
```
|
59
|
+
|
60
|
+
```json
|
61
|
+
{
|
62
|
+
"StartAt": "Login",
|
63
|
+
"States": {
|
64
|
+
"Login": {
|
65
|
+
"Type": "Task",
|
66
|
+
"Resource": "docker://login:latest",
|
67
|
+
"Credentials": {
|
68
|
+
"username.$": "$.username",
|
69
|
+
"password.$": "$.password"
|
70
|
+
},
|
71
|
+
"ResultPath": "$.Credentials",
|
72
|
+
"Next": "DoSomething"
|
73
|
+
},
|
74
|
+
"DoSomething": {
|
75
|
+
"Type": "Task",
|
76
|
+
"Resource": "docker://do-something:latest",
|
77
|
+
"Credentials": {
|
78
|
+
"token.$": "$.bearer_token"
|
79
|
+
},
|
80
|
+
"End": true
|
81
|
+
}
|
82
|
+
}
|
83
|
+
}
|
84
|
+
```
|
85
|
+
|
54
86
|
### Ruby Library
|
55
87
|
|
56
88
|
```ruby
|
@@ -0,0 +1,26 @@
|
|
1
|
+
{
|
2
|
+
"Comment": "An example showing how to set a credential.",
|
3
|
+
"StartAt": "Login",
|
4
|
+
"States": {
|
5
|
+
"Login": {
|
6
|
+
"Type": "Task",
|
7
|
+
"Resource": "docker://docker.io/agrare/echo:latest",
|
8
|
+
"Parameters": {
|
9
|
+
"ECHO": "TOKEN"
|
10
|
+
},
|
11
|
+
"ResultPath": "$.Credentials",
|
12
|
+
"ResultSelector": {
|
13
|
+
"bearer_token.$": "$.echo"
|
14
|
+
},
|
15
|
+
"Next": "DoSomething"
|
16
|
+
},
|
17
|
+
"DoSomething": {
|
18
|
+
"Type": "Task",
|
19
|
+
"Resource": "docker://docker.io/agrare/hello-world:latest",
|
20
|
+
"Credentials": {
|
21
|
+
"bearer_token.$": "$.bearer_token"
|
22
|
+
},
|
23
|
+
"End": true
|
24
|
+
}
|
25
|
+
}
|
26
|
+
}
|
data/floe.gemspec
CHANGED
data/lib/floe/version.rb
CHANGED
data/lib/floe/workflow/path.rb
CHANGED
@@ -9,6 +9,8 @@ module Floe
|
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
12
|
+
attr_reader :payload
|
13
|
+
|
12
14
|
def initialize(payload)
|
13
15
|
@payload = payload
|
14
16
|
|
@@ -28,10 +30,6 @@ module Floe
|
|
28
30
|
|
29
31
|
results.count < 2 ? results.first : results
|
30
32
|
end
|
31
|
-
|
32
|
-
private
|
33
|
-
|
34
|
-
attr_reader :payload
|
35
33
|
end
|
36
34
|
end
|
37
35
|
end
|
@@ -4,37 +4,54 @@ module Floe
|
|
4
4
|
class Workflow
|
5
5
|
class ReferencePath < Path
|
6
6
|
class << self
|
7
|
+
def get(payload, context)
|
8
|
+
new(payload).get(context)
|
9
|
+
end
|
10
|
+
|
7
11
|
def set(payload, context, value)
|
8
12
|
new(payload).set(context, value)
|
9
13
|
end
|
10
14
|
end
|
11
15
|
|
12
|
-
|
13
|
-
require "more_core_extensions/core_ext/hash/nested"
|
14
|
-
require "more_core_extensions/core_ext/array/nested"
|
16
|
+
attr_reader :path
|
15
17
|
|
18
|
+
def initialize(*)
|
16
19
|
super
|
17
20
|
|
18
21
|
raise Floe::InvalidWorkflowError, "Invalid Reference Path" if payload.match?(/@|,|:|\?/)
|
22
|
+
@path = JsonPath.new(payload)
|
23
|
+
.path[1..]
|
24
|
+
.map { |v| v.match(/\[(?<name>.+)\]/)["name"] }
|
25
|
+
.map { |v| v[0] == "'" ? v.delete("'") : v.to_i }
|
26
|
+
.compact
|
27
|
+
end
|
28
|
+
|
29
|
+
def get(context)
|
30
|
+
return context if path.empty?
|
31
|
+
|
32
|
+
context.dig(*path)
|
19
33
|
end
|
20
34
|
|
21
35
|
def set(context, value)
|
22
36
|
result = context.dup
|
23
37
|
|
24
|
-
path = JsonPath.new(payload)
|
25
|
-
.path[1..]
|
26
|
-
.map { |v| v.match(/\[(?<name>.+)\]/)["name"] }
|
27
|
-
.map { |v| v[0] == "'" ? v.delete("'") : v.to_i }
|
28
|
-
.compact
|
29
|
-
|
30
38
|
# If the payload is '$' then merge the value into the context
|
31
|
-
# otherwise
|
39
|
+
# otherwise store the value under the path
|
32
40
|
#
|
33
41
|
# TODO: how to handle non-hash values, raise error if path=$ and value not a hash?
|
34
42
|
if path.empty?
|
35
43
|
result.merge!(value)
|
36
44
|
else
|
37
|
-
result
|
45
|
+
child = result
|
46
|
+
keys = path.dup
|
47
|
+
last_key = keys.pop
|
48
|
+
|
49
|
+
keys.each do |key|
|
50
|
+
child[key] = {} if child[key].nil?
|
51
|
+
child = child[key]
|
52
|
+
end
|
53
|
+
|
54
|
+
child[last_key] = value
|
38
55
|
end
|
39
56
|
|
40
57
|
result
|
@@ -30,12 +30,11 @@ module Floe
|
|
30
30
|
|
31
31
|
begin
|
32
32
|
runner_context["container_ref"] = run_container(image, env, runner_context["secrets_ref"])
|
33
|
-
|
33
|
+
runner_context
|
34
|
+
rescue AwesomeSpawn::CommandResultError => err
|
34
35
|
cleanup(runner_context)
|
35
|
-
|
36
|
+
{"Error" => "States.TaskFailed", "Cause" => err.to_s}
|
36
37
|
end
|
37
|
-
|
38
|
-
runner_context
|
39
38
|
end
|
40
39
|
|
41
40
|
def cleanup(runner_context)
|
@@ -46,11 +45,13 @@ module Floe
|
|
46
45
|
end
|
47
46
|
|
48
47
|
def status!(runner_context)
|
48
|
+
return if runner_context.key?("Error")
|
49
|
+
|
49
50
|
runner_context["container_state"] = inspect_container(runner_context["container_ref"]).first&.dig("State")
|
50
51
|
end
|
51
52
|
|
52
53
|
def running?(runner_context)
|
53
|
-
runner_context.dig("container_state", "Running")
|
54
|
+
!!runner_context.dig("container_state", "Running")
|
54
55
|
end
|
55
56
|
|
56
57
|
def success?(runner_context)
|
@@ -58,6 +59,8 @@ module Floe
|
|
58
59
|
end
|
59
60
|
|
60
61
|
def output(runner_context)
|
62
|
+
return runner_context.slice("Error", "Cause") if runner_context.key?("Error")
|
63
|
+
|
61
64
|
output = docker!("logs", runner_context["container_ref"], :combined_output => true).output
|
62
65
|
runner_context["output"] = output
|
63
66
|
end
|
@@ -67,6 +70,15 @@ module Floe
|
|
67
70
|
attr_reader :network
|
68
71
|
|
69
72
|
def run_container(image, env, secrets_file)
|
73
|
+
params = run_container_params(image, env, secrets_file)
|
74
|
+
|
75
|
+
logger.debug("Running #{AwesomeSpawn.build_command_line("docker", params)}")
|
76
|
+
|
77
|
+
result = docker!(*params)
|
78
|
+
result.output
|
79
|
+
end
|
80
|
+
|
81
|
+
def run_container_params(image, env, secrets_file)
|
70
82
|
params = ["run"]
|
71
83
|
params << :detach
|
72
84
|
params += env.map { |k, v| [:e, "#{k}=#{v}"] }
|
@@ -75,11 +87,6 @@ module Floe
|
|
75
87
|
params << [:v, "#{secrets_file}:/run/secrets:z"] if secrets_file
|
76
88
|
params << [:name, container_name(image)]
|
77
89
|
params << image
|
78
|
-
|
79
|
-
logger.debug("Running docker: #{AwesomeSpawn.build_command_line("docker", params)}")
|
80
|
-
|
81
|
-
result = docker!(*params)
|
82
|
-
result.output
|
83
90
|
end
|
84
91
|
|
85
92
|
def inspect_container(container_id)
|
@@ -56,15 +56,16 @@ module Floe
|
|
56
56
|
|
57
57
|
begin
|
58
58
|
create_pod!(name, image, env, secret)
|
59
|
-
|
59
|
+
runner_context
|
60
|
+
rescue Kubeclient::HttpError => err
|
60
61
|
cleanup(runner_context)
|
61
|
-
|
62
|
+
{"Error" => "States.TaskFailed", "Cause" => err.to_s}
|
62
63
|
end
|
63
|
-
|
64
|
-
runner_context
|
65
64
|
end
|
66
65
|
|
67
66
|
def status!(runner_context)
|
67
|
+
return if runner_context.key?("Error")
|
68
|
+
|
68
69
|
runner_context["container_state"] = pod_info(runner_context["container_ref"]).to_h.deep_stringify_keys["status"]
|
69
70
|
end
|
70
71
|
|
@@ -83,13 +84,14 @@ module Floe
|
|
83
84
|
end
|
84
85
|
|
85
86
|
def output(runner_context)
|
86
|
-
runner_context
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
87
|
+
if runner_context.key?("Error")
|
88
|
+
runner_context.slice("Error", "Cause")
|
89
|
+
elsif container_failed?(runner_context)
|
90
|
+
failed_state = failed_container_states(runner_context).first
|
91
|
+
{"Error" => failed_state["reason"], "Cause" => failed_state["message"]}
|
92
|
+
else
|
93
|
+
runner_context["output"] = kubeclient.get_pod_log(runner_context["container_ref"], namespace).body
|
94
|
+
end
|
93
95
|
end
|
94
96
|
|
95
97
|
def cleanup(runner_context)
|
@@ -30,7 +30,7 @@ module Floe
|
|
30
30
|
|
31
31
|
private
|
32
32
|
|
33
|
-
def
|
33
|
+
def run_container_params(image, env, secret)
|
34
34
|
params = ["run"]
|
35
35
|
params << :detach
|
36
36
|
params += env.map { |k, v| [:e, "#{k}=#{v}"] }
|
@@ -39,11 +39,6 @@ module Floe
|
|
39
39
|
params << [:secret, secret] if secret
|
40
40
|
params << [:name, container_name(image)]
|
41
41
|
params << image
|
42
|
-
|
43
|
-
logger.debug("Running podman: #{AwesomeSpawn.build_command_line("podman", params)}")
|
44
|
-
|
45
|
-
result = podman!(*params)
|
46
|
-
result.output
|
47
42
|
end
|
48
43
|
|
49
44
|
def create_secret(secrets)
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Floe
|
4
|
+
class Workflow
|
5
|
+
module States
|
6
|
+
module InputOutputMixin
|
7
|
+
def process_input(input)
|
8
|
+
input = input_path.value(context, input)
|
9
|
+
input = parameters.value(context, input) if parameters
|
10
|
+
input
|
11
|
+
end
|
12
|
+
|
13
|
+
def process_output(input, results)
|
14
|
+
return input if results.nil?
|
15
|
+
return if output_path.nil?
|
16
|
+
|
17
|
+
results = result_selector.value(context, results) if @result_selector
|
18
|
+
if result_path.payload.start_with?("$.Credentials")
|
19
|
+
credentials = result_path.set(workflow.credentials, results)["Credentials"]
|
20
|
+
workflow.credentials.merge!(credentials)
|
21
|
+
output = input
|
22
|
+
else
|
23
|
+
output = result_path.set(input, results)
|
24
|
+
end
|
25
|
+
|
26
|
+
output_path.value(context, output)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -4,6 +4,7 @@ module Floe
|
|
4
4
|
class Workflow
|
5
5
|
module States
|
6
6
|
class Pass < Floe::Workflow::State
|
7
|
+
include InputOutputMixin
|
7
8
|
include NonTerminalMixin
|
8
9
|
|
9
10
|
attr_reader :end, :next, :result, :parameters, :input_path, :output_path, :result_path
|
@@ -25,12 +26,11 @@ module Floe
|
|
25
26
|
|
26
27
|
def start(input)
|
27
28
|
super
|
28
|
-
output = input_path.value(context, input)
|
29
|
-
output = result_path.set(output, result) if result && result_path
|
30
|
-
output = output_path.value(context, output)
|
31
29
|
|
30
|
+
input = process_input(input)
|
31
|
+
|
32
|
+
context.output = process_output(input, result)
|
32
33
|
context.next_state = end? ? nil : @next
|
33
|
-
context.output = output
|
34
34
|
end
|
35
35
|
|
36
36
|
def running?
|
@@ -4,6 +4,7 @@ module Floe
|
|
4
4
|
class Workflow
|
5
5
|
module States
|
6
6
|
class Task < Floe::Workflow::State
|
7
|
+
include InputOutputMixin
|
7
8
|
include NonTerminalMixin
|
8
9
|
|
9
10
|
attr_reader :credentials, :end, :heartbeat_seconds, :next, :parameters,
|
@@ -49,7 +50,7 @@ module Floe
|
|
49
50
|
|
50
51
|
if success?
|
51
52
|
output = parse_output(output)
|
52
|
-
context.state["Output"] = process_output
|
53
|
+
context.state["Output"] = process_output(context.input.dup, output)
|
53
54
|
context.next_state = next_state
|
54
55
|
else
|
55
56
|
error = parse_error(output)
|
@@ -126,12 +127,6 @@ module Floe
|
|
126
127
|
context.state["Error"] = context.output["Error"]
|
127
128
|
end
|
128
129
|
|
129
|
-
def process_input(input)
|
130
|
-
input = input_path.value(context, input)
|
131
|
-
input = parameters.value(context, input) if parameters
|
132
|
-
input
|
133
|
-
end
|
134
|
-
|
135
130
|
def parse_error(output)
|
136
131
|
return if output.nil?
|
137
132
|
return output if output.kind_of?(Hash)
|
@@ -150,16 +145,6 @@ module Floe
|
|
150
145
|
nil
|
151
146
|
end
|
152
147
|
|
153
|
-
def process_output!(results)
|
154
|
-
output = context.input.dup
|
155
|
-
return output if results.nil?
|
156
|
-
return if output_path.nil?
|
157
|
-
|
158
|
-
results = result_selector.value(context, results) if result_selector
|
159
|
-
output = result_path.set(output, results)
|
160
|
-
output_path.value(context, output)
|
161
|
-
end
|
162
|
-
|
163
148
|
def next_state
|
164
149
|
end? ? nil : @next
|
165
150
|
end
|
data/lib/floe.rb
CHANGED
@@ -25,6 +25,7 @@ require_relative "floe/workflow/runner/podman"
|
|
25
25
|
require_relative "floe/workflow/state"
|
26
26
|
require_relative "floe/workflow/states/choice"
|
27
27
|
require_relative "floe/workflow/states/fail"
|
28
|
+
require_relative "floe/workflow/states/input_output_mixin"
|
28
29
|
require_relative "floe/workflow/states/map"
|
29
30
|
require_relative "floe/workflow/states/non_terminal_mixin"
|
30
31
|
require_relative "floe/workflow/states/parallel"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: floe
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- ManageIQ Developers
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-12-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: awesome_spawn
|
@@ -52,20 +52,6 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '4.7'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: more_core_extensions
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - ">="
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '0'
|
62
|
-
type: :runtime
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - ">="
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '0'
|
69
55
|
- !ruby/object:Gem::Dependency
|
70
56
|
name: optimist
|
71
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -96,6 +82,7 @@ files:
|
|
96
82
|
- Gemfile
|
97
83
|
- README.md
|
98
84
|
- Rakefile
|
85
|
+
- examples/set-credential.asl
|
99
86
|
- examples/workflow.asl
|
100
87
|
- exe/floe
|
101
88
|
- floe.gemspec
|
@@ -123,6 +110,7 @@ files:
|
|
123
110
|
- lib/floe/workflow/state.rb
|
124
111
|
- lib/floe/workflow/states/choice.rb
|
125
112
|
- lib/floe/workflow/states/fail.rb
|
113
|
+
- lib/floe/workflow/states/input_output_mixin.rb
|
126
114
|
- lib/floe/workflow/states/map.rb
|
127
115
|
- lib/floe/workflow/states/non_terminal_mixin.rb
|
128
116
|
- lib/floe/workflow/states/parallel.rb
|