diff --git a/README.md b/README.md
index 73c2b4e2..0bdf01dd 100644
--- a/README.md
+++ b/README.md
@@ -35,7 +35,7 @@ Download the latest JAR or grab [via Maven][maven-search]
```java
// Create a client based on DOCKER_HOST and DOCKER_CERT_PATH env vars
-final DockerClient docker = new JerseyDockerClientBuilder().fromEnv().build();
+final DockerClient docker = DockerClientBuilder.fromEnv().build();
// Pull an image
docker.pull("busybox");
diff --git a/pom.xml b/pom.xml
index 090135f0..d4994c85 100644
--- a/pom.xml
+++ b/pom.xml
@@ -129,8 +129,6 @@
provided
-
-
com.google.auth
google-auth-library-oauth2-http
diff --git a/src/main/java/org/mandas/docker/client/builder/BaseDockerClientBuilder.java b/src/main/java/org/mandas/docker/client/builder/BaseDockerClientBuilder.java
deleted file mode 100644
index 37e18749..00000000
--- a/src/main/java/org/mandas/docker/client/builder/BaseDockerClientBuilder.java
+++ /dev/null
@@ -1,321 +0,0 @@
-/*-
- * -\-\-
- * docker-client
- * --
- * Copyright (C) 2019-2020 Dimitris Mandalidis
- * --
- * Licensed 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.
- * -/-/-
-*/
-package org.mandas.docker.client.builder;
-
-import static java.util.Arrays.asList;
-import static java.util.Objects.requireNonNull;
-import static java.util.Optional.ofNullable;
-import static java.util.concurrent.TimeUnit.SECONDS;
-import static org.mandas.docker.client.DockerHost.certPathFromEnv;
-import static org.mandas.docker.client.DockerHost.configPathFromEnv;
-import static org.mandas.docker.client.DockerHost.defaultAddress;
-import static org.mandas.docker.client.DockerHost.defaultCertPath;
-import static org.mandas.docker.client.DockerHost.defaultPort;
-
-import java.net.URI;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.NoSuchElementException;
-import java.util.Optional;
-
-import jakarta.ws.rs.client.Client;
-
-import org.apache.http.config.Registry;
-import org.apache.http.config.RegistryBuilder;
-import org.apache.http.conn.HttpClientConnectionManager;
-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.BasicHttpClientConnectionManager;
-import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
-import org.mandas.docker.client.DefaultDockerClient;
-import org.mandas.docker.client.DockerCertificates;
-import org.mandas.docker.client.DockerCertificatesStore;
-import org.mandas.docker.client.DockerHost;
-import org.mandas.docker.client.ObjectMapperProvider;
-import org.mandas.docker.client.UnixConnectionSocketFactory;
-import org.mandas.docker.client.auth.ConfigFileRegistryAuthSupplier;
-import org.mandas.docker.client.auth.RegistryAuthSupplier;
-import org.mandas.docker.client.exceptions.DockerCertificateException;
-import org.mandas.docker.client.npipe.NpipeConnectionSocketFactory;
-
-/**
- * A convenience base class for implementing {@link DockerClientBuilder}s
- * @author Dimitris Mandalidis
- * @param the type of the builder
- */
-public abstract class BaseDockerClientBuilder> implements DockerClientBuilder {
-
- protected String UNIX_SCHEME = "unix";
- protected String NPIPE_SCHEME = "npipe";
- protected long DEFAULT_CONNECT_TIMEOUT_MILLIS = SECONDS.toMillis(5);
- protected long DEFAULT_READ_TIMEOUT_MILLIS = SECONDS.toMillis(30);
- protected int DEFAULT_CONNECTION_POOL_SIZE = 100;
- protected String ERROR_MESSAGE = "LOGIC ERROR: DefaultDockerClient does not support being built "
- + "with both `registryAuth` and `registryAuthSupplier`. "
- + "Please build with at most one of these options.";
- protected URI uri;
- protected String apiVersion;
- protected long connectTimeoutMillis = DEFAULT_CONNECT_TIMEOUT_MILLIS;
- protected long readTimeoutMillis = DEFAULT_READ_TIMEOUT_MILLIS;
- protected int connectionPoolSize = DEFAULT_CONNECTION_POOL_SIZE;
- protected DockerCertificatesStore dockerCertificatesStore;
- protected boolean useProxy = true;
- protected RegistryAuthSupplier registryAuthSupplier;
- protected Map headers = new HashMap<>();
- protected Client client;
- protected EntityProcessing entityProcessing;
-
- private B self() {
- return (B) this;
- }
-
- /**
- * Sets or overwrites {@link #uri()} and {@link #dockerCertificates(DockerCertificatesStore)} according to the values
- * present in DOCKER_HOST and DOCKER_CERT_PATH environment variables.
- *
- * @return Modifies a builder that can be used to further customize and then build the client.
- * @throws DockerCertificateException if we could not build a DockerCertificates object
- */
- @Override
- public B fromEnv() throws DockerCertificateException {
- final String endpoint = DockerHost.endpointFromEnv();
- final Path dockerCertPath = Paths.get(asList(certPathFromEnv(), configPathFromEnv(), defaultCertPath())
- .stream()
- .filter(cert -> cert != null)
- .findFirst()
- .orElseThrow(() -> new NoSuchElementException("Cannot find docker certificated path")));
-
- final Optional certs = DockerCertificates.builder().dockerCertPath(dockerCertPath).build();
-
- if (endpoint.startsWith(UNIX_SCHEME + "://")) {
- this.uri(endpoint);
- } else if (endpoint.startsWith(NPIPE_SCHEME + "://")) {
- this.uri(endpoint);
- } else {
- final String stripped = endpoint.replaceAll(".*://", "");
- final String scheme = certs.isPresent() ? "https" : "http";
- URI initialUri = URI.create(scheme + "://" + stripped);
- if (initialUri.getPort() == -1 && initialUri.getHost() == null) {
- initialUri = URI.create(scheme + "://" + defaultAddress() + ":" + defaultPort());
- } else if (initialUri.getHost() == null) {
- initialUri = URI.create(scheme + "://" + defaultAddress()+ ":" + initialUri.getPort());
- } else if (initialUri.getPort() == -1) {
- initialUri = URI.create(scheme + "://" + initialUri.getHost() + ":" + defaultPort());
- }
- this.uri(initialUri);
- }
-
- if (certs.isPresent()) {
- this.dockerCertificates(certs.get());
- }
-
- return self();
- }
-
- @Override
- public B uri(final URI uri) {
- this.uri = uri;
- return self();
- }
-
- /**
- * Set the URI for connections to Docker.
- *
- * @param uri URI String for connections to Docker
- * @return Builder
- */
- @Override
- public B uri(final String uri) {
- return uri(URI.create(uri));
- }
-
- /**
- * Set the Docker API version that will be used in the HTTP requests to Docker daemon.
- *
- * @param apiVersion String for Docker API version
- * @return Builder
- */
- @Override
- public B apiVersion(final String apiVersion) {
- this.apiVersion = apiVersion;
- return self();
- }
-
- @Override
- public B connectTimeoutMillis(final long connectTimeoutMillis) {
- this.connectTimeoutMillis = connectTimeoutMillis;
- return self();
- }
-
- @Override
- public B readTimeoutMillis(final long readTimeoutMillis) {
- this.readTimeoutMillis = readTimeoutMillis;
- return self();
- }
-
- @Override
- public B dockerCertificates(final DockerCertificatesStore dockerCertificatesStore) {
- this.dockerCertificatesStore = dockerCertificatesStore;
- return self();
- }
-
- @Override
- public B connectionPoolSize(final int connectionPoolSize) {
- this.connectionPoolSize = connectionPoolSize;
- return self();
- }
-
- @Override
- public B useProxy(final boolean useProxy) {
- this.useProxy = useProxy;
- return self();
- }
-
- @Override
- public B registryAuthSupplier(final RegistryAuthSupplier registryAuthSupplier) {
- if (this.registryAuthSupplier != null) {
- throw new IllegalStateException(ERROR_MESSAGE);
- }
- this.registryAuthSupplier = registryAuthSupplier;
- return self();
- }
-
- @Override
- public B header(String name, Object value) {
- headers.put(name, value);
- return self();
- }
-
- @Override
- public URI uri() {
- return uri;
- }
-
- @Override
- public B entityProcessing(final EntityProcessing entityProcessing) {
- this.entityProcessing = entityProcessing;
- return self();
- }
-
- private String toRegExp(String hostnameWithWildcards) {
- return hostnameWithWildcards.replace(".", "\\.").replace("*", ".*");
- }
-
- protected abstract Client createClient();
-
- protected ProxyConfiguration proxyFromEnv() {
- final String proxyHost = System.getProperty("http.proxyHost");
- if (proxyHost == null) {
- return null;
- }
-
- String nonProxyHosts = System.getProperty("http.nonProxyHosts");
- if (nonProxyHosts != null) {
- // Remove quotes, if any. Refer to https://docs.oracle.com/javase/8/docs/technotes/guides/net/proxies.html
- String[] nonProxy = nonProxyHosts
- .replaceAll("^\\s*\"", "")
- .replaceAll("\\s*\"$", "")
- .split("\\|");
- String host = ofNullable(uri.getHost()).orElse("localhost");
- for (String h: nonProxy) {
- if (host.matches(toRegExp(h))) {
- return null;
- }
- }
- }
-
- return ProxyConfiguration.builder()
- .host(proxyHost)
- .port(System.getProperty("http.proxyPort"))
- .username(System.getProperty("http.proxyUser"))
- .password(System.getProperty("http.proxyPassword"))
- .build();
- }
-
- @Override
- public DefaultDockerClient build() {
- requireNonNull(uri, "uri");
- requireNonNull(uri.getScheme(), "url has null scheme");
-
- if ((dockerCertificatesStore != null) && !uri.getScheme().equals("https")) {
- throw new IllegalArgumentException(
- "An HTTPS URI for DOCKER_HOST must be provided to use Docker client certificates");
- }
-
- if (uri.getScheme().startsWith(UNIX_SCHEME) || uri.getScheme().startsWith(NPIPE_SCHEME)) {
- this.useProxy = false;
- }
-
- this.client = createClient()
- .register(ObjectMapperProvider.class);
-
- if (uri.getScheme().equals(UNIX_SCHEME)) {
- this.uri = UnixConnectionSocketFactory.sanitizeUri(uri);
- } else if (uri.getScheme().equals(NPIPE_SCHEME)) {
- this.uri = NpipeConnectionSocketFactory.sanitizeUri(uri);
- }
-
- // read the docker config file for auth info if nothing else was specified
- if (registryAuthSupplier == null) {
- registryAuthSupplier(new ConfigFileRegistryAuthSupplier());
- }
-
- return new DefaultDockerClient(apiVersion, registryAuthSupplier, uri, client, headers);
- }
-
- protected HttpClientConnectionManager getConnectionManager(URI uri, Registry schemeRegistry, int connectionPoolSize) {
- if (uri.getScheme().equals(NPIPE_SCHEME)) {
- return new BasicHttpClientConnectionManager(schemeRegistry);
- }
- final PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(schemeRegistry);
- // Use all available connections instead of artificially limiting ourselves to 2 per server.
- cm.setMaxTotal(connectionPoolSize);
- cm.setDefaultMaxPerRoute(cm.getMaxTotal());
- return cm;
- }
-
- protected Registry getSchemeRegistry(URI uri, DockerCertificatesStore certificateStore) {
- final SSLConnectionSocketFactory https;
- if (dockerCertificatesStore == null) {
- https = SSLConnectionSocketFactory.getSocketFactory();
- } else {
- https = new SSLConnectionSocketFactory(dockerCertificatesStore.sslContext(),
- dockerCertificatesStore.hostnameVerifier());
- }
-
- final RegistryBuilder registryBuilder = RegistryBuilder
- .create()
- .register("https", https)
- .register("http", PlainConnectionSocketFactory.getSocketFactory());
-
- if (uri.getScheme().equals(UNIX_SCHEME)) {
- registryBuilder.register(UNIX_SCHEME, new UnixConnectionSocketFactory(uri));
- }
-
- if (uri.getScheme().equals(NPIPE_SCHEME)) {
- registryBuilder.register(NPIPE_SCHEME, new NpipeConnectionSocketFactory(uri));
- }
-
- return registryBuilder.build();
- }
-}
diff --git a/src/main/java/org/mandas/docker/client/builder/DockerClientBuilder.java b/src/main/java/org/mandas/docker/client/builder/DockerClientBuilder.java
index c2f881d0..3b357056 100644
--- a/src/main/java/org/mandas/docker/client/builder/DockerClientBuilder.java
+++ b/src/main/java/org/mandas/docker/client/builder/DockerClientBuilder.java
@@ -19,33 +19,148 @@
*/
package org.mandas.docker.client.builder;
+import static java.util.Arrays.asList;
+import static java.util.Objects.requireNonNull;
+import static java.util.Optional.ofNullable;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.mandas.docker.client.DockerHost.certPathFromEnv;
+import static org.mandas.docker.client.DockerHost.configPathFromEnv;
+import static org.mandas.docker.client.DockerHost.defaultAddress;
+import static org.mandas.docker.client.DockerHost.defaultCertPath;
+import static org.mandas.docker.client.DockerHost.defaultPort;
+
import java.net.URI;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Optional;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.config.Registry;
+import org.apache.http.config.RegistryBuilder;
+import org.apache.http.conn.HttpClientConnectionManager;
+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.BasicHttpClientConnectionManager;
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+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;
+import org.glassfish.jersey.client.RequestEntityProcessing;
+import org.glassfish.jersey.jackson.JacksonFeature;
import org.mandas.docker.client.DefaultDockerClient;
+import org.mandas.docker.client.DockerCertificates;
import org.mandas.docker.client.DockerCertificatesStore;
+import org.mandas.docker.client.DockerHost;
+import org.mandas.docker.client.ObjectMapperProvider;
+import org.mandas.docker.client.UnixConnectionSocketFactory;
+import org.mandas.docker.client.auth.ConfigFileRegistryAuthSupplier;
import org.mandas.docker.client.auth.RegistryAuthSupplier;
import org.mandas.docker.client.exceptions.DockerCertificateException;
+import org.mandas.docker.client.npipe.NpipeConnectionSocketFactory;
+
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.ClientBuilder;
/**
- * DockerClientBuilder
is an interface which has to be implemented from clients
- * when they need to use a JAXRS client implementation other than the provided Jersey
- *
+ * Docker client builder
* @author Dimitris Mandalidis
- * @param the type of the builder
- * @see BaseDockerClientBuilder
*/
-public interface DockerClientBuilder> {
+public class DockerClientBuilder {
- /**
- * @return the URI of the Docker engine
- */
- URI uri();
-
- enum EntityProcessing {
+ public enum EntityProcessing {
CHUNKED,
BUFFERED;
}
+
+ private static String UNIX_SCHEME = "unix";
+ private static String NPIPE_SCHEME = "npipe";
+ private long DEFAULT_CONNECT_TIMEOUT_MILLIS = SECONDS.toMillis(5);
+ private long DEFAULT_READ_TIMEOUT_MILLIS = SECONDS.toMillis(30);
+ private int DEFAULT_CONNECTION_POOL_SIZE = 100;
+ private String ERROR_MESSAGE = "LOGIC ERROR: DefaultDockerClient does not support being built "
+ + "with both `registryAuth` and `registryAuthSupplier`. "
+ + "Please build with at most one of these options.";
+ private URI uri;
+ private String apiVersion;
+ private long connectTimeoutMillis = DEFAULT_CONNECT_TIMEOUT_MILLIS;
+ private long readTimeoutMillis = DEFAULT_READ_TIMEOUT_MILLIS;
+ private int connectionPoolSize = DEFAULT_CONNECTION_POOL_SIZE;
+ private DockerCertificatesStore dockerCertificatesStore;
+ private boolean useProxy = true;
+ private RegistryAuthSupplier registryAuthSupplier;
+ private Map headers = new HashMap<>();
+ private Client client;
+ private EntityProcessing entityProcessing;
+
+ private ClientConfig updateProxy(ClientConfig config) {
+ ProxyConfiguration proxyConfiguration = proxyFromEnv();
+ if (proxyConfiguration == null) {
+ return config;
+ }
+
+ String proxyHost = proxyConfiguration.host();
+
+ config.property(ClientProperties.PROXY_URI, (!proxyHost.startsWith("http") ? "http://" : "")
+ + proxyHost + ":" + proxyConfiguration.port());
+
+ if (proxyConfiguration.username() != null) {
+ config.property(ClientProperties.PROXY_USERNAME, proxyConfiguration.username());
+ }
+ if (proxyConfiguration.password() != null) {
+ config.property(ClientProperties.PROXY_PASSWORD, proxyConfiguration.password());
+ }
+
+ //ensure Content-Length is populated before sending request via proxy.
+ config.property(ClientProperties.REQUEST_ENTITY_PROCESSING, RequestEntityProcessing.BUFFERED);
+ return config;
+ }
+
+ private Client createClient() {
+ Registry schemeRegistry = getSchemeRegistry(uri, dockerCertificatesStore);
+
+ final HttpClientConnectionManager cm = getConnectionManager(uri, schemeRegistry, connectionPoolSize);
+
+ final RequestConfig requestConfig = RequestConfig.custom()
+ .setConnectionRequestTimeout((int) connectTimeoutMillis)
+ .setConnectTimeout((int) connectTimeoutMillis)
+ .setSocketTimeout((int) readTimeoutMillis)
+ .build();
+
+ ClientConfig config = new ClientConfig(JacksonFeature.class);
+
+ if (useProxy) {
+ config = updateProxy(config);
+ }
+
+ config
+ .connectorProvider(new ApacheConnectorProvider())
+ .property(ApacheClientProperties.CONNECTION_MANAGER, cm)
+ .property(ApacheClientProperties.CONNECTION_MANAGER_SHARED, "true")
+ .property(ApacheClientProperties.REQUEST_CONFIG, requestConfig);
+ if (entityProcessing != null) {
+ switch (entityProcessing) {
+ case BUFFERED:
+ config.property(ClientProperties.REQUEST_ENTITY_PROCESSING, RequestEntityProcessing.BUFFERED);
+ break;
+ case CHUNKED:
+ config.property(ClientProperties.REQUEST_ENTITY_PROCESSING, RequestEntityProcessing.CHUNKED);
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid entity processing mode " + entityProcessing);
+ }
+ }
+
+ return ClientBuilder.newBuilder()
+ .withConfig(config)
+ .build();
+ }
+
/**
* Sets or overwrites {@link #uri()} and {@link #dockerCertificates(DockerCertificatesStore)} according to the values
* present in DOCKER_HOST and DOCKER_CERT_PATH environment variables.
@@ -53,42 +168,65 @@ enum EntityProcessing {
* @return Modifies a builder that can be used to further customize and then build the client.
* @throws DockerCertificateException if we could not build a DockerCertificates object
*/
- B fromEnv() throws DockerCertificateException;
-
- B uri(URI uri);
-
- B uri(String uri);
+ public static DockerClientBuilder fromEnv() throws DockerCertificateException {
+ final String endpoint = DockerHost.endpointFromEnv();
+ final Path dockerCertPath = Paths.get(asList(certPathFromEnv(), configPathFromEnv(), defaultCertPath())
+ .stream()
+ .filter(cert -> cert != null)
+ .findFirst()
+ .orElseThrow(() -> new NoSuchElementException("Cannot find docker certificated path")));
- DefaultDockerClient build();
-
- /**
- * Adds additional headers to be sent in all requests to the Docker Remote API.
- * @param name the header name
- * @param value the header value
- * @return this
- */
- B header(String name, Object value);
-
- B registryAuthSupplier(RegistryAuthSupplier registryAuthSupplier);
+ final Optional certs = DockerCertificates.builder().dockerCertPath(dockerCertPath).build();
+
+ URI uri = null;
+ if (endpoint.startsWith(UNIX_SCHEME + "://")) {
+ uri = URI.create(endpoint);
+ } else if (endpoint.startsWith(NPIPE_SCHEME + "://")) {
+ uri = URI.create(endpoint);
+ } else {
+ final String stripped = endpoint.replaceAll(".*://", "");
+ final String scheme = certs.isPresent() ? "https" : "http";
+ URI initialUri = URI.create(scheme + "://" + stripped);
+ if (initialUri.getPort() == -1 && initialUri.getHost() == null) {
+ initialUri = URI.create(scheme + "://" + defaultAddress() + ":" + defaultPort());
+ } else if (initialUri.getHost() == null) {
+ initialUri = URI.create(scheme + "://" + defaultAddress()+ ":" + initialUri.getPort());
+ } else if (initialUri.getPort() == -1) {
+ initialUri = URI.create(scheme + "://" + initialUri.getHost() + ":" + defaultPort());
+ }
+ uri = initialUri;
+ }
+
+ if (certs.isPresent()) {
+ return new DockerClientBuilder(uri, certs.get());
+ }
+
+ return new DockerClientBuilder(uri);
+ }
- /**
- * Set the size of the connection pool for connections to Docker. Note that due to a known
- * issue, DefaultDockerClient maintains two separate connection pools, each of which is capped
- * at this size. Therefore, the maximum number of concurrent connections to Docker may be up to
- * 2 * connectionPoolSize.
- *
- * @param connectionPoolSize connection pool size
- * @return Builder
- */
- B connectionPoolSize(int connectionPoolSize);
+ private DockerClientBuilder(final URI uri) {
+ this(uri, null);
+ }
+
+ private DockerClientBuilder(final URI uri, final DockerCertificatesStore certs) {
+ this.uri = uri;
+ this.dockerCertificatesStore = certs;
+ }
+
+ public DockerClientBuilder uri(final URI uri) {
+ this.uri = uri;
+ return this;
+ }
/**
- * Allows connecting to Docker Daemon using HTTP proxy.
+ * Set the URI for connections to Docker.
*
- * @param useProxy tells if Docker Client has to connect to docker daemon using HTTP Proxy
+ * @param uri URI String for connections to Docker
* @return Builder
*/
- B useProxy(boolean useProxy);
+ public DockerClientBuilder uri(final String uri) {
+ return uri(URI.create(uri));
+ }
/**
* Set the Docker API version that will be used in the HTTP requests to Docker daemon.
@@ -96,7 +234,10 @@ enum EntityProcessing {
* @param apiVersion String for Docker API version
* @return Builder
*/
- B apiVersion(String apiVersion);
+ public DockerClientBuilder apiVersion(final String apiVersion) {
+ this.apiVersion = apiVersion;
+ return this;
+ }
/**
* Set the timeout in milliseconds until a connection to Docker is established. A timeout value
@@ -105,7 +246,10 @@ enum EntityProcessing {
* @param connectTimeoutMillis connection timeout to Docker daemon in milliseconds
* @return Builder
*/
- B connectTimeoutMillis(long connectTimeoutMillis);
+ public DockerClientBuilder connectTimeoutMillis(final long connectTimeoutMillis) {
+ this.connectTimeoutMillis = connectTimeoutMillis;
+ return this;
+ }
/**
* Set the SO_TIMEOUT in milliseconds. This is the maximum period of inactivity between
@@ -114,7 +258,10 @@ enum EntityProcessing {
* @param readTimeoutMillis read timeout to Docker daemon in milliseconds
* @return Builder
*/
- B readTimeoutMillis(long readTimeoutMillis);
+ public DockerClientBuilder readTimeoutMillis(final long readTimeoutMillis) {
+ this.readTimeoutMillis = readTimeoutMillis;
+ return this;
+ }
/**
* Provide certificates to secure the connection to Docker.
@@ -122,7 +269,61 @@ enum EntityProcessing {
* @param dockerCertificatesStore DockerCertificatesStore object
* @return Builder
*/
- B dockerCertificates(DockerCertificatesStore dockerCertificatesStore);
+ public DockerClientBuilder dockerCertificates(final DockerCertificatesStore dockerCertificatesStore) {
+ this.dockerCertificatesStore = dockerCertificatesStore;
+ return this;
+ }
+
+ /**
+ * Set the size of the connection pool for connections to Docker. Note that due to a known
+ * issue, DefaultDockerClient maintains two separate connection pools, each of which is capped
+ * at this size. Therefore, the maximum number of concurrent connections to Docker may be up to
+ * 2 * connectionPoolSize.
+ *
+ * @param connectionPoolSize connection pool size
+ * @return Builder
+ */
+ public DockerClientBuilder connectionPoolSize(final int connectionPoolSize) {
+ this.connectionPoolSize = connectionPoolSize;
+ return this;
+ }
+
+ /**
+ * Allows connecting to Docker Daemon using HTTP proxy.
+ *
+ * @param useProxy tells if Docker Client has to connect to docker daemon using HTTP Proxy
+ * @return Builder
+ */
+ public DockerClientBuilder useProxy(final boolean useProxy) {
+ this.useProxy = useProxy;
+ return this;
+ }
+
+ public DockerClientBuilder registryAuthSupplier(final RegistryAuthSupplier registryAuthSupplier) {
+ if (this.registryAuthSupplier != null) {
+ throw new IllegalStateException(ERROR_MESSAGE);
+ }
+ this.registryAuthSupplier = registryAuthSupplier;
+ return this;
+ }
+
+ /**
+ * Adds additional headers to be sent in all requests to the Docker Remote API.
+ * @param name the header name
+ * @param value the header value
+ * @return this
+ */
+ public DockerClientBuilder header(String name, Object value) {
+ headers.put(name, value);
+ return this;
+ }
+
+ /**
+ * @return the URI of the Docker engine
+ */
+ public URI uri() {
+ return uri;
+ }
/**
* Allows setting transfer encoding. CHUNKED does not send the content-length header
@@ -135,5 +336,107 @@ enum EntityProcessing {
* daemon (tcp protocol).
* @return Builder
*/
- B entityProcessing(EntityProcessing entityProcessing);
+ public DockerClientBuilder entityProcessing(final EntityProcessing entityProcessing) {
+ this.entityProcessing = entityProcessing;
+ return this;
+ }
+
+ private String toRegExp(String hostnameWithWildcards) {
+ return hostnameWithWildcards.replace(".", "\\.").replace("*", ".*");
+ }
+
+ private ProxyConfiguration proxyFromEnv() {
+ final String proxyHost = System.getProperty("http.proxyHost");
+ if (proxyHost == null) {
+ return null;
+ }
+
+ String nonProxyHosts = System.getProperty("http.nonProxyHosts");
+ if (nonProxyHosts != null) {
+ // Remove quotes, if any. Refer to https://docs.oracle.com/javase/8/docs/technotes/guides/net/proxies.html
+ String[] nonProxy = nonProxyHosts
+ .replaceAll("^\\s*\"", "")
+ .replaceAll("\\s*\"$", "")
+ .split("\\|");
+ String host = ofNullable(uri.getHost()).orElse("localhost");
+ for (String h: nonProxy) {
+ if (host.matches(toRegExp(h))) {
+ return null;
+ }
+ }
+ }
+
+ return ProxyConfiguration.builder()
+ .host(proxyHost)
+ .port(System.getProperty("http.proxyPort"))
+ .username(System.getProperty("http.proxyUser"))
+ .password(System.getProperty("http.proxyPassword"))
+ .build();
+ }
+
+ public DefaultDockerClient build() {
+ requireNonNull(uri, "uri");
+ requireNonNull(uri.getScheme(), "url has null scheme");
+
+ if ((dockerCertificatesStore != null) && !uri.getScheme().equals("https")) {
+ throw new IllegalArgumentException(
+ "An HTTPS URI for DOCKER_HOST must be provided to use Docker client certificates");
+ }
+
+ if (uri.getScheme().startsWith(UNIX_SCHEME) || uri.getScheme().startsWith(NPIPE_SCHEME)) {
+ this.useProxy = false;
+ }
+
+ this.client = createClient()
+ .register(ObjectMapperProvider.class);
+
+ if (uri.getScheme().equals(UNIX_SCHEME)) {
+ this.uri = UnixConnectionSocketFactory.sanitizeUri(uri);
+ } else if (uri.getScheme().equals(NPIPE_SCHEME)) {
+ this.uri = NpipeConnectionSocketFactory.sanitizeUri(uri);
+ }
+
+ // read the docker config file for auth info if nothing else was specified
+ if (registryAuthSupplier == null) {
+ registryAuthSupplier(new ConfigFileRegistryAuthSupplier());
+ }
+
+ return new DefaultDockerClient(apiVersion, registryAuthSupplier, uri, client, headers);
+ }
+
+ private HttpClientConnectionManager getConnectionManager(URI uri, Registry schemeRegistry, int connectionPoolSize) {
+ if (uri.getScheme().equals(NPIPE_SCHEME)) {
+ return new BasicHttpClientConnectionManager(schemeRegistry);
+ }
+ final PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(schemeRegistry);
+ // Use all available connections instead of artificially limiting ourselves to 2 per server.
+ cm.setMaxTotal(connectionPoolSize);
+ cm.setDefaultMaxPerRoute(cm.getMaxTotal());
+ return cm;
+ }
+
+ private Registry getSchemeRegistry(URI uri, DockerCertificatesStore certificateStore) {
+ final SSLConnectionSocketFactory https;
+ if (dockerCertificatesStore == null) {
+ https = SSLConnectionSocketFactory.getSocketFactory();
+ } else {
+ https = new SSLConnectionSocketFactory(dockerCertificatesStore.sslContext(),
+ dockerCertificatesStore.hostnameVerifier());
+ }
+
+ final RegistryBuilder registryBuilder = RegistryBuilder
+ .create()
+ .register("https", https)
+ .register("http", PlainConnectionSocketFactory.getSocketFactory());
+
+ if (uri.getScheme().equals(UNIX_SCHEME)) {
+ registryBuilder.register(UNIX_SCHEME, new UnixConnectionSocketFactory(uri));
+ }
+
+ if (uri.getScheme().equals(NPIPE_SCHEME)) {
+ registryBuilder.register(NPIPE_SCHEME, new NpipeConnectionSocketFactory(uri));
+ }
+
+ return registryBuilder.build();
+ }
}
diff --git a/src/main/java/org/mandas/docker/client/builder/ProxyConfiguration.java b/src/main/java/org/mandas/docker/client/builder/ProxyConfiguration.java
index c056ab87..447007d2 100644
--- a/src/main/java/org/mandas/docker/client/builder/ProxyConfiguration.java
+++ b/src/main/java/org/mandas/docker/client/builder/ProxyConfiguration.java
@@ -25,7 +25,7 @@
/**
* Object representing a host's proxy configuration
* @author Dimitris Mandalidis
- * @see BaseDockerClientBuilder#proxyFromEnv()
+ * @see DockerClientBuilder#proxyFromEnv()
*/
@Immutable
public interface ProxyConfiguration {
diff --git a/src/main/java/org/mandas/docker/client/builder/jersey/JerseyDockerClientBuilder.java b/src/main/java/org/mandas/docker/client/builder/jersey/JerseyDockerClientBuilder.java
deleted file mode 100644
index ccf80fd1..00000000
--- a/src/main/java/org/mandas/docker/client/builder/jersey/JerseyDockerClientBuilder.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*-
- * -\-\-
- * docker-client
- * --
- * Copyright (C) 2019-2020 Dimitris Mandalidis
- * --
- * Licensed 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.
- * -/-/-
-*/
-package org.mandas.docker.client.builder.jersey;
-
-import org.apache.http.client.config.RequestConfig;
-import org.apache.http.config.Registry;
-import org.apache.http.conn.HttpClientConnectionManager;
-import org.apache.http.conn.socket.ConnectionSocketFactory;
-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;
-import org.glassfish.jersey.client.RequestEntityProcessing;
-import org.glassfish.jersey.jackson.JacksonFeature;
-import org.mandas.docker.client.builder.BaseDockerClientBuilder;
-import org.mandas.docker.client.builder.ProxyConfiguration;
-
-import jakarta.ws.rs.client.Client;
-import jakarta.ws.rs.client.ClientBuilder;
-
-public class JerseyDockerClientBuilder extends BaseDockerClientBuilder {
-
- private ClientConfig updateProxy(ClientConfig config) {
- ProxyConfiguration proxyConfiguration = proxyFromEnv();
- if (proxyConfiguration == null) {
- return config;
- }
-
- String proxyHost = proxyConfiguration.host();
-
- config.property(ClientProperties.PROXY_URI, (!proxyHost.startsWith("http") ? "http://" : "")
- + proxyHost + ":" + proxyConfiguration.port());
-
- if (proxyConfiguration.username() != null) {
- config.property(ClientProperties.PROXY_USERNAME, proxyConfiguration.username());
- }
- if (proxyConfiguration.password() != null) {
- config.property(ClientProperties.PROXY_PASSWORD, proxyConfiguration.password());
- }
-
- //ensure Content-Length is populated before sending request via proxy.
- config.property(ClientProperties.REQUEST_ENTITY_PROCESSING, RequestEntityProcessing.BUFFERED);
- return config;
- }
-
- @Override
- protected Client createClient() {
- Registry schemeRegistry = getSchemeRegistry(uri, dockerCertificatesStore);
-
- final HttpClientConnectionManager cm = getConnectionManager(uri, schemeRegistry, connectionPoolSize);
-
- final RequestConfig requestConfig = RequestConfig.custom()
- .setConnectionRequestTimeout((int) connectTimeoutMillis)
- .setConnectTimeout((int) connectTimeoutMillis)
- .setSocketTimeout((int) readTimeoutMillis)
- .build();
-
- ClientConfig config = new ClientConfig(JacksonFeature.class);
-
- if (useProxy) {
- config = updateProxy(config);
- }
-
- config
- .connectorProvider(new ApacheConnectorProvider())
- .property(ApacheClientProperties.CONNECTION_MANAGER, cm)
- .property(ApacheClientProperties.CONNECTION_MANAGER_SHARED, "true")
- .property(ApacheClientProperties.REQUEST_CONFIG, requestConfig);
-
- if (entityProcessing != null) {
- switch (entityProcessing) {
- case BUFFERED:
- config.property(ClientProperties.REQUEST_ENTITY_PROCESSING, RequestEntityProcessing.BUFFERED);
- break;
- case CHUNKED:
- config.property(ClientProperties.REQUEST_ENTITY_PROCESSING, RequestEntityProcessing.CHUNKED);
- break;
- default:
- throw new IllegalArgumentException("Invalid entity processing mode " + entityProcessing);
- }
- }
-
- return ClientBuilder.newBuilder()
- .withConfig(config)
- .build();
- }
-}
\ No newline at end of file
diff --git a/src/test/java/org/mandas/docker/DockerClientBuilderFactory.java b/src/test/java/org/mandas/docker/DockerClientBuilderFactory.java
deleted file mode 100644
index 4208d66a..00000000
--- a/src/test/java/org/mandas/docker/DockerClientBuilderFactory.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*-
- * -\-\-
- * docker-client
- * --
- * Copyright (C) 2019-2020 Dimitris Mandalidis
- * --
- * Licensed 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.
- * -/-/-
-*/
-package org.mandas.docker;
-
-import org.mandas.docker.client.builder.DockerClientBuilder;
-import org.mandas.docker.client.builder.jersey.JerseyDockerClientBuilder;
-
-public class DockerClientBuilderFactory {
-
- private DockerClientBuilderFactory() {}
-
- public static DockerClientBuilder newInstance() {
- return new JerseyDockerClientBuilder();
- }
-}
diff --git a/src/test/java/org/mandas/docker/client/DefaultDockerClientTest.java b/src/test/java/org/mandas/docker/client/DefaultDockerClientTest.java
index e9262493..67e4a1ff 100644
--- a/src/test/java/org/mandas/docker/client/DefaultDockerClientTest.java
+++ b/src/test/java/org/mandas/docker/client/DefaultDockerClientTest.java
@@ -160,7 +160,6 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
-import org.mandas.docker.DockerClientBuilderFactory;
import org.mandas.docker.client.DockerClient.AttachParameter;
import org.mandas.docker.client.DockerClient.BuildParam;
import org.mandas.docker.client.DockerClient.EventsParam;
@@ -183,7 +182,6 @@
import org.mandas.docker.client.messages.Container;
import org.mandas.docker.client.messages.ContainerChange;
import org.mandas.docker.client.messages.ContainerConfig;
-import org.mandas.docker.client.messages.Healthcheck;
import org.mandas.docker.client.messages.ContainerCreation;
import org.mandas.docker.client.messages.ContainerExit;
import org.mandas.docker.client.messages.ContainerInfo;
@@ -195,6 +193,7 @@
import org.mandas.docker.client.messages.Event;
import org.mandas.docker.client.messages.ExecCreation;
import org.mandas.docker.client.messages.ExecState;
+import org.mandas.docker.client.messages.Healthcheck;
import org.mandas.docker.client.messages.HostConfig;
import org.mandas.docker.client.messages.HostConfig.Bind;
import org.mandas.docker.client.messages.HostConfig.Ulimit;
@@ -294,7 +293,7 @@ public class DefaultDockerClientTest {
@Before
public void setup() throws Exception {
- final DockerClientBuilder> builder = DockerClientBuilderFactory.newInstance().fromEnv();
+ final DockerClientBuilder builder = DockerClientBuilder.fromEnv();
// Make it easier to test no read timeout occurs by using a smaller value
// Such test methods should end in 'NoTimeout'
if (testName.getMethodName().endsWith("NoTimeout")) {
@@ -1103,7 +1102,7 @@ public void integrationTest() throws Exception {
@Test(expected = DockerException.class)
public void testConnectTimeout() throws Exception {
// Attempt to connect to reserved IP -> should timeout
- try (final DefaultDockerClient connectTimeoutClient = DockerClientBuilderFactory.newInstance()
+ try (final DefaultDockerClient connectTimeoutClient = DockerClientBuilder.fromEnv()
.uri("http://240.0.0.1:2375")
.connectTimeoutMillis(100)
.readTimeoutMillis(NO_TIMEOUT)
@@ -1118,7 +1117,7 @@ public void testReadTimeout() throws Exception {
// Bind and listen but do not accept -> read will time out.
socket.bind(new InetSocketAddress("127.0.0.1", 0));
awaitConnectable(socket.getInetAddress(), socket.getLocalPort());
- try (final DockerClient connectTimeoutClient = DockerClientBuilderFactory.newInstance()
+ try (final DockerClient connectTimeoutClient = DockerClientBuilder.fromEnv()
.uri("http://127.0.0.1:" + socket.getLocalPort())
.connectTimeoutMillis(NO_TIMEOUT)
.readTimeoutMillis(100)
@@ -1139,7 +1138,7 @@ public void testConnectionRequestTimeout() throws Exception {
// Spawn and wait on many more containers than the connection pool size.
// This should cause a timeout once the connection pool is exhausted.
- try (final DockerClient dockerClient = DockerClientBuilderFactory.newInstance().fromEnv()
+ try (final DockerClient dockerClient = DockerClientBuilder.fromEnv()
.connectionPoolSize(connectionPoolSize)
.build()) {
// Create container
diff --git a/src/test/java/org/mandas/docker/client/DefaultDockerClientUnitTest.java b/src/test/java/org/mandas/docker/client/DefaultDockerClientUnitTest.java
index f8c600e8..60b09568 100644
--- a/src/test/java/org/mandas/docker/client/DefaultDockerClientUnitTest.java
+++ b/src/test/java/org/mandas/docker/client/DefaultDockerClientUnitTest.java
@@ -65,12 +65,12 @@
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
-import org.mandas.docker.DockerClientBuilderFactory;
import org.mandas.docker.client.DockerClient.Signal;
import org.mandas.docker.client.auth.RegistryAuthSupplier;
import org.mandas.docker.client.builder.DockerClientBuilder;
import org.mandas.docker.client.builder.DockerClientBuilder.EntityProcessing;
import org.mandas.docker.client.exceptions.ConflictException;
+import org.mandas.docker.client.exceptions.DockerCertificateException;
import org.mandas.docker.client.exceptions.DockerException;
import org.mandas.docker.client.exceptions.NodeNotFoundException;
import org.mandas.docker.client.exceptions.NonSwarmNodeException;
@@ -142,13 +142,13 @@ public class DefaultDockerClientUnitTest {
private final MockWebServer server = new MockWebServer();
- private DockerClientBuilder> builder;
+ private DockerClientBuilder builder;
@Before
public void setup() throws Exception {
server.start();
- builder = DockerClientBuilderFactory.newInstance();
+ builder = DockerClientBuilder.fromEnv();
builder.uri(server.url("/").uri());
}
@@ -158,32 +158,32 @@ public void tearDown() throws Exception {
}
@Test
- public void testHostForUnixSocket() {
- try (final DefaultDockerClient client = DockerClientBuilderFactory.newInstance()
+ public void testHostForUnixSocket() throws DockerCertificateException {
+ try (final DefaultDockerClient client = DockerClientBuilder.fromEnv()
.uri("unix:///var/run/docker.sock").build()) {
assertThat(client.getHost(), equalTo("localhost"));
}
}
@Test
- public void testHostForLocalHttps() {
- try (final DefaultDockerClient client = DockerClientBuilderFactory.newInstance()
+ public void testHostForLocalHttps() throws DockerCertificateException {
+ try (final DefaultDockerClient client = DockerClientBuilder.fromEnv()
.uri("https://localhost:2375").build()) {
assertThat(client.getHost(), equalTo("localhost"));
}
}
@Test
- public void testHostForFqdnHttps() {
- try (final DefaultDockerClient client = DockerClientBuilderFactory.newInstance()
+ public void testHostForFqdnHttps() throws DockerCertificateException {
+ try (final DefaultDockerClient client = DockerClientBuilder.fromEnv()
.uri("https://perdu.com:2375").build()) {
assertThat(client.getHost(), equalTo("perdu.com"));
}
}
@Test
- public void testHostForIpHttps() {
- try (final DefaultDockerClient client = DockerClientBuilderFactory.newInstance()
+ public void testHostForIpHttps() throws DockerCertificateException {
+ try (final DefaultDockerClient client = DockerClientBuilder.fromEnv()
.uri("https://192.168.53.103:2375").build()) {
assertThat(client.getHost(), equalTo("192.168.53.103"));
}