floe 0.15.0 → 0.15.1

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: 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