floe 0.20.1 → 0.21.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: f9620d0ace078bd773422b4a4a2f47c41891a312260f64a3a79c140aef0f9991
4
- data.tar.gz: afd5e5e8c2f72845df2a0851714b77a08d2cd34dfdc081542b52bbeb63a90a23
3
+ metadata.gz: 17d64b12761527067ed26cda1737c6f4bd20c8d1c9affbb5edf78b176b4713c7
4
+ data.tar.gz: 22de6a27ac450b71d1467fb5b5fa304c6802e155f956bf2d0a72f6e097717aaa
5
5
  SHA512:
6
- metadata.gz: 89130117cdb776734d58e28961a61e0c4fce39e465097e15200a907139bdd9c7597b05a0123099d8fdd5b186d349df6a0aacf971fc96cb436ffb415670c60a56
7
- data.tar.gz: 152b98a4c2dae077b54a122dca7e2b138dc03c6b04809316c60709c53a550af109d5bf8cfef458a49882179360ef48d1427344bccf995517145f49a84e80245a
6
+ metadata.gz: 5e13a84b4568e4de952db7c05f5a29e6fc8fabaa0c944dc8b571a1514b5d9aae1118b94a4332e2e5456cdb0c776a629d1a19ef0d4a9803dd23e0844dc228cc03
7
+ data.tar.gz: 5512f1229704cb82a6e354388fc1873fe2eaf7b91d0410931a7e2e9baaadd5cf53815faaa3d4cee4cc4994d5ce8713b52a8ab451d241c3e5c857fc0bfc36f5a5
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.21.0] - 2026-09-16
8
+ ### Added
9
+ - Add volumes: option to run_async! for all container runners ([#347](https://github.com/ManageIQ/floe/pull/347))
10
+ - Add entrypoint and command overrides to container runners ([#349](https://github.com/ManageIQ/floe/pull/349))
11
+
7
12
  ## [0.20.1] - 2026-09-15
8
13
  ### Fixed
9
14
  - Fix Execution Context not passed to Parallel branches ([#348](https://github.com/ManageIQ/floe/pull/348))
@@ -351,7 +356,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
351
356
  ### Added
352
357
  - Initial release
353
358
 
354
- [Unreleased]: https://github.com/ManageIQ/floe/compare/v0.20.1...HEAD
359
+ [Unreleased]: https://github.com/ManageIQ/floe/compare/v0.21.0...HEAD
360
+ [0.21.0]: https://github.com/ManageIQ/floe/compare/v0.20.1...v0.21.0
355
361
  [0.20.1]: https://github.com/ManageIQ/floe/compare/v0.20.0...v0.20.1
356
362
  [0.20.0]: https://github.com/ManageIQ/floe/compare/v0.19.2...v0.20.0
357
363
  [0.19.2]: https://github.com/ManageIQ/floe/compare/v0.19.1...v0.19.2
@@ -18,8 +18,10 @@ 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, volumes: [], entrypoint: nil, command: nil)
22
22
  raise ArgumentError, "Invalid resource" unless resource&.start_with?("docker://")
23
+ raise ArgumentError, "entrypoint must be a String" if entrypoint && !entrypoint.kind_of?(String)
24
+ raise ArgumentError, "command must be an Array" if command && !command.kind_of?(Array)
23
25
 
24
26
  image = resource.sub("docker://", "")
25
27
  execution_id = context.execution["Id"]
@@ -30,7 +32,7 @@ module Floe
30
32
  end
31
33
 
32
34
  begin
33
- runner_context["container_ref"] = run_container(image, env, execution_id, runner_context["secrets_ref"], context.logger)
35
+ runner_context["container_ref"] = run_container(image, env, execution_id, runner_context["secrets_ref"], context.logger, volumes, entrypoint, command)
34
36
  runner_context
35
37
  rescue AwesomeSpawn::CommandResultError => err
36
38
  cleanup(runner_context)
@@ -128,8 +130,8 @@ module Floe
128
130
 
129
131
  attr_reader :network
130
132
 
131
- def run_container(image, env, execution_id, secrets_file, logger)
132
- params = run_container_params(image, env, execution_id, secrets_file)
133
+ def run_container(image, env, execution_id, secrets_file, logger, volumes, entrypoint, command)
134
+ params = run_container_params(image, env, execution_id, secrets_file, volumes, entrypoint, command)
133
135
 
134
136
  logger.debug("Running #{AwesomeSpawn.build_command_line(self.class::DOCKER_COMMAND, params)}")
135
137
 
@@ -137,7 +139,7 @@ module Floe
137
139
  result.output.chomp
138
140
  end
139
141
 
140
- def run_container_params(image, env, execution_id, secrets_file)
142
+ def run_container_params(image, env, execution_id, secrets_file, volumes, entrypoint, command)
141
143
  params = ["run"]
142
144
  params << :detach
143
145
  params += env.map { |k, v| [:e, "#{k}=#{v}"] }
@@ -146,8 +148,20 @@ module Floe
146
148
  params << [:net, "host"] if @network == "host"
147
149
  params << [:label, "execution_id=#{execution_id}"]
148
150
  params << [:v, "#{secrets_file}:/run/secrets:z"] if secrets_file
151
+ params += volumes.map { |v| [:v, volume_to_flag(v)] }
152
+ params << [:entrypoint, entrypoint] if entrypoint
149
153
  params << [:name, container_name(image)]
150
154
  params << image
155
+ params.concat(command) if command
156
+ params
157
+ end
158
+
159
+ def volume_to_flag(volume)
160
+ options = volume.fetch(:options, "z")
161
+ source = volume[:volume_name] || volume[:host_path]
162
+ flag = "#{source}:#{volume[:container_path]}"
163
+ flag += ":#{options}" if options && !options.empty?
164
+ flag
151
165
  end
152
166
 
153
167
  def wait_params(until_timestamp)
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Floe
4
+ class ContainerRunner
5
+ class Kubernetes
6
+ # Mixin that handles host_path volumes for Kubernetes by staging the
7
+ # host-side directory to S3 for download by an init container.
8
+ module HostPathVolumeHandler
9
+ DEFAULT_INIT_IMAGE = "curlimages/curl:latest"
10
+ INIT_CONTAINER_NAME = "floe-init"
11
+
12
+ def init_host_path_volume_options(options)
13
+ @s3_endpoint = options["s3_endpoint"]
14
+ @s3_bucket = options["s3_bucket"]
15
+ @s3_access_key = options["s3_access_key"]
16
+ @s3_secret_key = options["s3_secret_key"]
17
+ @init_image = options.fetch("init_image", DEFAULT_INIT_IMAGE)
18
+ end
19
+
20
+ # Deletes every S3 object represented by staged_volumes, suppressing
21
+ # errors for each deletion.
22
+ def cleanup_staged_volumes(staged_volumes)
23
+ Array(staged_volumes).each do |volume|
24
+ key = volume[:s3_key] || volume["s3_key"]
25
+ Floe.logger.debug("Deleting s3://#{s3_bucket}/#{key}...")
26
+ delete_s3_object(key)
27
+ Floe.logger.debug("Deleting s3://#{s3_bucket}/#{key}...Complete")
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ attr_reader :s3_endpoint, :s3_bucket, :s3_access_key, :s3_secret_key, :init_image
34
+
35
+ def host_path_volume_instance_variables_to_hide
36
+ %i[@s3_access_key @s3_client @s3_secret_key]
37
+ end
38
+
39
+ # Upload each volume's host_path as a .tar.gz to S3, returning an array
40
+ # of hashes with :volume, :s3_key, :presigned_input_url.
41
+ def stage_host_path_volumes(volumes, execution_id, logger)
42
+ raise ArgumentError, "S3 must be configured (s3_endpoint, s3_bucket, s3_access_key, s3_secret_key) to use volumes with Kubernetes" unless s3_configured?
43
+
44
+ volumes.map do |volume|
45
+ s3_key = "floe/#{execution_id}/#{File.basename(volume[:container_path])}.tar.gz"
46
+ logger.debug("Staging volume #{volume[:host_path]} -> s3://#{s3_bucket}/#{s3_key}...")
47
+
48
+ tarball = create_tarball(volume[:host_path])
49
+ upload_to_s3(s3_key, tarball)
50
+ logger.debug("Staging volume #{volume[:host_path]} -> s3://#{s3_bucket}/#{s3_key}...Complete")
51
+ presigned_url = presign_s3_get(s3_key)
52
+
53
+ {:volume => volume, :s3_key => s3_key, :presigned_input_url => presigned_url}
54
+ end
55
+ end
56
+
57
+ def delete_s3_object(key)
58
+ s3_client.delete_object(:bucket => s3_bucket, :key => key)
59
+ rescue StandardError
60
+ nil
61
+ end
62
+
63
+ # Mutates spec in-place to add the emptyDir shared volumes and init
64
+ # container for staged volumes.
65
+ def add_host_path_volumes_to_spec!(spec, staged_volumes)
66
+ spec[:spec][:volumes] ||= []
67
+ primary = spec[:spec][:containers][0]
68
+ primary[:volumeMounts] ||= []
69
+
70
+ init_cmds = []
71
+ init_volume_mounts = []
72
+
73
+ staged_volumes.each_with_index do |sv, idx|
74
+ volume = sv[:volume]
75
+ share_name = "floe-volume-#{idx}"
76
+
77
+ # emptyDir shared between init container and primary
78
+ spec[:spec][:volumes] << {:name => share_name, :emptyDir => {}}
79
+
80
+ primary[:volumeMounts] << {
81
+ :name => share_name,
82
+ :mountPath => volume[:container_path]
83
+ }
84
+
85
+ init_volume_mounts << {
86
+ :name => share_name,
87
+ :mountPath => volume[:container_path]
88
+ }
89
+
90
+ # Init container downloads and unpacks each volume's tarball
91
+ init_cmds << "curl -fsSL '#{sv[:presigned_input_url]}' | tar -xzf - -C '#{volume[:container_path]}'"
92
+ end
93
+
94
+ # Init container: runs to completion before the primary starts, populating
95
+ # all shared volumes from S3. Primary command/args are left untouched.
96
+ spec[:spec][:initContainers] ||= []
97
+ spec[:spec][:initContainers] << {
98
+ :name => INIT_CONTAINER_NAME,
99
+ :image => init_image,
100
+ :command => ["sh", "-c", init_cmds.join(" && ")],
101
+ :volumeMounts => init_volume_mounts
102
+ }
103
+ end
104
+
105
+ def s3_configured?
106
+ !!(s3_endpoint && s3_bucket && s3_access_key && s3_secret_key)
107
+ end
108
+
109
+ def s3_client
110
+ require "aws-sdk-s3"
111
+
112
+ @s3_client ||= Aws::S3::Client.new(
113
+ :endpoint => s3_endpoint,
114
+ :region => "us-east-1", # required by SDK even for non-AWS endpoints
115
+ :access_key_id => s3_access_key,
116
+ :secret_access_key => s3_secret_key,
117
+ :force_path_style => true # required for MinIO and other non-AWS endpoints
118
+ )
119
+ end
120
+
121
+ def presign_s3_get(key)
122
+ require "aws-sdk-s3"
123
+
124
+ presigner = Aws::S3::Presigner.new(:client => s3_client)
125
+ presigner.presigned_url(:get_object, :bucket => s3_bucket, :key => key, :expires_in => 3600)
126
+ end
127
+
128
+ def create_tarball(source_path)
129
+ require "rubygems/package"
130
+ require "zlib"
131
+
132
+ gz_buffer = StringIO.new
133
+ Zlib::GzipWriter.wrap(gz_buffer) do |gz|
134
+ Gem::Package::TarWriter.new(gz) do |tar|
135
+ Dir.glob("#{source_path}/**/*", File::FNM_DOTMATCH).sort.each do |file|
136
+ relative = file.sub("#{source_path}/", "")
137
+ next if relative == "." || relative.empty?
138
+
139
+ stat = File.stat(file)
140
+ if stat.directory?
141
+ tar.mkdir(relative, stat.mode)
142
+ else
143
+ tar.add_file_simple(relative, stat.mode, stat.size) do |io|
144
+ File.open(file, "rb") { |f| IO.copy_stream(f, io) }
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
150
+ gz_buffer.string
151
+ end
152
+
153
+ def upload_to_s3(key, data)
154
+ s3_client.put_object(:bucket => s3_bucket, :key => key, :body => data)
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Floe
4
+ class ContainerRunner
5
+ class Kubernetes
6
+ # Mixin that attaches persistent volumes (e.g. pre-created docker volumes or
7
+ # PersistentVolumeClaims) directly to a pod spec without any staging.
8
+ # Each volume entry must have a :volume_name key (the PVC claim name) and
9
+ # a :container_path key. An optional :read_only key sets readOnly on the
10
+ # mount.
11
+ module PersistentVolumeHandler
12
+ private
13
+
14
+ # Mutates spec in-place to attach persistent volumes as PVCs. Each entry
15
+ # must have :volume_name (the PVC claim name) and :container_path.
16
+ def add_persistent_volumes_to_spec!(spec, persistent_volumes)
17
+ spec[:spec][:volumes] ||= []
18
+ primary = spec[:spec][:containers][0]
19
+ primary[:volumeMounts] ||= []
20
+
21
+ persistent_volumes.each_with_index do |vol, idx|
22
+ vol_name = "floe-persistent-volume-#{idx}"
23
+
24
+ spec[:spec][:volumes] << {
25
+ :name => vol_name,
26
+ :persistentVolumeClaim => {:claimName => vol[:volume_name]}
27
+ }
28
+
29
+ volume_mount = {
30
+ :name => vol_name,
31
+ :mountPath => vol[:container_path]
32
+ }
33
+ volume_mount[:readOnly] = true if vol[:read_only]
34
+
35
+ primary[:volumeMounts] << volume_mount
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -5,6 +5,12 @@ module Floe
5
5
  class Kubernetes < Floe::Runner
6
6
  include Floe::ContainerRunner::DockerMixin
7
7
 
8
+ require_relative "kubernetes/host_path_volume_handler"
9
+ include Floe::ContainerRunner::Kubernetes::HostPathVolumeHandler
10
+
11
+ require_relative "kubernetes/persistent_volume_handler"
12
+ include Floe::ContainerRunner::Kubernetes::PersistentVolumeHandler
13
+
8
14
  TOKEN_FILE = "/run/secrets/kubernetes.io/serviceaccount/token"
9
15
  CA_CERT_FILE = "/run/secrets/kubernetes.io/serviceaccount/ca.crt"
10
16
  RUNNING_PHASES = %w[Pending Running].freeze
@@ -42,11 +48,15 @@ module Floe
42
48
  @pull_policy = options["pull-policy"]
43
49
  @task_service_account = options["task_service_account"]
44
50
 
51
+ init_host_path_volume_options(options)
52
+
45
53
  super
46
54
  end
47
55
 
48
- def run_async!(resource, env, secrets, context)
56
+ def run_async!(resource, env, secrets, context, volumes: [], entrypoint: nil, command: nil)
49
57
  raise ArgumentError, "Invalid resource" unless resource&.start_with?("docker://")
58
+ raise ArgumentError, "entrypoint must be a String" if entrypoint && !entrypoint.kind_of?(String)
59
+ raise ArgumentError, "command must be an Array" if command && !command.kind_of?(Array)
50
60
 
51
61
  image = resource.sub("docker://", "")
52
62
  name = container_name(image)
@@ -54,8 +64,17 @@ module Floe
54
64
  execution_id = context.execution["Id"]
55
65
  runner_context = {"container_ref" => name, "container_state" => {"phase" => "Pending"}, "secrets_ref" => secret}
56
66
 
67
+ persistent_volumes, host_volumes = volumes.partition { |v| v[:volume_name] }
68
+ staged_volumes = stage_host_path_volumes(host_volumes, execution_id, context.logger) if host_volumes.any?
69
+ runner_context["staged_volumes"] = staged_volumes if staged_volumes
70
+
57
71
  begin
58
- create_pod!(name, image, env, execution_id, secret)
72
+ spec = pod_spec(name, image, env, execution_id, secret, staged_volumes || [], persistent_volumes || [], entrypoint, command)
73
+ context.logger.debug("Running pod #{name} with image #{image}")
74
+ kubeclient.create_pod(spec)
75
+ # Always record the primary container name so get_pod_log targets the
76
+ # right container even when init containers are present.
77
+ runner_context["primary_container"] = spec.dig(:spec, :containers, 0, :name)
59
78
  runner_context
60
79
  rescue Kubeclient::HttpError => err
61
80
  cleanup(runner_context)
@@ -93,7 +112,9 @@ module Floe
93
112
  failed_state = failed_container_states(runner_context).first
94
113
  {"Error" => failed_state["reason"], "Cause" => failed_state["message"]}
95
114
  else
96
- runner_context["output"] = kubeclient.get_pod_log(runner_context["container_ref"], namespace).body
115
+ log_options = {}
116
+ log_options[:container] = runner_context["primary_container"] if runner_context["primary_container"]
117
+ runner_context["output"] = kubeclient.get_pod_log(runner_context["container_ref"], namespace, **log_options).body
97
118
  end
98
119
  end
99
120
 
@@ -102,6 +123,8 @@ module Floe
102
123
 
103
124
  delete_pod(pod) if pod
104
125
  delete_secret(secret) if secret
126
+
127
+ cleanup_staged_volumes(runner_context["staged_volumes"])
105
128
  end
106
129
 
107
130
  def wait(timeout: nil, events: %i[create update delete])
@@ -152,31 +175,25 @@ module Floe
152
175
  end
153
176
  end
154
177
 
178
+ def inspect
179
+ vars = instance_variables_to_inspect.map { |ivar| "#{ivar}=#{instance_variable_get(ivar).inspect}" }.join(", ")
180
+ prefix = Kernel.instance_method(:inspect).bind_call(self).split(' ', 2).first
181
+ "#{prefix} #{vars}>"
182
+ end
183
+
155
184
  private
156
185
 
157
186
  attr_reader :ca_file, :kubeconfig_file, :kubeconfig_context, :namespace, :server, :token, :verify_ssl
158
187
 
159
- def pod_info(pod_name)
160
- kubeclient.get_pod(pod_name, namespace)
161
- rescue Kubeclient::HttpError => err
162
- raise Floe::ExecutionError, "Failed to get status for pod #{namespace}/#{pod_name}: #{err}"
163
- end
164
-
165
- def pod_running?(context)
166
- RUNNING_PHASES.include?(context.dig("container_state", "phase"))
167
- end
168
-
169
- def failed_container_states(context)
170
- container_statuses = context.dig("container_state", "containerStatuses") || []
171
- container_statuses.filter_map { |status| status["state"]&.values&.first }
172
- .select { |state| FAILURE_REASONS.include?(state["reason"]) }
188
+ def instance_variables_to_inspect
189
+ instance_variables - %i[@kubeclient @token] - host_path_volume_instance_variables_to_hide
173
190
  end
174
191
 
175
- def container_failed?(context)
176
- failed_container_states(context).any?
177
- end
192
+ # ------------------------------------------------------------------
193
+ # Pod spec construction
194
+ # ------------------------------------------------------------------
178
195
 
179
- def pod_spec(name, image, env, execution_id, secret = nil)
196
+ def pod_spec(name, image, env, execution_id, secret, staged_volumes, persistent_volumes, entrypoint, command)
180
197
  spec = {
181
198
  :kind => "Pod",
182
199
  :apiVersion => "v1",
@@ -186,13 +203,7 @@ module Floe
186
203
  :labels => {"execution_id" => execution_id}
187
204
  },
188
205
  :spec => {
189
- :containers => [
190
- {
191
- :name => name[0...-9], # remove the random suffix and its leading hyphen
192
- :image => image,
193
- :env => env.map { |k, v| {:name => k, :value => v.to_s} }
194
- }
195
- ],
206
+ :containers => [build_container_spec(name, image, env, entrypoint, command)],
196
207
  :restartPolicy => "Never"
197
208
  }
198
209
  }
@@ -222,11 +233,45 @@ module Floe
222
233
  ]
223
234
  end
224
235
 
236
+ add_host_path_volumes_to_spec!(spec, staged_volumes) if staged_volumes.any?
237
+ add_persistent_volumes_to_spec!(spec, persistent_volumes) if persistent_volumes.any?
238
+
225
239
  spec
226
240
  end
227
241
 
228
- def create_pod!(name, image, env, execution_id, secret = nil)
229
- kubeclient.create_pod(pod_spec(name, image, env, execution_id, secret))
242
+ def build_container_spec(name, image, env, entrypoint, command)
243
+ container = {
244
+ :name => name[0...-9], # remove the random suffix and its leading hyphen
245
+ :image => image,
246
+ :env => env.map { |k, v| {:name => k, :value => v.to_s} }
247
+ }
248
+ container[:command] = Array(entrypoint) if entrypoint
249
+ container[:args] = command if command
250
+ container
251
+ end
252
+
253
+ # ------------------------------------------------------------------
254
+ # Pod / secret lifecycle
255
+ # ------------------------------------------------------------------
256
+
257
+ def pod_info(pod_name)
258
+ kubeclient.get_pod(pod_name, namespace)
259
+ rescue Kubeclient::HttpError => err
260
+ raise Floe::ExecutionError, "Failed to get status for pod #{namespace}/#{pod_name}: #{err}"
261
+ end
262
+
263
+ def pod_running?(context)
264
+ RUNNING_PHASES.include?(context.dig("container_state", "phase"))
265
+ end
266
+
267
+ def failed_container_states(context)
268
+ container_statuses = context.dig("container_state", "containerStatuses") || []
269
+ container_statuses.filter_map { |status| status["state"]&.values&.first }
270
+ .select { |state| FAILURE_REASONS.include?(state["reason"]) }
271
+ end
272
+
273
+ def container_failed?(context)
274
+ failed_container_states(context).any?
230
275
  end
231
276
 
232
277
  def delete_pod!(name)
@@ -30,7 +30,7 @@ module Floe
30
30
 
31
31
  private
32
32
 
33
- def run_container_params(image, env, execution_id, secret)
33
+ def run_container_params(image, env, execution_id, secret, volumes, entrypoint, command)
34
34
  params = ["run"]
35
35
  params << :detach
36
36
  params += env.map { |k, v| [:e, "#{k}=#{v}"] }
@@ -39,8 +39,12 @@ module Floe
39
39
  params << [:net, "host"] if @network == "host"
40
40
  params << [:label, "execution_id=#{execution_id}"]
41
41
  params << [:secret, secret] if secret
42
+ params += volumes.map { |v| [:v, volume_to_flag(v)] }
43
+ params << [:entrypoint, entrypoint] if entrypoint
42
44
  params << [:name, container_name(image)]
43
45
  params << image
46
+ params.concat(command) if command
47
+ params
44
48
  end
45
49
 
46
50
  def create_secret(secrets)
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.20.1"
4
+ VERSION = "0.21.0"
5
5
  end
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.20.1
4
+ version: 0.21.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ManageIQ Developers
@@ -23,6 +23,20 @@ dependencies:
23
23
  - - ">"
24
24
  - !ruby/object:Gem::Version
25
25
  version: '5.2'
26
+ - !ruby/object:Gem::Dependency
27
+ name: aws-sdk-s3
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '1.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.0'
26
40
  - !ruby/object:Gem::Dependency
27
41
  name: awesome_spawn
28
42
  requirement: !ruby/object:Gem::Requirement
@@ -258,6 +272,8 @@ files:
258
272
  - lib/floe/container_runner/docker.rb
259
273
  - lib/floe/container_runner/docker_mixin.rb
260
274
  - lib/floe/container_runner/kubernetes.rb
275
+ - lib/floe/container_runner/kubernetes/host_path_volume_handler.rb
276
+ - lib/floe/container_runner/kubernetes/persistent_volume_handler.rb
261
277
  - lib/floe/container_runner/podman.rb
262
278
  - lib/floe/logging.rb
263
279
  - lib/floe/null_logger.rb