Skip to content

Commit

Permalink
Improve error reporting when image loading fails
Browse files Browse the repository at this point in the history
Closes gh-31243
  • Loading branch information
wilkinsona committed Nov 8, 2024
1 parent 03a3425 commit 2fa28fb
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ public void load(ImageArchive archive, UpdateListener<LoadImageUpdateEvent> list
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();
LoadImageUpdateListener streamListener = new LoadImageUpdateListener(archive);
listener.onStart();
try {
try (Response response = http().post(loadUri, "application/x-tar", archive::writeTo)) {
Expand All @@ -242,9 +242,7 @@ public void load(ImageArchive archive, UpdateListener<LoadImageUpdateEvent> list
listener.onUpdate(event);
});
}
Assert.state(StringUtils.hasText(streamListener.getCapturedStream()),
"Invalid response received when loading image "
+ ((archive.getTag() != null) ? "\"" + archive.getTag() + "\"" : ""));
streamListener.assertValidResponseReceived();
}
finally {
listener.onFinish();
Expand Down Expand Up @@ -482,19 +480,33 @@ public void onUpdate(ProgressUpdateEvent event) {
}

/**
* {@link UpdateListener} used to ensure an image load response stream.
* {@link UpdateListener} for an image load response stream.
*/
private static final class StreamCaptureUpdateListener implements UpdateListener<LoadImageUpdateEvent> {
private static final class LoadImageUpdateListener implements UpdateListener<LoadImageUpdateEvent> {

private final ImageArchive archive;

private String stream;

private LoadImageUpdateListener(ImageArchive archive) {
this.archive = archive;
}

@Override
public void onUpdate(LoadImageUpdateEvent event) {
Assert.state(event.getErrorDetail() == null,
() -> "Error response received when loading image" + image() + ": " + event.getErrorDetail());
this.stream = event.getStream();
}

String getCapturedStream() {
return this.stream;
private String image() {
ImageReference tag = this.archive.getTag();
return (tag != null) ? " \"" + tag + "\"" : "";
}

private void assertValidResponseReceived() {
Assert.state(StringUtils.hasText(this.stream),
() -> "Invalid response received when loading image" + image());
}

}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -17,6 +17,7 @@
package org.springframework.boot.buildpack.platform.docker;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

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

private final String stream;

private final ErrorDetail errorDetail;

@JsonCreator
public LoadImageUpdateEvent(String stream, String status, ProgressDetail progressDetail, String progress) {
public LoadImageUpdateEvent(String stream, String status, ProgressDetail progressDetail, String progress,
ErrorDetail errorDetail) {
super(status, progressDetail, progress);
this.stream = stream;
this.errorDetail = errorDetail;
}

/**
Expand All @@ -42,4 +47,42 @@ public String getStream() {
return this.stream;
}

/**
* Return the error detail or {@code null} if no error occurred.
* @return the error detail, if any
* @since 3.2.12
*/
public ErrorDetail getErrorDetail() {
return this.errorDetail;
}

/**
* Details of an error embedded in a response stream.
*
* @since 3.2.12
*/
public static class ErrorDetail {

private final String message;

@JsonCreator
public ErrorDetail(@JsonProperty("message") String message) {
this.message = message;
}

/**
* Returns the message field from the error detail.
* @return the message
*/
public String getMessage() {
return this.message;
}

@Override
public String toString() {
return this.message;
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,16 @@ void loadWithEmptyResponseThrowsException() throws Exception {
.withMessageContaining("Invalid response received");
}

@Test // gh-31243
void loadWithErrorResponseThrowsException() 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(responseOf("load-error.json"));
assertThatIllegalStateException().isThrownBy(() -> this.api.load(archive, this.loadListener))
.withMessageContaining("Error response received");
}

@Test
void loadLoadsImage() throws Exception {
Image image = Image.of(getClass().getResourceAsStream("type/image.json"));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -18,6 +18,7 @@

import org.junit.jupiter.api.Test;

import org.springframework.boot.buildpack.platform.docker.LoadImageUpdateEvent.ErrorDetail;
import org.springframework.boot.buildpack.platform.docker.ProgressUpdateEvent.ProgressDetail;

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

@Test
void getErrorDetailReturnsErrorDetail() {
LoadImageUpdateEvent event = createEvent();
assertThat(event.getErrorDetail()).extracting(ErrorDetail::getMessage).isEqualTo("max depth exceeded");
}

@Override
protected LoadImageUpdateEvent createEvent(String status, ProgressDetail progressDetail, String progress) {
return new LoadImageUpdateEvent("stream", status, progressDetail, progress);
return new LoadImageUpdateEvent("stream", status, progressDetail, progress,
new ErrorDetail("max depth exceeded"));
}

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

0 comments on commit 2fa28fb

Please sign in to comment.