package com.github.dockerjava.core; import static com.google.common.base.Preconditions.checkNotNull; import static org.apache.commons.lang.BooleanUtils.isTrue; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.HashSet; import java.util.Map; import java.util.Properties; import java.util.Set; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.builder.EqualsBuilder; import org.apache.commons.lang.builder.HashCodeBuilder; import org.apache.commons.lang.builder.ToStringBuilder; import org.apache.commons.lang.builder.ToStringStyle; import com.github.dockerjava.api.exception.DockerClientException; import com.github.dockerjava.api.model.AuthConfig; import com.github.dockerjava.api.model.AuthConfigurations; import com.github.dockerjava.core.NameParser.HostnameReposName; import com.github.dockerjava.core.NameParser.ReposTag; /** * Respects some of the docker CLI options. See https://docs.docker.com/engine/reference/commandline/cli/#environment-variables */ public class DefaultDockerClientConfig implements Serializable, DockerClientConfig { private static final long serialVersionUID = 1L; public static final String DOCKER_HOST = "DOCKER_HOST"; public static final String DOCKER_TLS_VERIFY = "DOCKER_TLS_VERIFY"; public static final String DOCKER_CONFIG = "DOCKER_CONFIG"; public static final String DOCKER_CERT_PATH = "DOCKER_CERT_PATH"; public static final String API_VERSION = "api.version"; public static final String REGISTRY_USERNAME = "registry.username"; public static final String REGISTRY_PASSWORD = "registry.password"; public static final String REGISTRY_EMAIL = "registry.email"; public static final String REGISTRY_URL = "registry.url"; private static final String DOCKER_JAVA_PROPERTIES = "docker-java.properties"; private static final String DOCKER_CFG = ".dockercfg"; private static final String CONFIG_JSON = "config.json"; private static final Set<String> CONFIG_KEYS = new HashSet<String>(); static { CONFIG_KEYS.add(DOCKER_HOST); CONFIG_KEYS.add(DOCKER_TLS_VERIFY); CONFIG_KEYS.add(DOCKER_CONFIG); CONFIG_KEYS.add(DOCKER_CERT_PATH); CONFIG_KEYS.add(API_VERSION); CONFIG_KEYS.add(REGISTRY_USERNAME); CONFIG_KEYS.add(REGISTRY_PASSWORD); CONFIG_KEYS.add(REGISTRY_EMAIL); CONFIG_KEYS.add(REGISTRY_URL); } private final URI dockerHost; private final String registryUsername, registryPassword, registryEmail, registryUrl, dockerConfig; private final SSLConfig sslConfig; private final RemoteApiVersion apiVersion; DefaultDockerClientConfig(URI dockerHost, String dockerConfig, String apiVersion, String registryUrl, String registryUsername, String registryPassword, String registryEmail, SSLConfig sslConfig) { this.dockerHost = checkDockerHostScheme(dockerHost); this.dockerConfig = dockerConfig; this.apiVersion = RemoteApiVersion.parseConfigWithDefault(apiVersion); this.sslConfig = sslConfig; this.registryUsername = registryUsername; this.registryPassword = registryPassword; this.registryEmail = registryEmail; this.registryUrl = registryUrl; } private URI checkDockerHostScheme(URI dockerHost) { if ("tcp".equals(dockerHost.getScheme()) || "unix".equals(dockerHost.getScheme())) { return dockerHost; } else { throw new DockerClientException("Unsupported protocol scheme found: '" + dockerHost + "'. Only 'tcp://' or 'unix://' supported."); } } private static Properties loadIncludedDockerProperties(Properties systemProperties) { try (InputStream is = DefaultDockerClientConfig.class.getResourceAsStream("/" + DOCKER_JAVA_PROPERTIES)) { Properties p = new Properties(); p.load(is); replaceProperties(p, systemProperties); return p; } catch (IOException e) { throw new RuntimeException(e); } } private static void replaceProperties(Properties properties, Properties replacements) { for (Object objectKey : properties.keySet()) { String key = objectKey.toString(); properties.setProperty(key, replaceProperties(properties.getProperty(key), replacements)); } } private static String replaceProperties(String s, Properties replacements) { for (Map.Entry<Object, Object> entry : replacements.entrySet()) { String key = "${" + entry.getKey() + "}"; while (s.contains(key)) { s = s.replace(key, String.valueOf(entry.getValue())); } } return s; } /** * Creates a new Properties object containing values overridden from ${user.home}/.docker.io.properties * * @param p * The original set of properties to override * @return A copy of the original Properties with overridden values */ private static Properties overrideDockerPropertiesWithSettingsFromUserHome(Properties p, Properties systemProperties) { Properties overriddenProperties = new Properties(); overriddenProperties.putAll(p); final File usersDockerPropertiesFile = new File(systemProperties.getProperty("user.home"), "." + DOCKER_JAVA_PROPERTIES); if (usersDockerPropertiesFile.isFile()) { try (FileInputStream in = new FileInputStream(usersDockerPropertiesFile)) { overriddenProperties.load(in); } catch (IOException e) { throw new RuntimeException(e); } } return overriddenProperties; } private static Properties overrideDockerPropertiesWithEnv(Properties properties, Map<String, String> env) { Properties overriddenProperties = new Properties(); overriddenProperties.putAll(properties); // special case which is a sensible default if (env.containsKey(DOCKER_HOST)) { overriddenProperties.setProperty(DOCKER_HOST, env.get(DOCKER_HOST)); } for (Map.Entry<String, String> envEntry : env.entrySet()) { String envKey = envEntry.getKey(); if (CONFIG_KEYS.contains(envKey)) { overriddenProperties.setProperty(envKey, envEntry.getValue()); } } return overriddenProperties; } /** * Creates a new Properties object containing values overridden from the System properties * * @param p * The original set of properties to override * @return A copy of the original Properties with overridden values */ private static Properties overrideDockerPropertiesWithSystemProperties(Properties p, Properties systemProperties) { Properties overriddenProperties = new Properties(); overriddenProperties.putAll(p); for (String key : CONFIG_KEYS) { if (systemProperties.containsKey(key)) { overriddenProperties.setProperty(key, systemProperties.getProperty(key)); } } return overriddenProperties; } public static Builder createDefaultConfigBuilder() { return createDefaultConfigBuilder(System.getenv(), (Properties) System.getProperties().clone()); } /** * Allows you to build the config without system environment interfering for more robust testing */ static Builder createDefaultConfigBuilder(Map<String, String> env, Properties systemProperties) { Properties properties = loadIncludedDockerProperties(systemProperties); properties = overrideDockerPropertiesWithSettingsFromUserHome(properties, systemProperties); properties = overrideDockerPropertiesWithEnv(properties, env); properties = overrideDockerPropertiesWithSystemProperties(properties, systemProperties); return new Builder().withProperties(properties); } @Override public URI getDockerHost() { return dockerHost; } @Override public RemoteApiVersion getApiVersion() { return apiVersion; } @Override public String getRegistryUsername() { return registryUsername; } @Override public String getRegistryPassword() { return registryPassword; } @Override public String getRegistryEmail() { return registryEmail; } @Override public String getRegistryUrl() { return registryUrl; } public String getDockerConfig() { return dockerConfig; } private AuthConfig getAuthConfig() { AuthConfig authConfig = null; if (getRegistryUsername() != null && getRegistryPassword() != null && getRegistryEmail() != null && getRegistryUrl() != null) { authConfig = new AuthConfig() .withUsername(getRegistryUsername()) .withPassword(getRegistryPassword()) .withEmail(getRegistryEmail()) .withRegistryAddress(getRegistryUrl()); } return authConfig; } @Override public AuthConfig effectiveAuthConfig(String imageName) { AuthConfig authConfig = null; File dockerCfgFile = getDockerConfigFile(); if (dockerCfgFile != null) { AuthConfigFile authConfigFile; try { authConfigFile = AuthConfigFile.loadConfig(dockerCfgFile); } catch (IOException e) { throw new DockerClientException("Failed to parse dockerCfgFile", e); } ReposTag reposTag = NameParser.parseRepositoryTag(imageName); HostnameReposName hostnameReposName = NameParser.resolveRepositoryName(reposTag.repos); authConfig = authConfigFile.resolveAuthConfig(hostnameReposName.hostname); } AuthConfig otherAuthConfig = getAuthConfig(); if (otherAuthConfig != null) { authConfig = otherAuthConfig; } return authConfig; } @Override public AuthConfigurations getAuthConfigurations() { File dockerCfgFile = getDockerConfigFile(); if (dockerCfgFile != null) { AuthConfigFile authConfigFile; try { authConfigFile = AuthConfigFile.loadConfig(dockerCfgFile); } catch (IOException e) { throw new DockerClientException("Failed to parse dockerCfgFile: " + dockerCfgFile.getAbsolutePath(), e); } return authConfigFile.getAuthConfigurations(); } return new AuthConfigurations(); } private File getDockerConfigFile() { final Path configJson = Paths.get(getDockerConfig(), CONFIG_JSON); final Path dockerCfg = Paths.get(getDockerConfig(), DOCKER_CFG); if (Files.exists(configJson)) { return configJson.toFile(); } else if (Files.exists(dockerCfg)) { return dockerCfg.toFile(); } else { return null; } } @Override public SSLConfig getSSLConfig() { return sslConfig; } @Override public boolean equals(Object o) { return EqualsBuilder.reflectionEquals(this, o); } @Override public int hashCode() { return HashCodeBuilder.reflectionHashCode(this); } @Override public String toString() { return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE); } public static class Builder { private URI dockerHost; private String apiVersion, registryUsername, registryPassword, registryEmail, registryUrl, dockerConfig, dockerCertPath; private Boolean dockerTlsVerify; private SSLConfig customSslConfig = null; /** * This will set all fields in the builder to those contained in the Properties object. The Properties object should contain the * following docker-java configuration keys: DOCKER_HOST, DOCKER_TLS_VERIFY, api.version, registry.username, registry.password, * registry.email, DOCKER_CERT_PATH, and DOCKER_CONFIG. */ public Builder withProperties(Properties p) { return withDockerHost(p.getProperty(DOCKER_HOST)) .withDockerTlsVerify(p.getProperty(DOCKER_TLS_VERIFY)) .withDockerConfig(p.getProperty(DOCKER_CONFIG)) .withDockerCertPath(p.getProperty(DOCKER_CERT_PATH)) .withApiVersion(p.getProperty(API_VERSION)) .withRegistryUsername(p.getProperty(REGISTRY_USERNAME)) .withRegistryPassword(p.getProperty(REGISTRY_PASSWORD)) .withRegistryEmail(p.getProperty(REGISTRY_EMAIL)) .withRegistryUrl(p.getProperty(REGISTRY_URL)); } /** * configure DOCKER_HOST */ public final Builder withDockerHost(String dockerHost) { checkNotNull(dockerHost, "uri was not specified"); this.dockerHost = URI.create(dockerHost); return this; } public final Builder withApiVersion(RemoteApiVersion apiVersion) { this.apiVersion = apiVersion.getVersion(); return this; } public final Builder withApiVersion(String apiVersion) { this.apiVersion = apiVersion; return this; } public final Builder withRegistryUsername(String registryUsername) { this.registryUsername = registryUsername; return this; } public final Builder withRegistryPassword(String registryPassword) { this.registryPassword = registryPassword; return this; } public final Builder withRegistryEmail(String registryEmail) { this.registryEmail = registryEmail; return this; } public Builder withRegistryUrl(String registryUrl) { this.registryUrl = registryUrl; return this; } public final Builder withDockerCertPath(String dockerCertPath) { this.dockerCertPath = dockerCertPath; return this; } public final Builder withDockerConfig(String dockerConfig) { this.dockerConfig = dockerConfig; return this; } public final Builder withDockerTlsVerify(String dockerTlsVerify) { if (dockerTlsVerify != null) { String trimmed = dockerTlsVerify.trim(); this.dockerTlsVerify = "true".equalsIgnoreCase(trimmed) || "1".equals(trimmed); } else { this.dockerTlsVerify = false; } return this; } public final Builder withDockerTlsVerify(Boolean dockerTlsVerify) { this.dockerTlsVerify = dockerTlsVerify; return this; } /** * Overrides the default {@link SSLConfig} that is used when calling {@link Builder#withDockerTlsVerify(java.lang.Boolean)} and * {@link Builder#withDockerCertPath(String)}. This way it is possible to pass a custom {@link SSLConfig} to the resulting * {@link DockerClientConfig} that may be created by other means than the local file system. */ public final Builder withCustomSslConfig(SSLConfig customSslConfig) { this.customSslConfig = customSslConfig; return this; } public DefaultDockerClientConfig build() { SSLConfig sslConfig = null; if (customSslConfig == null) { if (isTrue(dockerTlsVerify)) { dockerCertPath = checkDockerCertPath(dockerCertPath); sslConfig = new LocalDirectorySSLConfig(dockerCertPath); } } else { sslConfig = customSslConfig; } return new DefaultDockerClientConfig(dockerHost, dockerConfig, apiVersion, registryUrl, registryUsername, registryPassword, registryEmail, sslConfig); } private String checkDockerCertPath(String dockerCertPath) { if (StringUtils.isEmpty(dockerCertPath)) { throw new DockerClientException( "Enabled TLS verification (DOCKER_TLS_VERIFY=1) but certifate path (DOCKER_CERT_PATH) is not defined."); } File certPath = new File(dockerCertPath); if (!certPath.exists()) { throw new DockerClientException( "Enabled TLS verification (DOCKER_TLS_VERIFY=1) but certificate path (DOCKER_CERT_PATH) '" + dockerCertPath + "' doesn't exist."); } else if (!certPath.isDirectory()) { throw new DockerClientException( "Enabled TLS verification (DOCKER_TLS_VERIFY=1) but certificate path (DOCKER_CERT_PATH) '" + dockerCertPath + "' doesn't point to a directory."); } return dockerCertPath; } } }