Support local socket path in DOCKER_HOST
Prior to this commit, if a DOCKER_HOST environment variable was present when attempting to communicate with a Docker daemon, it was assumed that the value of that variable was an address that could be used to create an HTTP connection to a remote daemon. In some cases, the value of the variable is the path to a local socket file, which would cause the HTTP connection to fail. This commit adds additional validation of the value of the DOCKER_HOST environment variable to determine whether it is a remote address or a local socket file and create the appropriate connection type. Fixes gh-21173
This commit is contained in:
@@ -29,6 +29,7 @@ import org.springframework.boot.buildpack.platform.system.Environment;
|
||||
* HTTP transport used for docker access.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Scott Frederick
|
||||
* @since 2.3.0
|
||||
*/
|
||||
public interface HttpTransport {
|
||||
@@ -94,7 +95,7 @@ public interface HttpTransport {
|
||||
*/
|
||||
static HttpTransport create(Environment environment) {
|
||||
HttpTransport remote = RemoteHttpClientTransport.createIfPossible(environment);
|
||||
return (remote != null) ? remote : LocalHttpClientTransport.create();
|
||||
return (remote != null) ? remote : LocalHttpClientTransport.create(environment);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -40,6 +40,7 @@ import org.apache.http.util.Args;
|
||||
|
||||
import org.springframework.boot.buildpack.platform.socket.DomainSocket;
|
||||
import org.springframework.boot.buildpack.platform.socket.NamedPipeSocket;
|
||||
import org.springframework.boot.buildpack.platform.system.Environment;
|
||||
|
||||
/**
|
||||
* {@link HttpClientTransport} that talks to local Docker.
|
||||
@@ -49,15 +50,17 @@ import org.springframework.boot.buildpack.platform.socket.NamedPipeSocket;
|
||||
*/
|
||||
final class LocalHttpClientTransport extends HttpClientTransport {
|
||||
|
||||
private static final String DOCKER_HOST = "DOCKER_HOST";
|
||||
|
||||
private static final HttpHost LOCAL_DOCKER_HOST = HttpHost.create("docker://localhost");
|
||||
|
||||
private LocalHttpClientTransport(CloseableHttpClient client) {
|
||||
super(client, LOCAL_DOCKER_HOST);
|
||||
}
|
||||
|
||||
static LocalHttpClientTransport create() {
|
||||
static LocalHttpClientTransport create(Environment environment) {
|
||||
HttpClientBuilder builder = HttpClients.custom();
|
||||
builder.setConnectionManager(new LocalConnectionManager());
|
||||
builder.setConnectionManager(new LocalConnectionManager(environment.get(DOCKER_HOST)));
|
||||
builder.setSchemePortResolver(new LocalSchemePortResolver());
|
||||
return new LocalHttpClientTransport(builder.build());
|
||||
}
|
||||
@@ -67,13 +70,13 @@ final class LocalHttpClientTransport extends HttpClientTransport {
|
||||
*/
|
||||
private static class LocalConnectionManager extends BasicHttpClientConnectionManager {
|
||||
|
||||
LocalConnectionManager() {
|
||||
super(getRegistry(), null, null, new LocalDnsResolver());
|
||||
LocalConnectionManager(String host) {
|
||||
super(getRegistry(host), null, null, new LocalDnsResolver());
|
||||
}
|
||||
|
||||
private static Registry<ConnectionSocketFactory> getRegistry() {
|
||||
private static Registry<ConnectionSocketFactory> getRegistry(String host) {
|
||||
RegistryBuilder<ConnectionSocketFactory> builder = RegistryBuilder.create();
|
||||
builder.register("docker", new LocalConnectionSocketFactory());
|
||||
builder.register("docker", new LocalConnectionSocketFactory(host));
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@@ -103,12 +106,18 @@ final class LocalHttpClientTransport extends HttpClientTransport {
|
||||
|
||||
private static final String WINDOWS_NAMED_PIPE_PATH = "//./pipe/docker_engine";
|
||||
|
||||
private final String host;
|
||||
|
||||
LocalConnectionSocketFactory(String host) {
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(HttpContext context) throws IOException {
|
||||
if (Platform.isWindows()) {
|
||||
return NamedPipeSocket.get(WINDOWS_NAMED_PIPE_PATH);
|
||||
return NamedPipeSocket.get((this.host != null) ? this.host : WINDOWS_NAMED_PIPE_PATH);
|
||||
}
|
||||
return DomainSocket.get(DOMAIN_SOCKET_PATH);
|
||||
return DomainSocket.get((this.host != null) ? this.host : DOMAIN_SOCKET_PATH);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
package org.springframework.boot.buildpack.platform.docker.transport;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
||||
import org.apache.http.HttpHost;
|
||||
@@ -53,7 +56,10 @@ final class RemoteHttpClientTransport extends HttpClientTransport {
|
||||
|
||||
static RemoteHttpClientTransport createIfPossible(Environment environment, SslContextFactory sslContextFactory) {
|
||||
String host = environment.get(DOCKER_HOST);
|
||||
return (host != null) ? create(environment, sslContextFactory, HttpHost.create(host)) : null;
|
||||
if (host == null || Files.exists(Paths.get(host))) {
|
||||
return null;
|
||||
}
|
||||
return create(environment, sslContextFactory, HttpHost.create(host));
|
||||
}
|
||||
|
||||
private static RemoteHttpClientTransport create(Environment environment, SslContextFactory sslContextFactory,
|
||||
|
||||
@@ -16,10 +16,14 @@
|
||||
|
||||
package org.springframework.boot.buildpack.platform.docker.transport;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@@ -27,16 +31,25 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* Tests for {@link HttpTransport}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
class HttpTransportTests {
|
||||
|
||||
@Test
|
||||
void createWhenHasDockerHostVariableReturnsRemote() {
|
||||
Map<String, String> environment = Collections.singletonMap("DOCKER_HOST", "192.168.1.0");
|
||||
void createWhenDockerHostVariableIsAddressReturnsRemote() {
|
||||
Map<String, String> environment = Collections.singletonMap("DOCKER_HOST", "tcp://192.168.1.0");
|
||||
HttpTransport transport = HttpTransport.create(environment::get);
|
||||
assertThat(transport).isInstanceOf(RemoteHttpClientTransport.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenDockerHostVariableIsFileReturnsLocal(@TempDir Path tempDir) throws IOException {
|
||||
String dummySocketFilePath = Files.createTempFile(tempDir, "http-transport", null).toAbsolutePath().toString();
|
||||
Map<String, String> environment = Collections.singletonMap("DOCKER_HOST", dummySocketFilePath);
|
||||
HttpTransport transport = HttpTransport.create(environment::get);
|
||||
assertThat(transport).isInstanceOf(LocalHttpClientTransport.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenDoesNotHaveDockerHostVariableReturnsLocal() {
|
||||
HttpTransport transport = HttpTransport.create((name) -> null);
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
package org.springframework.boot.buildpack.platform.docker.transport;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
@@ -24,6 +27,7 @@ import javax.net.ssl.SSLContext;
|
||||
|
||||
import org.apache.http.HttpHost;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import org.springframework.boot.buildpack.platform.docker.ssl.SslContextFactory;
|
||||
|
||||
@@ -40,7 +44,7 @@ import static org.mockito.Mockito.mock;
|
||||
*/
|
||||
class RemoteHttpClientTransportTests {
|
||||
|
||||
private Map<String, String> environment = new LinkedHashMap<>();
|
||||
private final Map<String, String> environment = new LinkedHashMap<>();
|
||||
|
||||
@Test
|
||||
void createIfPossibleWhenDockerHostIsNotSetReturnsNull() {
|
||||
@@ -49,7 +53,16 @@ class RemoteHttpClientTransportTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void createIfPossibleWhenDockerHostIsSetReturnsTransport() {
|
||||
void createIfPossibleWhenDockerHostIsFileReturnsNull(@TempDir Path tempDir) throws IOException {
|
||||
String dummySocketFilePath = Files.createTempFile(tempDir, "remote-transport", null).toAbsolutePath()
|
||||
.toString();
|
||||
this.environment.put("DOCKER_HOST", dummySocketFilePath);
|
||||
RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(this.environment::get);
|
||||
assertThat(transport).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void createIfPossibleWhenDockerHostIsAddressReturnsTransport() {
|
||||
this.environment.put("DOCKER_HOST", "tcp://192.168.1.2:2376");
|
||||
RemoteHttpClientTransport transport = RemoteHttpClientTransport.createIfPossible(this.environment::get);
|
||||
assertThat(transport).isNotNull();
|
||||
|
||||
Reference in New Issue
Block a user