floe 0.9.0 → 0.10.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: 8c7a74a5297258d481fb588ae0fa6eb1b22b7ecf5c049865b77ad23d6fb135cb
4
- data.tar.gz: 82f73726e293e5345d3e7fa55a0049f881f2dce6e6d46570d2352968907c04b9
3
+ metadata.gz: 2b158e514e08902a1138c7b2878bb8633ca7c1abb59d4b77b6d9ce568c65710b
4
+ data.tar.gz: 32d053bba54e8c35645a636771964692e435ad72ee38e03c8c22b4da5bce25ec
5
5
  SHA512:
6
- metadata.gz: 32d58e28cd76d936f31f9af2c1091d8a7dd930e47a2197b532c02d6d48df2c82feee696072af701aeca5f2af5437040ea3bace5df622c1ad5d0e47e388884ad2
7
- data.tar.gz: 1ee0628fbfde496d00fae67812ac869582b1aca754aca7cf44caa7170edc1b0f6c9100a587770d6f6bb97bc0a948dd8fe0123fd108b9ed037ad71bc62bb8e104
6
+ metadata.gz: e39301eed1de9189b66f07a7ecdda807974bf47495b4906bd57f6f8ed862fab38448668155f79e12c87da8935f4054d14dbcd08e292429b194768d2234fa7c46
7
+ data.tar.gz: 5cf0476521a1cd4fc0dcc4edeccdd00c6c72ee88bf7428103b86f5f30097608f934f19e75499a61b5d7e7380f2e1451dccfd82f796eee6bcef041dd6f3db8664
data/CHANGELOG.md CHANGED
@@ -4,6 +4,19 @@ This project adheres to [Semantic Versioning](http://semver.org/).
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.10.0] - 2024-04-05
8
+ ### Fixed
9
+ - Fix rubocops ([#164](https://github.com/ManageIQ/floe/pull/164))
10
+ - Output should contain errors ([#165](https://github.com/ManageIQ/floe/pull/165))
11
+
12
+ ### Added
13
+ - Add simplecov ([#162](https://github.com/ManageIQ/floe/pull/162))
14
+ - Add ability to pass context on the command line ([#161](https://github.com/ManageIQ/floe/pull/161))
15
+ - Add specs for `Workflow#wait_until`, `#waiting?` ([#166](https://github.com/ManageIQ/floe/pull/166))
16
+
17
+ ### Changed
18
+ - Drop non-standard Error/Cause fields ([#167](https://github.com/ManageIQ/floe/pull/167))
19
+
7
20
  ## [0.9.0] - 2024-02-19
8
21
  ### Changed
9
22
  - Default to wait indefinitely ([#157](https://github.com/ManageIQ/floe/pull/157))
@@ -136,7 +149,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
136
149
  ### Added
137
150
  - Initial release
138
151
 
139
- [Unreleased]: https://github.com/ManageIQ/floe/compare/v0.9.0...HEAD
152
+ [Unreleased]: https://github.com/ManageIQ/floe/compare/v0.10.0...HEAD
153
+ [0.10.0]: https://github.com/ManageIQ/floe/compare/v0.9.0...v0.10.0
140
154
  [0.9.0]: https://github.com/ManageIQ/floe/compare/v0.8.0...v0.9.0
141
155
  [0.8.0]: https://github.com/ManageIQ/floe/compare/v0.7.0...v0.8.0
142
156
  [0.7.0]: https://github.com/ManageIQ/floe/compare/v0.6.1...v0.7.0
data/Gemfile CHANGED
@@ -7,9 +7,3 @@ require File.join(Bundler::Plugin.index.load_paths("bundler-inject")[0], "bundle
7
7
 
8
8
  # Specify your gem's dependencies in floe.gemspec
9
9
  gemspec
10
-
11
- gem "manageiq-style"
12
- gem "rake", "~> 13.0"
13
- gem "rspec"
14
- gem "rubocop"
15
- gem "timecop"
data/exe/floe CHANGED
@@ -10,6 +10,7 @@ opts = Optimist.options do
10
10
 
11
11
  opt :workflow, "Path to your workflow json (legacy)", :type => :string
12
12
  opt :input, "JSON payload to input to the workflow (legacy)", :type => :string
13
+ opt :context, "JSON payload of the Context", :type => :string
13
14
  opt :credentials, "JSON payload with credentials", :type => :string
14
15
  opt :credentials_file, "Path to a file with credentials", :type => :string
15
16
  opt :docker_runner, "Type of runner for docker images", :type => :string, :short => 'r'
@@ -49,7 +50,7 @@ credentials =
49
50
 
50
51
  workflows =
51
52
  args.each_slice(2).map do |workflow, input|
52
- context = Floe::Workflow::Context.new(:input => input || opts[:input] || "{}")
53
+ context = Floe::Workflow::Context.new(opts[:context], :input => input || opts[:input] || "{}")
53
54
  Floe::Workflow.load(workflow, context, credentials)
54
55
  end
55
56
 
data/floe.gemspec CHANGED
@@ -34,4 +34,11 @@ Gem::Specification.new do |spec|
34
34
  spec.add_dependency "jsonpath", "~>1.1"
35
35
  spec.add_dependency "kubeclient", "~>4.7"
36
36
  spec.add_dependency "optimist", "~>3.0"
37
+
38
+ spec.add_development_dependency "manageiq-style"
39
+ spec.add_development_dependency "rake", "~> 13.0"
40
+ spec.add_development_dependency "rspec"
41
+ spec.add_development_dependency "rubocop"
42
+ spec.add_development_dependency "simplecov", ">= 0.21.2"
43
+ spec.add_development_dependency "timecop"
37
44
  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.9.0".freeze
4
+ VERSION = "0.10.0"
5
5
  end
@@ -3,19 +3,19 @@
3
3
  module Floe
4
4
  class Workflow
5
5
  class Context
6
+ # @param context [Json|Hash] (default, create another with input and execution params)
7
+ # @param input [Hash] (default: {})
6
8
  def initialize(context = nil, input: {})
7
9
  context = JSON.parse(context) if context.kind_of?(String)
8
10
  input = JSON.parse(input) if input.kind_of?(String)
9
11
 
10
- @context = context || {
11
- "Execution" => {
12
- "Input" => input
13
- },
14
- "State" => {},
15
- "StateHistory" => [],
16
- "StateMachine" => {},
17
- "Task" => {}
18
- }
12
+ @context = context || {}
13
+ self["Execution"] ||= {}
14
+ self["Execution"]["Input"] ||= input
15
+ self["State"] ||= {}
16
+ self["StateHistory"] ||= []
17
+ self["StateMachine"] ||= {}
18
+ self["Task"] ||= {}
19
19
  end
20
20
 
21
21
  def execution
@@ -30,6 +30,10 @@ module Floe
30
30
  started? && !ended?
31
31
  end
32
32
 
33
+ def failed?
34
+ output&.key?("Error") || false
35
+ end
36
+
33
37
  def ended?
34
38
  execution.key?("EndTime")
35
39
  end
@@ -67,7 +71,7 @@ module Floe
67
71
  "pending"
68
72
  elsif running?
69
73
  "running"
70
- elsif state["Error"]
74
+ elsif failed?
71
75
  "failure"
72
76
  else
73
77
  "success"
@@ -19,11 +19,11 @@ module Floe
19
19
  super
20
20
 
21
21
  raise Floe::InvalidWorkflowError, "Invalid Reference Path" if payload.match?(/@|,|:|\?/)
22
+
22
23
  @path = JsonPath.new(payload)
23
24
  .path[1..]
24
25
  .map { |v| v.match(/\[(?<name>.+)\]/)["name"] }
25
- .map { |v| v[0] == "'" ? v.delete("'") : v.to_i }
26
- .compact
26
+ .filter_map { |v| v[0] == "'" ? v.delete("'") : v.to_i }
27
27
  end
28
28
 
29
29
  def get(context)
@@ -6,9 +6,10 @@ module Floe
6
6
  image.match(%r{^(?<repository>.+/)?(?<image>.+):(?<tag>.+)$})&.named_captures&.dig("image")
7
7
  end
8
8
 
9
- MAX_CONTAINER_NAME_SIZE = 63 - 5 - 9 # 63 is the max kubernetes pod name length
10
- # -5 for the "floe-" prefix
11
- # -9 for the random hex suffix and leading hyphen
9
+ # 63 is the max kubernetes pod name length
10
+ # -5 for the "floe-" prefix
11
+ # -9 for the random hex suffix and leading hyphen
12
+ MAX_CONTAINER_NAME_SIZE = 63 - 5 - 9
12
13
 
13
14
  def container_name(image)
14
15
  name = image_name(image)
@@ -164,7 +164,7 @@ module Floe
164
164
 
165
165
  def failed_container_states(context)
166
166
  container_statuses = context.dig("container_state", "containerStatuses") || []
167
- container_statuses.map { |status| status["state"]&.values&.first }.compact
167
+ container_statuses.filter_map { |status| status["state"]&.values&.first }
168
168
  .select { |state| FAILURE_REASONS.include?(state["reason"]) }
169
169
  end
170
170
 
@@ -58,7 +58,7 @@ module Floe
58
58
  context.state["Guid"] = SecureRandom.uuid
59
59
  context.state["EnteredTime"] = start_time
60
60
 
61
- logger.info("Running state: [#{context.state_name}] with input [#{context.input}]...")
61
+ logger.info("Running state: [#{long_name}] with input [#{context.input}]...")
62
62
  end
63
63
 
64
64
  def finish
@@ -70,7 +70,8 @@ module Floe
70
70
  context.state["Duration"] = finished_time - entered_time
71
71
  context.execution["EndTime"] = finished_time_iso if context.next_state.nil?
72
72
 
73
- logger.info("Running state: [#{context.state_name}] with input [#{context.input}]...Complete - next state: [#{context.next_state}] output: [#{context.output}]")
73
+ level = context.output&.[]("Error") ? :error : :info
74
+ logger.public_send(level, "Running state: [#{long_name}] with input [#{context.input}]...Complete #{context.next_state ? "- next state [#{context.next_state}]" : "workflow -"} output: [#{context.output}]")
74
75
 
75
76
  context.state_history << context.state
76
77
 
@@ -101,6 +102,10 @@ module Floe
101
102
  context.state["WaitUntil"] && Time.parse(context.state["WaitUntil"])
102
103
  end
103
104
 
105
+ def long_name
106
+ "#{self.class.name.split("::").last}:#{name}"
107
+ end
108
+
104
109
  private
105
110
 
106
111
  def wait_until!(seconds: nil, time: nil)
@@ -18,14 +18,14 @@ module Floe
18
18
  @output_path = Path.new(payload.fetch("OutputPath", "$"))
19
19
  end
20
20
 
21
- def start(input)
22
- super
23
- input = input_path.value(context, input)
21
+ def finish
22
+ input = input_path.value(context, context.input)
24
23
  next_state = choices.detect { |choice| choice.true?(context, input) }&.next || default
25
24
  output = output_path.value(context, input)
26
25
 
27
26
  context.next_state = next_state
28
27
  context.output = output
28
+ super
29
29
  end
30
30
 
31
31
  def running?
@@ -15,18 +15,16 @@ module Floe
15
15
  @error_path = Path.new(payload["ErrorPath"]) if payload["ErrorPath"]
16
16
  end
17
17
 
18
- def start(input)
19
- super
18
+ def finish
20
19
  context.next_state = nil
21
20
  # TODO: support intrinsic functions here
22
21
  # see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-fail-state.html
23
22
  # https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html#asl-intrsc-func-generic
24
23
  context.output = {
25
- "Error" => @error_path ? @error_path.value(context, input) : error,
26
- "Cause" => @cause_path ? @cause_path.value(context, input) : cause
24
+ "Error" => @error_path ? @error_path.value(context, context.input) : error,
25
+ "Cause" => @cause_path ? @cause_path.value(context, context.input) : cause
27
26
  }.compact
28
- context.state["Error"] = context.output["Error"]
29
- context.state["Cause"] = context.output["Cause"]
27
+ super
30
28
  end
31
29
 
32
30
  def running?
@@ -24,13 +24,11 @@ module Floe
24
24
  validate_state!
25
25
  end
26
26
 
27
- def start(input)
28
- super
29
-
30
- input = process_input(input)
31
-
27
+ def finish
28
+ input = process_input(context.input)
32
29
  context.output = process_output(input, result)
33
30
  context.next_state = end? ? nil : @next
31
+ super
34
32
  end
35
33
 
36
34
  def running?
@@ -10,10 +10,10 @@ module Floe
10
10
  super
11
11
  end
12
12
 
13
- def start(input)
14
- super
13
+ def finish
15
14
  context.next_state = nil
16
- context.output = input
15
+ context.output = context.input
16
+ super
17
17
  end
18
18
 
19
19
  def running?
@@ -46,19 +46,18 @@ module Floe
46
46
  end
47
47
 
48
48
  def finish
49
- super
50
-
51
49
  output = runner.output(context.state["RunnerContext"])
52
50
 
53
51
  if success?
54
52
  output = parse_output(output)
55
- context.state["Output"] = process_output(context.input.dup, output)
53
+ context.output = process_output(context.input.dup, output)
54
+ super
56
55
  else
57
56
  context.next_state = nil
58
- error = parse_error(output)
57
+ context.output = error = parse_error(output)
58
+ super
59
59
  retry_state!(error) || catch_error!(error) || fail_workflow!(error)
60
60
  end
61
-
62
61
  ensure
63
62
  runner.cleanup(context.state["RunnerContext"])
64
63
  end
@@ -110,6 +109,7 @@ module Floe
110
109
 
111
110
  wait_until!(:seconds => retrier.sleep_duration(context["State"]["RetryCount"]))
112
111
  context.next_state = context.state_name
112
+ logger.info("Running state: [#{long_name}] with input [#{context.input}]...Retry - delay: #{wait_until}")
113
113
  true
114
114
  end
115
115
 
@@ -119,13 +119,15 @@ module Floe
119
119
 
120
120
  context.next_state = catcher.next
121
121
  context.output = catcher.result_path.set(context.input, error)
122
+ logger.info("Running state: [#{long_name}] with input [#{context.input}]...CatchError - next state: [#{context.next_state}] output: [#{context.output}]")
123
+
122
124
  true
123
125
  end
124
126
 
125
127
  def fail_workflow!(error)
126
128
  context.next_state = nil
127
129
  context.output = {"Error" => error["Error"], "Cause" => error["Cause"]}.compact
128
- context.state["Error"] = context.output["Error"]
130
+ logger.error("Running state: [#{long_name}] with input [#{context.input}]...Complete workflow - output: [#{context.output}]")
129
131
  end
130
132
 
131
133
  def parse_error(output)
@@ -29,8 +29,7 @@ module Floe
29
29
  def start(input)
30
30
  super
31
31
 
32
- input = input_path.value(context, input)
33
- context.output = output_path.value(context, input)
32
+ input = input_path.value(context, context.input)
34
33
 
35
34
  wait_until!(
36
35
  :seconds => seconds_path ? seconds_path.value(context, input).to_i : seconds,
@@ -38,6 +37,12 @@ module Floe
38
37
  )
39
38
  end
40
39
 
40
+ def finish
41
+ input = input_path.value(context, context.input)
42
+ context.output = output_path.value(context, input)
43
+ super
44
+ end
45
+
41
46
  def running?
42
47
  waiting?
43
48
  end
data/lib/floe/workflow.rb CHANGED
@@ -85,7 +85,7 @@ module Floe
85
85
  end
86
86
  end
87
87
 
88
- attr_reader :context, :credentials, :payload, :states, :states_by_name, :start_at, :name
88
+ attr_reader :context, :credentials, :payload, :states, :states_by_name, :start_at, :name, :comment
89
89
 
90
90
  def initialize(payload, context = nil, credentials = {}, name = nil)
91
91
  payload = JSON.parse(payload) if payload.kind_of?(String)
@@ -100,6 +100,7 @@ module Floe
100
100
  @payload = payload
101
101
  @context = context
102
102
  @credentials = credentials || {}
103
+ @comment = payload["Comment"]
103
104
  @start_at = payload["StartAt"]
104
105
 
105
106
  @states = payload["States"].to_a.map { |state_name, state| State.build!(self, state_name, state) }
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.9.0
4
+ version: 0.10.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: 2024-02-19 00:00:00.000000000 Z
11
+ date: 2024-04-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: awesome_spawn
@@ -80,6 +80,90 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: manageiq-style
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rake
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '13.0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '13.0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rspec
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rubocop
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: simplecov
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: 0.21.2
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: 0.21.2
153
+ - !ruby/object:Gem::Dependency
154
+ name: timecop
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
83
167
  description: Simple Workflow Runner.
84
168
  email:
85
169
  executables: