diff --git a/lib/kitchen/docker/helpers/image_helper.rb b/lib/kitchen/docker/helpers/image_helper.rb index 913825e6..1d0c488c 100644 --- a/lib/kitchen/docker/helpers/image_helper.rb +++ b/lib/kitchen/docker/helpers/image_helper.rb @@ -27,6 +27,9 @@ module ImageHelper include Kitchen::Docker::Helpers::CliHelper include Kitchen::Docker::Helpers::ContainerHelper + # An id on a line of its own, which is all `docker build -q` prints. + QUIET_BUILD_IMAGE_ID = /\A(sha256:[[:xdigit:]]{64})\z/ + # Pulls the built image's id out of `docker build` output. # # Scanned in reverse, and against several patterns, because the wording has @@ -37,6 +40,13 @@ module ImageHelper # @raise [Kitchen::ActionFailed] if no id could be found 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 diff --git a/spec/image_helper_spec.rb b/spec/image_helper_spec.rb index 312a4f21..cf936638 100644 --- a/spec/image_helper_spec.rb +++ b/spec/image_helper_spec.rb @@ -16,6 +16,30 @@ describe Kitchen::Docker::Helpers::ImageHelper do describe "#parse_image_id" do + # From #225. `docker build -q` prints the id on a line of its own and + # nothing else -- no "writing image", no "naming to", no "successfully + # built" -- so every pattern the parser had missed it, and a build with + # `build_options: -q` failed with "Could not parse Docker build output for + # image ID" rather than producing an instance. + context "with a quiet build" do + it "reads the id docker printed on its own" do + expect(helper.parse_image_id(DockerOutput::BUILD_QUIET)) + .to eq DockerOutput::BUILD_QUIET_IMAGE_ID + end + + it "reads it when docker also wrote a warning to stderr" do + expect(helper.parse_image_id("WARNING: something happened\n#{DockerOutput::BUILD_QUIET}")) + .to eq DockerOutput::BUILD_QUIET_IMAGE_ID + end + + it "does not mistake a digest that is part of a longer line for the id" do + # Ordinary build output is full of "... sha256:... done" lines. Only a + # line that is nothing but a digest is the quiet form. + expect { helper.parse_image_id("#6 exporting manifest sha256:#{"a" * 64} done\n") } + .to raise_error(Kitchen::ActionFailed) + end + end + # Docker has changed how it reports the built image's id several times, and # each change has broken this parser. Every format the driver claims to # support gets a case here, against output copied from a real build. diff --git a/spec/support/docker_output.rb b/spec/support/docker_output.rb index 479886c3..0ddedb79 100644 --- a/spec/support/docker_output.rb +++ b/spec/support/docker_output.rb @@ -81,6 +81,15 @@ module DockerOutput BUILD_LEGACY_IMAGE_ID = "1a2b3c4d5e6f".freeze + # `docker build -q` on Docker 29.7.2. Quiet mode prints the id and nothing + # else -- no step lines, no exporting lines, none of the wording the other + # fixtures here are built around. Reachable through the driver as + # `build_options: -q`. + BUILD_QUIET = "sha256:ab86ce908a36ffb7de411a72550e416a4d8c268570a0f7313c284c79344d6c0f\n".freeze + + BUILD_QUIET_IMAGE_ID = + "sha256:ab86ce908a36ffb7de411a72550e416a4d8c268570a0f7313c284c79344d6c0f".freeze + # `docker run -d`, the ordinary case: the id and nothing else. RUN_CONTAINER_ID = "b89e1e8b07664a1ee0bd09decb833146eaf9cc810a152950e3576a56745944db".freeze RUN_CLEAN = "#{RUN_CONTAINER_ID}\n".freeze