floe 0.21.0 → 0.22.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: 17d64b12761527067ed26cda1737c6f4bd20c8d1c9affbb5edf78b176b4713c7
4
- data.tar.gz: 22de6a27ac450b71d1467fb5b5fa304c6802e155f956bf2d0a72f6e097717aaa
3
+ metadata.gz: bf92b02c87a1fa72f7a6424a6f01413fed9830a6e35194762095e7af6e76c366
4
+ data.tar.gz: 6d9b6209e73739fc7d6f7572b7e630366127de06eb2dca550542dc7558790ca6
5
5
  SHA512:
6
- metadata.gz: 5e13a84b4568e4de952db7c05f5a29e6fc8fabaa0c944dc8b571a1514b5d9aae1118b94a4332e2e5456cdb0c776a629d1a19ef0d4a9803dd23e0844dc228cc03
7
- data.tar.gz: 5512f1229704cb82a6e354388fc1873fe2eaf7b91d0410931a7e2e9baaadd5cf53815faaa3d4cee4cc4994d5ce8713b52a8ab451d241c3e5c857fc0bfc36f5a5
6
+ metadata.gz: d03f3cde0036103469210a5a28eb20f2198c4cea7bb5306960a2fada722b109a2030f83858b51d9796a97cb30b6ce7a2f4b3be12209085e561009b9918d8a102
7
+ data.tar.gz: eca7b964bc9dbd5af65d122e01852650d4892731dc26fc30b71896bf256e98837d5d8c426ea84c59466aee6c4a8c383d67a455e8256c9f5b2fc5be97954065d6
data/CHANGELOG.md CHANGED
@@ -4,6 +4,15 @@ This project adheres to [Semantic Versioning](http://semver.org/).
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.22.0] - 2026-10-23
8
+ ### Fixed
9
+ - Fix TypeError in Docker#wait when timeout is given ([#352](https://github.com/ManageIQ/floe/pull/352))
10
+
11
+ ### Added
12
+ - Implement StateMachine TimeoutSeconds ([#350](https://github.com/ManageIQ/floe/pull/350))
13
+ - Add StateMachine Version ([#350](https://github.com/ManageIQ/floe/pull/350))
14
+ - Add timeout_at to Workflow and State ([#351](https://github.com/ManageIQ/floe/pull/351))
15
+
7
16
  ## [0.21.0] - 2026-09-16
8
17
  ### Added
9
18
  - Add volumes: option to run_async! for all container runners ([#347](https://github.com/ManageIQ/floe/pull/347))
@@ -356,7 +365,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
356
365
  ### Added
357
366
  - Initial release
358
367
 
359
- [Unreleased]: https://github.com/ManageIQ/floe/compare/v0.21.0...HEAD
368
+ [Unreleased]: https://github.com/ManageIQ/floe/compare/v0.22.0...HEAD
369
+ [0.22.0]: https://github.com/ManageIQ/floe/compare/v0.21.0...v0.22.0
360
370
  [0.21.0]: https://github.com/ManageIQ/floe/compare/v0.20.1...v0.21.0
361
371
  [0.20.1]: https://github.com/ManageIQ/floe/compare/v0.20.0...v0.20.1
362
372
  [0.20.0]: https://github.com/ManageIQ/floe/compare/v0.19.2...v0.20.0
@@ -48,7 +48,7 @@ module Floe
48
48
  end
49
49
 
50
50
  def wait(timeout: nil, events: %i[create update delete], &block)
51
- until_timestamp = (Time.now.utc + timeout).to_i if timeout
51
+ until_timestamp = Time.now.utc + timeout if timeout
52
52
 
53
53
  r, w = IO.pipe
54
54
 
@@ -168,7 +168,7 @@ module Floe
168
168
  since_timestamp = Time.now.utc.to_i
169
169
 
170
170
  params = ["events", "--format", "{{json .}}", "--filter", "type=container", "--since", since_timestamp.to_s]
171
- params += ["--until", until_timestamp.to_s] if until_timestamp
171
+ params += ["--until", until_timestamp.to_i.to_s] if until_timestamp
172
172
  params
173
173
  end
174
174
 
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.21.0"
4
+ VERSION = "0.22.0"
5
5
  end
@@ -26,7 +26,7 @@ module Floe
26
26
  raise Floe::InvalidExecutionInput, "Invalid State Machine Execution Input: #{err}: was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')"
27
27
  end
28
28
 
29
- def prepare_start(start_at)
29
+ def prepare_start(start_at, timeout_seconds: nil)
30
30
  return if started?
31
31
 
32
32
  state["Name"] = start_at
@@ -35,6 +35,7 @@ module Floe
35
35
 
36
36
  execution["Id"] ||= SecureRandom.uuid
37
37
  execution["StartTime"] = Time.now.utc.iso8601
38
+ execution["TimeoutAt"] = (Time.now.utc + timeout_seconds).iso8601 if timeout_seconds
38
39
 
39
40
  if logger.respond_to?(:execution_id=)
40
41
  logger.execution_id = execution["Id"]
@@ -45,8 +45,9 @@ module Floe
45
45
  def run_nonblock!(context)
46
46
  # Only start the state if it isn't already started and it isn't waiting
47
47
  # from a prior Retry.
48
- start(context) unless started?(context) || waiting?(context)
49
- return Errno::EAGAIN unless ready?(context)
48
+ start(context) unless started?(context) || waiting?(context)
49
+ raise Floe::TimeoutError if timed_out?(context)
50
+ return Errno::EAGAIN unless ready?(context)
50
51
 
51
52
  finish(context)
52
53
  rescue Floe::ExecutionError => e
@@ -115,6 +116,15 @@ module Floe
115
116
  context.state["WaitUntil"] && Time.parse(context.state["WaitUntil"])
116
117
  end
117
118
 
119
+ def timeout_at(context)
120
+ context.execution["TimeoutAt"] && Time.parse(context.execution["TimeoutAt"])
121
+ end
122
+
123
+ def timed_out?(context)
124
+ t = timeout_at(context)
125
+ t && Time.now.utc > t
126
+ end
127
+
118
128
  def short_name
119
129
  name.last
120
130
  end
@@ -39,8 +39,7 @@ module Floe
39
39
  def start(context)
40
40
  super
41
41
 
42
- # Wakeup no later than timeout_seconds to check if the Resource has timed out
43
- wait_until!(context, :seconds => timeout_seconds) if timeout_seconds
42
+ set_timeout_at!(context)
44
43
 
45
44
  input = process_input(context)
46
45
  secrets = credentials&.value(context, context.input)
@@ -62,7 +61,6 @@ module Floe
62
61
  end
63
62
 
64
63
  def running?(context)
65
- raise Floe::TimeoutError if timed_out?(context)
66
64
  return false if finished?(context)
67
65
 
68
66
  runner.status!(context.state["RunnerContext"])
@@ -80,8 +78,25 @@ module Floe
80
78
  @end
81
79
  end
82
80
 
81
+ def timeout_at(context)
82
+ workflow_timeout = super
83
+ task_timeout = context.state["TimeoutAt"] && Time.parse(context.state["TimeoutAt"])
84
+
85
+ [workflow_timeout, task_timeout].compact.min
86
+ end
87
+
83
88
  private
84
89
 
90
+ def set_timeout_at!(context)
91
+ return if timeout_seconds.nil? && timeout_seconds_path.nil?
92
+
93
+ entered_time = Time.parse(context.state["EnteredTime"])
94
+ seconds = timeout_seconds || timeout_seconds_path.value(context, context.input)
95
+ raise Floe::PathError, "TimeoutSecondsPath references an invalid value [#{seconds}]" unless seconds.kind_of?(Integer) && seconds > 0
96
+
97
+ context.state["TimeoutAt"] = (entered_time + seconds).iso8601
98
+ end
99
+
85
100
  attr_reader :runner
86
101
 
87
102
  def validate_state!(workflow)
@@ -107,22 +122,6 @@ module Floe
107
122
  runner.success?(context.state["RunnerContext"])
108
123
  end
109
124
 
110
- def timed_out?(context)
111
- return false if timeout_seconds.nil? && timeout_seconds_path.nil?
112
-
113
- timeout = timeout_seconds || timeout_seconds_path.value(context, context.input)
114
- entered_time = Time.parse(context.state["EnteredTime"])
115
-
116
- Time.now.utc > entered_time + timeout
117
- end
118
-
119
- def task_timed_out!(context)
120
- context.state["RunnerContext"]["Error"] = "States.Timeout"
121
- context.state["RunnerContext"]["Cause"] = "Task timed out"
122
-
123
- false
124
- end
125
-
126
125
  def parse_error(output)
127
126
  return if output.nil?
128
127
  return output if output.kind_of?(Hash)
data/lib/floe/workflow.rb CHANGED
@@ -42,9 +42,10 @@ module Floe
42
42
  break if timeout && (timeout.zero? || Time.now.utc > run_until)
43
43
 
44
44
  # Find the earliest time that we should wakeup if no container events
45
- # are caught, either a workflow in a Wait or Retry state or we've
46
- # exceeded the requested timeout
45
+ # are caught, either a workflow in a Wait, Retry, or Timeout state or
46
+ # we've exceeded the requested timeout
47
47
  wait_until = workflows.map(&:wait_until)
48
+ .concat(workflows.map(&:timeout_at))
48
49
  .unshift(run_until)
49
50
  .compact
50
51
  .min
@@ -77,7 +78,7 @@ module Floe
77
78
  end
78
79
  end
79
80
 
80
- attr_reader :comment, :context
81
+ attr_reader :comment, :context, :timeout_seconds, :version
81
82
 
82
83
  def initialize(payload, context = nil, credentials = nil, name = nil)
83
84
  payload = JSON.parse(payload) if payload.kind_of?(String)
@@ -88,8 +89,10 @@ module Floe
88
89
  # caller should really put credentials into context and not pass that variable
89
90
  context.credentials = credentials if credentials
90
91
 
91
- @context = context
92
- @comment = payload["Comment"]
92
+ @context = context
93
+ @comment = payload["Comment"]
94
+ @timeout_seconds = payload["TimeoutSeconds"]
95
+ @version = payload["Version"] || "1.0"
93
96
 
94
97
  super(payload, name)
95
98
  rescue Floe::Error
@@ -136,6 +139,10 @@ module Floe
136
139
  current_state.wait_until(context)
137
140
  end
138
141
 
142
+ def timeout_at
143
+ current_state.timeout_at(context)
144
+ end
145
+
139
146
  def status
140
147
  context.status
141
148
  end
@@ -150,7 +157,7 @@ module Floe
150
157
 
151
158
  # setup a workflow
152
159
  def start_workflow
153
- context.prepare_start(start_at)
160
+ context.prepare_start(start_at, :timeout_seconds => timeout_seconds)
154
161
  self
155
162
  end
156
163
 
@@ -170,6 +177,13 @@ module Floe
170
177
 
171
178
  private
172
179
 
180
+ def validate_workflow!
181
+ super
182
+ return unless timeout_seconds
183
+
184
+ invalid_field_error!("TimeoutSeconds", timeout_seconds, "must be a positive, non-zero integer") unless timeout_seconds.kind_of?(Integer) && timeout_seconds.positive?
185
+ end
186
+
173
187
  def step!
174
188
  next_state = {"Name" => context.next_state, "Guid" => SecureRandom.uuid, "PreviousStateGuid" => context.state["Guid"]}
175
189
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: floe
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.21.0
4
+ version: 0.22.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ManageIQ Developers