podman 1.0.3

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.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/lib/podman.rb +1 -0
  3. data/lib/vagrant/podman/action/build.rb +99 -0
  4. data/lib/vagrant/podman/action/compare_synced_folders.rb +65 -0
  5. data/lib/vagrant/podman/action/connect_networks.rb +80 -0
  6. data/lib/vagrant/podman/action/create.rb +165 -0
  7. data/lib/vagrant/podman/action/destroy.rb +34 -0
  8. data/lib/vagrant/podman/action/destroy_build_image.rb +51 -0
  9. data/lib/vagrant/podman/action/destroy_network.rb +53 -0
  10. data/lib/vagrant/podman/action/forwarded_ports.rb +36 -0
  11. data/lib/vagrant/podman/action/has_ssh.rb +21 -0
  12. data/lib/vagrant/podman/action/host_machine.rb +75 -0
  13. data/lib/vagrant/podman/action/host_machine_build_dir.rb +49 -0
  14. data/lib/vagrant/podman/action/host_machine_port_checker.rb +34 -0
  15. data/lib/vagrant/podman/action/host_machine_port_warning.rb +40 -0
  16. data/lib/vagrant/podman/action/host_machine_required.rb +20 -0
  17. data/lib/vagrant/podman/action/host_machine_sync_folders.rb +176 -0
  18. data/lib/vagrant/podman/action/host_machine_sync_folders_disable.rb +91 -0
  19. data/lib/vagrant/podman/action/init_state.rb +23 -0
  20. data/lib/vagrant/podman/action/is_build.rb +19 -0
  21. data/lib/vagrant/podman/action/is_host_machine_created.rb +32 -0
  22. data/lib/vagrant/podman/action/login.rb +51 -0
  23. data/lib/vagrant/podman/action/prepare_forwarded_port_collision_params.rb +64 -0
  24. data/lib/vagrant/podman/action/prepare_networks.rb +397 -0
  25. data/lib/vagrant/podman/action/prepare_nfs_settings.rb +60 -0
  26. data/lib/vagrant/podman/action/prepare_nfs_valid_ids.rb +22 -0
  27. data/lib/vagrant/podman/action/prepare_ssh.rb +48 -0
  28. data/lib/vagrant/podman/action/pull.rb +30 -0
  29. data/lib/vagrant/podman/action/start.rb +24 -0
  30. data/lib/vagrant/podman/action/stop.rb +24 -0
  31. data/lib/vagrant/podman/action/wait_for_running.rb +71 -0
  32. data/lib/vagrant/podman/action.rb +319 -0
  33. data/lib/vagrant/podman/cap/has_communicator.rb +14 -0
  34. data/lib/vagrant/podman/cap/proxy_machine.rb +15 -0
  35. data/lib/vagrant/podman/cap/public_address.rb +26 -0
  36. data/lib/vagrant/podman/command/exec.rb +112 -0
  37. data/lib/vagrant/podman/command/logs.rb +111 -0
  38. data/lib/vagrant/podman/command/run.rb +76 -0
  39. data/lib/vagrant/podman/communicator.rb +199 -0
  40. data/lib/vagrant/podman/config.rb +368 -0
  41. data/lib/vagrant/podman/driver/compose.rb +315 -0
  42. data/lib/vagrant/podman/driver.rb +417 -0
  43. data/lib/vagrant/podman/errors.rb +108 -0
  44. data/lib/vagrant/podman/executor/local.rb +48 -0
  45. data/lib/vagrant/podman/executor/vagrant.rb +88 -0
  46. data/lib/vagrant/podman/hostmachine/Vagrantfile +3 -0
  47. data/lib/vagrant/podman/plugin.rb +89 -0
  48. data/lib/vagrant/podman/provider.rb +216 -0
  49. data/lib/vagrant/podman/synced_folder.rb +35 -0
  50. data/templates/locales/providers_podman.yml +321 -0
  51. metadata +103 -0
@@ -0,0 +1,199 @@
1
+ # Copyright (c) HashiCorp, Inc.
2
+ # SPDX-License-Identifier: BUSL-1.1
3
+
4
+ require "digest/md5"
5
+ require "tempfile"
6
+
7
+ module VagrantPlugins
8
+ module PodmanProvider
9
+ # This communicator uses the host VM as proxy to communicate to the
10
+ # actual Podman container via SSH.
11
+ class Communicator < Vagrant.plugin("2", :communicator)
12
+ def initialize(machine)
13
+ @machine = machine
14
+ @host_vm = machine.provider.host_vm
15
+
16
+ # We only work on the Podman provider
17
+ if machine.provider_name != :podman
18
+ raise Errors::CommunicatorNotPodman
19
+ end
20
+ end
21
+
22
+ #-------------------------------------------------------------------
23
+ # Communicator Methods
24
+ #-------------------------------------------------------------------
25
+
26
+ def ready?
27
+ # We can't be ready if we can't talk to the host VM
28
+ return false if !@host_vm.communicate.ready?
29
+
30
+ # We're ready if we can establish an SSH connection to the container
31
+ command = container_ssh_command
32
+ return false if !command
33
+ @host_vm.communicate.test("#{command} exit")
34
+ end
35
+
36
+ def download(from, to)
37
+ # Same process as upload, but in reverse
38
+
39
+ # First, we use `cat` to copy that file from the Podman container.
40
+ temp = "/tmp/podman_d#{Time.now.to_i}_#{rand(100000)}"
41
+ @host_vm.communicate.execute("#{container_ssh_command} 'cat #{from}' >#{temp}")
42
+
43
+ # Then, we download this from the host VM.
44
+ @host_vm.communicate.download(temp, to)
45
+
46
+ # Remove the temporary file
47
+ @host_vm.communicate.execute("rm -f #{temp}", error_check: false)
48
+ end
49
+
50
+ def execute(command, **opts, &block)
51
+ fence = {}
52
+ fence[:stderr] = "VAGRANT FENCE: #{Time.now.to_i} #{rand(100000)}"
53
+ fence[:stdout] = "VAGRANT FENCE: #{Time.now.to_i} #{rand(100000)}"
54
+
55
+ # We want to emulate how the SSH communicator actually executes
56
+ # things, so we build up the list of commands to execute in a
57
+ # giant shell script.
58
+ tf = Tempfile.new("vagrant")
59
+ tf.binmode
60
+ tf.write("export TERM=vt100\n")
61
+ tf.write("echo #{fence[:stdout]}\n")
62
+ tf.write("echo #{fence[:stderr]} >&2\n")
63
+ tf.write("#{command}\n")
64
+ tf.write("exit\n")
65
+ tf.close
66
+
67
+ # Upload the temp file to the remote machine
68
+ remote_temp = "/tmp/podman_#{Time.now.to_i}_#{rand(100000)}"
69
+ @host_vm.communicate.upload(tf.path, remote_temp)
70
+
71
+ # Determine the shell to execute. Prefer the explicitly passed in shell
72
+ # over the default configured shell. If we are using `sudo` then we
73
+ # need to wrap the shell in a `sudo` call.
74
+ shell_cmd = @machine.config.ssh.shell
75
+ shell_cmd = opts[:shell] if opts[:shell]
76
+ shell_cmd = "sudo -E -H #{shell_cmd}" if opts[:sudo]
77
+
78
+ acc = {}
79
+ fenced = {}
80
+ result = @host_vm.communicate.execute(
81
+ "#{container_ssh_command} '#{shell_cmd}' <#{remote_temp}",
82
+ opts) do |type, data|
83
+ # If we don't have a block, we don't care about the data
84
+ next if !block
85
+
86
+ # We only care about stdout and stderr output
87
+ next if ![:stdout, :stderr].include?(type)
88
+
89
+ # If we reached our fence, then just output
90
+ if fenced[type]
91
+ block.call(type, data)
92
+ next
93
+ end
94
+
95
+ # Otherwise, accumulate
96
+ acc[type] = data
97
+
98
+ # Look for the fence
99
+ index = acc[type].index(fence[type])
100
+ next if !index
101
+
102
+ fenced[type] = true
103
+ index += fence[type].length
104
+ data = acc[type][index..-1].chomp
105
+ acc[type] = ""
106
+ block.call(type, data)
107
+ end
108
+
109
+ @host_vm.communicate.execute("rm -f #{remote_temp}", error_check: false)
110
+
111
+ return result
112
+ end
113
+
114
+ def sudo(command, **opts, &block)
115
+ opts = { sudo: true }.merge(opts)
116
+ execute(command, opts, &block)
117
+ end
118
+
119
+ def test(command, **opts)
120
+ opts = { error_check: false }.merge(opts)
121
+ execute(command, opts) == 0
122
+ end
123
+
124
+ def upload(from, to)
125
+ # First, we upload this to the host VM to some temporary directory.
126
+ to_temp = "/tmp/podman_#{Time.now.to_i}_#{rand(100000)}"
127
+ @host_vm.communicate.upload(from, to_temp)
128
+
129
+ # Then, we use `cat` to get that file into the Podman container.
130
+ @host_vm.communicate.execute(
131
+ "#{container_ssh_command} 'cat >#{to}' <#{to_temp}")
132
+
133
+ # Remove the temporary file
134
+ @host_vm.communicate.execute("rm -f #{to_temp}", error_check: false)
135
+ end
136
+
137
+ #-------------------------------------------------------------------
138
+ # Other Methods
139
+ #-------------------------------------------------------------------
140
+
141
+ # This returns the raw SSH command string that can be used to
142
+ # connect via SSH to the container if you're on the same machine
143
+ # as the container.
144
+ #
145
+ # @return [String]
146
+ def container_ssh_command
147
+ # Get the container's SSH info
148
+ info = @machine.ssh_info
149
+ return nil if !info
150
+ info[:port] ||= 22
151
+
152
+ # Make sure our private keys are synced over to the host VM
153
+ ssh_args = sync_private_keys(info).map do |path|
154
+ "-i #{path}"
155
+ end
156
+
157
+ # Use ad-hoc SSH options for the hop on the podman proxy
158
+ if info[:forward_agent]
159
+ ssh_args << "-o ForwardAgent=yes"
160
+ end
161
+ ssh_args.concat(["-o Compression=yes",
162
+ "-o ConnectTimeout=5",
163
+ "-o StrictHostKeyChecking=no",
164
+ "-o UserKnownHostsFile=/dev/null"])
165
+
166
+ # Build the SSH command
167
+ "ssh #{info[:username]}@#{info[:host]} -p#{info[:port]} #{ssh_args.join(" ")}"
168
+ end
169
+
170
+ protected
171
+
172
+ def sync_private_keys(info)
173
+ @keys ||= {}
174
+
175
+ id = Digest::MD5.hexdigest(
176
+ @machine.env.root_path.to_s + @machine.name.to_s)
177
+
178
+ result = []
179
+ info[:private_key_path].each do |path|
180
+ if !@keys[path.to_s]
181
+ # We haven't seen this before, upload it!
182
+ guest_path = "/tmp/key_#{id}_#{Digest::MD5.hexdigest(path.to_s)}"
183
+ @host_vm.communicate.upload(path.to_s, guest_path)
184
+
185
+ # Make sure it has the proper chmod
186
+ @host_vm.communicate.execute("chmod 0600 #{guest_path}")
187
+
188
+ # Set it
189
+ @keys[path.to_s] = guest_path
190
+ end
191
+
192
+ result << @keys[path.to_s]
193
+ end
194
+
195
+ result
196
+ end
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,368 @@
1
+ # Copyright (c) HashiCorp, Inc.
2
+ # SPDX-License-Identifier: BUSL-1.1
3
+
4
+ require "pathname"
5
+
6
+ require "vagrant/util/platform"
7
+
8
+ module VagrantPlugins
9
+ module PodmanProvider
10
+ class Config < Vagrant.plugin("2", :config)
11
+ attr_accessor :image, :cmd, :ports, :volumes, :privileged, :network, :user, :userns
12
+
13
+ # Additional arguments to pass to `podman build` when creating
14
+ # an image using the build dir setting.
15
+ #
16
+ # @return [Array<String>]
17
+ attr_accessor :build_args
18
+
19
+ # The directory with a Podmanfile to build and use as the basis
20
+ # for this container. If this is set, neither "image" nor "git_repo"
21
+ # should be set.
22
+ #
23
+ # @return [String]
24
+ attr_accessor :build_dir
25
+
26
+ # The URL for a git repository with a Podmanfile to build and use
27
+ # as the basis for this container. If this is set, neither "image"
28
+ # nor "build_dir" should be set.
29
+ #
30
+ # @return [String]
31
+ attr_accessor :git_repo
32
+
33
+ # Use podman-compose to manage the lifecycle and environment for
34
+ # containers instead of using podman directly.
35
+ #
36
+ # @return [Boolean]
37
+ attr_accessor :compose
38
+
39
+ # Configuration Hash used for build the podman-compose composition
40
+ # file. This can be used for adding networks or volumes.
41
+ #
42
+ # @return [Hash]
43
+ attr_accessor :compose_configuration
44
+
45
+ # An optional file name of a Podmanfile to be used when building
46
+ # the image. This requires Podman >1.5.0.
47
+ #
48
+ # @return [String]
49
+ attr_accessor :podmanfile
50
+
51
+ # Additional arguments to pass to `podman run` when creating
52
+ # the container for the first time. This is an array of args.
53
+ #
54
+ # @return [Array<String>]
55
+ attr_accessor :create_args
56
+
57
+ # Environmental variables to set in the container.
58
+ #
59
+ # @return [Hash]
60
+ attr_accessor :env
61
+
62
+ # Ports to expose from the container but not to the host machine.
63
+ # This is useful for links.
64
+ #
65
+ # @return [Array<Integer>]
66
+ attr_accessor :expose
67
+
68
+ # Force using a proxy VM, even on Linux hosts.
69
+ #
70
+ # @return [Boolean]
71
+ attr_accessor :force_host_vm
72
+
73
+ # True if the Podman container exposes SSH access. If this is true,
74
+ # then Vagrant can do a bunch more things like setting the hostname,
75
+ # provisioning, etc.
76
+ attr_accessor :has_ssh
77
+
78
+ # Options for the build dir synced folder if a host VM is in use.
79
+ #
80
+ # @return [Hash]
81
+ attr_accessor :host_vm_build_dir_options
82
+
83
+ # The name for the container. This must be unique for all containers
84
+ # on the proxy machine if it is made.
85
+ #
86
+ # @return [String]
87
+ attr_accessor :name
88
+
89
+ # If true, the image will be pulled on every `up` and `reload`
90
+ # to ensure the latest image.
91
+ #
92
+ # @return [Bool]
93
+ attr_accessor :pull
94
+
95
+ # True if the podman container is meant to stay in the "running"
96
+ # state (is a long running process). By default this is true.
97
+ #
98
+ # @return [Boolean]
99
+ attr_accessor :remains_running
100
+
101
+ # The time to wait before sending a SIGTERM to the container
102
+ # when it is stopped.
103
+ #
104
+ # @return [Integer]
105
+ attr_accessor :stop_timeout
106
+
107
+ # The name of the machine in the Vagrantfile set with
108
+ # "vagrant_vagrantfile" that will be the podman host. Defaults
109
+ # to "default"
110
+ #
111
+ # See the "vagrant_vagrantfile" docs for more info.
112
+ #
113
+ # @return [String]
114
+ attr_accessor :vagrant_machine
115
+
116
+ # The path to the Vagrantfile that contains a VM that will be
117
+ # started as the Podman host if needed (Windows, OS X, Linux
118
+ # without container support).
119
+ #
120
+ # Defaults to a built-in Vagrantfile that will load boot2podman.
121
+ #
122
+ # NOTE: This only has an effect if Vagrant needs a Podman host.
123
+ # Vagrant determines this automatically based on the environment
124
+ # it is running in.
125
+ #
126
+ # @return [String]
127
+ attr_accessor :vagrant_vagrantfile
128
+
129
+ #--------------------------------------------------------------
130
+ # Auth Settings
131
+ #--------------------------------------------------------------
132
+
133
+ # Server to authenticate to. If blank, will use the default
134
+ # Podman authentication endpoint (which is the Podman Hub at the
135
+ # time of this comment).
136
+ #
137
+ # @return [String]
138
+ attr_accessor :auth_server
139
+
140
+ # Email for logging in to a remote Podman server.
141
+ #
142
+ # @return [String]
143
+ attr_accessor :email
144
+
145
+ # Email for logging in to a remote Podman server.
146
+ #
147
+ # @return [String]
148
+ attr_accessor :username
149
+
150
+ # Password for logging in to a remote Podman server. If this is
151
+ # not blank, then Vagrant will run `podman login` prior to any
152
+ # Podman runs.
153
+ #
154
+ # The presence of auth will also force the Podman environments to
155
+ # serialize on `up` so that different users/passwords don't overlap.
156
+ #
157
+ # @return [String]
158
+ attr_accessor :password
159
+
160
+ def initialize
161
+ @build_args = []
162
+ @build_dir = UNSET_VALUE
163
+ @git_repo = UNSET_VALUE
164
+ @cmd = UNSET_VALUE
165
+ @compose = UNSET_VALUE
166
+ @compose_configuration = {}
167
+ @create_args = UNSET_VALUE
168
+ @podmanfile = UNSET_VALUE
169
+ @env = {}
170
+ @expose = []
171
+ @force_host_vm = UNSET_VALUE
172
+ @has_ssh = UNSET_VALUE
173
+ @host_vm_build_dir_options = UNSET_VALUE
174
+ @image = UNSET_VALUE
175
+ @network = UNSET_VALUE
176
+ @user = UNSET_VALUE
177
+ @userns = UNSET_VALUE
178
+ @name = UNSET_VALUE
179
+ @links = []
180
+ @pull = UNSET_VALUE
181
+ @ports = UNSET_VALUE
182
+ @privileged = UNSET_VALUE
183
+ @remains_running = UNSET_VALUE
184
+ @stop_timeout = UNSET_VALUE
185
+ @volumes = []
186
+ @vagrant_machine = UNSET_VALUE
187
+ @vagrant_vagrantfile = UNSET_VALUE
188
+
189
+ @auth_server = UNSET_VALUE
190
+ @email = UNSET_VALUE
191
+ @username = UNSET_VALUE
192
+ @password = UNSET_VALUE
193
+ end
194
+
195
+ def link(name)
196
+ @links << name
197
+ end
198
+
199
+ def merge(other)
200
+ super.tap do |result|
201
+ # This is a bit confusing. The tests explain the purpose of this
202
+ # better than the code lets on, I believe.
203
+ has_image = (other.image != UNSET_VALUE)
204
+ has_build_dir = (other.build_dir != UNSET_VALUE)
205
+ has_git_repo = (other.git_repo != UNSET_VALUE)
206
+
207
+ if (has_image ^ has_build_dir ^ has_git_repo) && !(has_image && has_build_dir && has_git_repo)
208
+ # image
209
+ if has_image
210
+ if @build_dir != UNSET_VALUE
211
+ result.build_dir = nil
212
+ end
213
+ if @git_repo != UNSET_VALUE
214
+ result.git_repo = nil
215
+ end
216
+ end
217
+
218
+ # build_dir
219
+ if has_build_dir
220
+ if @image != UNSET_VALUE
221
+ result.image = nil
222
+ end
223
+ if @git_repo != UNSET_VALUE
224
+ result.git_repo = nil
225
+ end
226
+ end
227
+
228
+ # git_repo
229
+ if has_git_repo
230
+ if @build_dir != UNSET_VALUE
231
+ result.build_dir = nil
232
+ end
233
+ if @image != UNSET_VALUE
234
+ result.image = nil
235
+ end
236
+ end
237
+ end
238
+
239
+ env = {}
240
+ env.merge!(@env) if @env
241
+ env.merge!(other.env) if other.env
242
+ result.env = env
243
+
244
+ expose = self.expose.dup
245
+ expose += other.expose
246
+ result.instance_variable_set(:@expose, expose)
247
+
248
+ links = _links.dup
249
+ links += other._links
250
+ result.instance_variable_set(:@links, links)
251
+ end
252
+ end
253
+
254
+ def finalize!
255
+ @build_args = [] if @build_args == UNSET_VALUE
256
+ @build_dir = nil if @build_dir == UNSET_VALUE
257
+ @git_repo = nil if @git_repo == UNSET_VALUE
258
+ @cmd = [] if @cmd == UNSET_VALUE
259
+ @compose = false if @compose == UNSET_VALUE
260
+ @create_args = [] if @create_args == UNSET_VALUE
261
+ @podmanfile = nil if @podmanfile == UNSET_VALUE
262
+ @env ||= {}
263
+ @has_ssh = false if @has_ssh == UNSET_VALUE
264
+ @image = nil if @image == UNSET_VALUE
265
+ @network = nil if @network == UNSET_VALUE
266
+ @user = nil if @user == UNSET_VALUE
267
+ @userns = nil if @userns == UNSET_VALUE
268
+ @name = nil if @name == UNSET_VALUE
269
+ @pull = false if @pull == UNSET_VALUE
270
+ @ports = [] if @ports == UNSET_VALUE
271
+ @privileged = false if @privileged == UNSET_VALUE
272
+ @remains_running = true if @remains_running == UNSET_VALUE
273
+ @stop_timeout = 1 if @stop_timeout == UNSET_VALUE
274
+ @vagrant_machine = nil if @vagrant_machine == UNSET_VALUE
275
+ @vagrant_vagrantfile = nil if @vagrant_vagrantfile == UNSET_VALUE
276
+
277
+ @auth_server = nil if @auth_server == UNSET_VALUE
278
+ @email = "" if @email == UNSET_VALUE
279
+ @username = "" if @username == UNSET_VALUE
280
+ @password = "" if @password == UNSET_VALUE
281
+
282
+ if @host_vm_build_dir_options == UNSET_VALUE
283
+ @host_vm_build_dir_options = nil
284
+ end
285
+
286
+ # On non-linux platforms (where there is no native podman), force the
287
+ # host VM. Other users can optionally disable this by setting the
288
+ # value explicitly to false in their Vagrantfile.
289
+ if @force_host_vm == UNSET_VALUE
290
+ @force_host_vm = !Vagrant::Util::Platform.linux? &&
291
+ !Vagrant::Util::Platform.darwin? &&
292
+ !Vagrant::Util::Platform.windows?
293
+ end
294
+
295
+ # The machine name must be a symbol
296
+ @vagrant_machine = @vagrant_machine.to_sym if @vagrant_machine
297
+
298
+ @expose.uniq!
299
+
300
+ if @compose_configuration.is_a?(Hash)
301
+ # Ensures configuration is using basic types
302
+ @compose_configuration = JSON.parse(@compose_configuration.to_json)
303
+ end
304
+ end
305
+
306
+ def validate(machine)
307
+ errors = _detected_errors
308
+
309
+ if [@build_dir, @git_repo, @image].compact.size > 1
310
+ errors << I18n.t("podman_provider.errors.config.both_build_and_image_and_git")
311
+ end
312
+
313
+ if !@build_dir && !@git_repo && !@image
314
+ errors << I18n.t("podman_provider.errors.config.build_dir_or_image")
315
+ end
316
+
317
+ if @build_dir
318
+ build_dir_pn = Pathname.new(@build_dir)
319
+ if !build_dir_pn.directory?
320
+ errors << I18n.t("podman_provider.errors.config.build_dir_invalid")
321
+ end
322
+ end
323
+
324
+ # Comparison logic taken directly from podman's urlutil.go
325
+ if @git_repo && !( @git_repo =~ /^http(?:s)?:\/\/.*.git(?:#.+)?$/ || @git_repo =~ /^git(?:hub\.com|@|:\/\/)/)
326
+ errors << I18n.t("podman_provider.errors.config.git_repo_invalid")
327
+ end
328
+
329
+ if !@compose_configuration.is_a?(Hash)
330
+ errors << I18n.t("podman_provider.errors.config.compose_configuration_hash")
331
+ end
332
+
333
+ if @compose && @force_host_vm
334
+ errors << I18n.t("podman_provider.errors.config.compose_force_vm")
335
+ end
336
+
337
+ if !@create_args.is_a?(Array)
338
+ errors << I18n.t("podman_provider.errors.config.create_args_array")
339
+ end
340
+
341
+ @links.each do |link|
342
+ parts = link.split(":")
343
+ if parts.length != 2 || parts[0] == "" || parts[1] == ""
344
+ errors << I18n.t(
345
+ "podman_provider.errors.config.invalid_link", link: link)
346
+ end
347
+ end
348
+
349
+ if @vagrant_vagrantfile
350
+ vf_pn = Pathname.new(@vagrant_vagrantfile)
351
+ if !vf_pn.file?
352
+ errors << I18n.t("podman_provider.errors.config.invalid_vagrantfile")
353
+ end
354
+ end
355
+
356
+ { "podman provider" => errors }
357
+ end
358
+
359
+ #--------------------------------------------------------------
360
+ # Functions below should not be called by config files
361
+ #--------------------------------------------------------------
362
+
363
+ def _links
364
+ @links
365
+ end
366
+ end
367
+ end
368
+ end