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 8986c06b9e..e630018fab 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 @@ -178,11 +178,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(); @@ -352,4 +359,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 8abddb3996..c61cd6a4d3 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 @@ -50,6 +50,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"));