kitchen-docker 3.3.0 → 3.3.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.
@@ -24,44 +24,122 @@ require_relative "cli_helper"
24
24
 
25
25
  module Kitchen
26
26
  module Docker
27
+ # Mixins shared by the driver, transport, and container classes.
27
28
  module Helpers
28
- # rubocop:disable Metrics/ModuleLength, Style/Documentation
29
+ # rubocop:disable Metrics/ModuleLength
30
+ # Operations against a running container: exec, copy, inspect, remove.
29
31
  module ContainerHelper
30
32
  include Configurable
31
33
  include Kitchen::Docker::Helpers::CliHelper
32
34
 
35
+ # Pulls the container id out of `docker run` output.
36
+ #
37
+ # Docker prints ids in short (12) or full (64) hex form, on a line of
38
+ # their own. The id is looked for line by line rather than by taking the
39
+ # whole output, because {CliHelper#run_command} returns stdout and stderr
40
+ # together and some daemons write warnings to stderr on a run that
41
+ # otherwise succeeds. Rootless Docker emits "WARNING: IPv4 forwarding is
42
+ # disabled. Networking will not work." on every run; setting
43
+ # +run_options+ to +--net=host+ produces "WARNING: Published ports are
44
+ # discarded when using host network mode", since the driver always
45
+ # publishes port 22 for Linux containers. Treating the whole output as
46
+ # the id failed those runs *after* the container had been created,
47
+ # leaving it running and untracked.
48
+ #
49
+ # Scanning forward is deterministic: +run_command+ concatenates stdout
50
+ # before stderr, so the id always precedes anything a warning adds.
51
+ #
52
+ # @param output [String] the command output, stdout and stderr together
53
+ # @return [String] the container id
54
+ # @raise [Kitchen::ActionFailed] if no id could be found
33
55
  def parse_container_id(output)
34
- container_id = output.chomp
35
-
36
- unless [12, 64].include?(container_id.size)
37
- raise ActionFailed, "Could not parse Docker run output for container ID"
56
+ container_id = output.to_s.lines.map(&:strip).find do |line|
57
+ line.match?(/\A[0-9a-f]{12}(?:[0-9a-f]{52})?\z/)
38
58
  end
39
59
 
60
+ raise ActionFailed, "Could not parse Docker run output for container ID" unless container_id
61
+
40
62
  container_id
41
63
  end
42
64
 
65
+ # Renders the configured Dockerfile through ERB.
66
+ #
67
+ # @return [String] the rendered Dockerfile
43
68
  def dockerfile_template
44
69
  template = IO.read(File.expand_path(config[:dockerfile]))
45
70
  context = Kitchen::Docker::ERBContext.new(config.to_hash)
46
71
  ERB.new(template).result(context.get_binding)
47
72
  end
48
73
 
74
+ # @return [Boolean] whether the configured socket is a TCP one, meaning
75
+ # the daemon is not on this machine
49
76
  def remote_socket?
50
77
  config[:socket] ? socket_uri.scheme == "tcp" : false
51
78
  end
52
79
 
80
+ # @return [URI] the configured Docker socket
53
81
  def socket_uri
54
82
  URI.parse(config[:socket])
55
83
  end
56
84
 
85
+ # The path to pass to `docker build -f`.
86
+ #
87
+ # With a build context the path has to be relative to it; without one
88
+ # docker reads the Dockerfile from stdin and the absolute path is fine.
89
+ #
90
+ # @param file [File] the temp Dockerfile
91
+ # @return [String] the path to use
57
92
  def dockerfile_path(file)
58
93
  config[:build_context] ? Pathname.new(file.path).relative_path_from(Pathname.pwd).to_s : file.path
59
94
  end
60
95
 
96
+ # Whether the container named in state is present, running or not.
97
+ #
98
+ # Asked with `docker inspect` rather than `docker top`, which answers a
99
+ # different question: `top` lists processes, so it fails on a container
100
+ # that exists but has stopped. Reading that as "does not exist" made
101
+ # {Kitchen::Docker::Container#destroy} skip removal and leave the
102
+ # container behind, while Test Kitchen deleted the state file and
103
+ # reported success.
104
+ #
105
+ # @param state [Hash] instance state naming the container
106
+ # @return [Boolean] whether the container exists in any state
61
107
  def container_exists?(state)
62
- state[:container_id] && !!docker_command("top #{state[:container_id]}") rescue false
108
+ return false unless state[:container_id]
109
+
110
+ !!docker_command("inspect --type=container #{state[:container_id]}",
111
+ suppress_output: !logger.debug?)
112
+ rescue
113
+ false
114
+ end
115
+
116
+ # Whether the container named in state is running.
117
+ #
118
+ # Separate from {#container_exists?} because the two callers want
119
+ # different questions answered: destroy removes a container in any
120
+ # state, while create has to tell a container it can use from one that
121
+ # has stopped.
122
+ #
123
+ # @param state [Hash] instance state naming the container
124
+ # @return [Boolean] whether the container exists and is running
125
+ def container_running?(state)
126
+ return false unless state[:container_id]
127
+
128
+ output = docker_command(
129
+ "inspect --type=container --format '{{.State.Running}}' #{state[:container_id]}",
130
+ suppress_output: !logger.debug?
131
+ )
132
+ output.strip == "true"
133
+ rescue
134
+ false
63
135
  end
64
136
 
137
+ # Runs a command inside the container.
138
+ #
139
+ # @param state [Hash] instance state naming the container
140
+ # @param command [String] the command to run
141
+ # @return [String] the command's combined output
142
+ # @raise [RuntimeError] if the command fails
65
143
  def container_exec(state, command)
66
144
  cmd = build_exec_command(state, command)
67
145
  docker_command(cmd)
@@ -69,6 +147,13 @@ module Kitchen
69
147
  raise "Failed to execute command on Docker container. #{e}"
70
148
  end
71
149
 
150
+ # Creates a directory inside the container, on Linux or Windows.
151
+ #
152
+ # @param state [Hash] instance state naming the container
153
+ # @param path [String] the directory to create; environment variable
154
+ # references are expanded first
155
+ # @return [String] the command's combined output
156
+ # @raise [RuntimeError] if the directory cannot be created
72
157
  def create_dir_on_container(state, path)
73
158
  path = replace_env_variables(state, path)
74
159
  cmd = "mkdir -p #{path}"
@@ -84,6 +169,13 @@ module Kitchen
84
169
  raise "Failed to create directory #{path} on container. #{e}"
85
170
  end
86
171
 
172
+ # Copies a local file into the container.
173
+ #
174
+ # @param state [Hash] instance state naming the container
175
+ # @param local_file [String] source path
176
+ # @param remote_file [String] destination path inside the container
177
+ # @return [String] the command's combined output
178
+ # @raise [RuntimeError] if the copy fails
87
179
  def copy_file_to_container(state, local_file, remote_file)
88
180
  debug("Copying local file #{local_file} to #{remote_file} on container")
89
181
 
@@ -97,6 +189,11 @@ module Kitchen
97
189
  end
98
190
 
99
191
  # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
192
+
193
+ # Reads the container's environment.
194
+ #
195
+ # @param state [Hash] instance state naming the container
196
+ # @return [Hash] variable names to values
100
197
  def container_env_variables(state)
101
198
  # Retrieves all environment variables from inside container
102
199
  vars = {}
@@ -109,13 +206,28 @@ module Kitchen
109
206
  else
110
207
  cmd = build_exec_command(state, "printenv")
111
208
  stdout = docker_command(cmd, suppress_output: !logger.debug?).strip
112
- stdout.split("\n").each { |line| vars[line.split("=")[0]] = line.split("=")[1] }
209
+ # printenv writes NAME=VALUE, and values routinely contain "=" --
210
+ # LS_COLORS and anything -Dkey=value shaped do. Split on the first
211
+ # one only, or the value is truncated at it. Lines with no "=" are
212
+ # continuations of a multi-line value and carry no name.
213
+ stdout.split("\n").each do |line|
214
+ name, value = line.split("=", 2)
215
+ vars[name] = value unless value.nil?
216
+ end
113
217
  end
114
218
 
115
219
  vars
116
220
  end
117
221
  # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
118
222
 
223
+ # Expands a container-side environment variable reference in a path.
224
+ #
225
+ # Handles both +$env:TEMP+ and +$TEMP+ forms. The value has to be read from
226
+ # inside the container, since the workstation's environment is unrelated.
227
+ #
228
+ # @param state [Hash] instance state naming the container
229
+ # @param str [String] the string to expand
230
+ # @return [String] the expanded string
119
231
  def replace_env_variables(state, str)
120
232
  if str.include?("$env:")
121
233
  key = str[/\$env:(.*?)(\\|$)/, 1]
@@ -130,12 +242,20 @@ module Kitchen
130
242
  str
131
243
  end
132
244
 
245
+ # Runs the container and returns its id.
246
+ #
247
+ # @param state [Hash] instance state naming the image
248
+ # @param transport_port [Integer, nil] container port to publish, if any
249
+ # @return [String] the new container's id
133
250
  def run_container(state, transport_port = nil)
134
251
  cmd = build_run_command(state[:image_id], transport_port)
135
252
  output = docker_command(cmd)
136
253
  parse_container_id(output)
137
254
  end
138
255
 
256
+ # @param state [Hash] instance state naming the container
257
+ # @return [String] the container's address on the Docker network
258
+ # @raise [Kitchen::ActionFailed] if it cannot be determined
139
259
  def container_ip_address(state)
140
260
  cmd = "inspect --format '{{ .NetworkSettings.IPAddress }}'"
141
261
  cmd << " #{state[:container_id]}"
@@ -144,35 +264,41 @@ module Kitchen
144
264
  raise ActionFailed, "Error getting internal IP of Docker container"
145
265
  end
146
266
 
267
+ # Stops and removes the container.
268
+ #
269
+ # @param state [Hash] instance state naming the container
270
+ # @return [void]
147
271
  def remove_container(state)
148
272
  container_id = state[:container_id]
149
273
  docker_command("stop -t 0 #{container_id}")
150
274
  docker_command("rm #{container_id}")
151
275
  end
152
276
 
153
- # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
277
+ # Dockerfile ENV lines carrying the configured proxy settings.
278
+ #
279
+ # Each is emitted in both lower and upper case, because different tools
280
+ # inside the image read different spellings.
281
+ #
282
+ # @return [String] the ENV lines, empty when no proxy is configured
154
283
  def dockerfile_proxy_config
155
- env_variables = ""
156
- if config[:http_proxy]
157
- env_variables << "ENV http_proxy=#{config[:http_proxy]}\n"
158
- env_variables << "ENV HTTP_PROXY=#{config[:http_proxy]}\n"
159
- end
160
-
161
- if config[:https_proxy]
162
- env_variables << "ENV https_proxy=#{config[:https_proxy]}\n"
163
- env_variables << "ENV HTTPS_PROXY=#{config[:https_proxy]}\n"
164
- end
284
+ %i{http_proxy https_proxy no_proxy}.map do |proxy_type|
285
+ proxy_env_vars(proxy_type)
286
+ end.join
287
+ end
165
288
 
166
- if config[:no_proxy]
167
- env_variables << "ENV no_proxy=#{config[:no_proxy]}\n"
168
- env_variables << "ENV NO_PROXY=#{config[:no_proxy]}\n"
169
- end
289
+ # ENV lines for one proxy setting, in both spellings.
290
+ #
291
+ # @param proxy_type [Symbol] +:http_proxy+, +:https_proxy+, or
292
+ # +:no_proxy+
293
+ # @return [String] two ENV lines, or empty when that proxy is unset
294
+ def proxy_env_vars(proxy_type)
295
+ return "" unless config[proxy_type]
170
296
 
171
- env_variables
297
+ value = config[proxy_type]
298
+ "ENV #{proxy_type}=#{value}\nENV #{proxy_type.upcase}=#{value}\n"
172
299
  end
173
- # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
174
300
  end
175
- # rubocop:enable Metrics/ModuleLength, Style/Documentation
301
+ # rubocop:enable Metrics/ModuleLength
176
302
  end
177
303
  end
178
304
  end
@@ -16,10 +16,20 @@ require "kitchen/configurable"
16
16
 
17
17
  module Kitchen
18
18
  module Docker
19
+ # Mixins shared by the driver, transport, and container classes.
19
20
  module Helpers
21
+ # Per-distribution Dockerfile fragments that prepare a Linux image for SSH.
20
22
  module DockerfileHelper
21
23
  include Configurable
22
24
 
25
+ # Dockerfile lines that prepare the configured platform for SSH.
26
+ #
27
+ # Each distribution needs its own package manager invocation to install an
28
+ # SSH server and sudo, and its own host-key generation, which is why there
29
+ # is one method per family rather than a shared one.
30
+ #
31
+ # @return [String] the RUN lines for this platform
32
+ # @raise [Kitchen::ActionFailed] if the platform is not recognised
23
33
  def dockerfile_platform
24
34
  case config[:platform]
25
35
  when "arch"
@@ -51,6 +61,9 @@ module Kitchen
51
61
  end
52
62
  end
53
63
 
64
+ # Dockerfile lines installing an SSH server and sudo on Arch Linux.
65
+ #
66
+ # @return [String] the RUN lines
54
67
  def arch_platform
55
68
  <<-CODE
56
69
  RUN pacman --noconfirm -Sy archlinux-keyring
@@ -60,6 +73,9 @@ module Kitchen
60
73
  CODE
61
74
  end
62
75
 
76
+ # Dockerfile lines installing an SSH server and sudo on Debian and Ubuntu.
77
+ #
78
+ # @return [String] the RUN lines
63
79
  def debian_platform
64
80
  disable_upstart = <<-CODE
65
81
  RUN [ ! -f "/sbin/initctl" ] || dpkg-divert --local --rename --add /sbin/initctl \
@@ -74,6 +90,9 @@ module Kitchen
74
90
  config[:disable_upstart] ? disable_upstart + packages : packages
75
91
  end
76
92
 
93
+ # Dockerfile lines installing an SSH server and sudo on Fedora.
94
+ #
95
+ # @return [String] the RUN lines
77
96
  def fedora_platform
78
97
  <<-CODE
79
98
  ENV container=docker
@@ -83,6 +102,9 @@ module Kitchen
83
102
  CODE
84
103
  end
85
104
 
105
+ # Dockerfile lines installing an SSH server and sudo on Gentoo with Portage.
106
+ #
107
+ # @return [String] the RUN lines
86
108
  def gentoo_platform
87
109
  <<-CODE
88
110
  RUN emerge-webrsync
@@ -91,6 +113,9 @@ module Kitchen
91
113
  CODE
92
114
  end
93
115
 
116
+ # Dockerfile lines installing an SSH server and sudo on Gentoo with Paludis.
117
+ #
118
+ # @return [String] the RUN lines
94
119
  def gentoo_paludis_platform
95
120
  <<-CODE
96
121
  RUN cave sync
@@ -99,6 +124,9 @@ module Kitchen
99
124
  CODE
100
125
  end
101
126
 
127
+ # Dockerfile lines installing an SSH server and sudo on openSUSE and SLES.
128
+ #
129
+ # @return [String] the RUN lines
102
130
  def opensuse_platform
103
131
  <<-CODE
104
132
  ENV container=docker
@@ -107,6 +135,9 @@ module Kitchen
107
135
  CODE
108
136
  end
109
137
 
138
+ # Dockerfile lines installing an SSH server and sudo on RHEL, CentOS, and Oracle Linux.
139
+ #
140
+ # @return [String] the RUN lines
110
141
  def rhel_platform
111
142
  <<-CODE
112
143
  ENV container=docker
@@ -117,6 +148,9 @@ module Kitchen
117
148
  CODE
118
149
  end
119
150
 
151
+ # Dockerfile lines installing an SSH server and sudo on Amazon Linux.
152
+ #
153
+ # @return [String] the RUN lines
120
154
  def amazonlinux_platform
121
155
  <<-CODE
122
156
  ENV container=docker
@@ -126,6 +160,9 @@ module Kitchen
126
160
  CODE
127
161
  end
128
162
 
163
+ # Dockerfile lines installing an SSH server and sudo on CentOS Stream.
164
+ #
165
+ # @return [String] the RUN lines
129
166
  def centosstream_platform
130
167
  <<-CODE
131
168
  ENV container=docker
@@ -135,6 +172,9 @@ module Kitchen
135
172
  CODE
136
173
  end
137
174
 
175
+ # Dockerfile lines installing an SSH server and sudo on AlmaLinux.
176
+ #
177
+ # @return [String] the RUN lines
138
178
  def almalinux_platform
139
179
  <<-CODE
140
180
  ENV container=docker
@@ -144,6 +184,9 @@ module Kitchen
144
184
  CODE
145
185
  end
146
186
 
187
+ # Dockerfile lines installing an SSH server and sudo on Rocky Linux.
188
+ #
189
+ # @return [String] the RUN lines
147
190
  def rockylinux_platform
148
191
  <<-CODE
149
192
  ENV container=docker
@@ -153,6 +196,9 @@ module Kitchen
153
196
  CODE
154
197
  end
155
198
 
199
+ # Dockerfile lines installing an SSH server and sudo on Photon OS.
200
+ #
201
+ # @return [String] the RUN lines
156
202
  def photonos_platform
157
203
  <<-CODE
158
204
  ENV container=docker
@@ -163,6 +209,14 @@ module Kitchen
163
209
  CODE
164
210
  end
165
211
 
212
+ # Dockerfile lines creating the login user and its SSH directory.
213
+ #
214
+ # The user gets passwordless sudo and +Defaults !requiretty+, because Test
215
+ # Kitchen runs commands non-interactively and sudo would otherwise refuse.
216
+ #
217
+ # @param username [String] the login user to create
218
+ # @param homedir [String] that user's home directory
219
+ # @return [String] the RUN lines
166
220
  def dockerfile_base_linux(username, homedir)
167
221
  <<-CODE
168
222
  RUN if ! getent passwd #{username}; then \
@@ -15,24 +15,38 @@ require "fileutils" unless defined?(FileUtils)
15
15
 
16
16
  module Kitchen
17
17
  module Docker
18
+ # Mixins shared by the driver, transport, and container classes.
18
19
  module Helpers
20
+ # Local temp-file handling.
19
21
  module FileHelper
22
+ # Writes a temp file, creating its parent directory if needed.
23
+ #
24
+ # Written with +File.write+, which opens, writes and closes in one call.
25
+ # The previous implementation assigned the open file back over the +file+
26
+ # parameter and closed it in an +ensure+, so when opening failed -- a
27
+ # read-only directory, a parent that is not a directory, a full disk --
28
+ # +file+ was still the path String and the ensure raised
29
+ # "undefined method 'close' for an instance of String", replacing the
30
+ # real error with a Ruby one. Its rescue did not help either: it caught
31
+ # +IOError+, while opening a file fails with +Errno+ classes, which are
32
+ # +SystemCallError+ and not +IOError+.
33
+ #
34
+ # @param file [String] path to write
35
+ # @param contents [String] what to write
36
+ # @return [void]
37
+ # @raise [RuntimeError] if the file cannot be written, naming the path
38
+ # and the underlying cause
20
39
  def create_temp_file(file, contents)
21
40
  debug("[Docker] Creating temp file #{file}")
22
41
  debug("[Docker] --- Start Temp File Contents ---")
23
42
  debug(contents)
24
43
  debug("[Docker] --- End Temp File Contents ---")
25
44
 
26
- begin
27
- path = ::File.dirname(file)
28
- ::FileUtils.mkdir_p(path) unless ::Dir.exist?(path)
29
- file = ::File.open(file, "w")
30
- file.write(contents)
31
- rescue IOError => e
32
- raise "Failed to write temp file. Error Details: #{e}"
33
- ensure
34
- file.close unless file.nil?
35
- end
45
+ path = ::File.dirname(file)
46
+ ::FileUtils.mkdir_p(path) unless ::Dir.exist?(path)
47
+ ::File.write(file, contents)
48
+ rescue SystemCallError, IOError => e
49
+ raise "Failed to write temp file #{file}. Error Details: #{e}"
36
50
  end
37
51
  end
38
52
  end
@@ -19,12 +19,22 @@ require_relative "container_helper"
19
19
 
20
20
  module Kitchen
21
21
  module Docker
22
+ # Mixins shared by the driver, transport, and container classes.
22
23
  module Helpers
24
+ # Building, inspecting, and removing Docker images.
23
25
  module ImageHelper
24
26
  include Configurable
25
27
  include Kitchen::Docker::Helpers::CliHelper
26
28
  include Kitchen::Docker::Helpers::ContainerHelper
27
29
 
30
+ # Pulls the built image's id out of `docker build` output.
31
+ #
32
+ # Scanned in reverse, and against several patterns, because the wording has
33
+ # changed across Docker and BuildKit versions.
34
+ #
35
+ # @param output [String] the build output
36
+ # @return [String] the image id
37
+ # @raise [Kitchen::ActionFailed] if no id could be found
28
38
  def parse_image_id(output)
29
39
  output.split("\n").reverse_each do |line|
30
40
  if line =~ /writing image (sha256:[[:xdigit:]]{64})(?: \d*\.\ds)? done/i
@@ -44,6 +54,10 @@ module Kitchen
44
54
  raise ActionFailed, "Could not parse Docker build output for image ID"
45
55
  end
46
56
 
57
+ # Removes the built image, unless a container is still using it.
58
+ #
59
+ # @param state [Hash] instance state naming the image
60
+ # @return [void]
47
61
  def remove_image(state)
48
62
  image_id = state[:image_id]
49
63
  if image_in_use?(state)
@@ -54,10 +68,40 @@ module Kitchen
54
68
  end
55
69
  end
56
70
 
71
+ # Whether any container was created from the image.
72
+ #
73
+ # Asked with a filter rather than by searching `docker ps -a` output for
74
+ # the id. That output abbreviates the IMAGE column to twelve characters,
75
+ # while state carries the full +sha256:+ digest, so the substring never
76
+ # matched and the answer was always false -- which defeated the guard
77
+ # entirely and let {#remove_image} run `docker rmi` against an image a
78
+ # container was still using.
79
+ #
80
+ # @param state [Hash] instance state naming the image
81
+ # @return [Boolean] whether any container references it
57
82
  def image_in_use?(state)
58
- docker_command("ps -a", suppress_output: !logger.debug?).include?(state[:image_id])
83
+ return false unless state[:image_id]
84
+
85
+ output = docker_command("ps -a -q --filter ancestor=#{state[:image_id]}",
86
+ suppress_output: !logger.debug?)
87
+
88
+ # Matched line by line rather than by emptiness, so a warning docker
89
+ # writes to stderr is not mistaken for a container id.
90
+ output.lines.map(&:strip).any? do |line|
91
+ line.match?(/\A[0-9a-f]{12}(?:[0-9a-f]{52})?\z/)
92
+ end
59
93
  end
60
94
 
95
+ # Builds the image from the given Dockerfile.
96
+ #
97
+ # The Dockerfile is written to a temp file and also passed on stdin, so the
98
+ # build works both with a build context and without one. The temp file is
99
+ # removed whether or not the build succeeded.
100
+ #
101
+ # @param state [Hash] instance state
102
+ # @param dockerfile [String] the Dockerfile contents
103
+ # @return [String] the new image's id
104
+ # @raise [Kitchen::ActionFailed] if the id cannot be parsed from the output
61
105
  def build_image(state, dockerfile)
62
106
  cmd = "build"
63
107
  cmd << " --no-cache" unless config[:use_cache]
@@ -82,6 +126,8 @@ module Kitchen
82
126
  parse_image_id(output)
83
127
  end
84
128
 
129
+ # @param state [Hash] instance state naming the image
130
+ # @return [Boolean] whether the image is present locally
85
131
  def image_exists?(state)
86
132
  state[:image_id] && !!docker_command("inspect --type=image #{state[:image_id]}") rescue false
87
133
  end
@@ -52,6 +52,7 @@ end
52
52
 
53
53
  module Kitchen
54
54
  module Docker
55
+ # Mixins shared by the driver, transport, and container classes.
55
56
  module Helpers
56
57
  # Marker module included by the Docker transport Connection class.
57
58
  # Actual verifier patches are applied directly to verifier classes above.
@@ -26,6 +26,7 @@ require_relative "../docker/helpers/cli_helper"
26
26
  require_relative "../docker/helpers/container_helper"
27
27
 
28
28
  module Kitchen
29
+ # Test Kitchen's driver plugins.
29
30
  module Driver
30
31
  # Docker driver for Kitchen.
31
32
  #
@@ -111,28 +112,50 @@ module Kitchen
111
112
  end
112
113
  end
113
114
 
115
+ # Checks that the Docker CLI is installed and runnable.
116
+ #
117
+ # @return [void]
118
+ # @raise [Kitchen::UserError] if the binary cannot be run
114
119
  def verify_dependencies
115
120
  run_command("#{config[:binary]} >> #{dev_null} 2>&1", quiet: true, use_sudo: config[:use_sudo])
116
121
  rescue
117
122
  raise UserError, "You must first install the Docker CLI tool https://www.docker.com/get-started"
118
123
  end
119
124
 
125
+ # Builds the image and starts the container.
126
+ #
127
+ # @param state [Hash] mutable instance state
128
+ # @return [void]
120
129
  def create(state)
121
130
  container.create(state)
122
131
 
123
132
  wait_for_transport(state)
124
133
  end
125
134
 
135
+ # Removes the container, and its image when +remove_images+ is set.
136
+ #
137
+ # @param state [Hash] instance state naming the container
138
+ # @return [void]
126
139
  def destroy(state)
127
140
  container.destroy(state)
128
141
  end
129
142
 
143
+ # Waits for the transport to accept a connection, unless disabled.
144
+ #
145
+ # @param state [Hash] instance state describing how to connect
146
+ # @return [void]
130
147
  def wait_for_transport(state)
131
148
  if config[:wait_for_transport]
132
149
  instance.transport.connection(state, &:wait_until_ready)
133
150
  end
134
151
  end
135
152
 
153
+ # The Docker image implied by the platform name.
154
+ #
155
+ # +ubuntu-22.04+ becomes +ubuntu:22.04+. CentOS is special-cased, since its
156
+ # images are tagged +centos7+ rather than +centos:7+.
157
+ #
158
+ # @return [String] an image reference
136
159
  def default_image
137
160
  platform, release = instance.platform.name.split("-")
138
161
  if platform == "centos" && release
@@ -141,12 +164,16 @@ module Kitchen
141
164
  release ? [platform, release].join(":") : platform
142
165
  end
143
166
 
167
+ # @return [String] the platform family, e.g. +ubuntu+ from +ubuntu-22.04+
144
168
  def default_platform
145
169
  instance.platform.name.split("-").first
146
170
  end
147
171
 
148
172
  protected
149
173
 
174
+ # The container implementation for this platform.
175
+ #
176
+ # @return [Kitchen::Docker::Container] a Windows or Linux container
150
177
  def container
151
178
  @container ||= if windows_os?
152
179
  Kitchen::Docker::Container::Windows.new(config)