floe 0.13.1 → 0.14.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: 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