floe 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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