diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 000000000..a96e5522b
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,2 @@
+.git/
+target/
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 000000000..eea3db562
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,18 @@
+FROM java:7
+MAINTAINER https://github.com/docker-java/docker-java
+
+ENV HOME /root
+ENV M2_HOME /opt/apache-maven-3.0.5
+
+RUN wget http://www.eu.apache.org/dist/maven/maven-3/3.0.5/binaries/apache-maven-3.0.5-bin.tar.gz -O /tmp/apache-maven-3.0.5-bin.tar.gz \
+ && cd /tmp \
+ && tar xzf apache-maven-3.0.5-bin.tar.gz \
+ && mkdir -p /opt \
+ && mv apache-maven-3.0.5 /opt \
+ && rm apache-maven-3.0.5-bin.tar.gz
+
+WORKDIR /project
+ADD . /project/
+
+#ENTRYPOINT $M2_HOME/bin/mvn
+CMD $M2_HOME/bin/mvn verify
diff --git a/pom.xml b/pom.xml
index 0b85dc426..1e6a477e9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -39,6 +39,13 @@
+
+
+ jcenter
+ http://jcenter.bintray.com
+
+
+
UTF-8
UTF-8
@@ -64,6 +71,7 @@
0.3
18.0
1.51
+ 2014-11-16T14-41-27
1.0.1
@@ -93,12 +101,23 @@
+
+ org.glassfish.jersey.connectors
+ jersey-apache-connector
+ ${jersey.version}
+
org.glassfish.jersey.core
jersey-client
${jersey.version}
+
+ de.gesellix
+ unix-socket-factory
+ ${unix-socket-factory.version}
+
+
org.apache.commons
commons-compress
diff --git a/src/main/java/com/github/dockerjava/api/command/EventCallback.java b/src/main/java/com/github/dockerjava/api/command/EventCallback.java
index 45ac34dc8..18b8669ea 100644
--- a/src/main/java/com/github/dockerjava/api/command/EventCallback.java
+++ b/src/main/java/com/github/dockerjava/api/command/EventCallback.java
@@ -9,4 +9,5 @@ public interface EventCallback {
public void onEvent(Event event);
public void onException(Throwable throwable);
public void onCompletion(int numEvents);
+ public boolean isReceiving();
}
diff --git a/src/main/java/com/github/dockerjava/api/model/AuthConfig.java b/src/main/java/com/github/dockerjava/api/model/AuthConfig.java
index 87307bb5d..42c49bce0 100644
--- a/src/main/java/com/github/dockerjava/api/model/AuthConfig.java
+++ b/src/main/java/com/github/dockerjava/api/model/AuthConfig.java
@@ -8,7 +8,7 @@ public class AuthConfig {
/**
* For backwards compatibility. Make sure you update the properties if you change this.
*
- * @see /docker.io.properties
+ * @see "/docker.io.properties"
*/
public static final String DEFAULT_SERVER_ADDRESS = "https://index.docker.io/v1/";
diff --git a/src/main/java/com/github/dockerjava/core/DockerClientConfig.java b/src/main/java/com/github/dockerjava/core/DockerClientConfig.java
index c90c62cb1..21e5778fd 100644
--- a/src/main/java/com/github/dockerjava/core/DockerClientConfig.java
+++ b/src/main/java/com/github/dockerjava/core/DockerClientConfig.java
@@ -33,6 +33,9 @@ public class DockerClientConfig implements Serializable {
private static final String DOCKER_IO_ENABLE_LOGGING_FILTER_PROPERTY = "docker.io.enableLoggingFilter";
private static final String DOCKER_IO_DOCKER_CERT_PATH_PROPERTY = "docker.io.dockerCertPath";
private static final String DOCKER_IO_DOCKER_CFG_PATH_PROPERTY = "docker.io.dockerCfgPath";
+ // connection pooling properties
+ private static final String DOCKER_IO_MAX_PER_ROUTE_PROPERTY = "docker.io.perRouteConnections";
+ private static final String DOCKER_IO_MAX_TOTAL_PROPERTY = "docker.io.totalConnections";
/**
* A map from the environment name to the interval name.
*/
@@ -49,13 +52,18 @@ public class DockerClientConfig implements Serializable {
.put("DOCKER_CFG_PATH", DOCKER_IO_DOCKER_CFG_PATH_PROPERTY)
.build();
private static final String DOCKER_IO_PROPERTIES_PROPERTY = "docker.io.properties";
- private final URI uri;
+ private URI uri;
private final String version, username, password, email, serverAddress, dockerCfgPath;
private final Integer readTimeout;
private final boolean loggingFilterEnabled;
private final SSLConfig sslConfig;
+
+ private final int maxTotalConnections;
+ private final int maxPerRouteConnections;
- DockerClientConfig(URI uri, String version, String username, String password, String email, String serverAddress, String dockerCfgPath, Integer readTimeout, boolean loggingFilterEnabled, SSLConfig sslConfig) {
+ DockerClientConfig(URI uri, String version, String username, String password, String email, String serverAddress,
+ String dockerCfgPath, Integer readTimeout, boolean loggingFilterEnabled, SSLConfig sslConfig,
+ int maxTotalConns, int maxPerRouteConns) {
this.uri = uri;
this.version = version;
this.username = username;
@@ -66,6 +74,8 @@ public class DockerClientConfig implements Serializable {
this.readTimeout = readTimeout;
this.loggingFilterEnabled = loggingFilterEnabled;
this.sslConfig = sslConfig;
+ this.maxTotalConnections = maxTotalConns;
+ this.maxPerRouteConnections = maxPerRouteConns;
}
private static Properties loadIncludedDockerProperties(Properties systemProperties) {
@@ -194,6 +204,10 @@ public URI getUri() {
return uri;
}
+ public void setUri(URI uri) {
+ this.uri = uri;
+ }
+
public String getVersion() {
return version;
}
@@ -329,7 +343,7 @@ public String toString() {
public static class DockerClientConfigBuilder {
private URI uri;
private String version, username, password, email, serverAddress, dockerCfgPath;
- private Integer readTimeout;
+ private Integer readTimeout, maxTotalConnections, maxPerRouteConnections;
private boolean loggingFilterEnabled;
private SSLConfig sslConfig;
@@ -349,7 +363,10 @@ public DockerClientConfigBuilder withProperties(Properties p) {
.withReadTimeout(Integer.valueOf(p.getProperty(DOCKER_IO_READ_TIMEOUT_PROPERTY, "0")))
.withLoggingFilter(Boolean.valueOf(p.getProperty(DOCKER_IO_ENABLE_LOGGING_FILTER_PROPERTY, "true")))
.withDockerCertPath(p.getProperty(DOCKER_IO_DOCKER_CERT_PATH_PROPERTY))
- .withDockerCfgPath(p.getProperty(DOCKER_IO_DOCKER_CFG_PATH_PROPERTY));
+ .withDockerCfgPath(p.getProperty(DOCKER_IO_DOCKER_CFG_PATH_PROPERTY))
+ .withMaxPerRouteConnections(Integer.valueOf(p.getProperty(DOCKER_IO_MAX_PER_ROUTE_PROPERTY, "2")))
+ .withMaxTotalConnections(Integer.valueOf(p.getProperty(DOCKER_IO_MAX_TOTAL_PROPERTY, "20")))
+ ;
}
public final DockerClientConfigBuilder withUri(String uri) {
@@ -387,6 +404,16 @@ public final DockerClientConfigBuilder withReadTimeout(Integer readTimeout) {
this.readTimeout = readTimeout;
return this;
}
+
+ public final DockerClientConfigBuilder withMaxTotalConnections(Integer maxTotalConnections) {
+ this.maxTotalConnections = maxTotalConnections;
+ return this;
+ }
+
+ public final DockerClientConfigBuilder withMaxPerRouteConnections(Integer maxPerRouteConnections) {
+ this.maxPerRouteConnections = maxPerRouteConnections;
+ return this;
+ }
public final DockerClientConfigBuilder withLoggingFilter(boolean loggingFilterEnabled) {
this.loggingFilterEnabled = loggingFilterEnabled;
@@ -420,8 +447,18 @@ public DockerClientConfig build() {
dockerCfgPath,
readTimeout,
loggingFilterEnabled,
- sslConfig
+ sslConfig,
+ maxTotalConnections,
+ maxPerRouteConnections
);
}
}
+
+ public int getMaxTotalConnections() {
+ return maxTotalConnections;
+ }
+
+ public int getMaxPerRoutConnections() {
+ return maxPerRouteConnections;
+ }
}
diff --git a/src/main/java/com/github/dockerjava/core/DockerClientImpl.java b/src/main/java/com/github/dockerjava/core/DockerClientImpl.java
index cee364e00..6b829abda 100644
--- a/src/main/java/com/github/dockerjava/core/DockerClientImpl.java
+++ b/src/main/java/com/github/dockerjava/core/DockerClientImpl.java
@@ -8,18 +8,15 @@
import java.io.InputStream;
import com.github.dockerjava.api.DockerClient;
-import com.github.dockerjava.api.DockerClientException;
import com.github.dockerjava.api.command.*;
import com.github.dockerjava.api.model.AuthConfig;
-import com.github.dockerjava.core.NameParser.HostnameReposName;
-import com.github.dockerjava.core.NameParser.ReposTag;
import com.github.dockerjava.core.command.*;
import com.google.common.base.Preconditions;
/**
* @author Konstantin Pelykh (kpelykh@gmail.com)
*
- * @see https://github.com/docker/docker/blob/master/api/client/commands.go
+ * @see "https://github.com/docker/docker/blob/master/api/client/commands.go"
*/
public class DockerClientImpl implements Closeable, DockerClient {
diff --git a/src/main/java/com/github/dockerjava/core/LocalDirectorySSLConfig.java b/src/main/java/com/github/dockerjava/core/LocalDirectorySSLConfig.java
index ef57a0de3..6415d0c18 100644
--- a/src/main/java/com/github/dockerjava/core/LocalDirectorySSLConfig.java
+++ b/src/main/java/com/github/dockerjava/core/LocalDirectorySSLConfig.java
@@ -1,19 +1,16 @@
package com.github.dockerjava.core;
+import com.github.dockerjava.api.DockerClientException;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
-
-import com.github.dockerjava.api.DockerClientException;
-
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.glassfish.jersey.SslConfigurator;
+import javax.net.ssl.SSLContext;
import java.io.Serializable;
import java.security.KeyStore;
import java.security.Security;
-import javax.net.ssl.SSLContext;
-
/**
* SSL Config from local files.
*/
@@ -41,9 +38,6 @@ public SSLContext getSSLContext() {
Security.addProvider(new BouncyCastleProvider());
- KeyStore keyStore = CertificateUtils.createKeyStore(dockerCertPath);
- KeyStore trustStore = CertificateUtils.createTrustStore(dockerCertPath);
-
// properties acrobatics not needed for java > 1.6
String httpProtocols = System.getProperty("https.protocols");
System.setProperty("https.protocols", "TLSv1");
@@ -52,9 +46,9 @@ public SSLContext getSSLContext() {
System.setProperty("https.protocols", httpProtocols);
}
- sslConfig.keyStore(keyStore);
+ sslConfig.keyStore(CertificateUtils.createKeyStore(dockerCertPath));
sslConfig.keyStorePassword("docker");
- sslConfig.trustStore(trustStore);
+ sslConfig.trustStore(CertificateUtils.createTrustStore(dockerCertPath));
return sslConfig.createSSLContext();
diff --git a/src/main/java/com/github/dockerjava/core/SSLConfig.java b/src/main/java/com/github/dockerjava/core/SSLConfig.java
index 21024c4f6..ab6394890 100644
--- a/src/main/java/com/github/dockerjava/core/SSLConfig.java
+++ b/src/main/java/com/github/dockerjava/core/SSLConfig.java
@@ -1,9 +1,6 @@
package com.github.dockerjava.core;
-import java.security.KeyManagementException;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.UnrecoverableKeyException;
+import java.security.*;
import javax.net.ssl.SSLContext;
diff --git a/src/main/java/com/github/dockerjava/jaxrs/ApacheUnixSocket.java b/src/main/java/com/github/dockerjava/jaxrs/ApacheUnixSocket.java
new file mode 100644
index 000000000..a4adb1bb7
--- /dev/null
+++ b/src/main/java/com/github/dockerjava/jaxrs/ApacheUnixSocket.java
@@ -0,0 +1,327 @@
+package com.github.dockerjava.jaxrs;
+/*
+ * Copyright (c) 2014 Spotify AB.
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+
+
+import com.google.common.collect.Queues;
+
+import org.newsclub.net.unix.AFUNIXSocket;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.nio.channels.SocketChannel;
+import java.util.Queue;
+
+/**
+ * Provides a socket that wraps an org.newsclub.net.unix.AFUNIXSocket and delays setting options
+ * until the socket is connected. This is necessary because the Apache HTTP client attempts to
+ * set options prior to connecting the socket, which doesn't work for Unix sockets since options
+ * are being set on the underlying file descriptor. Until the socket is connected, the file
+ * descriptor doesn't exist.
+ *
+ * This class also noop's any calls to setReuseAddress, which is called by the Apache client but
+ * isn't supported by AFUnixSocket.
+ */
+public class ApacheUnixSocket extends Socket {
+
+ private final AFUNIXSocket inner;
+
+ private final Queue optionsToSet = Queues.newArrayDeque();
+
+ public ApacheUnixSocket() throws IOException {
+ this.inner = AFUNIXSocket.newInstance();
+ }
+
+ @Override
+ public void connect(final SocketAddress endpoint) throws IOException {
+ inner.connect(endpoint);
+ setAllSocketOptions();
+ }
+
+ @Override
+ public void connect(final SocketAddress endpoint, final int timeout) throws IOException {
+ inner.connect(endpoint, timeout);
+ setAllSocketOptions();
+ }
+
+ @Override
+ public void bind(final SocketAddress bindpoint) throws IOException {
+ inner.bind(bindpoint);
+ setAllSocketOptions();
+ }
+
+ @Override
+ public InetAddress getInetAddress() {
+ return inner.getInetAddress();
+ }
+
+ @Override
+ public InetAddress getLocalAddress() {
+ return inner.getLocalAddress();
+ }
+
+ @Override
+ public int getPort() {
+ return inner.getPort();
+ }
+
+ @Override
+ public int getLocalPort() {
+ return inner.getLocalPort();
+ }
+
+ @Override
+ public SocketAddress getRemoteSocketAddress() {
+ return inner.getRemoteSocketAddress();
+ }
+
+ @Override
+ public SocketAddress getLocalSocketAddress() {
+ return inner.getLocalSocketAddress();
+ }
+
+ @Override
+ public SocketChannel getChannel() {
+ return inner.getChannel();
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ return inner.getInputStream();
+ }
+
+ @Override
+ public OutputStream getOutputStream() throws IOException {
+ return inner.getOutputStream();
+ }
+
+ private void setSocketOption(final SocketOptionSetter s) throws SocketException {
+ if (inner.isConnected()) {
+ s.run();
+ } else {
+ if (!optionsToSet.offer(s)) {
+ throw new SocketException("Failed to queue option");
+ }
+ }
+ }
+
+ private void setAllSocketOptions() throws SocketException {
+ for (SocketOptionSetter s : optionsToSet) {
+ s.run();
+ }
+ }
+
+ @Override
+ public void setTcpNoDelay(final boolean on) throws SocketException {
+ setSocketOption(new SocketOptionSetter() {
+ @Override
+ public void run() throws SocketException {
+ inner.setTcpNoDelay(on);
+ }
+ });
+ }
+
+ @Override
+ public boolean getTcpNoDelay() throws SocketException {
+ return inner.getTcpNoDelay();
+ }
+
+ @Override
+ public void setSoLinger(final boolean on, final int linger) throws SocketException {
+ setSocketOption(new SocketOptionSetter() {
+ @Override
+ public void run() throws SocketException {
+ inner.setSoLinger(on, linger);
+ }
+ });
+ }
+
+ @Override
+ public int getSoLinger() throws SocketException {
+ return inner.getSoLinger();
+ }
+
+ @Override
+ public void sendUrgentData(final int data) throws IOException {
+ inner.sendUrgentData(data);
+ }
+
+ @Override
+ public void setOOBInline(final boolean on) throws SocketException {
+ setSocketOption(new SocketOptionSetter() {
+ @Override
+ public void run() throws SocketException {
+ inner.setOOBInline(on);
+ }
+ });
+ }
+
+ @Override
+ public boolean getOOBInline() throws SocketException {
+ return inner.getOOBInline();
+ }
+
+ @Override
+ public synchronized void setSoTimeout(final int timeout) throws SocketException {
+ setSocketOption(new SocketOptionSetter() {
+ @Override
+ public void run() throws SocketException {
+ inner.setSoTimeout(timeout);
+ }
+ });
+ }
+
+ @Override
+ public synchronized int getSoTimeout() throws SocketException {
+ return inner.getSoTimeout();
+ }
+
+ @Override
+ public synchronized void setSendBufferSize(final int size) throws SocketException {
+ setSocketOption(new SocketOptionSetter() {
+ @Override
+ public void run() throws SocketException {
+ inner.setSendBufferSize(size);
+ }
+ });
+ }
+
+ @Override
+ public synchronized int getSendBufferSize() throws SocketException {
+ return inner.getSendBufferSize();
+ }
+
+ @Override
+ public synchronized void setReceiveBufferSize(final int size) throws SocketException {
+ setSocketOption(new SocketOptionSetter() {
+ @Override
+ public void run() throws SocketException {
+ inner.setReceiveBufferSize(size);
+ }
+ });
+ }
+
+ @Override
+ public synchronized int getReceiveBufferSize() throws SocketException {
+ return inner.getReceiveBufferSize();
+ }
+
+ @Override
+ public void setKeepAlive(final boolean on) throws SocketException {
+ setSocketOption(new SocketOptionSetter() {
+ @Override
+ public void run() throws SocketException {
+ inner.setKeepAlive(on);
+ }
+ });
+ }
+
+ @Override
+ public boolean getKeepAlive() throws SocketException {
+ return inner.getKeepAlive();
+ }
+
+ @Override
+ public void setTrafficClass(final int tc) throws SocketException {
+ setSocketOption(new SocketOptionSetter() {
+ @Override
+ public void run() throws SocketException {
+ inner.setTrafficClass(tc);
+ }
+ });
+ }
+
+ @Override
+ public int getTrafficClass() throws SocketException {
+ return inner.getTrafficClass();
+ }
+
+ @Override
+ public void setReuseAddress(final boolean on) throws SocketException {
+ // not supported: Apache client tries to set it, but we want to just ignore it
+ }
+
+ @Override
+ public boolean getReuseAddress() throws SocketException {
+ return inner.getReuseAddress();
+ }
+
+ @Override
+ public synchronized void close() throws IOException {
+ inner.close();
+ }
+
+ @Override
+ public void shutdownInput() throws IOException {
+ inner.shutdownInput();
+ }
+
+ @Override
+ public void shutdownOutput() throws IOException {
+ inner.shutdownOutput();
+ }
+
+ @Override
+ public String toString() {
+ return inner.toString();
+ }
+
+ @Override
+ public boolean isConnected() {
+ return inner.isConnected();
+ }
+
+ @Override
+ public boolean isBound() {
+ return inner.isBound();
+ }
+
+ @Override
+ public boolean isClosed() {
+ return inner.isClosed();
+ }
+
+ @Override
+ public boolean isInputShutdown() {
+ return inner.isInputShutdown();
+ }
+
+ @Override
+ public boolean isOutputShutdown() {
+ return inner.isOutputShutdown();
+ }
+
+ @Override
+ public void setPerformancePreferences(final int connectionTime, final int latency,
+ final int bandwidth) {
+ inner.setPerformancePreferences(connectionTime, latency, bandwidth);
+ }
+
+ interface SocketOptionSetter {
+ void run() throws SocketException;
+ }
+}
diff --git a/src/main/java/com/github/dockerjava/jaxrs/ContainerDiffCmdExec.java b/src/main/java/com/github/dockerjava/jaxrs/ContainerDiffCmdExec.java
index 0a657ed90..2b7059b33 100644
--- a/src/main/java/com/github/dockerjava/jaxrs/ContainerDiffCmdExec.java
+++ b/src/main/java/com/github/dockerjava/jaxrs/ContainerDiffCmdExec.java
@@ -26,7 +26,8 @@ protected List execute(ContainerDiffCmd command) {
WebTarget webResource = getBaseResource().path("/containers/{id}/changes").resolveTemplate("id", command.getContainerId());
LOGGER.trace("GET: {}", webResource);
- return webResource.request().accept(MediaType.APPLICATION_JSON).get(new GenericType>() {
+ return webResource.request().accept(MediaType.APPLICATION_JSON)
+ .get(new GenericType>() {
});
}
diff --git a/src/main/java/com/github/dockerjava/jaxrs/DockerCmdExecFactoryImpl.java b/src/main/java/com/github/dockerjava/jaxrs/DockerCmdExecFactoryImpl.java
index 525e4a586..c91b0d565 100644
--- a/src/main/java/com/github/dockerjava/jaxrs/DockerCmdExecFactoryImpl.java
+++ b/src/main/java/com/github/dockerjava/jaxrs/DockerCmdExecFactoryImpl.java
@@ -8,7 +8,14 @@
import com.github.dockerjava.jaxrs.util.ResponseStatusExceptionFilter;
import com.github.dockerjava.jaxrs.util.SelectiveLoggingFilter;
import com.google.common.base.Preconditions;
+import org.apache.http.config.RegistryBuilder;
+import org.apache.http.conn.socket.ConnectionSocketFactory;
+import org.apache.http.conn.socket.PlainConnectionSocketFactory;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.glassfish.jersey.CommonProperties;
+import org.glassfish.jersey.apache.connector.ApacheClientProperties;
+import org.glassfish.jersey.apache.connector.ApacheConnectorProvider;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.client.ClientProperties;
@@ -17,6 +24,7 @@
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.WebTarget;
import java.io.IOException;
+import java.net.URI;
import java.util.logging.Logger;
public class DockerCmdExecFactoryImpl implements DockerCmdExecFactory {
@@ -30,6 +38,7 @@ public void init(DockerClientConfig dockerClientConfig) {
Preconditions.checkNotNull(dockerClientConfig, "config was not specified");
ClientConfig clientConfig = new ClientConfig();
+ clientConfig.connectorProvider(new ApacheConnectorProvider());
clientConfig.property(CommonProperties.FEATURE_AUTO_DISCOVERY_DISABLE, true);
clientConfig.register(ResponseStatusExceptionFilter.class);
@@ -45,20 +54,31 @@ public void init(DockerClientConfig dockerClientConfig) {
clientConfig.property(ClientProperties.READ_TIMEOUT, readTimeout);
}
- ClientBuilder clientBuilder = ClientBuilder.newBuilder().withConfig(clientConfig);
+ URI originalUri = dockerClientConfig.getUri();
+ SSLContext sslContext;
try {
- SSLContext ssl = dockerClientConfig.getSslConfig().getSSLContext();
-
- if (ssl != null)
- clientBuilder.sslContext(ssl);
+ sslContext = dockerClientConfig.getSslConfig().getSSLContext();
} catch(Exception ex) {
throw new DockerClientException("Error in SSL Configuration", ex);
}
+ PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(getSchemeRegistry(originalUri, sslContext));
+ connManager.setMaxTotal(dockerClientConfig.getMaxTotalConnections());
+ connManager.setDefaultMaxPerRoute(dockerClientConfig.getMaxPerRoutConnections());
+ clientConfig.property(ApacheClientProperties.CONNECTION_MANAGER, connManager);
+
+ ClientBuilder clientBuilder = ClientBuilder.newBuilder().withConfig(clientConfig);
+
+ if (sslContext != null) {
+ clientBuilder.sslContext(sslContext);
+ }
client = clientBuilder.build();
+ if (originalUri.getScheme().equals("unix")) {
+ dockerClientConfig.setUri(UnixConnectionSocketFactory.sanitizeUri(originalUri));
+ }
WebTarget webResource = client.target(dockerClientConfig.getUri());
if (dockerClientConfig.getVersion() == null || dockerClientConfig.getVersion().isEmpty()) {
@@ -66,9 +86,18 @@ public void init(DockerClientConfig dockerClientConfig) {
} else {
baseResource = webResource.path("v" + dockerClientConfig.getVersion());
}
-
}
+ private org.apache.http.config.Registry getSchemeRegistry(final URI originalUri, SSLContext sslContext) {
+ RegistryBuilder registryBuilder = RegistryBuilder.create();
+ registryBuilder.register("http", PlainConnectionSocketFactory.getSocketFactory());
+ if (sslContext != null) {
+ registryBuilder.register("https", new SSLConnectionSocketFactory(sslContext));
+ }
+ registryBuilder.register("unix", new UnixConnectionSocketFactory(originalUri));
+ return registryBuilder.build();
+ }
+
protected WebTarget getBaseResource() {
Preconditions.checkNotNull(baseResource, "Factory not initialized. You probably forgot to call init()!");
return baseResource;
diff --git a/src/main/java/com/github/dockerjava/jaxrs/EventsCmdExec.java b/src/main/java/com/github/dockerjava/jaxrs/EventsCmdExec.java
index 545d7db98..ffd757e93 100644
--- a/src/main/java/com/github/dockerjava/jaxrs/EventsCmdExec.java
+++ b/src/main/java/com/github/dockerjava/jaxrs/EventsCmdExec.java
@@ -67,7 +67,7 @@ public Void call() throws Exception {
response = webTarget.request().get(Response.class);
InputStream inputStream = response.readEntity(InputStream.class);
JsonParser jp = JSON_FACTORY.createParser(inputStream);
- while (jp.nextToken() != JsonToken.END_OBJECT && !jp.isClosed()) {
+ while (jp.nextToken() != JsonToken.END_OBJECT && !jp.isClosed() && eventCallback.isReceiving()) {
eventCallback.onEvent(OBJECT_MAPPER.readValue(jp, Event.class));
numEvents++;
}
diff --git a/src/main/java/com/github/dockerjava/jaxrs/RemoveContainerCmdExec.java b/src/main/java/com/github/dockerjava/jaxrs/RemoveContainerCmdExec.java
index 7f45bdda2..7a479ce2f 100644
--- a/src/main/java/com/github/dockerjava/jaxrs/RemoveContainerCmdExec.java
+++ b/src/main/java/com/github/dockerjava/jaxrs/RemoveContainerCmdExec.java
@@ -23,8 +23,8 @@ protected Void execute(RemoveContainerCmd command) {
.queryParam("force", command.hasForceEnabled() ? "1" : "0");
LOGGER.trace("DELETE: {}", webResource);
- String response = webResource.request().accept(MediaType.APPLICATION_JSON).delete(String.class);
- LOGGER.trace("Response: {}", response);
+ /*String response = */webResource.request().accept(MediaType.APPLICATION_JSON).delete().close();
+// LOGGER.trace("Response: {}", response);
return null;
}
diff --git a/src/main/java/com/github/dockerjava/jaxrs/RemoveImageCmdExec.java b/src/main/java/com/github/dockerjava/jaxrs/RemoveImageCmdExec.java
index 54fb327b4..6bfeecd71 100644
--- a/src/main/java/com/github/dockerjava/jaxrs/RemoveImageCmdExec.java
+++ b/src/main/java/com/github/dockerjava/jaxrs/RemoveImageCmdExec.java
@@ -23,7 +23,7 @@ protected Void execute(RemoveImageCmd command) {
.queryParam("noprune", command.hasNoPruneEnabled() ? "1" : "0");
LOGGER.trace("DELETE: {}", webResource);
- webResource.request().delete(Response.class);
+ webResource.request().delete().close();
return null;
}
diff --git a/src/main/java/com/github/dockerjava/jaxrs/UnixConnectionSocketFactory.java b/src/main/java/com/github/dockerjava/jaxrs/UnixConnectionSocketFactory.java
new file mode 100644
index 000000000..4b6cfbba3
--- /dev/null
+++ b/src/main/java/com/github/dockerjava/jaxrs/UnixConnectionSocketFactory.java
@@ -0,0 +1,85 @@
+package com.github.dockerjava.jaxrs;
+/*
+ * Copyright (c) 2014 Spotify AB.
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+
+
+import org.apache.http.HttpHost;
+import org.apache.http.annotation.Immutable;
+import org.apache.http.conn.ConnectTimeoutException;
+import org.apache.http.conn.socket.ConnectionSocketFactory;
+import org.apache.http.protocol.HttpContext;
+import org.newsclub.net.unix.AFUNIXSocketAddress;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketTimeoutException;
+import java.net.URI;
+
+/**
+ * Provides a ConnectionSocketFactory for connecting Apache HTTP clients to Unix sockets.
+ */
+@Immutable
+public class UnixConnectionSocketFactory implements ConnectionSocketFactory {
+
+ private File socketFile;
+
+ public UnixConnectionSocketFactory(final URI socketUri) {
+ super();
+
+ final String filename = socketUri.toString()
+ .replaceAll("^unix:///", "unix://localhost/")
+ .replaceAll("^unix://localhost", "");
+
+ this.socketFile = new File(filename);
+ }
+
+ public static URI sanitizeUri(final URI uri) {
+ if (uri.getScheme().equals("unix")) {
+ return URI.create("unix://localhost:80");
+ } else {
+ return uri;
+ }
+ }
+
+ @Override
+ public Socket createSocket(final HttpContext context) throws IOException {
+ return new ApacheUnixSocket();
+ }
+
+ @Override
+ public Socket connectSocket(final int connectTimeout,
+ final Socket socket,
+ final HttpHost host,
+ final InetSocketAddress remoteAddress,
+ final InetSocketAddress localAddress,
+ final HttpContext context) throws IOException {
+ try {
+ socket.connect(new AFUNIXSocketAddress(socketFile), connectTimeout);
+ } catch (SocketTimeoutException e) {
+ throw new ConnectTimeoutException(e, null, remoteAddress.getAddress());
+ }
+
+ return socket;
+ }
+}
diff --git a/src/main/java/com/github/dockerjava/jaxrs/util/ResponseStatusExceptionFilter.java b/src/main/java/com/github/dockerjava/jaxrs/util/ResponseStatusExceptionFilter.java
index 61d97f93b..42158e01f 100644
--- a/src/main/java/com/github/dockerjava/jaxrs/util/ResponseStatusExceptionFilter.java
+++ b/src/main/java/com/github/dockerjava/jaxrs/util/ResponseStatusExceptionFilter.java
@@ -2,6 +2,7 @@
import java.io.EOFException;
import java.io.IOException;
+import java.io.InputStream;
import java.nio.charset.Charset;
import javax.ws.rs.client.ClientRequestContext;
@@ -63,7 +64,9 @@ public String getBodyAsMessage(ClientResponseContext responseContext)
if (contentLength != -1) {
byte[] buffer = new byte[contentLength];
try {
- IOUtils.readFully(responseContext.getEntityStream(), buffer);
+ InputStream entityStream = responseContext.getEntityStream();
+ IOUtils.readFully(entityStream, buffer);
+ entityStream.close();
}
catch (EOFException e) {
return null;
diff --git a/src/test/java/com/github/dockerjava/client/AbstractDockerClientTest.java b/src/test/java/com/github/dockerjava/client/AbstractDockerClientTest.java
index 3ed64d9a7..71bfb9304 100644
--- a/src/test/java/com/github/dockerjava/client/AbstractDockerClientTest.java
+++ b/src/test/java/com/github/dockerjava/client/AbstractDockerClientTest.java
@@ -33,7 +33,6 @@ public abstract class AbstractDockerClientTest extends Assert {
public static final Logger LOG = LoggerFactory
.getLogger(AbstractDockerClientTest.class);
- public static final String DOCKER_JAVA = "dockerjava";
protected DockerClient dockerClient;
@@ -120,7 +119,8 @@ protected String asString(InputStream response) {
logwriter.write(line + (itr.hasNext() ? "\n" : ""));
//LOG.info("line: "+line);
}
-
+ response.close();
+
return logwriter.toString();
} catch (IOException e) {
throw new RuntimeException(e);
@@ -150,7 +150,7 @@ public static boolean available(int port) {
ds = new DatagramSocket(port);
ds.setReuseAddress(true);
return true;
- } catch (IOException e) {
+ } catch (IOException ignored) {
} finally {
if (ds != null) {
ds.close();
diff --git a/src/test/java/com/github/dockerjava/core/DockerClientConfigTest.java b/src/test/java/com/github/dockerjava/core/DockerClientConfigTest.java
index e452e300d..4611f4307 100644
--- a/src/test/java/com/github/dockerjava/core/DockerClientConfigTest.java
+++ b/src/test/java/com/github/dockerjava/core/DockerClientConfigTest.java
@@ -16,7 +16,7 @@ public class DockerClientConfigTest {
public static final DockerClientConfig EXAMPLE_CONFIG = newExampleConfig();
private static DockerClientConfig newExampleConfig() {
- return new DockerClientConfig(URI.create("http://foo"), "bar", "baz", "qux", "blam", "wham", "flam", 877, false, new LocalDirectorySSLConfig("flim"));
+ return new DockerClientConfig(URI.create("http://foo"), "bar", "baz", "qux", "blam", "wham", "flam", 877, false, new LocalDirectorySSLConfig("flim"), 20, 2);
}
@Test
@@ -36,6 +36,8 @@ public void environmentDockerHost() throws Exception {
// given docker host in env
Map env = new HashMap();
env.put("DOCKER_HOST", "tcp://baz:8768");
+ // and it looks to be SSL disabled
+ env.remove("DOCKER_CERT_PATH");
// when you build a config
DockerClientConfig config = buildConfig(env, new Properties());
@@ -82,7 +84,9 @@ public void environmentDockerHostWithInvalidTlsVerify() throws Exception {
// given docker host in env
Map env = new HashMap(System.getenv());
env.put("DOCKER_HOST", "tcp://bar:8768");
- // and it looks to be SSL enabled
+ // and it looks to be SSL disabled
+ env.remove("DOCKER_CERT_PATH");
+ // and it has an invalid TLS_VERIFY value
env.put("DOCKER_TLS_VERIFY", "any value different from '1'");
// when you build a config
@@ -92,6 +96,24 @@ public void environmentDockerHostWithInvalidTlsVerify() throws Exception {
assertEquals(config.getUri(), URI.create("http://bar:8768"));
}
+ @Test
+ public void environmentDockerHostWithInvalidTlsVerifyButWithCertPath() throws Exception {
+
+ // given docker host in env
+ Map env = new HashMap(System.getenv());
+ env.put("DOCKER_HOST", "tcp://bar:8768");
+ // and it looks to be SSL enabled
+ env.put("DOCKER_CERT_PATH", "any value");
+ // and it has an invalid TLS_VERIFY value
+ env.put("DOCKER_TLS_VERIFY", "any value different from '1'");
+
+ // when you build a config
+ DockerClientConfig config = buildConfig(env, new Properties());
+
+ // then the URL is that value with "tcp" changed to "https"
+ assertEquals(config.getUri(), URI.create("https://bar:8768"));
+ }
+
@Test
public void environment() throws Exception {
diff --git a/src/test/java/com/github/dockerjava/core/DockerClientImplTest.java b/src/test/java/com/github/dockerjava/core/DockerClientImplTest.java
index a91fa109e..1f5604fe8 100644
--- a/src/test/java/com/github/dockerjava/core/DockerClientImplTest.java
+++ b/src/test/java/com/github/dockerjava/core/DockerClientImplTest.java
@@ -10,7 +10,7 @@ public class DockerClientImplTest {
@Test
public void configuredInstanceAuthConfig() throws Exception {
// given a config with null serverAddress
- DockerClientConfig dockerClientConfig = new DockerClientConfig(null, null, "", "", "", null, null, 0, false, null);
+ DockerClientConfig dockerClientConfig = new DockerClientConfig(null, null, "", "", "", null, null, 0, false, null, 20, 2);
DockerClientImpl dockerClient = DockerClientImpl.getInstance(dockerClientConfig);
// when we get the auth config
diff --git a/src/test/java/com/github/dockerjava/core/GoLangFileMatchTest.java b/src/test/java/com/github/dockerjava/core/GoLangFileMatchTest.java
index b556a7e5e..215926a56 100644
--- a/src/test/java/com/github/dockerjava/core/GoLangFileMatchTest.java
+++ b/src/test/java/com/github/dockerjava/core/GoLangFileMatchTest.java
@@ -3,7 +3,6 @@
*/
package com.github.dockerjava.core;
-import java.io.File;
import java.io.IOException;
import junit.framework.Assert;
@@ -19,7 +18,7 @@ public void testMatch(MatchTestCase testCase) throws IOException {
String pattern = testCase.pattern;
String s = testCase.s;
if (GoLangFileMatch.IS_WINDOWS) {
- if (pattern.indexOf('\\') > 0) {
+ if (pattern.indexOf('\\') >= 0) {
// no escape allowed on windows.
return;
}
diff --git a/src/test/java/com/github/dockerjava/core/command/CopyFileFromContainerCmdImplTest.java b/src/test/java/com/github/dockerjava/core/command/CopyFileFromContainerCmdImplTest.java
index 945b61983..193b787ce 100644
--- a/src/test/java/com/github/dockerjava/core/command/CopyFileFromContainerCmdImplTest.java
+++ b/src/test/java/com/github/dockerjava/core/command/CopyFileFromContainerCmdImplTest.java
@@ -41,7 +41,7 @@ public void copyFromContainer() throws Exception {
// TODO extract this into a shared method
CreateContainerResponse container = dockerClient.createContainerCmd("busybox")
.withName("docker-java-itest-copyFromContainer")
- .withCmd("touch", "/test")
+ .withCmd("touch", "/copyFromContainer")
.exec();
LOG.info("Created container: {}", container);
@@ -49,16 +49,22 @@ public void copyFromContainer() throws Exception {
dockerClient.startContainerCmd(container.getId()).exec();
- InputStream response = dockerClient.copyFileFromContainerCmd(container.getId(), "/test").exec();
- assertTrue(response.available() > 0, "The file was not copied from the container.");
+ InputStream response = dockerClient.copyFileFromContainerCmd(container.getId(), "/copyFromContainer").exec();
+ boolean bytesAvailable = response.available() > 0;
+ assertTrue(bytesAvailable, "The file was not copied from the container.");
+
+ // read the stream fully. Otherwise, the underlying stream will not be closed.
+ String responseAsString = asString(response);
+ assertNotNull(responseAsString);
+ assertTrue(responseAsString.length() > 0);
}
@Test
public void copyFromNonExistingContainer() throws Exception {
- try {
- dockerClient.copyFileFromContainerCmd("non-existing", "/test").exec();
- fail("expected NotFoundException");
- } catch (NotFoundException e) {
- }
+ try {
+ dockerClient.copyFileFromContainerCmd("non-existing", "/test").exec();
+ fail("expected NotFoundException");
+ } catch (NotFoundException ignored) {
+ }
}
}
diff --git a/src/test/java/com/github/dockerjava/core/command/EventsCmdImplTest.java b/src/test/java/com/github/dockerjava/core/command/EventsCmdImplTest.java
index 6935227bc..941657a19 100644
--- a/src/test/java/com/github/dockerjava/core/command/EventsCmdImplTest.java
+++ b/src/test/java/com/github/dockerjava/core/command/EventsCmdImplTest.java
@@ -19,6 +19,7 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
@Test(groups = "integration")
public class EventsCmdImplTest extends AbstractDockerClientTest {
@@ -59,7 +60,7 @@ public void testEventStreamTimeBound() throws InterruptedException, IOException
String endTime = getEpochTime();
CountDownLatch countDownLatch = new CountDownLatch(expectedEvents);
- EventCallback eventCallback = new EventCallbackTest(countDownLatch);
+ EventCallbackTest eventCallback = new EventCallbackTest(countDownLatch);
EventsCmd eventsCmd = dockerClient.eventsCmd(eventCallback).withSince(startTime).withUntil(endTime);
ExecutorService executorService = eventsCmd.exec();
@@ -67,8 +68,8 @@ public void testEventStreamTimeBound() throws InterruptedException, IOException
boolean zeroCount = countDownLatch.await(5, TimeUnit.SECONDS);
executorService.shutdown();
-
-
+ eventCallback.close();
+
assertTrue(zeroCount, "Expected 4 events, [create, start, die, stop]");
}
@@ -78,7 +79,7 @@ public void testEventStreaming() throws InterruptedException, IOException {
TimeUnit.SECONDS.sleep(1);
CountDownLatch countDownLatch = new CountDownLatch(KNOWN_NUM_EVENTS);
- EventCallback eventCallback = new EventCallbackTest(countDownLatch);
+ EventCallbackTest eventCallback = new EventCallbackTest(countDownLatch);
EventsCmd eventsCmd = dockerClient.eventsCmd(eventCallback).withSince(getEpochTime());
ExecutorService executorService = eventsCmd.exec();
@@ -87,6 +88,7 @@ public void testEventStreaming() throws InterruptedException, IOException {
boolean zeroCount = countDownLatch.await(5, TimeUnit.SECONDS);
executorService.shutdown();
+ eventCallback.close();
assertTrue(zeroCount, "Expected 4 events, [create, start, die, stop]");
}
@@ -105,11 +107,16 @@ private int generateEvents() {
private class EventCallbackTest implements EventCallback {
private final CountDownLatch countDownLatch;
+ private final AtomicBoolean isReceiving = new AtomicBoolean(true);
public EventCallbackTest(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
+ public void close() {
+ isReceiving.set(false);
+ }
+
@Override
public void onEvent(Event event) {
LOG.info("Received event #{}: {}", countDownLatch.getCount(), event);
@@ -125,5 +132,10 @@ public void onException(Throwable throwable) {
public void onCompletion(int numEvents) {
LOG.info("Number of events received: {}", numEvents);
}
+
+ @Override
+ public boolean isReceiving() {
+ return isReceiving.get();
+ }
}
}
diff --git a/src/test/java/com/github/dockerjava/core/command/ExecStartCmdImplTest.java b/src/test/java/com/github/dockerjava/core/command/ExecStartCmdImplTest.java
index a8c3fe8a1..497c44c1e 100644
--- a/src/test/java/com/github/dockerjava/core/command/ExecStartCmdImplTest.java
+++ b/src/test/java/com/github/dockerjava/core/command/ExecStartCmdImplTest.java
@@ -42,19 +42,26 @@ public void execStartTest() throws Exception {
String containerName = "generated_" + new SecureRandom().nextInt();
CreateContainerResponse container = dockerClient
- .createContainerCmd("busybox").withCmd("top")
+ .createContainerCmd("busybox")
+ .withCmd("top")
.withName(containerName).exec();
-
LOG.info("Created container {}", container.toString());
-
assertThat(container.getId(), not(isEmptyString()));
dockerClient.startContainerCmd(container.getId()).exec();
- ExecCreateCmdResponse execCreateCmdResponse = dockerClient.execCreateCmd(container.getId()).withAttachStdout(true).withCmd("touch","file.log").exec();
+ ExecCreateCmdResponse execCreateCmdResponse = dockerClient.execCreateCmd(container.getId())
+ .withAttachStdout(true)
+ .withCmd("touch", "/execStartTest.log").exec();
dockerClient.execStartCmd(execCreateCmdResponse.getId()).exec();
- InputStream response = dockerClient.copyFileFromContainerCmd(container.getId(), "/file.log").exec();
- assertTrue(response.available() > 0, "The file was not copied from the container.");
+ InputStream response = dockerClient.copyFileFromContainerCmd(container.getId(), "/execStartTest.log").exec();
+ boolean bytesAvailable = response.available() > 0;
+ assertTrue(bytesAvailable, "The file was not copied from the container.");
+
+ // read the stream fully. Otherwise, the underlying stream will not be closed.
+ String responseAsString = asString(response);
+ assertNotNull(responseAsString);
+ assertTrue(responseAsString.length() > 0);
}
}
diff --git a/src/test/java/com/github/dockerjava/core/command/InfoCmdImplTest.java b/src/test/java/com/github/dockerjava/core/command/InfoCmdImplTest.java
index 37214418c..a0a272f2c 100644
--- a/src/test/java/com/github/dockerjava/core/command/InfoCmdImplTest.java
+++ b/src/test/java/com/github/dockerjava/core/command/InfoCmdImplTest.java
@@ -45,6 +45,7 @@ public void afterMethod(ITestResult result) {
public void info() throws DockerException {
// Make sure that there is at least one container for the assertion
// TODO extract this into a shared method
+ if (dockerClient.listContainersCmd().withShowAll(true).exec().size() == 0) {
CreateContainerResponse container = dockerClient.createContainerCmd("busybox")
.withName("docker-java-itest-info")
.withCmd("touch", "/test")
@@ -54,8 +55,9 @@ public void info() throws DockerException {
assertThat(container.getId(), not(isEmptyOrNullString()));
dockerClient.startContainerCmd(container.getId()).exec();
+ }
- Info dockerInfo = dockerClient.infoCmd().exec();
+ Info dockerInfo = dockerClient.infoCmd().exec();
LOG.info(dockerInfo.toString());
assertTrue(dockerInfo.toString().contains("containers"));
@@ -68,6 +70,4 @@ public void info() throws DockerException {
assertTrue(dockerInfo.getNGoroutines() > 0);
assertTrue(dockerInfo.isMemoryLimit());
}
-
-
}
diff --git a/src/test/java/com/github/dockerjava/core/command/PullImageCmdImplTest.java b/src/test/java/com/github/dockerjava/core/command/PullImageCmdImplTest.java
index 394e86bd4..43291f07c 100644
--- a/src/test/java/com/github/dockerjava/core/command/PullImageCmdImplTest.java
+++ b/src/test/java/com/github/dockerjava/core/command/PullImageCmdImplTest.java
@@ -109,12 +109,14 @@ public void testPullImage() throws DockerException, IOException {
public void testPullNonExistingImage() throws DockerException, IOException {
// does not throw an exception
- dockerClient.pullImageCmd("nonexisting/foo").exec();
+ InputStream is = dockerClient.pullImageCmd("nonexisting/foo").exec();
+ // stream needs to be fully read in order to close the underlying connection
+ this.asString(is);
try {
dockerClient.pullImageCmd("non-existing/foo").exec();
fail("expected InternalServerErrorException");
- } catch (InternalServerErrorException e) {
+ } catch (InternalServerErrorException ignored) {
}
}
diff --git a/src/test/java/com/github/dockerjava/core/command/PushImageCmdImplTest.java b/src/test/java/com/github/dockerjava/core/command/PushImageCmdImplTest.java
index 2e2c9134d..f409aebf7 100644
--- a/src/test/java/com/github/dockerjava/core/command/PushImageCmdImplTest.java
+++ b/src/test/java/com/github/dockerjava/core/command/PushImageCmdImplTest.java
@@ -71,6 +71,5 @@ public void pushExistentImage() throws Exception {
assertThat(asString(dockerClient.pushImageCmd(username + "/xxx").exec()), containsString("error"));
}
-
}