kitchen-dokken 2.23.2 → 2.23.4
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 +4 -4
- data/Gemfile +10 -1
- data/Rakefile +18 -0
- data/lib/kitchen/driver/dokken.rb +370 -36
- data/lib/kitchen/driver/dokken_version.rb +1 -1
- data/lib/kitchen/helpers.rb +260 -30
- data/lib/kitchen/provisioner/dokken.rb +53 -4
- data/lib/kitchen/transport/dokken.rb +294 -73
- metadata +2 -2
|
@@ -19,11 +19,14 @@ require "kitchen"
|
|
|
19
19
|
require "net/scp"
|
|
20
20
|
require "tmpdir" unless defined?(Dir.mktmpdir)
|
|
21
21
|
require "digest/sha1" unless defined?(Digest::SHA1)
|
|
22
|
+
require "open3" unless defined?(Open3)
|
|
23
|
+
require "shellwords" unless defined?(Shellwords)
|
|
22
24
|
require_relative "../helpers"
|
|
23
25
|
|
|
24
26
|
include Dokken::Helpers
|
|
25
27
|
|
|
26
28
|
module Kitchen
|
|
29
|
+
# @see Kitchen::Transport::Dokken
|
|
27
30
|
module Transport
|
|
28
31
|
# Wrapped exception for any internally raised errors.
|
|
29
32
|
#
|
|
@@ -33,6 +36,11 @@ module Kitchen
|
|
|
33
36
|
# A Transport which uses Docker tricks to execute commands and
|
|
34
37
|
# transfer files.
|
|
35
38
|
#
|
|
39
|
+
# Commands run through `docker exec` against the runner container. File
|
|
40
|
+
# transfer only happens when the daemon cannot read the host filesystem;
|
|
41
|
+
# then the files go over ssh into the data container, which shares its
|
|
42
|
+
# volumes with the runner.
|
|
43
|
+
#
|
|
36
44
|
# @author Sean OMeara <sean@sean.io>
|
|
37
45
|
class Dokken < Kitchen::Transport::Base
|
|
38
46
|
kitchen_transport_api_version 2
|
|
@@ -47,7 +55,7 @@ module Kitchen
|
|
|
47
55
|
default_config :write_timeout, 3600
|
|
48
56
|
default_config :login_command, "docker"
|
|
49
57
|
default_config :host_ip_override do |transport|
|
|
50
|
-
if running_inside_docker_desktop?
|
|
58
|
+
if transport.running_inside_docker_desktop?
|
|
51
59
|
"host.docker.internal"
|
|
52
60
|
elsif transport.docker_for_mac_or_win?
|
|
53
61
|
"localhost"
|
|
@@ -57,6 +65,10 @@ module Kitchen
|
|
|
57
65
|
end
|
|
58
66
|
|
|
59
67
|
# (see Base#connection)
|
|
68
|
+
#
|
|
69
|
+
# @param state [Hash] mutable instance state
|
|
70
|
+
# @yieldparam connection [Connection] the connection, if a block is given
|
|
71
|
+
# @return [Connection] a connection to the runner container
|
|
60
72
|
def connection(state, &block)
|
|
61
73
|
options = connection_options(config.to_hash.merge(state))
|
|
62
74
|
|
|
@@ -67,12 +79,26 @@ module Kitchen
|
|
|
67
79
|
end
|
|
68
80
|
end
|
|
69
81
|
|
|
82
|
+
# A connection to one runner container.
|
|
83
|
+
#
|
|
70
84
|
# @author Sean OMeara <sean@sean.io>
|
|
71
|
-
class Connection < Kitchen::Transport::
|
|
85
|
+
class Connection < Kitchen::Transport::Base::Connection
|
|
86
|
+
# Where rsync is expected to live. Kept as a constant so the
|
|
87
|
+
# availability check and the command line cannot drift apart.
|
|
88
|
+
RSYNC_PATH = "/usr/bin/rsync".freeze
|
|
89
|
+
|
|
90
|
+
# The docker-api connection this transport talks to.
|
|
91
|
+
#
|
|
92
|
+
# @return [::Docker::Connection] a memoised connection
|
|
72
93
|
def docker_connection
|
|
73
94
|
@docker_connection ||= ::Docker::Connection.new(options[:docker_host_url], options[:docker_host_options])
|
|
74
95
|
end
|
|
75
96
|
|
|
97
|
+
# Run a command inside the runner container.
|
|
98
|
+
#
|
|
99
|
+
# @param command [String, nil] the command to run; nil is a no-op
|
|
100
|
+
# @return [void]
|
|
101
|
+
# @raise [Kitchen::Transport::DockerExecFailed] on a non-zero exit
|
|
76
102
|
def execute(command)
|
|
77
103
|
return if command.nil?
|
|
78
104
|
|
|
@@ -87,119 +113,313 @@ module Kitchen
|
|
|
87
113
|
raise Transport::DockerExecFailed.new("Docker Exec (#{@exit_code}) for command: [#{command}]", @exit_code) if @exit_code != 0
|
|
88
114
|
end
|
|
89
115
|
|
|
116
|
+
# Copy the kitchen sandbox into the data container.
|
|
117
|
+
#
|
|
118
|
+
# @param locals [Array<String>] local paths to copy
|
|
119
|
+
# @param remote [String] the destination path inside the container
|
|
120
|
+
# @return [void]
|
|
121
|
+
# @raise [Kitchen::UserError] if docker_host_url is not tcp:// or unix://
|
|
122
|
+
# @raise [Kitchen::Transport::TransportFailed] if the copy fails
|
|
90
123
|
def upload(locals, remote)
|
|
124
|
+
ssh_ip, ssh_port = ssh_endpoint
|
|
125
|
+
|
|
126
|
+
debug "ssh_ip : #{ssh_ip}"
|
|
127
|
+
debug "ssh_port : #{ssh_port}"
|
|
128
|
+
|
|
129
|
+
upload_files(locals, remote, ssh_ip, ssh_port, write_insecure_key)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
private
|
|
133
|
+
|
|
134
|
+
# Work out which address and port the data container's sshd can
|
|
135
|
+
# actually be reached on from where kitchen is running.
|
|
136
|
+
#
|
|
137
|
+
# @return [Array(String, String)] the address and port to ssh to
|
|
138
|
+
# @raise [Kitchen::UserError] if docker_host_url is not tcp:// or unix://
|
|
139
|
+
# @api private
|
|
140
|
+
def ssh_endpoint
|
|
91
141
|
if options[:host_ip_override]
|
|
92
142
|
# Allow connecting to any ip/hostname to support sibling containers
|
|
93
|
-
|
|
94
|
-
ssh_port = options[:data_container][:NetworkSettings][:Ports][:"22/tcp"][0][:HostPort]
|
|
95
|
-
|
|
143
|
+
[options[:host_ip_override], published_ssh_port]
|
|
96
144
|
elsif /unix:/.match?(options[:docker_host_url])
|
|
97
|
-
|
|
98
|
-
ssh_ip = options[:data_container][:NetworkSettings][:IPAddress]
|
|
99
|
-
ssh_port = "22"
|
|
100
|
-
else
|
|
101
|
-
# we should read the proper mapped ip, since this allows us to upload the files
|
|
102
|
-
ssh_ip = options[:data_container][:NetworkSettings][:Ports][:"22/tcp"][0][:HostIp]
|
|
103
|
-
ssh_port = options[:data_container][:NetworkSettings][:Ports][:"22/tcp"][0][:HostPort]
|
|
104
|
-
end
|
|
105
|
-
|
|
145
|
+
unix_ssh_endpoint
|
|
106
146
|
elsif /tcp:/.match?(options[:docker_host_url])
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
# DOCKER_HOST
|
|
110
|
-
docker_host_url_ip = options[:docker_host_url].split("tcp://")[1].split(":")[0]
|
|
111
|
-
|
|
112
|
-
# mapped IP of data container
|
|
113
|
-
candidate_ip = ::Docker::Container.all.find do |x|
|
|
114
|
-
x.info["Names"][0].eql?(name)
|
|
115
|
-
end.info["NetworkSettings"]["Networks"]["dokken"]["IPAddress"]
|
|
116
|
-
|
|
117
|
-
# mapped port
|
|
118
|
-
candidate_ssh_port = options[:data_container][:NetworkSettings][:Ports][:"22/tcp"][0][:HostPort]
|
|
119
|
-
|
|
120
|
-
debug "candidate_ip - #{candidate_ip}"
|
|
121
|
-
debug "candidate_ssh_port - #{candidate_ssh_port}"
|
|
122
|
-
|
|
123
|
-
if port_open?(candidate_ip, candidate_ssh_port)
|
|
124
|
-
debug "candidate_ip - #{candidate_ip}/#{candidate_ssh_port} open"
|
|
125
|
-
ssh_ip = candidate_ip
|
|
126
|
-
ssh_port = candidate_ssh_port
|
|
127
|
-
|
|
128
|
-
elsif port_open?(candidate_ip, "22")
|
|
129
|
-
ssh_ip = candidate_ip
|
|
130
|
-
ssh_port = "22"
|
|
131
|
-
debug "candidate_ip - #{candidate_ip}/22 open"
|
|
132
|
-
else
|
|
133
|
-
ssh_ip = docker_host_url_ip
|
|
134
|
-
ssh_port = candidate_ssh_port
|
|
135
|
-
end
|
|
147
|
+
tcp_ssh_endpoint
|
|
136
148
|
else
|
|
137
149
|
raise Kitchen::UserError, "docker_host_url must be tcp:// or unix://"
|
|
138
150
|
end
|
|
151
|
+
end
|
|
139
152
|
|
|
140
|
-
|
|
141
|
-
|
|
153
|
+
# The host port the data container's sshd is published on.
|
|
154
|
+
#
|
|
155
|
+
# @return [String] the published port
|
|
156
|
+
# @api private
|
|
157
|
+
def published_ssh_port
|
|
158
|
+
ssh_port_binding[:HostPort]
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# The first host binding for the data container's ssh port.
|
|
162
|
+
#
|
|
163
|
+
# @return [Hash] the `22/tcp` port binding
|
|
164
|
+
# @api private
|
|
165
|
+
def ssh_port_binding
|
|
166
|
+
options[:data_container][:NetworkSettings][:Ports][:"22/tcp"][0]
|
|
167
|
+
end
|
|
142
168
|
|
|
169
|
+
# Pick an endpoint for a daemon reached over a unix socket.
|
|
170
|
+
#
|
|
171
|
+
# When sshd is published on every interface we can talk to the
|
|
172
|
+
# container's own address directly; otherwise we have to go through
|
|
173
|
+
# the specific host mapping the daemon set up.
|
|
174
|
+
#
|
|
175
|
+
# @return [Array(String, String)] the address and port to ssh to
|
|
176
|
+
# @api private
|
|
177
|
+
def unix_ssh_endpoint
|
|
178
|
+
if ssh_port_binding[:HostIp] == "0.0.0.0"
|
|
179
|
+
[data_container_ip, "22"]
|
|
180
|
+
else
|
|
181
|
+
# we should read the proper mapped ip, since this allows us to upload the files
|
|
182
|
+
[ssh_port_binding[:HostIp], ssh_port_binding[:HostPort]]
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# The data container's own address on the docker network.
|
|
187
|
+
#
|
|
188
|
+
# NetworkSettings.IPAddress is only populated for the default bridge.
|
|
189
|
+
# The data container is handed a NetworkingConfig endpoint whenever
|
|
190
|
+
# network_mode names a user-defined network -- which dokken's own
|
|
191
|
+
# default does -- and its address then lives under
|
|
192
|
+
# NetworkSettings.Networks.<name>, leaving the legacy field empty.
|
|
193
|
+
# Uploading to that empty value produced `root@:/opt/kitchen` and an
|
|
194
|
+
# unresolvable hostname.
|
|
195
|
+
#
|
|
196
|
+
# @return [String] the container's address
|
|
197
|
+
# @raise [Kitchen::Transport::TransportFailed] if the daemon reported none
|
|
198
|
+
# @api private
|
|
199
|
+
def data_container_ip
|
|
200
|
+
settings = options[:data_container][:NetworkSettings]
|
|
201
|
+
|
|
202
|
+
legacy = settings[:IPAddress].to_s
|
|
203
|
+
return legacy unless legacy.empty?
|
|
204
|
+
|
|
205
|
+
(settings[:Networks] || {}).each_value do |network|
|
|
206
|
+
address = network[:IPAddress].to_s
|
|
207
|
+
return address unless address.empty?
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
raise Kitchen::Transport::TransportFailed,
|
|
211
|
+
"The data container has no address on any docker network: #{settings[:Networks].inspect}"
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Pick an endpoint for a daemon reached over tcp.
|
|
215
|
+
#
|
|
216
|
+
# The container's address on the dokken network is preferred, since it
|
|
217
|
+
# avoids a round trip through the host, but it is only usable when
|
|
218
|
+
# kitchen shares a route with the daemon. Fall back to the docker host
|
|
219
|
+
# itself when neither candidate port answers.
|
|
220
|
+
#
|
|
221
|
+
# @return [Array(String, String)] the address and port to ssh to
|
|
222
|
+
# @api private
|
|
223
|
+
def tcp_ssh_endpoint
|
|
224
|
+
name = options[:data_container][:Name]
|
|
225
|
+
|
|
226
|
+
# DOCKER_HOST
|
|
227
|
+
docker_host_url_ip = options[:docker_host_url].split("tcp://")[1].split(":")[0]
|
|
228
|
+
|
|
229
|
+
# mapped IP of data container
|
|
230
|
+
candidate_ip = ::Docker::Container.all.find do |x|
|
|
231
|
+
x.info["Names"][0].eql?(name)
|
|
232
|
+
end.info["NetworkSettings"]["Networks"]["dokken"]["IPAddress"]
|
|
233
|
+
|
|
234
|
+
# mapped port
|
|
235
|
+
candidate_ssh_port = published_ssh_port
|
|
236
|
+
|
|
237
|
+
debug "candidate_ip - #{candidate_ip}"
|
|
238
|
+
debug "candidate_ssh_port - #{candidate_ssh_port}"
|
|
239
|
+
|
|
240
|
+
if port_open?(candidate_ip, candidate_ssh_port)
|
|
241
|
+
debug "candidate_ip - #{candidate_ip}/#{candidate_ssh_port} open"
|
|
242
|
+
[candidate_ip, candidate_ssh_port]
|
|
243
|
+
elsif port_open?(candidate_ip, "22")
|
|
244
|
+
debug "candidate_ip - #{candidate_ip}/22 open"
|
|
245
|
+
[candidate_ip, "22"]
|
|
246
|
+
else
|
|
247
|
+
[docker_host_url_ip, candidate_ssh_port]
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# Write the built-in insecure private key somewhere ssh will accept it.
|
|
252
|
+
#
|
|
253
|
+
# ssh refuses to use a key file other users can read, so the file is
|
|
254
|
+
# written 0600 under a per-uid directory.
|
|
255
|
+
#
|
|
256
|
+
# @return [String] the directory holding the `id_rsa` file
|
|
257
|
+
# @api private
|
|
258
|
+
def write_insecure_key
|
|
143
259
|
tmpdir = Dir.tmpdir + "/dokken/"
|
|
144
260
|
FileUtils.mkdir_p tmpdir.to_s, mode: 0o777
|
|
145
261
|
tmpdir += Process.uid.to_s
|
|
146
262
|
FileUtils.mkdir_p tmpdir.to_s
|
|
147
263
|
File.write("#{tmpdir}/id_rsa", insecure_ssh_private_key)
|
|
148
264
|
FileUtils.chmod(0o600, "#{tmpdir}/id_rsa")
|
|
265
|
+
tmpdir
|
|
266
|
+
end
|
|
149
267
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
`#{rsync_cmd}`
|
|
166
|
-
rescue Errno::ENOENT
|
|
167
|
-
debug "Rsync is not installed. Falling back to SCP."
|
|
168
|
-
locals.each do |local|
|
|
169
|
-
Net::SCP.upload!(ssh_ip,
|
|
170
|
-
"root",
|
|
171
|
-
local,
|
|
172
|
-
remote,
|
|
173
|
-
recursive: true,
|
|
174
|
-
ssh: { port: ssh_port, keys: ["#{tmpdir}/id_rsa"] })
|
|
175
|
-
end
|
|
268
|
+
# Copy files into the data container, preferring rsync.
|
|
269
|
+
#
|
|
270
|
+
# @param locals [Array<String>] local paths to copy
|
|
271
|
+
# @param remote [String] the destination path inside the container
|
|
272
|
+
# @param ssh_ip [String] the address to ssh to
|
|
273
|
+
# @param ssh_port [String] the port to ssh to
|
|
274
|
+
# @param key_dir [String] directory holding the `id_rsa` file
|
|
275
|
+
# @return [void]
|
|
276
|
+
# @api private
|
|
277
|
+
def upload_files(locals, remote, ssh_ip, ssh_port, key_dir)
|
|
278
|
+
if rsync_available?
|
|
279
|
+
upload_via_rsync(locals, remote, ssh_ip, ssh_port, key_dir)
|
|
280
|
+
else
|
|
281
|
+
debug "Rsync is not installed at #{RSYNC_PATH}. Falling back to SCP."
|
|
282
|
+
upload_via_scp(locals, remote, ssh_ip, ssh_port, key_dir)
|
|
176
283
|
end
|
|
177
284
|
end
|
|
178
285
|
|
|
286
|
+
# Whether rsync is installed where we expect it.
|
|
287
|
+
#
|
|
288
|
+
# This is checked up front rather than inferred from a failed shell
|
|
289
|
+
# invocation: backticks run the command through /bin/sh, which reports
|
|
290
|
+
# a missing binary as exit status 127 and never raises Errno::ENOENT,
|
|
291
|
+
# so an exception-based check could never see it.
|
|
292
|
+
#
|
|
293
|
+
# @return [Boolean] true when rsync can be executed
|
|
294
|
+
# @api private
|
|
295
|
+
def rsync_available?
|
|
296
|
+
File.executable?(RSYNC_PATH)
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
# Build the rsync invocation used to copy the sandbox in.
|
|
300
|
+
#
|
|
301
|
+
# @param locals [Array<String>] local paths to copy
|
|
302
|
+
# @param remote [String] the destination path inside the container
|
|
303
|
+
# @param ssh_ip [String] the address to ssh to
|
|
304
|
+
# @param ssh_port [String] the port to ssh to
|
|
305
|
+
# @param key_dir [String] directory holding the `id_rsa` file
|
|
306
|
+
# @return [String] a shell command line
|
|
307
|
+
# @api private
|
|
308
|
+
def rsync_command(locals, remote, ssh_ip, ssh_port, key_dir)
|
|
309
|
+
ssh_opts = [
|
|
310
|
+
"ssh -2",
|
|
311
|
+
"-i #{key_dir}/id_rsa",
|
|
312
|
+
"-o CheckHostIP=no",
|
|
313
|
+
"-o Compression=no",
|
|
314
|
+
"-o PasswordAuthentication=no",
|
|
315
|
+
"-o StrictHostKeyChecking=no",
|
|
316
|
+
"-o UserKnownHostsFile=/dev/null",
|
|
317
|
+
"-o LogLevel=ERROR",
|
|
318
|
+
"-p #{ssh_port}",
|
|
319
|
+
].join(" ")
|
|
320
|
+
|
|
321
|
+
"#{RSYNC_PATH} -a -e '#{ssh_opts}' #{locals.join(" ")} root@#{ssh_ip}:#{remote}"
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
# Copy files in with rsync.
|
|
325
|
+
#
|
|
326
|
+
# A non-zero exit is raised rather than ignored: silently continuing
|
|
327
|
+
# leaves the converge running against a container with no cookbooks in
|
|
328
|
+
# it, which then fails much further along with a confusing error.
|
|
329
|
+
#
|
|
330
|
+
# @param locals [Array<String>] local paths to copy
|
|
331
|
+
# @param remote [String] the destination path inside the container
|
|
332
|
+
# @param ssh_ip [String] the address to ssh to
|
|
333
|
+
# @param ssh_port [String] the port to ssh to
|
|
334
|
+
# @param key_dir [String] directory holding the `id_rsa` file
|
|
335
|
+
# @return [void]
|
|
336
|
+
# @raise [Kitchen::Transport::TransportFailed] if rsync exits non-zero
|
|
337
|
+
# @api private
|
|
338
|
+
def upload_via_rsync(locals, remote, ssh_ip, ssh_port, key_dir)
|
|
339
|
+
cmd = rsync_command(locals, remote, ssh_ip, ssh_port, key_dir)
|
|
340
|
+
debug "rsync_cmd :#{cmd}:"
|
|
341
|
+
|
|
342
|
+
output, status = Open3.capture2e(cmd)
|
|
343
|
+
return if status.success?
|
|
344
|
+
|
|
345
|
+
raise Kitchen::Transport::TransportFailed.new(
|
|
346
|
+
"rsync exited #{status.exitstatus} while uploading to #{ssh_ip}:#{remote}: #{output.strip}",
|
|
347
|
+
status.exitstatus
|
|
348
|
+
)
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
# Copy files in with scp, for hosts without rsync.
|
|
352
|
+
#
|
|
353
|
+
# @param locals [Array<String>] local paths to copy
|
|
354
|
+
# @param remote [String] the destination path inside the container
|
|
355
|
+
# @param ssh_ip [String] the address to ssh to
|
|
356
|
+
# @param ssh_port [String] the port to ssh to
|
|
357
|
+
# @param key_dir [String] directory holding the `id_rsa` file
|
|
358
|
+
# @return [void]
|
|
359
|
+
# @api private
|
|
360
|
+
def upload_via_scp(locals, remote, ssh_ip, ssh_port, key_dir)
|
|
361
|
+
locals.each do |local|
|
|
362
|
+
Net::SCP.upload!(ssh_ip,
|
|
363
|
+
"root",
|
|
364
|
+
local,
|
|
365
|
+
remote,
|
|
366
|
+
recursive: true,
|
|
367
|
+
ssh: { port: ssh_port, keys: ["#{key_dir}/id_rsa"] })
|
|
368
|
+
end
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
public
|
|
372
|
+
|
|
373
|
+
# Build the command `kitchen login` runs to drop the user into the
|
|
374
|
+
# runner container.
|
|
375
|
+
#
|
|
376
|
+
# `tput` reports the terminal size with a trailing newline; that has to
|
|
377
|
+
# be stripped, or bash reads the embedded newline in COLUMNS as the
|
|
378
|
+
# start of another command line.
|
|
379
|
+
#
|
|
380
|
+
# @return [Kitchen::LoginCommand] the command to exec
|
|
179
381
|
def login_command
|
|
180
382
|
@runner = options[:instance_name].to_s
|
|
181
|
-
cols = `tput cols
|
|
182
|
-
lines = `tput lines
|
|
383
|
+
cols = `tput cols`.strip
|
|
384
|
+
lines = `tput lines`.strip
|
|
183
385
|
args = ["exec", "-e", "COLUMNS=#{cols}", "-e", "LINES=#{lines}", "-it", @runner, "/bin/bash", "-login", "-i"]
|
|
184
386
|
LoginCommand.new(options[:login_command], args)
|
|
185
387
|
end
|
|
186
388
|
|
|
187
389
|
private
|
|
188
390
|
|
|
391
|
+
# The runner container's name.
|
|
392
|
+
#
|
|
393
|
+
# @return [String] the container name
|
|
394
|
+
# @api private
|
|
189
395
|
def instance_name
|
|
190
396
|
options[:instance_name]
|
|
191
397
|
end
|
|
192
398
|
|
|
399
|
+
# The locally built image the runner was created from.
|
|
400
|
+
#
|
|
401
|
+
# @return [String] an image reference
|
|
402
|
+
# @api private
|
|
193
403
|
def work_image
|
|
194
404
|
return "#{image_prefix}/#{instance_name}" unless image_prefix.nil?
|
|
195
405
|
|
|
196
406
|
instance_name
|
|
197
407
|
end
|
|
198
408
|
|
|
409
|
+
# The configured image name prefix, if any.
|
|
410
|
+
#
|
|
411
|
+
# @return [String, nil] the prefix
|
|
412
|
+
# @api private
|
|
199
413
|
def image_prefix
|
|
200
414
|
options[:image_prefix]
|
|
201
415
|
end
|
|
202
416
|
|
|
417
|
+
# Retry a docker API call through the errors that retrying can fix.
|
|
418
|
+
#
|
|
419
|
+
# @yield the API call to attempt
|
|
420
|
+
# @return [Object] the block's value
|
|
421
|
+
# @raise [::Docker::Error::DockerError] if every attempt failed
|
|
422
|
+
# @api private
|
|
203
423
|
def with_retries
|
|
204
424
|
tries = 20
|
|
205
425
|
begin
|
|
@@ -258,6 +478,7 @@ module Kitchen
|
|
|
258
478
|
@connection.close
|
|
259
479
|
end
|
|
260
480
|
|
|
481
|
+
@connection_options = options
|
|
261
482
|
@connection = Kitchen::Transport::Dokken::Connection.new(options, &block)
|
|
262
483
|
end
|
|
263
484
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kitchen-dokken
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.23.
|
|
4
|
+
version: 2.23.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sean OMeara
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-08-23 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: docker-api
|