kitchen-dokken 2.23.3 → 2.23.5
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 +335 -23
- 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 +320 -63
- 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,349 @@ 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
|
-
|
|
147
|
+
tcp_ssh_endpoint
|
|
148
|
+
else
|
|
149
|
+
raise Kitchen::UserError, "docker_host_url must be tcp:// or unix://"
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
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
|
|
108
160
|
|
|
109
|
-
|
|
110
|
-
|
|
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
|
|
111
168
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
|
116
185
|
|
|
117
|
-
|
|
118
|
-
|
|
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
|
|
119
209
|
|
|
210
|
+
raise Kitchen::Transport::TransportFailed,
|
|
211
|
+
"The data container has no address on any docker network: #{settings[:Networks].inspect}"
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Every address the data container has, best candidate first.
|
|
215
|
+
#
|
|
216
|
+
# A user-defined network comes before the default bridge. That order
|
|
217
|
+
# matters when kitchen is itself a container: a sibling on the dokken
|
|
218
|
+
# network can reach the container there but has no route to its
|
|
219
|
+
# bridge address, and preferring the bridge would send every upload
|
|
220
|
+
# to the docker host instead.
|
|
221
|
+
#
|
|
222
|
+
# The legacy top-level `IPAddress` is the default bridge's, so it
|
|
223
|
+
# sorts with the bridge and is deduplicated against it.
|
|
224
|
+
#
|
|
225
|
+
# @return [Array<String>] addresses, most specific network first
|
|
226
|
+
# @api private
|
|
227
|
+
def data_container_addresses
|
|
228
|
+
settings = options[:data_container][:NetworkSettings]
|
|
229
|
+
networks = settings[:Networks] || {}
|
|
230
|
+
|
|
231
|
+
user_defined, default_bridge = networks.partition { |name, _| name.to_s != "bridge" }
|
|
232
|
+
|
|
233
|
+
addresses = (user_defined + default_bridge).map { |_, network| network[:IPAddress].to_s }
|
|
234
|
+
addresses << settings[:IPAddress].to_s
|
|
235
|
+
|
|
236
|
+
addresses.reject(&:empty?).uniq
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# Pick an endpoint for a daemon reached over tcp.
|
|
240
|
+
#
|
|
241
|
+
# The container's address on the dokken network is preferred, since it
|
|
242
|
+
# avoids a round trip through the host, but it is only usable when
|
|
243
|
+
# kitchen shares a route with the daemon. Fall back to the docker host
|
|
244
|
+
# itself when neither candidate port answers.
|
|
245
|
+
#
|
|
246
|
+
# @return [Array(String, String)] the address and port to ssh to
|
|
247
|
+
# @api private
|
|
248
|
+
def tcp_ssh_endpoint
|
|
249
|
+
# DOCKER_HOST
|
|
250
|
+
docker_host_url_ip = options[:docker_host_url].split("tcp://")[1].split(":")[0]
|
|
251
|
+
|
|
252
|
+
# mapped port
|
|
253
|
+
candidate_ssh_port = published_ssh_port
|
|
254
|
+
|
|
255
|
+
# The addresses come from the state the driver recorded after it
|
|
256
|
+
# started the container. This used to ask the daemon instead --
|
|
257
|
+
# Docker::Container.all, find ours by name, then read
|
|
258
|
+
# Networks["dokken"]["IPAddress"] -- which was wrong twice over.
|
|
259
|
+
#
|
|
260
|
+
# Container.all lists only *running* containers, so a data container
|
|
261
|
+
# that had exited made `find` return nil and the chained `.info`
|
|
262
|
+
# raise `undefined method 'info' for nil`. And the dokken network is
|
|
263
|
+
# only attached when network_mode is left at its default:
|
|
264
|
+
# start_data_container adds the endpoint
|
|
265
|
+
# "unless %w{host bridge}.include?" and names it after network_mode,
|
|
266
|
+
# so `bridge`, `host` and every custom network name produced
|
|
267
|
+
# `undefined method '[]' for nil` on a remote daemon.
|
|
268
|
+
data_container_addresses.each do |candidate_ip|
|
|
120
269
|
debug "candidate_ip - #{candidate_ip}"
|
|
121
270
|
debug "candidate_ssh_port - #{candidate_ssh_port}"
|
|
122
271
|
|
|
123
272
|
if port_open?(candidate_ip, candidate_ssh_port)
|
|
124
273
|
debug "candidate_ip - #{candidate_ip}/#{candidate_ssh_port} open"
|
|
125
|
-
|
|
126
|
-
ssh_port = candidate_ssh_port
|
|
127
|
-
|
|
274
|
+
return [candidate_ip, candidate_ssh_port]
|
|
128
275
|
elsif port_open?(candidate_ip, "22")
|
|
129
|
-
ssh_ip = candidate_ip
|
|
130
|
-
ssh_port = "22"
|
|
131
276
|
debug "candidate_ip - #{candidate_ip}/22 open"
|
|
132
|
-
|
|
133
|
-
ssh_ip = docker_host_url_ip
|
|
134
|
-
ssh_port = candidate_ssh_port
|
|
277
|
+
return [candidate_ip, "22"]
|
|
135
278
|
end
|
|
136
|
-
else
|
|
137
|
-
raise Kitchen::UserError, "docker_host_url must be tcp:// or unix://"
|
|
138
279
|
end
|
|
139
280
|
|
|
140
|
-
|
|
141
|
-
|
|
281
|
+
# Nothing answered, or -- with host networking -- the container has
|
|
282
|
+
# no address of its own to try. The docker host is the endpoint.
|
|
283
|
+
debug "no data container address answered; falling back to the docker host"
|
|
284
|
+
[docker_host_url_ip, candidate_ssh_port]
|
|
285
|
+
end
|
|
142
286
|
|
|
287
|
+
# Write the built-in insecure private key somewhere ssh will accept it.
|
|
288
|
+
#
|
|
289
|
+
# ssh refuses to use a key file other users can read, so the file is
|
|
290
|
+
# written 0600 under a per-uid directory.
|
|
291
|
+
#
|
|
292
|
+
# @return [String] the directory holding the `id_rsa` file
|
|
293
|
+
# @api private
|
|
294
|
+
def write_insecure_key
|
|
143
295
|
tmpdir = Dir.tmpdir + "/dokken/"
|
|
144
296
|
FileUtils.mkdir_p tmpdir.to_s, mode: 0o777
|
|
145
297
|
tmpdir += Process.uid.to_s
|
|
146
298
|
FileUtils.mkdir_p tmpdir.to_s
|
|
147
299
|
File.write("#{tmpdir}/id_rsa", insecure_ssh_private_key)
|
|
148
300
|
FileUtils.chmod(0o600, "#{tmpdir}/id_rsa")
|
|
301
|
+
tmpdir
|
|
302
|
+
end
|
|
149
303
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
304
|
+
# Copy files into the data container, preferring rsync.
|
|
305
|
+
#
|
|
306
|
+
# @param locals [Array<String>] local paths to copy
|
|
307
|
+
# @param remote [String] the destination path inside the container
|
|
308
|
+
# @param ssh_ip [String] the address to ssh to
|
|
309
|
+
# @param ssh_port [String] the port to ssh to
|
|
310
|
+
# @param key_dir [String] directory holding the `id_rsa` file
|
|
311
|
+
# @return [void]
|
|
312
|
+
# @api private
|
|
313
|
+
def upload_files(locals, remote, ssh_ip, ssh_port, key_dir)
|
|
314
|
+
if rsync_available?
|
|
315
|
+
upload_via_rsync(locals, remote, ssh_ip, ssh_port, key_dir)
|
|
316
|
+
else
|
|
317
|
+
debug "Rsync is not installed at #{RSYNC_PATH}. Falling back to SCP."
|
|
318
|
+
upload_via_scp(locals, remote, ssh_ip, ssh_port, key_dir)
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
# Whether rsync is installed where we expect it.
|
|
323
|
+
#
|
|
324
|
+
# This is checked up front rather than inferred from a failed shell
|
|
325
|
+
# invocation: backticks run the command through /bin/sh, which reports
|
|
326
|
+
# a missing binary as exit status 127 and never raises Errno::ENOENT,
|
|
327
|
+
# so an exception-based check could never see it.
|
|
328
|
+
#
|
|
329
|
+
# @return [Boolean] true when rsync can be executed
|
|
330
|
+
# @api private
|
|
331
|
+
def rsync_available?
|
|
332
|
+
File.executable?(RSYNC_PATH)
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
# Build the rsync invocation used to copy the sandbox in.
|
|
336
|
+
#
|
|
337
|
+
# @param locals [Array<String>] local paths to copy
|
|
338
|
+
# @param remote [String] the destination path inside the container
|
|
339
|
+
# @param ssh_ip [String] the address to ssh to
|
|
340
|
+
# @param ssh_port [String] the port to ssh to
|
|
341
|
+
# @param key_dir [String] directory holding the `id_rsa` file
|
|
342
|
+
# @return [String] a shell command line
|
|
343
|
+
# @api private
|
|
344
|
+
def rsync_command(locals, remote, ssh_ip, ssh_port, key_dir)
|
|
345
|
+
ssh_opts = [
|
|
346
|
+
"ssh -2",
|
|
347
|
+
"-i #{key_dir}/id_rsa",
|
|
348
|
+
"-o CheckHostIP=no",
|
|
349
|
+
"-o Compression=no",
|
|
350
|
+
"-o PasswordAuthentication=no",
|
|
351
|
+
"-o StrictHostKeyChecking=no",
|
|
352
|
+
"-o UserKnownHostsFile=/dev/null",
|
|
353
|
+
"-o LogLevel=ERROR",
|
|
354
|
+
"-p #{ssh_port}",
|
|
355
|
+
].join(" ")
|
|
356
|
+
|
|
357
|
+
"#{RSYNC_PATH} -a -e '#{ssh_opts}' #{locals.join(" ")} root@#{ssh_ip}:#{remote}"
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
# Copy files in with rsync.
|
|
361
|
+
#
|
|
362
|
+
# A non-zero exit is raised rather than ignored: silently continuing
|
|
363
|
+
# leaves the converge running against a container with no cookbooks in
|
|
364
|
+
# it, which then fails much further along with a confusing error.
|
|
365
|
+
#
|
|
366
|
+
# @param locals [Array<String>] local paths to copy
|
|
367
|
+
# @param remote [String] the destination path inside the container
|
|
368
|
+
# @param ssh_ip [String] the address to ssh to
|
|
369
|
+
# @param ssh_port [String] the port to ssh to
|
|
370
|
+
# @param key_dir [String] directory holding the `id_rsa` file
|
|
371
|
+
# @return [void]
|
|
372
|
+
# @raise [Kitchen::Transport::TransportFailed] if rsync exits non-zero
|
|
373
|
+
# @api private
|
|
374
|
+
def upload_via_rsync(locals, remote, ssh_ip, ssh_port, key_dir)
|
|
375
|
+
cmd = rsync_command(locals, remote, ssh_ip, ssh_port, key_dir)
|
|
376
|
+
debug "rsync_cmd :#{cmd}:"
|
|
377
|
+
|
|
378
|
+
output, status = Open3.capture2e(cmd)
|
|
379
|
+
return if status.success?
|
|
380
|
+
|
|
381
|
+
raise Kitchen::Transport::TransportFailed.new(
|
|
382
|
+
"rsync exited #{status.exitstatus} while uploading to #{ssh_ip}:#{remote}: #{output.strip}",
|
|
383
|
+
status.exitstatus
|
|
384
|
+
)
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
# Copy files in with scp, for hosts without rsync.
|
|
388
|
+
#
|
|
389
|
+
# @param locals [Array<String>] local paths to copy
|
|
390
|
+
# @param remote [String] the destination path inside the container
|
|
391
|
+
# @param ssh_ip [String] the address to ssh to
|
|
392
|
+
# @param ssh_port [String] the port to ssh to
|
|
393
|
+
# @param key_dir [String] directory holding the `id_rsa` file
|
|
394
|
+
# @return [void]
|
|
395
|
+
# @api private
|
|
396
|
+
def upload_via_scp(locals, remote, ssh_ip, ssh_port, key_dir)
|
|
397
|
+
locals.each do |local|
|
|
398
|
+
Net::SCP.upload!(ssh_ip,
|
|
399
|
+
"root",
|
|
400
|
+
local,
|
|
401
|
+
remote,
|
|
402
|
+
recursive: true,
|
|
403
|
+
ssh: { port: ssh_port, keys: ["#{key_dir}/id_rsa"] })
|
|
176
404
|
end
|
|
177
405
|
end
|
|
178
406
|
|
|
407
|
+
public
|
|
408
|
+
|
|
409
|
+
# Build the command `kitchen login` runs to drop the user into the
|
|
410
|
+
# runner container.
|
|
411
|
+
#
|
|
412
|
+
# `tput` reports the terminal size with a trailing newline; that has to
|
|
413
|
+
# be stripped, or bash reads the embedded newline in COLUMNS as the
|
|
414
|
+
# start of another command line.
|
|
415
|
+
#
|
|
416
|
+
# @return [Kitchen::LoginCommand] the command to exec
|
|
179
417
|
def login_command
|
|
180
418
|
@runner = options[:instance_name].to_s
|
|
181
|
-
cols = `tput cols
|
|
182
|
-
lines = `tput lines
|
|
419
|
+
cols = `tput cols`.strip
|
|
420
|
+
lines = `tput lines`.strip
|
|
183
421
|
args = ["exec", "-e", "COLUMNS=#{cols}", "-e", "LINES=#{lines}", "-it", @runner, "/bin/bash", "-login", "-i"]
|
|
184
422
|
LoginCommand.new(options[:login_command], args)
|
|
185
423
|
end
|
|
186
424
|
|
|
187
425
|
private
|
|
188
426
|
|
|
427
|
+
# The runner container's name.
|
|
428
|
+
#
|
|
429
|
+
# @return [String] the container name
|
|
430
|
+
# @api private
|
|
189
431
|
def instance_name
|
|
190
432
|
options[:instance_name]
|
|
191
433
|
end
|
|
192
434
|
|
|
435
|
+
# The locally built image the runner was created from.
|
|
436
|
+
#
|
|
437
|
+
# @return [String] an image reference
|
|
438
|
+
# @api private
|
|
193
439
|
def work_image
|
|
194
440
|
return "#{image_prefix}/#{instance_name}" unless image_prefix.nil?
|
|
195
441
|
|
|
196
442
|
instance_name
|
|
197
443
|
end
|
|
198
444
|
|
|
445
|
+
# The configured image name prefix, if any.
|
|
446
|
+
#
|
|
447
|
+
# @return [String, nil] the prefix
|
|
448
|
+
# @api private
|
|
199
449
|
def image_prefix
|
|
200
450
|
options[:image_prefix]
|
|
201
451
|
end
|
|
202
452
|
|
|
453
|
+
# Retry a docker API call through the errors that retrying can fix.
|
|
454
|
+
#
|
|
455
|
+
# @yield the API call to attempt
|
|
456
|
+
# @return [Object] the block's value
|
|
457
|
+
# @raise [::Docker::Error::DockerError] if every attempt failed
|
|
458
|
+
# @api private
|
|
203
459
|
def with_retries
|
|
204
460
|
tries = 20
|
|
205
461
|
begin
|
|
@@ -258,6 +514,7 @@ module Kitchen
|
|
|
258
514
|
@connection.close
|
|
259
515
|
end
|
|
260
516
|
|
|
517
|
+
@connection_options = options
|
|
261
518
|
@connection = Kitchen::Transport::Dokken::Connection.new(options, &block)
|
|
262
519
|
end
|
|
263
520
|
|
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.5
|
|
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
|