Improve error message from image building
Translate IOException to DockerException for a more meaningful error message when the Docker daemon is not available. Fixes gh-20151
This commit is contained in:
parent
e73ee7b3fe
commit
6f095d6fec
|
@ -19,6 +19,8 @@ package org.springframework.boot.buildpack.platform.docker;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.io.StringWriter;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
|
||||||
import org.apache.http.HttpEntity;
|
import org.apache.http.HttpEntity;
|
||||||
|
@ -36,7 +38,6 @@ import org.apache.http.entity.AbstractHttpEntity;
|
||||||
import org.apache.http.impl.client.CloseableHttpClient;
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
import org.apache.http.impl.client.HttpClientBuilder;
|
import org.apache.http.impl.client.HttpClientBuilder;
|
||||||
import org.apache.http.impl.client.HttpClients;
|
import org.apache.http.impl.client.HttpClients;
|
||||||
import org.apache.http.util.EntityUtils;
|
|
||||||
|
|
||||||
import org.springframework.boot.buildpack.platform.io.Content;
|
import org.springframework.boot.buildpack.platform.io.Content;
|
||||||
import org.springframework.boot.buildpack.platform.io.IOConsumer;
|
import org.springframework.boot.buildpack.platform.io.IOConsumer;
|
||||||
|
@ -46,6 +47,7 @@ import org.springframework.boot.buildpack.platform.json.SharedObjectMapper;
|
||||||
* {@link Http} implementation backed by a {@link HttpClient}.
|
* {@link Http} implementation backed by a {@link HttpClient}.
|
||||||
*
|
*
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
|
* @author Mike Smithson
|
||||||
*/
|
*/
|
||||||
class HttpClientHttp implements Http {
|
class HttpClientHttp implements Http {
|
||||||
|
|
||||||
|
@ -66,10 +68,9 @@ class HttpClientHttp implements Http {
|
||||||
* Perform a HTTP GET operation.
|
* Perform a HTTP GET operation.
|
||||||
* @param uri the destination URI
|
* @param uri the destination URI
|
||||||
* @return the operation response
|
* @return the operation response
|
||||||
* @throws IOException on IO error
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Response get(URI uri) throws IOException {
|
public Response get(URI uri) {
|
||||||
return execute(new HttpGet(uri));
|
return execute(new HttpGet(uri));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,10 +78,9 @@ class HttpClientHttp implements Http {
|
||||||
* Perform a HTTP POST operation.
|
* Perform a HTTP POST operation.
|
||||||
* @param uri the destination URI
|
* @param uri the destination URI
|
||||||
* @return the operation response
|
* @return the operation response
|
||||||
* @throws IOException on IO error
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Response post(URI uri) throws IOException {
|
public Response post(URI uri) {
|
||||||
return execute(new HttpPost(uri));
|
return execute(new HttpPost(uri));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,11 +90,10 @@ class HttpClientHttp implements Http {
|
||||||
* @param contentType the content type to write
|
* @param contentType the content type to write
|
||||||
* @param writer a content writer
|
* @param writer a content writer
|
||||||
* @return the operation response
|
* @return the operation response
|
||||||
* @throws IOException on IO error
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response post(URI uri, String contentType, IOConsumer<OutputStream> writer) throws IOException {
|
public Response post(URI uri, String contentType, IOConsumer<OutputStream> writer) {
|
||||||
return execute(new HttpPost(uri), contentType, writer);
|
return execute(new HttpPost(uri), contentType, writer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,11 +103,10 @@ class HttpClientHttp implements Http {
|
||||||
* @param contentType the content type to write
|
* @param contentType the content type to write
|
||||||
* @param writer a content writer
|
* @param writer a content writer
|
||||||
* @return the operation response
|
* @return the operation response
|
||||||
* @throws IOException on IO error
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response put(URI uri, String contentType, IOConsumer<OutputStream> writer) throws IOException {
|
public Response put(URI uri, String contentType, IOConsumer<OutputStream> writer) {
|
||||||
return execute(new HttpPut(uri), contentType, writer);
|
return execute(new HttpPut(uri), contentType, writer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,39 +114,44 @@ class HttpClientHttp implements Http {
|
||||||
* Perform a HTTP DELETE operation.
|
* Perform a HTTP DELETE operation.
|
||||||
* @param uri the destination URI
|
* @param uri the destination URI
|
||||||
* @return the operation response
|
* @return the operation response
|
||||||
* @throws IOException on IO error
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response delete(URI uri) throws IOException {
|
public Response delete(URI uri) {
|
||||||
return execute(new HttpDelete(uri));
|
return execute(new HttpDelete(uri));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Response execute(HttpEntityEnclosingRequestBase request, String contentType,
|
private Response execute(HttpEntityEnclosingRequestBase request, String contentType,
|
||||||
IOConsumer<OutputStream> writer) throws IOException {
|
IOConsumer<OutputStream> writer) {
|
||||||
request.setHeader(HttpHeaders.CONTENT_TYPE, contentType);
|
request.setHeader(HttpHeaders.CONTENT_TYPE, contentType);
|
||||||
request.setEntity(new WritableHttpEntity(writer));
|
request.setEntity(new WritableHttpEntity(writer));
|
||||||
return execute(request);
|
return execute(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Response execute(HttpUriRequest request) throws IOException {
|
private Response execute(HttpUriRequest request) {
|
||||||
CloseableHttpResponse response = this.client.execute(request);
|
CloseableHttpResponse response;
|
||||||
StatusLine statusLine = response.getStatusLine();
|
try {
|
||||||
int statusCode = statusLine.getStatusCode();
|
response = this.client.execute(request);
|
||||||
HttpEntity entity = response.getEntity();
|
StatusLine statusLine = response.getStatusLine();
|
||||||
if (statusCode >= 200 && statusCode < 300) {
|
int statusCode = statusLine.getStatusCode();
|
||||||
return new HttpClientResponse(response);
|
HttpEntity entity = response.getEntity();
|
||||||
}
|
|
||||||
Errors errors = null;
|
if (statusCode >= 400 && statusCode < 500) {
|
||||||
if (statusCode >= 400 && statusCode < 500) {
|
Errors errors = SharedObjectMapper.get().readValue(entity.getContent(), Errors.class);
|
||||||
try {
|
throw new DockerException(request.getURI(), statusCode, statusLine.getReasonPhrase(), errors);
|
||||||
errors = SharedObjectMapper.get().readValue(entity.getContent(), Errors.class);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
if (statusCode == 500) {
|
||||||
|
throw new DockerException(request.getURI(), statusCode, statusLine.getReasonPhrase(), null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EntityUtils.consume(entity);
|
catch (IOException ioe) {
|
||||||
throw new DockerException(request.getURI(), statusCode, statusLine.getReasonPhrase(), errors);
|
StringWriter stringWriter = new StringWriter();
|
||||||
|
PrintWriter printWriter = new PrintWriter(stringWriter);
|
||||||
|
ioe.printStackTrace(printWriter);
|
||||||
|
throw new DockerException(request.getURI(), 500, stringWriter.toString(), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new HttpClientResponse(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -175,7 +178,7 @@ class HttpClientHttp implements Http {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InputStream getContent() throws IOException, UnsupportedOperationException {
|
public InputStream getContent() throws UnsupportedOperationException {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,6 @@ import org.apache.http.HttpEntity;
|
||||||
import org.apache.http.HttpEntityEnclosingRequest;
|
import org.apache.http.HttpEntityEnclosingRequest;
|
||||||
import org.apache.http.HttpHeaders;
|
import org.apache.http.HttpHeaders;
|
||||||
import org.apache.http.StatusLine;
|
import org.apache.http.StatusLine;
|
||||||
import org.apache.http.client.ClientProtocolException;
|
|
||||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
import org.apache.http.client.methods.HttpDelete;
|
import org.apache.http.client.methods.HttpDelete;
|
||||||
import org.apache.http.client.methods.HttpGet;
|
import org.apache.http.client.methods.HttpGet;
|
||||||
|
@ -54,6 +53,7 @@ import static org.mockito.Mockito.verify;
|
||||||
* Tests for {@link HttpClientHttp}.
|
* Tests for {@link HttpClientHttp}.
|
||||||
*
|
*
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
|
* @author Mike Smithson
|
||||||
*/
|
*/
|
||||||
class HttpClientHttpTests {
|
class HttpClientHttpTests {
|
||||||
|
|
||||||
|
@ -132,7 +132,7 @@ class HttpClientHttpTests {
|
||||||
assertThat(entity.isRepeatable()).isFalse();
|
assertThat(entity.isRepeatable()).isFalse();
|
||||||
assertThat(entity.getContentLength()).isEqualTo(-1);
|
assertThat(entity.getContentLength()).isEqualTo(-1);
|
||||||
assertThat(entity.isStreaming()).isTrue();
|
assertThat(entity.isStreaming()).isTrue();
|
||||||
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> entity.getContent());
|
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(entity::getContent);
|
||||||
assertThat(writeToString(entity)).isEqualTo("test");
|
assertThat(writeToString(entity)).isEqualTo("test");
|
||||||
assertThat(response.getContent()).isSameAs(this.content);
|
assertThat(response.getContent()).isSameAs(this.content);
|
||||||
}
|
}
|
||||||
|
@ -152,7 +152,7 @@ class HttpClientHttpTests {
|
||||||
assertThat(entity.isRepeatable()).isFalse();
|
assertThat(entity.isRepeatable()).isFalse();
|
||||||
assertThat(entity.getContentLength()).isEqualTo(-1);
|
assertThat(entity.getContentLength()).isEqualTo(-1);
|
||||||
assertThat(entity.isStreaming()).isTrue();
|
assertThat(entity.isStreaming()).isTrue();
|
||||||
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> entity.getContent());
|
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(entity::getContent);
|
||||||
assertThat(writeToString(entity)).isEqualTo("test");
|
assertThat(writeToString(entity)).isEqualTo("test");
|
||||||
assertThat(response.getContent()).isSameAs(this.content);
|
assertThat(response.getContent()).isSameAs(this.content);
|
||||||
}
|
}
|
||||||
|
@ -171,7 +171,7 @@ class HttpClientHttpTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void executeWhenResposeIsIn400RangeShouldThrowDockerException() throws ClientProtocolException, IOException {
|
void executeWhenResposeIsIn400RangeShouldThrowDockerException() throws IOException {
|
||||||
given(this.entity.getContent()).willReturn(getClass().getResourceAsStream("errors.json"));
|
given(this.entity.getContent()).willReturn(getClass().getResourceAsStream("errors.json"));
|
||||||
given(this.statusLine.getStatusCode()).willReturn(404);
|
given(this.statusLine.getStatusCode()).willReturn(404);
|
||||||
assertThatExceptionOfType(DockerException.class).isThrownBy(() -> this.http.get(this.uri))
|
assertThatExceptionOfType(DockerException.class).isThrownBy(() -> this.http.get(this.uri))
|
||||||
|
@ -179,12 +179,20 @@ class HttpClientHttpTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void executeWhenResposeIsIn500RangeShouldThrowDockerException() throws ClientProtocolException, IOException {
|
void executeWhenResposeIsIn500RangeShouldThrowDockerException() {
|
||||||
given(this.statusLine.getStatusCode()).willReturn(500);
|
given(this.statusLine.getStatusCode()).willReturn(500);
|
||||||
assertThatExceptionOfType(DockerException.class).isThrownBy(() -> this.http.get(this.uri))
|
assertThatExceptionOfType(DockerException.class).isThrownBy(() -> this.http.get(this.uri))
|
||||||
.satisfies((ex) -> assertThat(ex.getErrors()).isNull());
|
.satisfies((ex) -> assertThat(ex.getErrors()).isNull());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void executeWhenClientExecutesRequestThrowsIOExceptionRethrowsAsDockerException() throws IOException {
|
||||||
|
given(this.client.execute(any())).willThrow(IOException.class);
|
||||||
|
assertThatExceptionOfType(DockerException.class).isThrownBy(() -> this.http.get(this.uri))
|
||||||
|
.satisfies((ex) -> assertThat(ex.getErrors()).isNull()).satisfies(DockerException::getStatusCode)
|
||||||
|
.withMessageContaining("500").satisfies((ex) -> assertThat(ex.getReasonPhrase())).isNotNull();
|
||||||
|
}
|
||||||
|
|
||||||
private String writeToString(HttpEntity entity) throws IOException {
|
private String writeToString(HttpEntity entity) throws IOException {
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
entity.writeTo(out);
|
entity.writeTo(out);
|
||||||
|
|
Loading…
Reference in New Issue