Module: Kitchen::Docker::Helpers::ImageHelper
- Includes:
- Configurable, CliHelper, ContainerHelper
- Included in:
- Container
- Defined in:
- lib/kitchen/docker/helpers/image_helper.rb
Overview
Building, inspecting, and removing Docker images.
Constant Summary collapse
- QUIET_BUILD_IMAGE_ID =
An id on a line of its own, which is all
docker build -qprints. /\A(sha256:[[:xdigit:]]{64})\z/
Constants included from ContainerHelper
ContainerHelper::COPIED_MARKER
Instance Method Summary collapse
-
#build_image(state, dockerfile) ⇒ String
Builds the image from the given Dockerfile.
-
#image_exists?(state) ⇒ Boolean
Whether the image named in state is present locally.
-
#image_in_use?(state) ⇒ Boolean
Whether any container was created from the image.
-
#parse_image_id(output) ⇒ String
Pulls the built image's id out of
docker buildoutput. -
#remove_image(state) ⇒ void
Removes the built image, unless a container is still using it.
Methods included from 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 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
#build_image(state, dockerfile) ⇒ String
Builds the image from the given Dockerfile.
The Dockerfile is written to a temp file and also passed on stdin, so the build works both with a build context and without one. The temp file is removed whether or not the build succeeded.
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
# File 'lib/kitchen/docker/helpers/image_helper.rb', line 115 def build_image(state, dockerfile) cmd = "build" cmd << " --no-cache" unless config[:use_cache] cmd << " --platform=#{config[:docker_platform]}" if config[:docker_platform] = (config[:build_options]) cmd << " #{}" unless .empty? dockerfile_contents = dockerfile file = Tempfile.new("Dockerfile-kitchen", Pathname.pwd + config[:build_tempdir]) cmd << " -f #{Shellwords.escape(dockerfile_path(file))}" if config[:build_context] build_context = config[:build_context] ? "." : "-" output = begin file.write(dockerfile) file.close docker_command("#{cmd} #{build_context}", input: dockerfile_contents, environment: { BUILDKIT_PROGRESS: "plain" }) ensure file.close unless file.closed? file.unlink end parse_image_id(output) end |
#image_exists?(state) ⇒ Boolean
Whether the image named in state is present locally.
The inspect is silenced, as every other predicate that shells out to
docker is. Left speaking, kitchen destroy on an instance with
remove_images set printed the image's entire docker inspect JSON
-- config, every layer digest, metadata -- into the middle of the
destroy output, between the container being removed and the image
being removed. Nothing read it: only whether the command succeeded is
used.
It is still printed under -l debug, where the rest of the driver's
docker traffic is.
154 155 156 157 158 159 160 161 |
# File 'lib/kitchen/docker/helpers/image_helper.rb', line 154 def image_exists?(state) return false unless state[:image_id] !!docker_command("inspect --type=image #{state[:image_id]}", suppress_output: !logger.debug?) rescue false end |
#image_in_use?(state) ⇒ Boolean
Whether any container was created from the image.
Asked with a filter rather than by searching docker ps -a output for
the id. That output abbreviates the IMAGE column to twelve characters,
while state carries the full sha256: digest, so the substring never
matched and the answer was always false -- which defeated the guard
entirely and let #remove_image run docker rmi against an image a
container was still using.
92 93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/kitchen/docker/helpers/image_helper.rb', line 92 def image_in_use?(state) return false unless state[:image_id] output = docker_command("ps -a -q --filter ancestor=#{state[:image_id]}", suppress_output: !logger.debug?) # Matched line by line rather than by emptiness, so a warning docker # writes to stderr is not mistaken for a container id. output.lines.map(&:strip).any? do |line| line.match?(/\A[0-9a-f]{12}(?:[0-9a-f]{52})?\z/) end end |
#parse_image_id(output) ⇒ String
Pulls the built image's id out of docker build output.
Scanned in reverse, and against several patterns, because the wording has changed across Docker and BuildKit versions.
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
# File 'lib/kitchen/docker/helpers/image_helper.rb', line 41 def parse_image_id(output) output.split("\n").reverse_each do |line| line = line.strip # `docker build -q` prints the id and nothing else -- none of the # wording below appears -- so a build with `build_options: -q` had # no line any of these matched and failed with "Could not parse # Docker build output for image ID" (#225). return Regexp.last_match(1) if line.match(QUIET_BUILD_IMAGE_ID) if line =~ /writing image (sha256:[[:xdigit:]]{64})(?: \d*\.\ds)? done/i img_id = line[/writing image (sha256:[[:xdigit:]]{64})(?: \d*\.\ds)? done/i, 1] return img_id end if line =~ /image id|build successful|successfully built/i img_id = line.split(/\s+/).last return img_id end # Docker ~v4.31 support if line =~ /naming to moby-dangling@(sha256:[[:xdigit:]]{64})(?: \d*\.\ds)? done/i img_id = line[/naming to moby-dangling@(sha256:[[:xdigit:]]{64})(?: \d*\.\ds)? done/i, 1] return img_id end end raise ActionFailed, "Could not parse Docker build output for image ID" end |
#remove_image(state) ⇒ void
This method returns an undefined value.
Removes the built image, unless a container is still using it.
71 72 73 74 75 76 77 78 79 |
# File 'lib/kitchen/docker/helpers/image_helper.rb', line 71 def remove_image(state) image_id = state[:image_id] if image_in_use?(state) info("[Docker] Image ID #{image_id} is in use. Skipping removal") else info("[Docker] Removing image with Image ID #{image_id}.") docker_command("rmi #{image_id}") end end |