Skip to content

Commit

Permalink
fix: Support for Podman REST API (when configured)
Browse files Browse the repository at this point in the history
- fix: Valid content type for REST docker-compatible API requests with body
- fix: Push works with Podman REST API
  • Loading branch information
manusa committed Nov 13, 2020
1 parent 308792b commit 06cdd90
Show file tree
Hide file tree
Showing 5 changed files with 271 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.io.File;
import java.io.IOException;
import java.net.URLConnection;
import java.nio.charset.Charset;
import java.util.Map;
import java.util.Map.Entry;
Expand Down Expand Up @@ -56,8 +57,7 @@ public int delete(String url, int... statusCodes) throws IOException {

public static class StatusCodeResponseHandler implements ResponseHandler<Integer> {
@Override
public Integer handleResponse(HttpResponse response)
throws IOException {
public Integer handleResponse(HttpResponse response) {
return response.getStatusLine().getStatusCode();
}

Expand Down Expand Up @@ -122,31 +122,36 @@ public int put(String url, Object body, int... statusCodes) throws IOException {

// =========================================================================================

private HttpUriRequest addDefaultHeaders(HttpUriRequest req) {
private HttpUriRequest addDefaultHeaders(HttpUriRequest req, Object body) {
req.addHeader(HttpHeaders.ACCEPT, "*/*");
req.addHeader(HttpHeaders.CONTENT_TYPE, "application/json");
if (body instanceof File) {
req.addHeader(HttpHeaders.CONTENT_TYPE, URLConnection.guessContentTypeFromName(((File)body).getName()));
}
if (body != null && !req.containsHeader(HttpHeaders.CONTENT_TYPE)) {
req.addHeader(HttpHeaders.CONTENT_TYPE, "application/json");
}
return req;
}


private HttpUriRequest newDelete(String url) {
return addDefaultHeaders(new HttpDelete(url));
return addDefaultHeaders(new HttpDelete(url), null);
}

private HttpUriRequest newGet(String url) {
return addDefaultHeaders(new HttpGet(url));
return addDefaultHeaders(new HttpGet(url), null);
}

private HttpUriRequest newPut(String url, Object body) {
HttpPut put = new HttpPut(url);
setEntityIfGiven(put, body);
return addDefaultHeaders(put);
return addDefaultHeaders(put, body);
}

private HttpUriRequest newPost(String url, Object body) {
HttpPost post = new HttpPost(url);
setEntityIfGiven(post, body);
return addDefaultHeaders(post);
return addDefaultHeaders(post, body);
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
import io.fabric8.maven.docker.access.UrlBuilder;
import io.fabric8.maven.docker.access.VolumeCreateConfig;
import io.fabric8.maven.docker.access.chunked.BuildJsonResponseHandler;
import io.fabric8.maven.docker.access.chunked.EntityStreamReaderUtil;
import io.fabric8.maven.docker.access.chunked.PullOrPushResponseJsonHandler;
import io.fabric8.maven.docker.access.hc.ApacheHttpClientDelegate.BodyAndStatusResponseHandler;
import io.fabric8.maven.docker.access.hc.ApacheHttpClientDelegate.HttpBodyAndStatus;
Expand Down Expand Up @@ -763,25 +762,6 @@ private static boolean isSSL(String url) {
return url != null && url.toLowerCase().startsWith("https");
}

// Preparation for performing requests
private static class HcChunkedResponseHandlerWrapper implements ResponseHandler<Object> {

private EntityStreamReaderUtil.JsonEntityResponseHandler handler;

HcChunkedResponseHandlerWrapper(EntityStreamReaderUtil.JsonEntityResponseHandler handler) {
this.handler = handler;
}

@Override
public Object handleResponse(HttpResponse response) throws IOException {
try (InputStream stream = response.getEntity().getContent()) {
// Parse text as json
EntityStreamReaderUtil.processJsonStream(handler, stream);
}
return null;
}
}

public String fetchApiVersionFromServer(String baseUrl, ApacheHttpClientDelegate delegate) throws IOException {
HttpGet get = new HttpGet(baseUrl + (baseUrl.endsWith("/") ? "" : "/") + "version");
get.addHeader(HttpHeaders.ACCEPT, "*/*");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package io.fabric8.maven.docker.access.hc;

import io.fabric8.maven.docker.access.chunked.EntityStreamReaderUtil;
import org.apache.http.HttpResponse;
import org.apache.http.client.ResponseHandler;

import java.io.IOException;
import java.io.InputStream;
import java.util.stream.Stream;

public class HcChunkedResponseHandlerWrapper implements ResponseHandler<Object> {
private final EntityStreamReaderUtil.JsonEntityResponseHandler handler;

HcChunkedResponseHandlerWrapper(EntityStreamReaderUtil.JsonEntityResponseHandler handler) {
this.handler = handler;
}

@Override
public Object handleResponse(HttpResponse response) throws IOException {
try (InputStream stream = response.getEntity().getContent()) {
// Parse text as json
if (isJson(response)) {
EntityStreamReaderUtil.processJsonStream(handler, stream);
}
}
return null;
}

private static boolean isJson(HttpResponse response) {
return Stream.of(response.getAllHeaders())
.filter(h -> h.getName().equalsIgnoreCase("Content-Type"))
.anyMatch(h -> h.getValue().toLowerCase().startsWith("application/json"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package io.fabric8.maven.docker.access.hc;

import io.fabric8.maven.docker.access.hc.util.ClientBuilder;
import mockit.Expectations;
import mockit.Mocked;
import mockit.Verifications;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.client.CloseableHttpClient;
import org.assertj.core.groups.Tuple;
import org.junit.Before;
import org.junit.Test;

import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.function.BiConsumer;

import static org.assertj.core.api.Assertions.assertThat;

@SuppressWarnings({"rawtypes", "unused"})
public class ApacheHttpClientDelegateTest {

@Mocked
private ClientBuilder clientBuilder;
@Mocked
private CloseableHttpClient httpClient;

private ApacheHttpClientDelegate apacheHttpClientDelegate;

@Before
public void setUp() throws Exception {
// @formatter:off
new Expectations() {{
clientBuilder.buildBasicClient();
result = httpClient;
}};
// @formatter:on
apacheHttpClientDelegate = new ApacheHttpClientDelegate(clientBuilder, false);
}

@Test
public void createBasicClient() {
final CloseableHttpClient result = apacheHttpClientDelegate.createBasicClient();
assertThat(result).isNotNull();
}

@Test
public void delete() throws IOException {
// Given
// @formatter:off
new Expectations() {{
httpClient.execute((HttpUriRequest) any, (ResponseHandler) any);
result = 1337;
}};
// @formatter:on
// When
final int result = apacheHttpClientDelegate.delete("http://example.com");
// Then
assertThat(result).isEqualTo(1337);
verifyHttpClientExecute((request, responseHandler) ->
assertThat(request.getAllHeaders())
.hasSize(1)
.extracting("name", "value")
.containsOnly(new Tuple("Accept", "*/*"))
);
}

@Test
public void get() throws IOException {
// Given
// @formatter:off
new Expectations() {{
httpClient.execute((HttpUriRequest) any, (ResponseHandler) any);
result = "Response";
}};
// @formatter:on
// When
final String response = apacheHttpClientDelegate.get("http://example.com");
// Then
assertThat(response).isEqualTo("Response");
verifyHttpClientExecute((request, responseHandler) -> {
assertThat(request.getAllHeaders())
.hasSize(1)
.extracting("name", "value")
.containsOnly(new Tuple("Accept", "*/*"));
assertThat(responseHandler)
.extracting("delegate")
.hasSize(1)
.hasOnlyElementsOfType(ApacheHttpClientDelegate.BodyResponseHandler.class);
});
}

@Test
public void postWithStringBody() throws IOException {
// Given
// @formatter:off
new Expectations() {{
httpClient.execute((HttpUriRequest) any, (ResponseHandler) any);
result = "Response";
}};
// @formatter:on
// When
final String response = apacheHttpClientDelegate.post(
"http://example.com", "{body}", Collections.singletonMap("EXTRA", "HEADER"), null);
// Then
assertThat(response).isEqualTo("Response");
verifyHttpClientExecute((request, responseHandler) ->
assertThat(request.getAllHeaders())
.hasSize(3)
.extracting("name", "value")
.containsOnly(
new Tuple("Accept", "*/*"),
new Tuple("Content-Type", "application/json"),
new Tuple("EXTRA", "HEADER"))
);
}

@Test
public void postWithFileBody() throws IOException {
// Given
// @formatter:off
new Expectations() {{
httpClient.execute((HttpUriRequest) any, (ResponseHandler) any);
result = "Response";
}};
// @formatter:on
// When
final String response = apacheHttpClientDelegate.post(
"http://example.com", new File("fake-file.tar"), null);
// Then
assertThat(response).isEqualTo("Response");
verifyHttpClientExecute((request, responseHandler) ->
assertThat(request.getAllHeaders())
.hasSize(2)
.extracting("name", "value")
.containsOnly(
new Tuple("Accept", "*/*"),
new Tuple("Content-Type", "application/x-tar"))
);
}

private <H extends ResponseHandler> void verifyHttpClientExecute(BiConsumer<HttpUriRequest, H> consumer) throws IOException {
// @formatter:off
new Verifications() {{
HttpUriRequest request;
H responseHandler;
httpClient.execute(request = withCapture(), responseHandler = withCapture());
consumer.accept(request, responseHandler);
}};
// @formatter:on
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package io.fabric8.maven.docker.access.hc;

import io.fabric8.maven.docker.access.chunked.EntityStreamReaderUtil;
import mockit.Expectations;
import mockit.Mocked;
import mockit.Verifications;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.message.BasicHeader;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;

@SuppressWarnings("unused")
public class HcChunkedResponseHandlerWrapperTest {

@Mocked
private EntityStreamReaderUtil.JsonEntityResponseHandler handler;
@Mocked
private HttpResponse response;
@Mocked
private EntityStreamReaderUtil entityStreamReaderUtil;

private Header[] headers;
private HcChunkedResponseHandlerWrapper hcChunkedResponseHandlerWrapper;

@Before
public void setUp() {
hcChunkedResponseHandlerWrapper = new HcChunkedResponseHandlerWrapper(handler);
}

@Test
public void handleResponseWithJsonResponse() throws IOException {
givenResponseHeaders(new BasicHeader("ConTenT-Type", "application/json; charset=UTF-8"));
hcChunkedResponseHandlerWrapper.handleResponse(response);
verifyProcessJsonStream(1);
}

@Test
public void handleResponseWithTextPlainResponse() throws IOException {
givenResponseHeaders(new BasicHeader("Content-Type", "text/plain"));
hcChunkedResponseHandlerWrapper.handleResponse(response);
verifyProcessJsonStream(0);
}

@Test
public void handleResponseWithNoContentType() throws IOException {
givenResponseHeaders();
hcChunkedResponseHandlerWrapper.handleResponse(response);
verifyProcessJsonStream(0);
}

private void givenResponseHeaders(Header... headers) {
// @formatter:off
new Expectations() {{
response.getAllHeaders(); result = headers;
}};
// @formatter:on
}

@SuppressWarnings("AccessStaticViaInstance")
private void verifyProcessJsonStream(int timesCalled) throws IOException {
// @formatter:off
new Verifications() {{
entityStreamReaderUtil.processJsonStream(handler, response.getEntity().getContent()); times = timesCalled;
}};
// @formatter:on
}
}

0 comments on commit 06cdd90

Please sign in to comment.