floe 0.15.0 → 0.15.1

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: 75d5be2f5b9cdcfc64b4b3994d32b9e3955486fd99bf4e05c62c2414485188c8
4
- data.tar.gz: 7257009d5942f157f2ccef6499c77e1f9033e0ac4846a08f3f589943bd4794ef
3
+ metadata.gz: 6f21cf2fcc6a7fa9f7536e0cca328d101a867c389f53f768e4746b6e69f643a5
4
+ data.tar.gz: f3d82401842f66504afdeaa4b051db01d5fcb9e79b704af368712d68b4950251
5
5
  SHA512:
6
- metadata.gz: bd9275c7e845841fc472e3e0ec92593354c6293bae3c8dc9d7258acb71fcd5db7abaca2c2aadcc4c7701952b65eae8a14da29c1e8e26d71a7d9df2c6aad9abeb
7
- data.tar.gz: 8cc089f45244d80428d92feeeb162d8c65fafcea915e00405c54f5340dee4f9106113ad87e36c97a18fd159c3d338bee0f78869e91d200f19b145df494e5edfd
6
+ metadata.gz: 3e73dbdd868ec57b8138a9e4b0b529c0f2c44cdb21d43c0ae29ca9094512f4044cc621a38e4ac217bf6b17e13f1ef3a361cf5917023eab87d30c7b4cc90eccfc
7
+ data.tar.gz: e174fabde62d6348f8517f7c2d019600b34612f5b8d822eb83c2c5579f5e224596b6edaec383bfe9cce75c3260e50a4a0b744764119cd96e2a973f92b49b7d5a
data/CHANGELOG.md CHANGED
@@ -4,6 +4,11 @@ This project adheres to [Semantic Versioning](http://semver.org/).
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.15.1] - 2024-11-21
8
+ ### Fixed
9
+ - Fix Map/Parallel States checking container_runner#status! of finished states ([#296](https://github.com/ManageIQ/floe/pull/296))
10
+ - Fix child workflow mixin tight loop ([#297](https://github.com/ManageIQ/floe/pull/297))
11
+
7
12
  ## [0.15.0] - 2024-10-28
8
13
  ### Added
9
14
  - Add WorkflowBase base class for Workflow ([#279](https://github.com/ManageIQ/floe/pull/279))
@@ -267,7 +272,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).
267
272
  ### Added
268
273
  - Initial release
269
274
 
270
- [Unreleased]: https://github.com/ManageIQ/floe/compare/v0.14.0...HEAD
275
+ [Unreleased]: https://github.com/ManageIQ/floe/compare/v0.15.1...HEAD
276
+ [0.15.1]: https://github.com/ManageIQ/floe/compare/v0.15.0...v0.15.1
277
+ [0.15.0]: https://github.com/ManageIQ/floe/compare/v0.14.0...v0.15.0
271
278
  [0.14.0]: https://github.com/ManageIQ/floe/compare/v0.13.1...v0.14.0
272
279
  [0.13.1]: https://github.com/ManageIQ/floe/compare/v0.13.0...v0.13.1
273
280
  [0.13.0]: https://github.com/ManageIQ/floe/compare/v0.12.0...v0.13.0
data/lib/floe/cli.rb CHANGED
@@ -1,9 +1,12 @@
1
+ require "floe"
2
+ require "floe/container_runner"
3
+
1
4
  module Floe
2
5
  class CLI
6
+ include Logging
7
+
3
8
  def initialize
4
9
  require "optimist"
5
- require "floe"
6
- require "floe/container_runner"
7
10
  require "logger"
8
11
 
9
12
  Floe.logger = Logger.new($stdout)
@@ -20,12 +23,22 @@ module Floe
20
23
  create_workflow(workflow, opts[:context], input, credentials)
21
24
  end
22
25
 
23
- Floe::Workflow.wait(workflows, &:run_nonblock)
26
+ output_streams = create_loggers(workflows, opts[:segment_output])
27
+
28
+ logger.info("Checking #{workflows.count} workflows...")
29
+ ready = Floe::Workflow.wait(workflows, &:run_nonblock)
30
+ logger.info("Checking #{workflows.count} workflows...Complete - #{ready.count} ready")
24
31
 
25
32
  # Display status
26
33
  workflows.each do |workflow|
27
- puts "", "#{workflow.name}#{" (#{workflow.status})" unless workflow.context.success?}", "===" if workflows.size > 1
28
- puts workflow.output
34
+ if workflows.size > 1
35
+ logger.info("")
36
+ logger.info("#{workflow.name}#{" (#{workflow.status})" unless workflow.context.success?}")
37
+ logger.info("===")
38
+ end
39
+
40
+ logger.info(output_streams[workflow].string) if output_streams[workflow]
41
+ logger.info(workflow.output)
29
42
  end
30
43
 
31
44
  workflows.all? { |workflow| workflow.context.success? }
@@ -49,6 +62,7 @@ module Floe
49
62
  opt :context, "JSON payload of the Context", :type => :string
50
63
  opt :credentials, "JSON payload with Credentials", :type => :string
51
64
  opt :credentials_file, "Path to a file with Credentials", :type => :string
65
+ opt :segment_output, "Segment output by each worker", :default => false
52
66
 
53
67
  Floe::ContainerRunner.cli_options(self)
54
68
 
@@ -89,5 +103,17 @@ module Floe
89
103
  context = Floe::Workflow::Context.new(context_payload, :input => input, :credentials => credentials)
90
104
  Floe::Workflow.load(workflow, context)
91
105
  end
106
+
107
+ def create_loggers(workflows, segment_output)
108
+ if workflows.size == 1 || !segment_output
109
+ # no extra work necessary
110
+ {}
111
+ else
112
+ workflows.each_with_object({}) do |workflow, h|
113
+ workflow.context.logger = Logger.new(output = StringIO.new)
114
+ h[workflow] = output
115
+ end
116
+ end
117
+ end
92
118
  end
93
119
  end
@@ -30,7 +30,7 @@ module Floe
30
30
  end
31
31
 
32
32
  begin
33
- runner_context["container_ref"] = run_container(image, env, execution_id, runner_context["secrets_ref"])
33
+ runner_context["container_ref"] = run_container(image, env, execution_id, runner_context["secrets_ref"], context.logger)
34
34
  runner_context
35
35
  rescue AwesomeSpawn::CommandResultError => err
36
36
  cleanup(runner_context)
@@ -123,7 +123,7 @@ module Floe
123
123
 
124
124
  attr_reader :network
125
125
 
126
- def run_container(image, env, execution_id, secrets_file)
126
+ def run_container(image, env, execution_id, secrets_file, logger)
127
127
  params = run_container_params(image, env, execution_id, secrets_file)
128
128
 
129
129
  logger.debug("Running #{AwesomeSpawn.build_command_line(self.class::DOCKER_COMMAND, params)}")
@@ -182,8 +182,8 @@ module Floe
182
182
 
183
183
  def inspect_container(container_id)
184
184
  JSON.parse(docker!("inspect", container_id).output).first
185
- rescue
186
- nil
185
+ rescue AwesomeSpawn::CommandResultError => err
186
+ raise Floe::ExecutionError.new("Failed to get status for container #{container_id}: #{err}")
187
187
  end
188
188
 
189
189
  def delete_container(container_id)
@@ -155,6 +155,8 @@ module Floe
155
155
 
156
156
  def pod_info(pod_name)
157
157
  kubeclient.get_pod(pod_name, namespace)
158
+ rescue Kubeclient::HttpError => err
159
+ raise Floe::ExecutionError.new("Failed to get status for pod #{namespace}/#{pod_name}: #{err}")
158
160
  end
159
161
 
160
162
  def pod_running?(context)
@@ -285,7 +287,8 @@ module Floe
285
287
  code = notice.object&.code
286
288
  reason = notice.object&.reason
287
289
 
288
- logger.warn("Received [#{code} #{reason}], [#{message}]")
290
+ # This feels like a global concern and not an end user's concern
291
+ Floe.logger.warn("Received [#{code} #{reason}], [#{message}]")
289
292
 
290
293
  true
291
294
  end
data/lib/floe/logging.rb CHANGED
@@ -7,7 +7,11 @@ module Floe
7
7
  end
8
8
 
9
9
  def logger
10
- Floe.logger
10
+ @logger || Floe.logger
11
+ end
12
+
13
+ def logger=(logger)
14
+ @logger = logger
11
15
  end
12
16
  end
13
17
  end
data/lib/floe/runner.rb CHANGED
@@ -2,8 +2,6 @@
2
2
 
3
3
  module Floe
4
4
  class Runner
5
- include Logging
6
-
7
5
  OUTPUT_MARKER = "__FLOE_OUTPUT__\n"
8
6
 
9
7
  def initialize(_options = {}) # rubocop:disable Style/RedundantInitialize
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.15.0"
4
+ VERSION = "0.15.1"
5
5
  end
@@ -11,7 +11,7 @@ module Floe
11
11
  # e.g.: (String)(LessThan)(Path), (Numeric)(GreaterThanEquals)()
12
12
  OPERATION = /^(#{(TYPES - %w[Null Present]).join("|")})(#{COMPARES.join("|")})(Path)?$/.freeze
13
13
 
14
- attr_reader :variable, :compare_key, :type, :compare_predicate, :path
14
+ attr_reader :variable, :compare_key, :operation, :type, :compare_predicate, :path
15
15
 
16
16
  def initialize(_workflow, _name, payload)
17
17
  super
@@ -25,39 +25,7 @@ module Floe
25
25
 
26
26
  lhs = variable_value(context, input)
27
27
  rhs = compare_value(context, input)
28
-
29
- case compare_key
30
- when "IsNull" then is_null?(lhs, rhs)
31
- when "IsNumeric" then is_numeric?(lhs, rhs)
32
- when "IsString" then is_string?(lhs, rhs)
33
- when "IsBoolean" then is_boolean?(lhs, rhs)
34
- when "IsTimestamp" then is_timestamp?(lhs, rhs)
35
- when "StringEquals", "StringEqualsPath",
36
- "NumericEquals", "NumericEqualsPath",
37
- "BooleanEquals", "BooleanEqualsPath",
38
- "TimestampEquals", "TimestampEqualsPath"
39
- lhs == rhs
40
- when "StringLessThan", "StringLessThanPath",
41
- "NumericLessThan", "NumericLessThanPath",
42
- "TimestampLessThan", "TimestampLessThanPath"
43
- lhs < rhs
44
- when "StringGreaterThan", "StringGreaterThanPath",
45
- "NumericGreaterThan", "NumericGreaterThanPath",
46
- "TimestampGreaterThan", "TimestampGreaterThanPath"
47
- lhs > rhs
48
- when "StringLessThanEquals", "StringLessThanEqualsPath",
49
- "NumericLessThanEquals", "NumericLessThanEqualsPath",
50
- "TimestampLessThanEquals", "TimestampLessThanEqualsPath"
51
- lhs <= rhs
52
- when "StringGreaterThanEquals", "StringGreaterThanEqualsPath",
53
- "NumericGreaterThanEquals", "NumericGreaterThanEqualsPath",
54
- "TimestampGreaterThanEquals", "TimestampGreaterThanEqualsPath"
55
- lhs >= rhs
56
- when "StringMatches"
57
- lhs.match?(Regexp.escape(rhs).gsub('\*', '.*?'))
58
- else
59
- raise Floe::InvalidWorkflowError, "Invalid choice [#{compare_key}]"
60
- end
28
+ send(operation, lhs, rhs)
61
29
  end
62
30
 
63
31
  private
@@ -112,26 +80,53 @@ module Floe
112
80
  # rubocop:enable Naming/PredicateName
113
81
  # rubocop:enable Style/OptionalBooleanParameter
114
82
 
83
+ def equals?(lhs, rhs)
84
+ lhs == rhs
85
+ end
86
+
87
+ def lessthan?(lhs, rhs)
88
+ lhs < rhs
89
+ end
90
+
91
+ def greaterthan?(lhs, rhs)
92
+ lhs > rhs
93
+ end
94
+
95
+ def lessthanequals?(lhs, rhs)
96
+ lhs <= rhs
97
+ end
98
+
99
+ def greaterthanequals?(lhs, rhs)
100
+ lhs >= rhs
101
+ end
102
+
103
+ def matches?(lhs, rhs)
104
+ lhs.match?(Regexp.escape(rhs).gsub('\*', '.*?'))
105
+ end
106
+
115
107
  # parse the compare key at initialization time
116
108
  def parse_compare_key
117
109
  payload.each_key do |key|
118
110
  # e.g. (String)(GreaterThan)(Path)
119
111
  if (match_values = OPERATION.match(key))
120
112
  @compare_key = key
121
- @type, _operator, @path = match_values.captures
113
+ @type, operator, @path = match_values.captures
114
+ @operation = "#{operator.downcase}?".to_sym
122
115
  @compare_predicate = parse_predicate(type)
123
116
  break
124
117
  end
125
118
  # e.g. (Is)(String)
126
- if TYPE_CHECK.match?(key)
119
+ if (match_value = TYPE_CHECK.match(key))
127
120
  @compare_key = key
121
+ _operator, type = match_value.captures
128
122
  # type: nil means no runtime type checking.
129
123
  @type = @path = nil
124
+ @operation = "is_#{type.downcase}?".to_sym
130
125
  @compare_predicate = parse_predicate("Boolean")
131
126
  break
132
127
  end
133
128
  end
134
- parser_error!("requires a compare key") unless compare_key
129
+ parser_error!("requires a compare key") if compare_key.nil? || operation.nil?
135
130
  end
136
131
 
137
132
  # parse predicate at initilization time
@@ -3,11 +3,13 @@
3
3
  module Floe
4
4
  class Workflow
5
5
  class Context
6
+ include Logging
7
+
6
8
  attr_accessor :credentials
7
9
 
8
10
  # @param context [Json|Hash] (default, create another with input and execution params)
9
11
  # @param input [Hash] (default: {})
10
- def initialize(context = nil, input: nil, credentials: {})
12
+ def initialize(context = nil, input: nil, credentials: {}, logger: nil)
11
13
  context = JSON.parse(context) if context.kind_of?(String)
12
14
  input = JSON.parse(input || "{}")
13
15
 
@@ -20,6 +22,8 @@ module Floe
20
22
  self["Task"] ||= {}
21
23
 
22
24
  @credentials = credentials || {}
25
+
26
+ self.logger = logger if logger
23
27
  rescue JSON::ParserError => err
24
28
  raise Floe::InvalidExecutionInput, "Invalid State Machine Execution Input: #{err}: was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')"
25
29
  end
@@ -3,7 +3,6 @@
3
3
  module Floe
4
4
  class Workflow
5
5
  class State
6
- include Logging
7
6
  include ValidationMixin
8
7
 
9
8
  class << self
@@ -56,14 +55,22 @@ module Floe
56
55
  mark_started(context)
57
56
  end
58
57
 
58
+ def started?(context)
59
+ context.state_started?
60
+ end
61
+
59
62
  def finish(context)
60
63
  mark_finished(context)
61
64
  end
62
65
 
66
+ def finished?(context)
67
+ context.state_finished?
68
+ end
69
+
63
70
  def mark_started(context)
64
71
  context.state["EnteredTime"] = Time.now.utc.iso8601
65
72
 
66
- logger.info("Running state: [#{long_name}] with input [#{context.json_input}]...")
73
+ context.logger.info("Running state: [#{long_name}] with input [#{context.json_input}]...")
67
74
  end
68
75
 
69
76
  def mark_finished(context)
@@ -74,7 +81,7 @@ module Floe
74
81
  context.state["Duration"] = finished_time - entered_time
75
82
 
76
83
  level = context.failed? ? :error : :info
77
- logger.public_send(level, "Running state: [#{long_name}] with input [#{context.json_input}]...Complete #{context.next_state ? "- next state [#{context.next_state}]" : "workflow -"} output: [#{context.json_output}]")
84
+ context.logger.public_send(level, "Running state: [#{long_name}] with input [#{context.json_input}]...Complete #{context.next_state ? "- next state [#{context.next_state}]" : "workflow -"} output: [#{context.json_output}]")
78
85
 
79
86
  0
80
87
  end
@@ -88,7 +95,7 @@ module Floe
88
95
  end
89
96
 
90
97
  def ready?(context)
91
- !context.state_started? || !running?(context)
98
+ !started?(context) || !running?(context)
92
99
  end
93
100
 
94
101
  def running?(context)
@@ -6,8 +6,7 @@ module Floe
6
6
  module ChildWorkflowMixin
7
7
  def run_nonblock!(context)
8
8
  start(context) unless context.state_started?
9
-
10
- step_nonblock!(context) while running?(context)
9
+ step_nonblock!(context)
11
10
  return Errno::EAGAIN unless ready?(context)
12
11
 
13
12
  finish(context) if ended?(context)
@@ -29,7 +29,7 @@ module Floe
29
29
  wait_until!(context, :seconds => retrier.sleep_duration(context["State"]["RetryCount"]))
30
30
  context.next_state = context.state_name
31
31
  context.output = error
32
- logger.info("Running state: [#{long_name}] with input [#{context.json_input}] got error[#{context.json_output}]...Retry - delay: #{wait_until(context)}")
32
+ context.logger.info("Running state: [#{long_name}] with input [#{context.json_input}] got error[#{context.json_output}]...Retry - delay: #{wait_until(context)}")
33
33
  true
34
34
  end
35
35
 
@@ -39,7 +39,7 @@ module Floe
39
39
 
40
40
  context.next_state = catcher.next
41
41
  context.output = catcher.result_path.set(context.input, error)
42
- logger.info("Running state: [#{long_name}] with input [#{context.json_input}]...CatchError - next state: [#{context.next_state}] output: [#{context.json_output}]")
42
+ context.logger.info("Running state: [#{long_name}] with input [#{context.json_input}]...CatchError - next state: [#{context.next_state}] output: [#{context.json_output}]")
43
43
 
44
44
  true
45
45
  end
@@ -49,7 +49,7 @@ module Floe
49
49
  # keeping in here for completeness
50
50
  context.next_state = nil
51
51
  context.output = error
52
- logger.error("Running state: [#{long_name}] with input [#{context.json_input}]...Complete workflow - output: [#{context.json_output}]")
52
+ context.logger.error("Running state: [#{long_name}] with input [#{context.json_input}]...Complete workflow - output: [#{context.json_output}]")
53
53
  end
54
54
  end
55
55
  end
@@ -61,7 +61,8 @@ module Floe
61
61
  end
62
62
 
63
63
  def running?(context)
64
- return true if waiting?(context)
64
+ return true if waiting?(context)
65
+ return false if finished?(context)
65
66
 
66
67
  runner.status!(context.state["RunnerContext"])
67
68
  runner.running?(context.state["RunnerContext"])
data/lib/floe/workflow.rb CHANGED
@@ -18,7 +18,6 @@ module Floe
18
18
 
19
19
  def wait(workflows, timeout: nil, &block)
20
20
  workflows = [workflows] if workflows.kind_of?(self)
21
- logger.info("Checking #{workflows.count} workflows...")
22
21
 
23
22
  run_until = Time.now.utc + timeout if timeout.to_i > 0
24
23
  ready = []
@@ -72,7 +71,6 @@ module Floe
72
71
  sleep_thread&.kill
73
72
  end
74
73
 
75
- logger.info("Checking #{workflows.count} workflows...Complete - #{ready.count} ready")
76
74
  ready
77
75
  ensure
78
76
  wait_thread&.kill
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.15.0
4
+ version: 0.15.1
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-10-28 00:00:00.000000000 Z
11
+ date: 2024-11-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: awesome_spawn