kitchen-dokken 2.23.7 → 2.23.8
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/lib/kitchen/driver/dokken.rb +80 -17
- data/lib/kitchen/driver/dokken_version.rb +1 -1
- data/lib/kitchen/helpers.rb +55 -5
- data/lib/kitchen/provisioner/dokken.rb +10 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a0dffedb0556f642eb7b9011de7ce16d3002b7fc94fc4f99e1f0350a3e593865
|
|
4
|
+
data.tar.gz: 0542043deb31d066f65640e32faaf45f66c62d6c5e77b8efa569d30dbbfa0ecf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f0717edbd835bdba7ba34d86f051b19427506871cde23003d7cd11c9ccf2fdc32a876d02b32c66fb0b370382648863053b671046d921e04318d5ebf13af0a580
|
|
7
|
+
data.tar.gz: 7d99975e0a3532d9c62acd42cf84cc258514bb591621d63de40f53f1b814b090cc8d7eac777b7fbd179d7a6b81e1a38ae461e957db6a551bb91fd7a53c98491a
|
|
@@ -240,17 +240,41 @@ module Kitchen
|
|
|
240
240
|
|
|
241
241
|
# Pull the human-readable reason out of a failed build response.
|
|
242
242
|
#
|
|
243
|
-
# The daemon usually answers with a JSON document carrying an "error"
|
|
244
|
-
# key, but a proxy or a plain-text 500 does not -- and a JSON::ParserError
|
|
245
|
-
# raised from inside the rescue clause would bury the real failure.
|
|
246
|
-
#
|
|
247
243
|
# @param error [Exception] the error docker-api raised
|
|
248
244
|
# @return [String] the daemon's explanation, or the raw response
|
|
249
245
|
def build_error_detail(error)
|
|
250
|
-
|
|
251
|
-
|
|
246
|
+
daemon_message(error.to_s.split("\r\n").last.to_s)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# Pull the human-readable reason out of a docker-api error.
|
|
250
|
+
#
|
|
251
|
+
# docker-api raises with the daemon's response body as the message, so
|
|
252
|
+
# what reaches a rescue clause is a JSON document rather than a sentence:
|
|
253
|
+
# `{"message":"driver failed programming external connectivity ..."}`.
|
|
254
|
+
# Reporting that verbatim is barely better than reporting nothing.
|
|
255
|
+
#
|
|
256
|
+
# @param error [Exception] the error docker-api raised
|
|
257
|
+
# @return [String] the daemon's explanation, or the raw response
|
|
258
|
+
def docker_error_detail(error)
|
|
259
|
+
daemon_message(error.to_s)
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# The explanation carried in a docker daemon response body.
|
|
263
|
+
#
|
|
264
|
+
# Most endpoints answer with a "message" key and the /build stream with
|
|
265
|
+
# an "error" one. A proxy or a plain-text 500 answers with neither -- and
|
|
266
|
+
# a JSON::ParserError raised from inside a rescue clause would bury the
|
|
267
|
+
# very failure it was called on to explain -- so anything unparseable
|
|
268
|
+
# comes back unchanged.
|
|
269
|
+
#
|
|
270
|
+
# @param body [String] a response body
|
|
271
|
+
# @return [String] the explanation, or the body unchanged
|
|
272
|
+
def daemon_message(body)
|
|
273
|
+
parsed = JSON.parse(body)
|
|
274
|
+
detail = parsed["message"] || parsed["error"]
|
|
275
|
+
detail.nil? ? body : detail.to_s
|
|
252
276
|
rescue JSON::ParserError, TypeError
|
|
253
|
-
|
|
277
|
+
body
|
|
254
278
|
end
|
|
255
279
|
|
|
256
280
|
# The Dockerfile for the work image.
|
|
@@ -899,7 +923,11 @@ module Kitchen
|
|
|
899
923
|
end
|
|
900
924
|
rescue ::Docker::Error::DockerError => e
|
|
901
925
|
debug "driver - error :#{e}:"
|
|
902
|
-
|
|
926
|
+
# The daemon's reason used to be dropped here, leaving `kitchen
|
|
927
|
+
# create` to report a bare "failed to create_container <name>" with
|
|
928
|
+
# nothing to act on -- the explanation existed, but only for someone
|
|
929
|
+
# who already knew to re-run at `-l debug`.
|
|
930
|
+
raise "driver - failed to create_container #{args["name"]}: #{docker_error_detail(e)}"
|
|
903
931
|
end
|
|
904
932
|
end
|
|
905
933
|
|
|
@@ -943,15 +971,35 @@ module Kitchen
|
|
|
943
971
|
# @raise [Kitchen::ActionFailed] if the container will not stay running
|
|
944
972
|
def run_container(args, platform: nil)
|
|
945
973
|
@container = create_container(args, platform: platform)
|
|
946
|
-
|
|
947
|
-
@container.start
|
|
948
|
-
@container = ::Docker::Container.get(args["name"], {}, docker_connection)
|
|
949
|
-
wait_running_state(args["name"], true)
|
|
950
|
-
end
|
|
974
|
+
start_container!(args["name"])
|
|
951
975
|
assert_running!(args["name"])
|
|
952
976
|
@container
|
|
953
977
|
end
|
|
954
978
|
|
|
979
|
+
# Start a container and wait for it to be running.
|
|
980
|
+
#
|
|
981
|
+
# `start!`, not `start`: docker-api defines the unsuffixed form as "the
|
|
982
|
+
# same, but rescue from ServerErrors", so every reason the daemon
|
|
983
|
+
# refused -- a port already bound, an invalid mount, a capability the
|
|
984
|
+
# kernel will not grant -- was swallowed on the way past. `with_retries`
|
|
985
|
+
# then had nothing to retry and `assert_running!` was left to report a
|
|
986
|
+
# container that "exited immediately" when in truth it had never
|
|
987
|
+
# started, sending the user off to read `docker logs` output that does
|
|
988
|
+
# not exist.
|
|
989
|
+
#
|
|
990
|
+
# @param name [String] the container name
|
|
991
|
+
# @return [void]
|
|
992
|
+
# @raise [Kitchen::ActionFailed] if the daemon would not start it
|
|
993
|
+
def start_container!(name)
|
|
994
|
+
with_retries do
|
|
995
|
+
@container.start!
|
|
996
|
+
@container = ::Docker::Container.get(name, {}, docker_connection)
|
|
997
|
+
wait_running_state(name, true)
|
|
998
|
+
end
|
|
999
|
+
rescue ::Docker::Error::DockerError => e
|
|
1000
|
+
raise ActionFailed, "The #{name} container could not be started: #{docker_error_detail(e)}"
|
|
1001
|
+
end
|
|
1002
|
+
|
|
955
1003
|
# Fail the action when a container that has to run is not running.
|
|
956
1004
|
#
|
|
957
1005
|
# wait_running_state stops polling as soon as `FinishedAt` is set, which
|
|
@@ -970,10 +1018,25 @@ module Kitchen
|
|
|
970
1018
|
def assert_running!(name)
|
|
971
1019
|
return if container_state["Running"]
|
|
972
1020
|
|
|
973
|
-
raise ActionFailed,
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
1021
|
+
raise ActionFailed, "The #{name} container is not running. #{not_running_reason(name)}"
|
|
1022
|
+
end
|
|
1023
|
+
|
|
1024
|
+
# Why a container that should be running is not.
|
|
1025
|
+
#
|
|
1026
|
+
# The daemon records its own refusal in `State.Error` and leaves
|
|
1027
|
+
# `FinishedAt` unset, which is a different failure from a pid 1 that
|
|
1028
|
+
# started and exited -- and pointing at `docker logs` for the first kind
|
|
1029
|
+
# sends the user to an empty file.
|
|
1030
|
+
#
|
|
1031
|
+
# @param name [String] the container name
|
|
1032
|
+
# @return [String] a sentence naming the likely cause
|
|
1033
|
+
def not_running_reason(name)
|
|
1034
|
+
daemon_error = container_state["Error"].to_s
|
|
1035
|
+
return "The docker daemon refused to start it: #{daemon_error}" unless daemon_error.empty?
|
|
1036
|
+
|
|
1037
|
+
"Its pid 1 exited immediately (exit code #{container_state["ExitCode"]}): " \
|
|
1038
|
+
"check `pid_one_command`, `entrypoint`, and that the image can boot " \
|
|
1039
|
+
"(`docker logs #{name}` shows why)."
|
|
977
1040
|
end
|
|
978
1041
|
|
|
979
1042
|
# The current container's `State` payload.
|
data/lib/kitchen/helpers.rb
CHANGED
|
@@ -351,10 +351,15 @@ module Dokken
|
|
|
351
351
|
# each optionally suffixed with `/protocol` and each allowing an
|
|
352
352
|
# inclusive `low-high` container port range.
|
|
353
353
|
#
|
|
354
|
-
# @param v [String] a port specification
|
|
354
|
+
# @param v [String, Integer] a port specification
|
|
355
355
|
# @return [Array<Hash>] one entry per container port
|
|
356
|
-
# @raise [Kitchen::UserError] if a port range is inverted
|
|
356
|
+
# @raise [Kitchen::UserError] if a port range is inverted or unpairable
|
|
357
357
|
def parse_port(v)
|
|
358
|
+
# `ports: [8080]` is a perfectly natural thing to write, and YAML hands
|
|
359
|
+
# it over as an Integer. That used to reach String#split and abort the
|
|
360
|
+
# create with `undefined method 'split' for an instance of Integer`,
|
|
361
|
+
# which names a type rather than the line of kitchen.yml at fault.
|
|
362
|
+
v = v.to_s
|
|
358
363
|
parts = v.split(":")
|
|
359
364
|
case parts.length
|
|
360
365
|
when 3
|
|
@@ -390,18 +395,63 @@ module Dokken
|
|
|
390
395
|
"Invalid port spec #{v.inspect}: no container port"
|
|
391
396
|
end
|
|
392
397
|
|
|
393
|
-
|
|
398
|
+
container_ports = port_range.include?("-") ? expand_port_range(port_range, v) : [port_range]
|
|
394
399
|
# qualify the port-binding protocol even when it is implicitly tcp #427.
|
|
395
400
|
protocol = "tcp" if protocol.nil?
|
|
396
|
-
|
|
401
|
+
host_ports = expand_host_ports(host_port, container_ports.length, v)
|
|
402
|
+
|
|
403
|
+
container_ports.each_with_index.map do |port, i|
|
|
397
404
|
{
|
|
398
405
|
"host_ip" => host_ip,
|
|
399
|
-
"host_port" =>
|
|
406
|
+
"host_port" => host_ports[i],
|
|
400
407
|
"container_port" => "#{port}/#{protocol}",
|
|
401
408
|
}
|
|
402
409
|
end
|
|
403
410
|
end
|
|
404
411
|
|
|
412
|
+
# Work out the host port that pairs with each container port.
|
|
413
|
+
#
|
|
414
|
+
# Docker pairs ranges off one for one -- `8080-8082:9090-9092` publishes
|
|
415
|
+
# 9090 on 8080, 9091 on 8081 and 9092 on 8082 -- but only the container
|
|
416
|
+
# side used to be expanded here, so every binding was handed the whole
|
|
417
|
+
# host range as its `HostPort`. That reads as "any free port in this
|
|
418
|
+
# range" to the daemon, which meant the mapping was only correct while the
|
|
419
|
+
# whole range happened to be free, and otherwise came out shifted or
|
|
420
|
+
# failed outright with "all ports are allocated" rather than naming the
|
|
421
|
+
# port that was taken.
|
|
422
|
+
#
|
|
423
|
+
# @param host_port [String] the host half of the spec
|
|
424
|
+
# @param count [Integer] how many container ports were asked for
|
|
425
|
+
# @param spec [String] the whole port spec, for the error message
|
|
426
|
+
# @return [Array<String>] one host port per container port
|
|
427
|
+
# @raise [Kitchen::UserError] if the two sides cannot be paired
|
|
428
|
+
def expand_host_ports(host_port, count, spec)
|
|
429
|
+
# `ports: '8080'` names no host port at all; the daemon picks one.
|
|
430
|
+
return Array.new(count, host_port) if host_port.empty?
|
|
431
|
+
|
|
432
|
+
unless host_port.include?("-")
|
|
433
|
+
return [host_port] if count == 1
|
|
434
|
+
|
|
435
|
+
raise Kitchen::UserError,
|
|
436
|
+
"Invalid port spec #{spec.inspect}: a single host port cannot be paired with " \
|
|
437
|
+
"a range of #{count} container ports. Give a host range of the same size, " \
|
|
438
|
+
"as in 8080-8082:9090-9092."
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
# A host range against a single container port is docker's "publish this
|
|
442
|
+
# on any free port in the range". The daemon does the choosing, so the
|
|
443
|
+
# range is handed over untouched.
|
|
444
|
+
return [host_port] if count == 1
|
|
445
|
+
|
|
446
|
+
host_ports = expand_port_range(host_port, spec)
|
|
447
|
+
return host_ports.map(&:to_s) if host_ports.length == count
|
|
448
|
+
|
|
449
|
+
raise Kitchen::UserError,
|
|
450
|
+
"Invalid port spec #{spec.inspect}: the host range covers #{host_ports.length} " \
|
|
451
|
+
"ports and the container range covers #{count}. Docker pairs them off one for " \
|
|
452
|
+
"one, so both ranges have to be the same size."
|
|
453
|
+
end
|
|
454
|
+
|
|
405
455
|
# Expand an inclusive `low-high` container port range.
|
|
406
456
|
#
|
|
407
457
|
# @param range [String] the range, e.g. "8080-8082"
|
|
@@ -166,6 +166,16 @@ module Kitchen
|
|
|
166
166
|
# just use the defaults
|
|
167
167
|
config[:chef_log_level] = "warn" if config[:chef_log_level].empty?
|
|
168
168
|
config[:chef_output_format] = "doc" if config[:chef_output_format].empty?
|
|
169
|
+
|
|
170
|
+
# ChefBase#chef_args appends `--log_level #{config[:log_level]}` to
|
|
171
|
+
# whatever base command it is handed, so the parent's flag lands
|
|
172
|
+
# *after* the `-l` built below. `-l` and `--log_level` are the same
|
|
173
|
+
# chef-client option and the last occurrence wins, which meant
|
|
174
|
+
# chef_log_level was parsed, written into the staged run_command, and
|
|
175
|
+
# then silently overridden by the parent's default of "auto".
|
|
176
|
+
# Keeping the two in step makes the documented setting the one chef
|
|
177
|
+
# actually runs at.
|
|
178
|
+
config[:log_level] = config[:chef_log_level]
|
|
169
179
|
end
|
|
170
180
|
|
|
171
181
|
private
|