8000 Support docker context by skagedal · Pull Request #2036 · docker-java/docker-java · GitHub
[go: up one dir, main page]

Skip to content

Support docker context #2036

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Feb 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.github.dockerjava.api.model.AuthConfigurations;
import com.github.dockerjava.core.NameParser.HostnameReposName;
import com.github.dockerjava.core.NameParser.ReposTag;
import java.util.Optional;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.apache.commons.lang3.builder.EqualsBuilder;
Expand Down Expand Up @@ -37,6 +38,8 @@ public class DefaultDockerClientConfig implements Serializable, DockerClientConf

public static final String DOCKER_HOST = "DOCKER_HOST";

public static final String DOCKER_CONTEXT = "DOCKER_CONTEXT";

public static final String DOCKER_TLS_VERIFY = "DOCKER_TLS_VERIFY";

public static final String DOCKER_CONFIG = "DOCKER_CONFIG";
Expand Down Expand Up @@ -87,11 +90,13 @@ public class DefaultDockerClientConfig implements Serializable, DockerClientConf

private final RemoteApiVersion apiVersion;

private DockerConfigFile dockerConfig = null;
private final DockerConfigFile dockerConfig;

DefaultDockerClientConfig(URI dockerHost, String dockerConfigPath, String apiVersion, String registryUrl,
String registryUsername, String registryPassword, String registryEmail, SSLConfig sslConfig) {
DefaultDockerClientConfig(URI dockerHost, DockerConfigFile dockerConfigFile, String dockerConfigPath, String apiVersion,
String registryUrl, String registryUsername, String registryPassword, String registryEmail,
SSLConfig sslConfig) {
this.dockerHost = checkDockerHostScheme(dockerHost);
this.dockerConfig = dockerConfigFile;
this.dockerConfigPath = dockerConfigPath;
this.apiVersion = RemoteApiVersion.parseConfigWithDefault(apiVersion);
this.sslConfig = sslConfig;
Expand Down Expand Up @@ -174,6 +179,13 @@ private static Properties overrideDockerPropertiesWithEnv(Properties properties,
}
}

if (env.containsKey(DOCKER_CONTEXT)) {
String value = env.get(DOCKER_CONTEXT);
if (value != null && value.trim().length() != 0) {
overriddenProperties.setProperty(DOCKER_CONTEXT, value);
}
}

for (Map.Entry<String, String> envEntry : env.entrySet()) {
String envKey = envEntry.getKey();
if (CONFIG_KEYS.contains(envKey)) {
Expand Down Expand Up @@ -258,13 +270,6 @@ public String getDockerConfigPath() {

@Nonnull
public DockerConfigFile getDockerConfig() {
if (dockerConfig == null) {
try {
dockerConfig = DockerConfigFile.loadConfig(getObjectMapper(), getDockerConfigPath());
} catch (IOException e) {
throw new DockerClientException("Failed to parse docker configuration file", e);
}
}
return dockerConfig;
}

Expand Down Expand Up @@ -325,7 +330,7 @@ public static class Builder {
private URI dockerHost;

private String apiVersion, registryUsername, registryPassword, registryEmail, registryUrl, dockerConfig,
dockerCertPath;
dockerCertPath, dockerContext;

private Boolean dockerTlsVerify;

Expand All @@ -343,6 +348,7 @@ public Builder withProperties(Properties p) {
}

return withDockerTlsVerify(p.getProperty(DOCKER_TLS_VERIFY))
.withDockerContext(p.getProperty(DOCKER_CONTEXT))
.withDockerConfig(p.getProperty(DOCKER_CONFIG))
.withDockerCertPath(p.getProperty(DOCKER_CERT_PATH))
.withApiVersion(p.getProperty(API_VERSION))
Expand Down Expand Up @@ -401,6 +407,11 @@ public final Builder withDockerConfig(String dockerConfig) {
return this;
}

public final Builder withDockerContext(String dockerContext) {
this.dockerContext = dockerContext;
return this;
}

public final Builder withDockerTlsVerify(String dockerTlsVerify) {
if (dockerTlsVerify != null) {
String trimmed = dockerTlsVerify.trim();
Expand Down Expand Up @@ -443,14 +454,33 @@ public DefaultDockerClientConfig build() {
sslConfig = customSslConfig;
}

final DockerConfigFile dockerConfigFile = readDockerConfig();

final String context = (dockerContext != null) ? dockerContext : dockerConfigFile.getCurrentContext();
URI dockerHostUri = dockerHost != null
? dockerHost
: URI.create(SystemUtils.IS_OS_WINDOWS ? WINDOWS_DEFAULT_DOCKER_HOST : DEFAULT_DOCKER_HOST);
: resolveDockerHost(context);

return new DefaultDockerClientConfig(dockerHostUri, dockerConfig, apiVersion, registryUrl, registryUsername,
return new DefaultDockerClientConfig(dockerHostUri, dockerConfigFile, dockerConfig, apiVersion, registryUrl, registryUsername,
registryPassword, registryEmail, sslConfig);
}

private DockerConfigFile readDockerConfig() {
try {
return DockerConfigFile.loadConfig(DockerClientConfig.getDefaultObjectMapper(), dockerConfig);
} catch (IOException e) {
throw new DockerClientException("Failed to parse docker configuration file", e);
}
}

private URI resolveDockerHost(String dockerContext) {
return URI.create(Optional.ofNullable(dockerContext)
.flatMap(context -> DockerContex 8000 tMetaFile.resolveContextMetaFile(
DockerClientConfig.getDefaultObjectMapper(), new File(dockerConfig), context))
.flatMap(DockerContextMetaFile::host)
.orElse(SystemUtils.IS_OS_WINDOWS ? WINDOWS_DEFAULT_DOCKER_HOST : DEFAULT_DOCKER_HOST));
}

private String checkDockerCertPath(String dockerCertPath) {
if (StringUtils.isEmpty(dockerCertPath)) {
throw new DockerClientException(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.dockerjava.api.model.AuthConfig;
import com.github.dockerjava.api.model.AuthConfigurations;
import java.util.Objects;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;

Expand All @@ -29,6 +30,9 @@ public class DockerConfigFile {
@JsonProperty
private final Map<String, AuthConfig> auths;

@JsonProperty
private String currentContext;

public DockerConfigFile() {
this(new HashMap<>());
}
Expand All @@ -46,6 +50,14 @@ void addAuthConfig(AuthConfig config) {
auths.put(config.getRegistryAddress(), config);
}

void setCurrentContext(String currentContext) {
this.currentContext = currentContext;
}

public String getCurrentContext() {
return currentContext;
}

@CheckForNull
public AuthConfig resolveAuthConfig(@CheckForNull String hostname) {
if (StringUtils.isEmpty(hostname) || AuthConfig.DEFAULT_SERVER_ADDRESS.equals(hostname)) {
Expand Down Expand Up @@ -104,14 +116,17 @@ public boolean equals(Object obj) {
return false;
} else if (!auths.equals(other.auths))
return false;
if (!Objects.equals(currentContext, other.currentContext)) {
return false;
}
return true;
}

// CHECKSTYLE:ON

@Override
public String toString() {
return "DockerConfigFile [auths=" + auths + "]";
return "DockerConfigFile [auths=" + auths + ", currentContext='" + currentContext + "']";
}

@Nonnull
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.github.dockerjava.core;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Optional;

public class DockerContextMetaFile {
private static HashFunction metaHashFunction = Hashing.sha256();

@JsonProperty("Name")
String name;

@JsonProperty("Endpoints")
Endpoints endpoints;

public static class Endpoints {
@JsonProperty("docker")
Docker docker;

public static class Docker {
@JsonProperty("Host")
String host;

@JsonProperty("SkipTLSVerify")
boolean skipTLSVerify;
}
}

public Optional<String> host() {
if (endpoints != null && endpoints.docker != null) {
return Optional.ofNullable(endpoints.docker.host);
}
return Optional.empty();
}

public static Optional<DockerContextMetaFile> resolveContextMetaFile(ObjectMapper objectMapper, File dockerConfigPath, String context) {
final File path = dockerConfigPath.toPath()
.resolve("contexts")
.resolve("meta")
.resolve(metaHashFunction.hashString(context, StandardCharsets.UTF_8).toString())
.resolve("meta.json")
.toFile();
return Optional.ofNullable(loadContextMetaFile(objectMapper, path));
}

public static DockerContextMetaFile loadContextMetaFile(ObjectMapper objectMapper, File dockerContextMetaFile) {
try {
return parseContextMetaFile(objectMapper, dockerContextMetaFile);
} catch (Exception exception) {
return null;
}
}

public static DockerContextMetaFile parseContextMetaFile(ObjectMapper objectMapper, File dockerContextMetaFile) throws IOException {
try {
return objectMapper.readValue(dockerContextMetaFile, DockerContextMetaFile.class);
} catch (IOException e) {
throw new IOException("Failed to parse docker context meta file " + dockerContextMetaFile, e);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package com.github.dockerjava.core;

import com.github.dockerjava.api.exception.DockerClientException;
import com.github.dockerjava.api.model.AuthConfig;
import com.github.dockerjava.api.model.AuthConfigurations;
import com.google.common.io.Resources;
import java.io.IOException;
import org.apache.commons.lang3.SerializationUtils;
import org.junit.Test;

Expand All @@ -27,13 +27,26 @@
public class DefaultDockerClientConfigTest {

public static final DefaultDockerClientConfig EXAMPLE_CONFIG = newExampleConfig();
public static final DefaultDockerClientConfig EXAMPLE_CONFIG_FULLY_LOADED = newExampleConfigFullyLoaded();

private static DefaultDockerClientConfig newExampleConfig() {

String dockerCertPath = dockerCertPath();
return new DefaultDockerClientConfig(URI.create("tcp://foo"), null, "dockerConfig", "apiVersion", "registryUrl",
"registryUsername", "registryPassword", "registryEmail",
new LocalDirectorySSLConfig(dockerCertPath));
}

return new DefaultDockerClientConfig(URI.create("tcp://foo"), "dockerConfig", "apiVersion", "registryUrl", "registryUsername", "registryPassword", "registryEmail",
private static DefaultDockerClientConfig newExampleConfigFullyLoaded() {
try {
String dockerCertPath = dockerCertPath();
String dockerConfig = "dockerConfig";
DockerConfigFile loadedConfigFile = DockerConfigFile.loadConfig(DockerClientConfig.getDefaultObjectMapper(), dockerConfig);
return new DefaultDockerClientConfig(URI.create("tcp://foo"), loadedConfigFile, dockerConfig, "apiVersion", "registryUrl",
"registryUsername", "registryPassword", "registryEmail",
new LocalDirectorySSLConfig(dockerCertPath));
} catch (IOException exception) {
throw new RuntimeException(exception);
}
}

private static String homeDir() {
Expand Down Expand Up @@ -69,6 +82,37 @@ public void environmentDockerHost() throws Exception {
assertEquals(config.getDockerHost(), URI.create("tcp://baz:8768"));
}

@Test
public void dockerContextFromConfig() throws Exception {
// given home directory with docker contexts configured
Properties systemProperties = new Properties();
systemProperties.setProperty("user.home", "target/test-classes/dockerContextHomeDir");

// and an empty environment
Map<String, String> env = new HashMap<>();

// when you build a config
DefaultDockerClientConfig config = buildConfig(env, systemProperties);

assertEquals(URI.create("unix:///configcontext.sock"), config.getDockerHost());
}

@Test
public void dockerContextFromEnvironmentVariable() throws Exception {
// given home directory with docker contexts
Properties systemProperties = new Properties();
systemProperties.setProperty("user.home", "target/test-classes/dockerContextHomeDir");

// and an environment variable that overrides docker context
Map<String, String> env = new HashMap<>();
env.put(DefaultDockerClientConfig.DOCKER_CONTEXT, "envvarcontext");

// when you build a config
DefaultDockerClientConfig config = buildConfig(env, systemProperties);

assertEquals(URI.create("unix:///envvarcontext.sock"), config.getDockerHost());
}

@Test
public void environment() throws Exception {

Expand All @@ -88,7 +132,7 @@ public void environment() throws Exception {
DefaultDockerClientConfig config = buildConfig(env, new Properties());

// then we get the example object
assertEquals(config, EXAMPLE_CONFIG);
assertEquals(EXAMPLE_CONFIG_FULLY_LOADED, config);
}

@Test
Expand Down Expand Up @@ -147,7 +191,7 @@ public void systemProperties() throws Exception {
DefaultDockerClientConfig config = buildConfig(Collections.<String, String> emptyMap(), systemProperties);

// then it is the same as the example
assertEquals(config, EXAMPLE_CONFIG);
assertEquals(EXAMPLE_CONFIG_FULLY_LOADED, config);

}

Expand All @@ -161,22 +205,22 @@ public void serializableTest() {

@Test()
public void testSslContextEmpty() throws Exception {
new DefaultDockerClientConfig(URI.create("tcp://foo"), "dockerConfig", "apiVersion", "registryUrl", "registryUsername", "registryPassword", "registryEmail",
new DefaultDockerClientConfig(URI.create("tcp://foo"), new DockerConfigFile(), "dockerConfig", "apiVersion", "registryUrl", "registryUsername", "registryPassword", "registryEmail",
null);
}



@Test()
public void testTlsVerifyAndCertPath() throws Exception {
new DefaultDockerClientConfig(URI.create("tcp://foo"), "dockerConfig", "apiVersion", "registryUrl", "registryUsername", "registryPassword", "registryEmail",
new DefaultDockerClientConfig(URI.create("tcp://foo"), new DockerConfigFile(), "dockerConfig", "apiVersion", "registryUrl", "registryUsername", "registryPassword", "registryEmail",
new LocalDirectorySSLConfig(dockerCertPath()));
}

@Test()
public void testAnyHostScheme() throws Exception {
URI dockerHost = URI.create("a" + UUID.randomUUID().toString().replace("-", "") + "://foo");
new DefaultDockerClientConfig(dockerHost, "dockerConfig", "apiVersion", "registryUrl", "registryUsername", "registryPassword", "registryEmail",
new DefaultDockerClientConfig(dockerHost, new DockerConfigFile(), "dockerConfig", "apiVersion", "registryUrl", "registryUsername", "registryPassword", "registryEmail",
null);
}

Expand Down Expand Up @@ -249,10 +293,12 @@ public void dockerHostSetExplicitlyIfSetToDefaultByUser() {


@Test
public void testGetAuthConfigurationsFromDockerCfg() throws URISyntaxException {
public void testGetAuthConfigurationsFromDockerCfg() throws URISyntaxException, IOException {
File cfgFile = new File(Resources.getResource("com.github.dockerjava.core/registry.v1").toURI());
DockerConfigFile dockerConfigFile =
DockerConfigFile.loadConfig(DockerClientConfig.getDefaultObjectMapper(), cfgFile.getAbsolutePath());
DefaultDockerClientConfig clientConfig = new DefaultDockerClientConfig(URI.create(
"unix://foo"), cfgFile.getAbsolutePath(), "apiVersion", "registryUrl", "registryUsername", "registryPassword",
"unix://foo"), dockerConfigFile, cfgFile.getAbsolutePath(), "apiVersion", "registryUrl", "registryUsername", "registryPassword",
"registryEmail", null);

AuthConfigurations authConfigurations = clientConfig.getAuthConfigurations();
Expand All @@ -265,10 +311,12 @@ public void testGetAuthConfigurationsFromDockerCfg() throws URISyntaxException {
}

@Test
public void testGetAuthConfigurationsFromConfigJson() throws URISyntaxException {
public void testGetAuthConfigurationsFromConfigJson() throws URISyntaxException, IOException {
File cfgFile = new File(Resources.getResource("com.github.dockerjava.core/registry.v2").toURI());
DockerConfigFile dockerConfigFile =
DockerConfigFile.loadConfig(DockerClientConfig.getDefaultObjectMapper(), cfgFile.getAbsolutePath());
DefaultDockerClientConfig clientConfig = new DefaultDockerClientConfig(URI.create(
"unix://foo"), cfgFile.getAbsolutePath(), "apiVersion", "registryUrl", "registryUsername", "registryPassword",
"unix://foo"), dockerConfigFile, cfgFile.getAbsolutePath(), "apiVersion", "registryUrl", "registryUsername", "registryPassword",
"registryEmail", null);

AuthConfigurations authConfigurations = clientConfig.getAuthConfigurations();
Expand Down
Loading
0