diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/DockerApi.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/DockerApi.java index 053aeeb6e8d..7e9aa49776c 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/DockerApi.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/DockerApi.java @@ -175,11 +175,18 @@ public class DockerApi { Assert.notNull(archive, "Archive must not be null"); Assert.notNull(listener, "Listener must not be null"); URI loadUri = buildUrl("/images/load"); + StreamCaptureUpdateListener streamListener = new StreamCaptureUpdateListener(); listener.onStart(); try { try (Response response = http().post(loadUri, "application/x-tar", archive::writeTo)) { - jsonStream().get(response.getContent(), LoadImageUpdateEvent.class, listener::onUpdate); + jsonStream().get(response.getContent(), LoadImageUpdateEvent.class, (event) -> { + streamListener.onUpdate(event); + listener.onUpdate(event); + }); } + Assert.state(StringUtils.hasText(streamListener.getCapturedStream()), + "Invalid response received when loading image " + + ((archive.getTag() != null) ? "\"" + archive.getTag() + "\"" : "")); } finally { listener.onFinish(); @@ -363,4 +370,22 @@ public class DockerApi { } + /** + * {@link UpdateListener} used to ensure an image load response stream. + */ + private static class StreamCaptureUpdateListener implements UpdateListener { + + private String stream; + + @Override + public void onUpdate(LoadImageUpdateEvent event) { + this.stream = event.getStream(); + } + + String getCapturedStream() { + return this.stream; + } + + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/DockerApiTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/DockerApiTests.java index dc72c38622d..66a3bd09f23 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/DockerApiTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/DockerApiTests.java @@ -51,6 +51,7 @@ import org.springframework.boot.buildpack.platform.io.TarArchive; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; @@ -172,6 +173,16 @@ class DockerApiTests { .withMessage("Listener must not be null"); } + @Test // gh-23130 + void loadWithEmptyResponseThrowsException() throws Exception { + Image image = Image.of(getClass().getResourceAsStream("type/image.json")); + ImageArchive archive = ImageArchive.from(image); + URI loadUri = new URI(IMAGES_URL + "/load"); + given(http().post(eq(loadUri), eq("application/x-tar"), any())).willReturn(emptyResponse()); + assertThatIllegalStateException().isThrownBy(() -> this.api.load(archive, this.loadListener)) + .withMessageContaining("Invalid response received"); + } + @Test void loadLoadsImage() throws Exception { Image image = Image.of(getClass().getResourceAsStream("type/image.json"));