Skip to content

Commit 2fa28fb

Browse files
committed
Improve error reporting when image loading fails
Closes gh-31243
1 parent 03a3425 commit 2fa28fb

File tree

5 files changed

+86
-12
lines changed

5 files changed

+86
-12
lines changed

spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/DockerApi.java

+20-8
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ public void load(ImageArchive archive, UpdateListener<LoadImageUpdateEvent> list
233233
Assert.notNull(archive, "Archive must not be null");
234234
Assert.notNull(listener, "Listener must not be null");
235235
URI loadUri = buildUrl("/images/load");
236-
StreamCaptureUpdateListener streamListener = new StreamCaptureUpdateListener();
236+
LoadImageUpdateListener streamListener = new LoadImageUpdateListener(archive);
237237
listener.onStart();
238238
try {
239239
try (Response response = http().post(loadUri, "application/x-tar", archive::writeTo)) {
@@ -242,9 +242,7 @@ public void load(ImageArchive archive, UpdateListener<LoadImageUpdateEvent> list
242242
listener.onUpdate(event);
243243
});
244244
}
245-
Assert.state(StringUtils.hasText(streamListener.getCapturedStream()),
246-
"Invalid response received when loading image "
247-
+ ((archive.getTag() != null) ? "\"" + archive.getTag() + "\"" : ""));
245+
streamListener.assertValidResponseReceived();
248246
}
249247
finally {
250248
listener.onFinish();
@@ -482,19 +480,33 @@ public void onUpdate(ProgressUpdateEvent event) {
482480
}
483481

484482
/**
485-
* {@link UpdateListener} used to ensure an image load response stream.
483+
* {@link UpdateListener} for an image load response stream.
486484
*/
487-
private static final class StreamCaptureUpdateListener implements UpdateListener<LoadImageUpdateEvent> {
485+
private static final class LoadImageUpdateListener implements UpdateListener<LoadImageUpdateEvent> {
486+
487+
private final ImageArchive archive;
488488

489489
private String stream;
490490

491+
private LoadImageUpdateListener(ImageArchive archive) {
492+
this.archive = archive;
493+
}
494+
491495
@Override
492496
public void onUpdate(LoadImageUpdateEvent event) {
497+
Assert.state(event.getErrorDetail() == null,
498+
() -> "Error response received when loading image" + image() + ": " + event.getErrorDetail());
493499
this.stream = event.getStream();
494500
}
495501

496-
String getCapturedStream() {
497-
return this.stream;
502+
private String image() {
503+
ImageReference tag = this.archive.getTag();
504+
return (tag != null) ? " \"" + tag + "\"" : "";
505+
}
506+
507+
private void assertValidResponseReceived() {
508+
Assert.state(StringUtils.hasText(this.stream),
509+
() -> "Invalid response received when loading image" + image());
498510
}
499511

500512
}

spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/LoadImageUpdateEvent.java

+45-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2020 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
1717
package org.springframework.boot.buildpack.platform.docker;
1818

1919
import com.fasterxml.jackson.annotation.JsonCreator;
20+
import com.fasterxml.jackson.annotation.JsonProperty;
2021

2122
/**
2223
* A {@link ProgressUpdateEvent} fired as an image is loaded.
@@ -28,10 +29,14 @@ public class LoadImageUpdateEvent extends ProgressUpdateEvent {
2829

2930
private final String stream;
3031

32+
private final ErrorDetail errorDetail;
33+
3134
@JsonCreator
32-
public LoadImageUpdateEvent(String stream, String status, ProgressDetail progressDetail, String progress) {
35+
public LoadImageUpdateEvent(String stream, String status, ProgressDetail progressDetail, String progress,
36+
ErrorDetail errorDetail) {
3337
super(status, progressDetail, progress);
3438
this.stream = stream;
39+
this.errorDetail = errorDetail;
3540
}
3641

3742
/**
@@ -42,4 +47,42 @@ public String getStream() {
4247
return this.stream;
4348
}
4449

50+
/**
51+
* Return the error detail or {@code null} if no error occurred.
52+
* @return the error detail, if any
53+
* @since 3.2.12
54+
*/
55+
public ErrorDetail getErrorDetail() {
56+
return this.errorDetail;
57+
}
58+
59+
/**
60+
* Details of an error embedded in a response stream.
61+
*
62+
* @since 3.2.12
63+
*/
64+
public static class ErrorDetail {
65+
66+
private final String message;
67+
68+
@JsonCreator
69+
public ErrorDetail(@JsonProperty("message") String message) {
70+
this.message = message;
71+
}
72+
73+
/**
74+
* Returns the message field from the error detail.
75+
* @return the message
76+
*/
77+
public String getMessage() {
78+
return this.message;
79+
}
80+
81+
@Override
82+
public String toString() {
83+
return this.message;
84+
}
85+
86+
}
87+
4588
}

spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/DockerApiTests.java

+10
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,16 @@ void loadWithEmptyResponseThrowsException() throws Exception {
252252
.withMessageContaining("Invalid response received");
253253
}
254254

255+
@Test // gh-31243
256+
void loadWithErrorResponseThrowsException() throws Exception {
257+
Image image = Image.of(getClass().getResourceAsStream("type/image.json"));
258+
ImageArchive archive = ImageArchive.from(image);
259+
URI loadUri = new URI(IMAGES_URL + "/load");
260+
given(http().post(eq(loadUri), eq("application/x-tar"), any())).willReturn(responseOf("load-error.json"));
261+
assertThatIllegalStateException().isThrownBy(() -> this.api.load(archive, this.loadListener))
262+
.withMessageContaining("Error response received");
263+
}
264+
255265
@Test
256266
void loadLoadsImage() throws Exception {
257267
Image image = Image.of(getClass().getResourceAsStream("type/image.json"));

spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/LoadImageUpdateEventTests.java

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2020 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@
1818

1919
import org.junit.jupiter.api.Test;
2020

21+
import org.springframework.boot.buildpack.platform.docker.LoadImageUpdateEvent.ErrorDetail;
2122
import org.springframework.boot.buildpack.platform.docker.ProgressUpdateEvent.ProgressDetail;
2223

2324
import static org.assertj.core.api.Assertions.assertThat;
@@ -36,9 +37,16 @@ void getStreamReturnsStream() {
3637
assertThat(event.getStream()).isEqualTo("stream");
3738
}
3839

40+
@Test
41+
void getErrorDetailReturnsErrorDetail() {
42+
LoadImageUpdateEvent event = createEvent();
43+
assertThat(event.getErrorDetail()).extracting(ErrorDetail::getMessage).isEqualTo("max depth exceeded");
44+
}
45+
3946
@Override
4047
protected LoadImageUpdateEvent createEvent(String status, ProgressDetail progressDetail, String progress) {
41-
return new LoadImageUpdateEvent("stream", status, progressDetail, progress);
48+
return new LoadImageUpdateEvent("stream", status, progressDetail, progress,
49+
new ErrorDetail("max depth exceeded"));
4250
}
4351

4452
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"errorDetail":{"message":"max depth exceeded"}}

0 commit comments

Comments
 (0)