floe 0.9.0 → 0.10.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: 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: