Class: Kitchen::Driver::Docker

Inherits:
Base
  • Object
show all
Includes:
Kitchen::Docker::Helpers::CliHelper, Kitchen::Docker::Helpers::ContainerHelper, ShellOut
Defined in:
lib/kitchen/driver/docker.rb

Overview

Docker driver for Kitchen.

Author:

Constant Summary

Constants included from Kitchen::Docker::Helpers::ContainerHelper

Kitchen::Docker::Helpers::ContainerHelper::COPIED_MARKER

Instance Method Summary collapse

Methods included from Kitchen::Docker::Helpers::ContainerHelper

#container_env_variables, #container_exec, #container_exists?, #container_ip_address, #container_running?, #copy_file_to_container, #create_dir_on_container, #dockerfile_path, #dockerfile_proxy_config, #dockerfile_template, #file_on_container?, #ip_address?, #parse_container_id, #proxy_env_vars, #remote_socket?, #remove_container, #replace_env_variables, #run_container, #socket_uri, #verify_file_copied

Methods included from Kitchen::Docker::Helpers::CliHelper

#build_copy_command, #build_env_variable_args, #build_exec_command, #build_powershell_command, #build_run_command, #config_to_options, #dev_null, #docker_command, #docker_shell_opts, #docker_sudo_opts, #run_command, #shell_escape

Instance Method Details

#containerKitchen::Docker::Container (protected)

The container implementation for this platform.

Returns:



300
301
302
303
304
305
306
307
# File 'lib/kitchen/driver/docker.rb', line 300

def container
  @container ||= if windows_os?
                   Kitchen::Docker::Container::Windows.new(config)
                 else
                   Kitchen::Docker::Container::Linux.new(config)
                 end
  @container
end

#create(state) ⇒ void

This method returns an undefined value.

Builds the image and starts the container.

Parameters:

  • state (Hash)

    mutable instance state



138
139
140
141
142
# File 'lib/kitchen/driver/docker.rb', line 138

def create(state)
  container.create(state)

  wait_for_transport(state)
end

#default_imageString

The Docker image implied by the platform name.

ubuntu-22.04 becomes ubuntu:22.04. CentOS is special-cased, since its images are tagged centos7 rather than centos:7.

Returns:

  • (String)

    an image reference



244
245
246
247
248
249
250
# File 'lib/kitchen/driver/docker.rb', line 244

def default_image
  platform, release = instance.platform.name.split("-")
  if platform == "centos" && release
    release = "centos" + release.split(".").first
  end
  release ? [platform, release].join(":") : platform
end

#default_platformString

Returns the platform family, e.g. ubuntu from ubuntu-22.04.

Returns:

  • (String)

    the platform family, e.g. ubuntu from ubuntu-22.04



253
254
255
# File 'lib/kitchen/driver/docker.rb', line 253

def default_platform
  instance.platform.name.split("-").first
end

#destroy(state) ⇒ void

This method returns an undefined value.

Removes the container, and its image when remove_images is set.

Parameters:

  • state (Hash)

    instance state naming the container



148
149
150
# File 'lib/kitchen/driver/docker.rb', line 148

def destroy(state)
  container.destroy(state)
end

#doctor(state) ⇒ Boolean

Checks the configuration and the daemon it points at.

Run by kitchen doctor. A true return is how Test Kitchen decides to exit non-zero, so every check runs and the results are OR-ed together rather than returning at the first problem -- somebody running doctor wants the whole list, not the first item on it.

Parameters:

  • state (Hash)

    instance state

Returns:

  • (Boolean)

    whether a problem was found



194
195
196
197
198
199
200
# File 'lib/kitchen/driver/docker.rb', line 194

def doctor(state)
  [
    doctor_daemon,
    doctor_files,
    doctor_container(state),
  ].any?
end

#doctor_container(state) ⇒ Boolean (protected)

Returns whether state names a container that is gone.

Parameters:

  • state (Hash)

    instance state naming the container

Returns:

  • (Boolean)

    whether state names a container that is gone



288
289
290
291
292
293
294
295
# File 'lib/kitchen/driver/docker.rb', line 288

def doctor_container(state)
  return false unless state[:container_id]
  return false if container_exists?(state)

  error("The state file names container #{state[:container_id]}, which the daemon does " \
        "not have. Run `kitchen destroy` to clear it.")
  true
end

#doctor_daemonBoolean (protected)

Returns whether the daemon could not be reached.

Returns:

  • (Boolean)

    whether the daemon could not be reached



260
261
262
263
264
265
266
267
# File 'lib/kitchen/driver/docker.rb', line 260

def doctor_daemon
  version = docker_command("version --format '{{.Server.Version}}'", suppress_output: true).strip
  info("Docker daemon at #{config[:socket]} is reachable, running #{version}.")
  false
rescue => e
  error("Cannot reach the Docker daemon at #{config[:socket]}. #{e}")
  true
end

#doctor_filesBoolean (protected)

Checks paths the configuration names.

A missing TLS file or Dockerfile is worth catching here because docker reports it far from the cause -- a missing client certificate surfaces as a connection error rather than as a missing file.

Returns:

  • (Boolean)

    whether any named path is missing



276
277
278
279
280
281
282
283
284
# File 'lib/kitchen/driver/docker.rb', line 276

def doctor_files
  %i{tls_cacert tls_cert tls_key dockerfile}.map do |key|
    path = config[key]
    next false if path.nil? || ::File.exist?(::File.expand_path(path))

    error("#{key} is set to #{path}, which does not exist.")
    true
  end.any?
end

#package(state) ⇒ void

This method returns an undefined value.

Commits the container to a Docker image.

kitchen package asks a driver to turn a converged instance into something reusable. For Docker that is an image: docker commit on the running container, which is the artifact every other docker tool already takes. Run docker save against the result for a tarball.

Parameters:

  • state (Hash)

    instance state naming the container

Raises:

  • (Kitchen::ActionFailed)

    if the instance has not been created



162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/kitchen/driver/docker.rb', line 162

def package(state)
  unless state[:container_id]
    raise ActionFailed, "Cannot package #{instance.name}: it has not been created."
  end

  # Asked here rather than left to `docker commit`, which reports a
  # container that is gone as a bare "Error response from daemon: No such
  # container: <64 hex characters>" with nothing about the instance or
  # what to do next.
  unless container_exists?(state)
    raise ActionFailed, "Cannot package #{instance.name}: the state file names container " \
                        "#{state[:container_id]}, which the daemon does not have. " \
                        "Run `kitchen destroy` to clear it."
  end

  name = config[:package_name]
  info("[Docker] Committing container #{state[:container_id]} to #{name}")
  output = docker_command("commit #{shell_escape(state[:container_id])} #{shell_escape(name)}",
    suppress_output: !logger.debug?)
  image_id = output.lines.map(&:strip).find { |line| line.match?(/\Asha256:[[:xdigit:]]{64}\z/) }
  info("[Docker] Packaged #{instance.name} as #{name}#{" (#{image_id})" if image_id}")
end

#status(state) ⇒ Hash

Reports whether the container backing this instance is up.

Read by kitchen list --live, which showed "unknown" for every instance: Base cannot know, and this driver never said. Docker can answer directly, and these are the same two questions create and destroy already ask.

Parameters:

  • state (Hash)

    instance state naming the container

Returns:

  • (Hash)

    normalized status data



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/kitchen/driver/docker.rb', line 211

def status(state)
  common = { source: "driver", checked_at: Time.now.utc.iso8601, resource_id: state[:container_id] }

  if !state[:container_id]
    common.merge(live: false, state: "not created",
      message: "No container is recorded in the state file")
  elsif !container_exists?(state)
    common.merge(live: false, state: "gone",
      message: "The state file names a container the daemon does not have")
  elsif container_running?(state)
    common.merge(live: true, state: "running")
  else
    common.merge(live: false, state: "stopped",
      message: "The container exists but is not running")
  end
end

#verify_dependenciesvoid

This method returns an undefined value.

Checks that the Docker CLI is installed and runnable.

Raises:

  • (Kitchen::UserError)

    if the binary cannot be run



128
129
130
131
132
# File 'lib/kitchen/driver/docker.rb', line 128

def verify_dependencies
  run_command("#{config[:binary]} >> #{dev_null} 2>&1", quiet: true, use_sudo: config[:use_sudo])
rescue
  raise UserError, "You must first install the Docker CLI tool https://www.docker.com/get-started"
end

#wait_for_transport(state) ⇒ void

This method returns an undefined value.

Waits for the transport to accept a connection, unless disabled.

Parameters:

  • state (Hash)

    instance state describing how to connect



232
233
234
235
236
# File 'lib/kitchen/driver/docker.rb', line 232

def wait_for_transport(state)
  if config[:wait_for_transport]
    instance.transport.connection(state, &:wait_until_ready)
  end
end