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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 58ace49051c911efbe352b2b882cc663aea3be231e6c8dcf138864e340ae2de6
4
- data.tar.gz: d422be1ce106663dd1dc40b7ddb52024f9eb9b0e85f807572d66f31fcdf16e51
3
+ metadata.gz: 8e3870ee415e08857191014d61d92dac1b84be70aeff69bed7dbb6dd6f5f7af3
4
+ data.tar.gz: f6695006e7043837a078a8092ba1c544e23750a9179018ed29e84520e78424eb
5
5
  SHA512:
6
- metadata.gz: e5eab9d1763c723d8bb913e31bb736d0ba3c3e6d6d63585031e30adc5cbd3049105c756b89eee6999df2d6a0dcfd35e6a1ae4a39c10ce949387e91aae30acf32
7
- data.tar.gz: 3249cce513b7195ce5ed791c300d9df62159febeb522c45db36dda38244c76f9290af3157ac744fa0757417ce327f95eea80c704375310ed3181e05426208e3b
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.6.0...HEAD
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
@@ -32,6 +32,5 @@ Gem::Specification.new do |spec|
32
32
  spec.add_dependency "awesome_spawn", "~>1.0"
33
33
  spec.add_dependency "jsonpath", "~>1.1"
34
34
  spec.add_dependency "kubeclient", "~>4.7"
35
- spec.add_dependency "more_core_extensions"
36
35
  spec.add_dependency "optimist", "~>3.0"
37
36
  end
data/lib/floe/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Floe
4
- VERSION = "0.6.0".freeze
4
+ VERSION = "0.7.0".freeze
5
5
  end
@@ -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
- def initialize(*)
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 use store path to set the value to a sub-key
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.store_path(path, value)
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
- rescue
33
+ runner_context
34
+ rescue AwesomeSpawn::CommandResultError => err
34
35
  cleanup(runner_context)
35
- raise
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
- rescue
59
+ runner_context
60
+ rescue Kubeclient::HttpError => err
60
61
  cleanup(runner_context)
61
- raise
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["output"] =
87
- if container_failed?(runner_context)
88
- failed_state = failed_container_states(runner_context).first
89
- {"Error" => failed_state["reason"], "Cause" => failed_state["message"]}
90
- else
91
- kubeclient.get_pod_log(runner_context["container_ref"], namespace).body
92
- end
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 run_container(image, env, secret)
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!(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.6.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-09 00:00:00.000000000 Z
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