floe 0.13.1 → 0.14.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: 6fb1e855fda7d4a00a72e494b820a89304350945c14a71675d5cc6306e69f079
4
- data.tar.gz: 9780e210073c41b4a5c534bdabd301c2df6275c13328dbb1ee43569470e8d590
3
+ metadata.gz: e0e0b86c08d322e2ebc8e115e5193d0493f8257c831f7377b18c32d4284ae6f2
4
+ data.tar.gz: 1bf5ed62abafe2e1af6025dbaf47d9faa3b1c93ffec8fd88fbeacd4445efc795
5
5
  SHA512:
6
- metadata.gz: bec5c7f6337c74258e185b76abfc3953f05ef16cf4de640972d23b7dfa81bf4439df3ce00dd539c618f7d18905d783bfa55dc777365e1a48969942b182448107
7
- data.tar.gz: cf584d349c69927dec6945fe21d652b79e96f6228754b1a91a57b3241825661ed6d977a7d53e3ebfae526551da2a4a573148e9d855840d8edc52b8ebeffc948b
6
+ metadata.gz: 34f64555f2472c121fcea9dda8baac7fbe853cdbc5e949c831d7b7f070878d3ef2f86ed90d907b5a7033539fcad6aeea64a4dee6475a752d7f89e4f679a4b269
7
+ data.tar.gz: 378c5bcd562d4bbeb7e3eb13eb55b1d5d8e7aed0ea4749c2b65f951cf3be0e5f13d3e288cdbf63b4a5729e98c5010656d6dca3db60ffbf8463988008980edd83
data/CHANGELOG.md CHANGED
@@ -4,6 +4,13 @@ This project adheres to [Semantic Versioning](http://semver.org/).
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.14.0] - 2024-08-20
8
+ ### Added
9
+ - Implement "IsNumeric": false ([#266](https://github.com/ManageIQ/floe/pull/266))
10
+ - Support choices that do not have a Default defined ([#267](https://github.com/ManageIQ/floe/pull/267))
11
+ - Label containers/pods with workflow Execution ID ([#268](https://github.com/ManageIQ/floe/pull/268))
12
+ - Allow for Execution Id to be passed in ([#269](https://github.com/ManageIQ/floe/pull/269))
13
+
7
14
  ## [0.13.1] - 2024-08-16
8
15
  ### Fixed
9
16
  - Fix podman/docker container_ref trailing newline ([#265](https://github.com/ManageIQ/floe/pull/265))
@@ -242,7 +249,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
242
249
  ### Added
243
250
  - Initial release
244
251
 
245
- [Unreleased]: https://github.com/ManageIQ/floe/compare/v0.13.1...HEAD
252
+ [Unreleased]: https://github.com/ManageIQ/floe/compare/v0.14.0...HEAD
253
+ [0.14.0]: https://github.com/ManageIQ/floe/compare/v0.13.1...v0.14.0
246
254
  [0.13.1]: https://github.com/ManageIQ/floe/compare/v0.13.0...v0.13.1
247
255
  [0.13.0]: https://github.com/ManageIQ/floe/compare/v0.12.0...v0.13.0
248
256
  [0.12.0]: https://github.com/ManageIQ/floe/compare/v0.11.3...v0.12.0
@@ -18,11 +18,11 @@ module Floe
18
18
  @pull_policy = options["pull-policy"]
19
19
  end
20
20
 
21
- def run_async!(resource, env = {}, secrets = {}, _context = {})
21
+ def run_async!(resource, env, secrets, context)
22
22
  raise ArgumentError, "Invalid resource" unless resource&.start_with?("docker://")
23
23
 
24
- image = resource.sub("docker://", "")
25
-
24
+ image = resource.sub("docker://", "")
25
+ execution_id = context.execution["Id"]
26
26
  runner_context = {}
27
27
 
28
28
  if secrets && !secrets.empty?
@@ -30,7 +30,7 @@ module Floe
30
30
  end
31
31
 
32
32
  begin
33
- runner_context["container_ref"] = run_container(image, env, runner_context["secrets_ref"])
33
+ runner_context["container_ref"] = run_container(image, env, execution_id, runner_context["secrets_ref"])
34
34
  runner_context
35
35
  rescue AwesomeSpawn::CommandResultError => err
36
36
  cleanup(runner_context)
@@ -123,8 +123,8 @@ module Floe
123
123
 
124
124
  attr_reader :network
125
125
 
126
- def run_container(image, env, secrets_file)
127
- params = run_container_params(image, env, secrets_file)
126
+ def run_container(image, env, execution_id, secrets_file)
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)}")
130
130
 
@@ -132,13 +132,14 @@ module Floe
132
132
  result.output.chomp
133
133
  end
134
134
 
135
- def run_container_params(image, env, secrets_file)
135
+ def run_container_params(image, env, execution_id, secrets_file)
136
136
  params = ["run"]
137
137
  params << :detach
138
138
  params += env.map { |k, v| [:e, "#{k}=#{v}"] }
139
139
  params << [:e, "_CREDENTIALS=/run/secrets"] if secrets_file
140
140
  params << [:pull, @pull_policy] if @pull_policy
141
141
  params << [:net, "host"] if @network == "host"
142
+ params << [:label, "execution_id=#{execution_id}"]
142
143
  params << [:v, "#{secrets_file}:/run/secrets:z"] if secrets_file
143
144
  params << [:name, container_name(image)]
144
145
  params << image
@@ -157,11 +158,11 @@ module Floe
157
158
  event = docker_event_status_to_event(status)
158
159
  running = event != :delete
159
160
 
160
- name, exit_code = notice.dig("Actor", "Attributes")&.values_at("name", "exitCode")
161
+ name, exit_code, execution_id = notice.dig("Actor", "Attributes")&.values_at("name", "exitCode", "execution_id")
161
162
 
162
163
  runner_context = {"container_ref" => name, "container_state" => {"Running" => running, "ExitCode" => exit_code.to_i}}
163
164
 
164
- [event, runner_context]
165
+ [event, {"execution_id" => execution_id, "runner_context" => runner_context}]
165
166
  rescue JSON::ParserError
166
167
  []
167
168
  end
@@ -45,17 +45,17 @@ module Floe
45
45
  super
46
46
  end
47
47
 
48
- def run_async!(resource, env = {}, secrets = {}, _context = {})
48
+ def run_async!(resource, env, secrets, context)
49
49
  raise ArgumentError, "Invalid resource" unless resource&.start_with?("docker://")
50
50
 
51
51
  image = resource.sub("docker://", "")
52
52
  name = container_name(image)
53
53
  secret = create_secret!(secrets) if secrets && !secrets.empty?
54
-
54
+ execution_id = context.execution["Id"]
55
55
  runner_context = {"container_ref" => name, "container_state" => {"phase" => "Pending"}, "secrets_ref" => secret}
56
56
 
57
57
  begin
58
- create_pod!(name, image, env, secret)
58
+ create_pod!(name, image, env, execution_id, secret)
59
59
  runner_context
60
60
  rescue Kubeclient::HttpError => err
61
61
  cleanup(runner_context)
@@ -171,13 +171,14 @@ module Floe
171
171
  failed_container_states(context).any?
172
172
  end
173
173
 
174
- def pod_spec(name, image, env, secret = nil)
174
+ def pod_spec(name, image, env, execution_id, secret = nil)
175
175
  spec = {
176
176
  :kind => "Pod",
177
177
  :apiVersion => "v1",
178
178
  :metadata => {
179
179
  :name => name,
180
- :namespace => namespace
180
+ :namespace => namespace,
181
+ :labels => {"execution_id" => execution_id}
181
182
  },
182
183
  :spec => {
183
184
  :containers => [
@@ -219,8 +220,8 @@ module Floe
219
220
  spec
220
221
  end
221
222
 
222
- def create_pod!(name, image, env, secret = nil)
223
- kubeclient.create_pod(pod_spec(name, image, env, secret))
223
+ def create_pod!(name, image, env, execution_id, secret = nil)
224
+ kubeclient.create_pod(pod_spec(name, image, env, execution_id, secret))
224
225
  end
225
226
 
226
227
  def delete_pod!(name)
@@ -294,9 +295,10 @@ module Floe
294
295
 
295
296
  pod = notice.object
296
297
  container_ref = pod.metadata.name
298
+ execution_id = pod.metadata.labels["execution_id"]
297
299
  container_state = pod.to_h[:status].deep_stringify_keys
298
300
 
299
- {"container_ref" => container_ref, "container_state" => container_state}
301
+ {"execution_id" => execution_id, "runner_context" => {"container_ref" => container_ref, "container_state" => container_state}}
300
302
  end
301
303
 
302
304
  def kubeclient
@@ -30,13 +30,14 @@ module Floe
30
30
 
31
31
  private
32
32
 
33
- def run_container_params(image, env, secret)
33
+ def run_container_params(image, env, execution_id, secret)
34
34
  params = ["run"]
35
35
  params << :detach
36
36
  params += env.map { |k, v| [:e, "#{k}=#{v}"] }
37
37
  params << [:e, "_CREDENTIALS=/run/secrets/#{secret}"] if secret
38
38
  params << [:pull, @pull_policy] if @pull_policy
39
39
  params << [:net, "host"] if @network == "host"
40
+ params << [:label, "execution_id=#{execution_id}"]
40
41
  params << [:secret, secret] if secret
41
42
  params << [:name, container_name(image)]
42
43
  params << image
@@ -55,14 +56,16 @@ module Floe
55
56
  end
56
57
 
57
58
  def parse_notice(notice)
58
- id, status, exit_code = JSON.parse(notice).values_at("ID", "Status", "ContainerExitCode")
59
+ notice = JSON.parse(notice)
60
+ id, status, exit_code, attributes = notice.values_at("ID", "Status", "ContainerExitCode", "Attributes")
59
61
 
60
- event = podman_event_status_to_event(status)
61
- running = event != :delete
62
+ execution_id = attributes&.dig("execution_id")
63
+ event = podman_event_status_to_event(status)
64
+ running = event != :delete
62
65
 
63
66
  runner_context = {"container_ref" => id, "container_state" => {"Running" => running, "ExitCode" => exit_code.to_i}}
64
67
 
65
- [event, runner_context]
68
+ [event, {"execution_id" => execution_id, "runner_context" => runner_context}]
66
69
  rescue JSON::ParserError
67
70
  []
68
71
  end
@@ -18,6 +18,10 @@ module Floe
18
18
  self.class.invalid_field_error!(name, field_name, field_value, comment)
19
19
  end
20
20
 
21
+ def runtime_field_error!(field_name, field_value, comment, floe_error: "States.Runtime")
22
+ raise Floe::ExecutionError.new(self.class.field_error_text(name, field_name, field_value, comment), floe_error)
23
+ end
24
+
21
25
  def workflow_state?(field_value, workflow)
22
26
  workflow.payload["States"] ? workflow.payload["States"].include?(field_value) : true
23
27
  end
@@ -39,10 +43,14 @@ module Floe
39
43
  end
40
44
 
41
45
  def invalid_field_error!(name, field_name, field_value, comment)
46
+ raise Floe::InvalidWorkflowError, field_error_text(name, field_name, field_value, comment)
47
+ end
48
+
49
+ def field_error_text(name, field_name, field_value, comment = nil)
42
50
  # instead of displaying a large hash or array, just displaying the word Hash or Array
43
51
  field_value = field_value.class if field_value.kind_of?(Hash) || field_value.kind_of?(Array)
44
52
 
45
- parser_error!(name, "field \"#{field_name}\"#{" value \"#{field_value}\"" unless field_value.nil?} #{comment}")
53
+ "#{Array(name).join(".")} field \"#{field_name}\"#{" value \"#{field_value}\"" unless field_value.nil?} #{comment}"
46
54
  end
47
55
  end
48
56
  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.13.1"
4
+ VERSION = "0.14.0"
5
5
  end
@@ -23,11 +23,11 @@ module Floe
23
23
  rhs = compare_value(context, input)
24
24
 
25
25
  case compare_key
26
- when "IsNull" then is_null?(lhs)
27
- when "IsNumeric" then is_numeric?(lhs)
28
- when "IsString" then is_string?(lhs)
29
- when "IsBoolean" then is_boolean?(lhs)
30
- when "IsTimestamp" then is_timestamp?(lhs)
26
+ when "IsNull" then is_null?(lhs, rhs)
27
+ when "IsNumeric" then is_numeric?(lhs, rhs)
28
+ when "IsString" then is_string?(lhs, rhs)
29
+ when "IsBoolean" then is_boolean?(lhs, rhs)
30
+ when "IsTimestamp" then is_timestamp?(lhs, rhs)
31
31
  when "StringEquals", "StringEqualsPath",
32
32
  "NumericEquals", "NumericEqualsPath",
33
33
  "BooleanEquals", "BooleanEqualsPath",
@@ -62,47 +62,51 @@ module Floe
62
62
  # Get the right hand side for {"Variable": "$.foo", "IsPresent": true} i.e.: true
63
63
  # If true then return true when present.
64
64
  # If false then return true when not present.
65
- rhs = compare_value(context, input)
65
+ predicate = compare_value(context, input)
66
66
  # Don't need the variable_value, just need to see if the path finds the value.
67
67
  variable_value(context, input)
68
68
 
69
69
  # The variable_value is present
70
- # If rhs is true, then presence check was successful, return true.
71
- rhs
70
+ # If predicate is true, then presence check was successful, return true.
71
+ predicate
72
72
  rescue Floe::PathError
73
73
  # variable_value is not present. (the path lookup threw an error)
74
- # If rhs is false, then it successfully wasn't present, return true.
75
- !rhs
74
+ # If predicate is false, then it successfully wasn't present, return true.
75
+ !predicate
76
76
  end
77
77
 
78
- def is_null?(value) # rubocop:disable Naming/PredicateName
79
- value.nil?
78
+ # rubocop:disable Naming/PredicateName
79
+ # rubocop:disable Style/OptionalBooleanParameter
80
+ def is_null?(value, predicate = true)
81
+ value.nil? == predicate
80
82
  end
81
83
 
82
- def is_present?(value) # rubocop:disable Naming/PredicateName
83
- !value.nil?
84
+ def is_present?(value, predicate = true)
85
+ !value.nil? == predicate
84
86
  end
85
87
 
86
- def is_numeric?(value) # rubocop:disable Naming/PredicateName
87
- value.kind_of?(Numeric)
88
+ def is_numeric?(value, predicate = true)
89
+ value.kind_of?(Numeric) == predicate
88
90
  end
89
91
 
90
- def is_string?(value) # rubocop:disable Naming/PredicateName
91
- value.kind_of?(String)
92
+ def is_string?(value, predicate = true)
93
+ value.kind_of?(String) == predicate
92
94
  end
93
95
 
94
- def is_boolean?(value) # rubocop:disable Naming/PredicateName
95
- [true, false].include?(value)
96
+ def is_boolean?(value, predicate = true)
97
+ [true, false].include?(value) == predicate
96
98
  end
97
99
 
98
- def is_timestamp?(value) # rubocop:disable Naming/PredicateName
100
+ def is_timestamp?(value, predicate = true)
99
101
  require "date"
100
102
 
101
103
  DateTime.rfc3339(value)
102
- true
104
+ predicate
103
105
  rescue TypeError, Date::Error
104
- false
106
+ !predicate
105
107
  end
108
+ # rubocop:enable Naming/PredicateName
109
+ # rubocop:enable Style/OptionalBooleanParameter
106
110
 
107
111
  def parse_compare_key
108
112
  @compare_key = payload.keys.detect { |key| key.match?(/^(#{COMPARE_KEYS.join("|")})/) }
@@ -48,7 +48,7 @@ module Floe
48
48
  return Errno::EAGAIN unless ready?(context)
49
49
 
50
50
  finish(context)
51
- rescue Floe::Error => e
51
+ rescue Floe::ExecutionError => e
52
52
  mark_error(context, e)
53
53
  end
54
54
 
@@ -82,7 +82,7 @@ module Floe
82
82
  def mark_error(context, exception)
83
83
  # InputPath or OutputPath were bad.
84
84
  context.next_state = nil
85
- context.output = {"Error" => "States.Runtime", "Cause" => exception.message}
85
+ context.output = {"Error" => exception.floe_error, "Cause" => exception.message}
86
86
  # Since finish threw an exception, super was never called. Calling that now.
87
87
  mark_finished(context)
88
88
  end
@@ -23,6 +23,7 @@ module Floe
23
23
  output = output_path.value(context, input)
24
24
  next_state = choices.detect { |choice| choice.true?(context, output) }&.next || default
25
25
 
26
+ runtime_field_error!("Default", nil, "not defined and no match found", :floe_error => "States.NoChoiceMatched") if next_state.nil?
26
27
  context.next_state = next_state
27
28
  context.output = output
28
29
  super
data/lib/floe/workflow.rb CHANGED
@@ -63,9 +63,11 @@ module Floe
63
63
 
64
64
  loop do
65
65
  # Block until an event is raised
66
- event, runner_context = queue.pop
66
+ event, data = queue.pop
67
67
  break if event.nil?
68
68
 
69
+ _execution_id, runner_context = data.values_at("execution_id", "runner_context")
70
+
69
71
  # If the event is for one of our workflows set the updated runner_context
70
72
  workflows.each do |workflow|
71
73
  next unless workflow.context.state.dig("RunnerContext", "container_ref") == runner_context["container_ref"]
@@ -175,7 +177,7 @@ module Floe
175
177
  context.state["Input"] = context.execution["Input"].dup
176
178
  context.state["Guid"] = SecureRandom.uuid
177
179
 
178
- context.execution["Id"] = SecureRandom.uuid
180
+ context.execution["Id"] ||= SecureRandom.uuid
179
181
  context.execution["StartTime"] = Time.now.utc.iso8601
180
182
 
181
183
  self
data/lib/floe.rb CHANGED
@@ -43,7 +43,18 @@ module Floe
43
43
  class Error < StandardError; end
44
44
  class InvalidWorkflowError < Error; end
45
45
  class InvalidExecutionInput < Error; end
46
- class PathError < Error; end
46
+
47
+ class ExecutionError < Error
48
+ attr_reader :floe_error
49
+
50
+ def initialize(message, floe_error = "States.Runtime")
51
+ super(message)
52
+ @floe_error = floe_error
53
+ end
54
+ end
55
+
56
+ class PathError < ExecutionError
57
+ end
47
58
 
48
59
  def self.logger
49
60
  @logger ||= NullLogger.new
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.13.1
4
+ version: 0.14.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-08-16 00:00:00.000000000 Z
11
+ date: 2024-08-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: awesome_spawn