Skip to content

Commit

Permalink
Add a quick and dirty 'docker context' reader implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
gesellix committed Nov 2, 2022
1 parent e983f5a commit 770c27a
Show file tree
Hide file tree
Showing 18 changed files with 580 additions and 102 deletions.
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
package de.gesellix.docker.authentication;

import com.squareup.moshi.Moshi;
import de.gesellix.docker.engine.DockerConfigReader;
import de.gesellix.docker.engine.DockerEnv;
import okio.Okio;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.Map;

import static de.gesellix.docker.authentication.AuthConfig.EMPTY_AUTH_CONFIG;
Expand All @@ -17,21 +14,21 @@ public class AuthConfigReader {

private final static Logger log = LoggerFactory.getLogger(AuthConfigReader.class);

private final Moshi moshi = new Moshi.Builder().build();

private final DockerEnv env;
private DockerConfigReader dockerConfigReader;

public AuthConfigReader() {
this(new DockerEnv());
}

public AuthConfigReader(DockerEnv env) {
this.env = env;
this.dockerConfigReader = env.getDockerConfigReader();
}

// @Override
public AuthConfig readDefaultAuthConfig() {
return readAuthConfig(null, env.getDockerConfigFile());
return readAuthConfig(null, dockerConfigReader.getDockerConfigFile());
}

// @Override
Expand All @@ -42,7 +39,7 @@ public AuthConfig readAuthConfig(String hostname, File dockerCfg) {
hostname = env.getIndexUrl_v1();
}

Map parsedDockerCfg = readDockerConfigFile(dockerCfg);
Map parsedDockerCfg = dockerConfigReader.readDockerConfigFile(dockerCfg);
if (parsedDockerCfg == null || parsedDockerCfg.isEmpty()) {
return EMPTY_AUTH_CONFIG;
}
Expand All @@ -51,24 +48,6 @@ public AuthConfig readAuthConfig(String hostname, File dockerCfg) {
return credsStore.getAuthConfig(hostname);
}

public Map readDockerConfigFile(File dockerCfg) {
if (dockerCfg == null) {
dockerCfg = env.getDockerConfigFile();
}
if (dockerCfg == null || !dockerCfg.exists()) {
log.info("docker config '${dockerCfg}' doesn't exist");
return Collections.emptyMap();
}
log.debug("reading auth info from {}", dockerCfg);
try {
return moshi.adapter(Map.class).fromJson(Okio.buffer(Okio.source(dockerCfg)));
}
catch (Exception e) {
log.debug(MessageFormat.format("failed to read auth info from {}", dockerCfg), e);
return Collections.emptyMap();
}
}

public CredsStore getCredentialsStore(Map parsedDockerCfg) {
return getCredentialsStore(parsedDockerCfg, "");
}
Expand Down
32 changes: 32 additions & 0 deletions engine/src/main/java/de/gesellix/docker/context/ContextStore.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package de.gesellix.docker.context;

import de.gesellix.docker.engine.DockerEnv;

import java.io.File;
import java.util.Objects;

public class ContextStore {

private final MetadataStore metadataStore;

public ContextStore(DockerEnv env) {
File metaRoot = new File(env.getDockerContextStoreDir(), MetadataStore.metadataDir);
// final String tlsDir = "tls";
// File tlsRoot = new File(env.getDockerContextStoreDir(), tlsDir);
metadataStore = new MetadataStore(metaRoot);
}

public Metadata getMetadata(String contextName) {
if (Objects.equals(contextName, DockerEnv.dockerDefaultContextName)) {
// should return the equivalent metadata of `docker context inspect default`
Metadata metadata = new Metadata(DockerEnv.dockerDefaultContextName);
metadata.setMetadata(new DockerContext(""));
metadata.getEndpoints().put(
DockerEnv.dockerEndpointDefaultName,
new EndpointMetaBase(DockerEnv.getDockerHostFromSystemPropertyOrEnvironment(), false));
return metadata;
}

return metadataStore.getMetadata(contextName);
}
}
14 changes: 14 additions & 0 deletions engine/src/main/java/de/gesellix/docker/context/DockerContext.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package de.gesellix.docker.context;

import java.util.Map;

public class DockerContext {
String description;

// e.g. `"StackOrchestrator": "swarm"`
Map<String, Object> additionalFields;

public DockerContext(String description) {
this.description = description;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package de.gesellix.docker.context;

import com.squareup.moshi.Moshi;
import de.gesellix.docker.engine.DockerConfigReader;
import de.gesellix.docker.engine.DockerEnv;
import okio.Okio;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.Map;

public class DockerContextResolver {

private final static Logger log = LoggerFactory.getLogger(DockerContextResolver.class);

private final Moshi moshi = new Moshi.Builder().build();

// see the original implementation at https://github.com/docker/cli/blob/de6020a240ff95c97150f07d7a0dd59981143868/cli/command/cli.go#L448
public String resolveDockerContextName(DockerConfigReader dockerConfigReader) {
String dockerHost = DockerEnv.getDockerHostFromSystemPropertyOrEnvironment();
String dockerContext = DockerEnv.getDockerContextFromSystemPropertyOrEnvironment();
// if (dockerContext != null && dockerHost != null) {
// throw new IllegalStateException("Conflicting options: either specify --host or --context, not both");
// }
if (dockerContext != null) {
return dockerContext;
}
if (dockerHost != null) {
return DockerEnv.dockerDefaultContextName;
}
Map<String, Object> configFile = dockerConfigReader.readDockerConfigFile();
if (configFile != null && configFile.containsKey("currentContext")) {
// TODO ensure `currentContext` to be valid
// _, err := contextstore.GetMetadata(config.CurrentContext)
// if errdefs.IsNotFound(err) {
// return "", errors.Errorf("current context %q is not found on the file system, please check your config file at %s", config.CurrentContext, config.Filename)
// }
return (String) configFile.get("currentContext");
}
return DockerEnv.dockerDefaultContextName;
}

// see the original implementation at https://github.com/docker/cli/blob/de6020a240ff95c97150f07d7a0dd59981143868/cli/command/cli.go#L278
public EndpointMetaBase resolveDockerEndpoint(ContextStore store, String contextName) {
Metadata metadata = store.getMetadata(contextName);
if (metadata == null || metadata.getEndpoints() == null || !metadata.getEndpoints().containsKey(DockerEnv.dockerEndpointDefaultName)) {
throw new IllegalStateException("cannot find docker endpoint in context " + contextName);
}
if (!(metadata.getEndpoints().get(DockerEnv.dockerEndpointDefaultName) instanceof EndpointMetaBase)) {
throw new IllegalStateException("endpoint " + DockerEnv.dockerEndpointDefaultName + " is not of type EndpointMetaBase");
// throw new IllegalStateException("endpoint " + DockerEnv.dockerEndpointDefaultName + " is not of type EndpointMeta");
}
// TODO TLSData
return (EndpointMetaBase) metadata.getEndpoints().get(DockerEnv.dockerEndpointDefaultName);
}

public Map<String, Object> readDockerConfigFile(DockerEnv env) {
File dockerCfg = env.getDockerConfigFile();
if (dockerCfg == null || !dockerCfg.exists()) {
log.info("docker config '${dockerCfg}' doesn't exist");
return Collections.emptyMap();
}
log.debug("reading context info from {}", dockerCfg);
try {
return moshi.adapter(Map.class).fromJson(Okio.buffer(Okio.source(dockerCfg)));
} catch (Exception e) {
log.debug(MessageFormat.format("failed to read context info from {}", dockerCfg), e);
return Collections.emptyMap();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package de.gesellix.docker.context;

import java.util.Objects;

public class EndpointMetaBase {
private String host;
private Boolean skipTLSVerify;

public EndpointMetaBase(String host, Boolean skipTLSVerify) {
this.host = host;
this.skipTLSVerify = skipTLSVerify;
}

public String getHost() {
return host;
}

public Boolean getSkipTLSVerify() {
return skipTLSVerify;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
EndpointMetaBase that = (EndpointMetaBase) o;
return Objects.equals(getHost(), that.getHost()) && Objects.equals(getSkipTLSVerify(), that.getSkipTLSVerify());
}

@Override
public int hashCode() {
return Objects.hash(getHost(), getSkipTLSVerify());
}
}
32 changes: 32 additions & 0 deletions engine/src/main/java/de/gesellix/docker/context/Metadata.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package de.gesellix.docker.context;

import java.util.HashMap;
import java.util.Map;

public class Metadata {
private String name;

private Object metadata;

private Map<String, Object> endpoints = new HashMap<>();

public Metadata(String name) {
this.name = name;
}

public String getName() {
return name;
}

public Object getMetadata() {
return metadata;
}

public void setMetadata(Object metadata) {
this.metadata = metadata;
}

public Map<String, Object> getEndpoints() {
return endpoints;
}
}
84 changes: 84 additions & 0 deletions engine/src/main/java/de/gesellix/docker/context/MetadataStore.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package de.gesellix.docker.context;

import com.squareup.moshi.Moshi;
import de.gesellix.docker.engine.DockerEnv;
import okio.Okio;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileNotFoundException;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.Map;

public class MetadataStore {
private final static Logger log = LoggerFactory.getLogger(MetadataStore.class);

public final static String metadataDir = "meta";
final String metaFile = "meta.json";

private final Moshi moshi = new Moshi.Builder().build();

File root;
// Config config;

public MetadataStore(File root) {
this.root = root;
}

public Metadata getMetadata(String contextName) {
return getByID(getContextDir(contextName));
}

public Metadata getByID(String contextDirectory) {
Map payload = getMetadataPayload(contextDirectory);
Metadata metadata = new Metadata((String) payload.get("Name"));
// TODO `metadata` should be read type safe
// see https://github.com/docker/cli/blob/09c94c1c21cb2ed02d347934de85b6163dc62ddf/cli/context/store/metadatastore.go#L83
metadata.setMetadata(payload.get("Metadata"));
// TODO each `endpoint` should be read type safe
// see https://github.com/docker/cli/blob/09c94c1c21cb2ed02d347934de85b6163dc62ddf/cli/context/store/metadatastore.go#L87
metadata.getEndpoints().putAll((Map<String, Object>) payload.get("Endpoints"));
if (metadata.getEndpoints().containsKey(DockerEnv.dockerEndpointDefaultName)) {
Map<String, Object> endpointMeta = (Map<String, Object>) metadata.getEndpoints().get(DockerEnv.dockerEndpointDefaultName);
metadata.getEndpoints().put(
DockerEnv.dockerEndpointDefaultName,
new EndpointMetaBase((String) endpointMeta.get("Host"), false));
}
return metadata;
}

// Code taken from https://stackoverflow.com/a/62401723/372019
// SHA256 ist the current implementation in the docker/cli,
// see https://github.com/docker/cli/blob/cd7c493ea2cfb8c6db0beb65cf9830c8df23a9f9/cli/context/store/store.go#L8
public String getContextDir(String contextName) {
MessageDigest messageDigest;
try {
messageDigest = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
byte hashBytes[] = messageDigest.digest(contextName.getBytes(StandardCharsets.UTF_8));
BigInteger noHash = new BigInteger(1, hashBytes);
String hashStr = noHash.toString(16);
return hashStr;
}

private Map getMetadataPayload(String contextDirectory) {
File contextMetadata = new File(new File(root, contextDirectory), metaFile);
if (!contextMetadata.exists()) {
throw new IllegalStateException("context does not exist", new FileNotFoundException(contextMetadata.getAbsolutePath()));
}
try {
return moshi.adapter(Map.class).fromJson(Okio.buffer(Okio.source(contextMetadata)));
} catch (Exception e) {
log.debug(MessageFormat.format("failed to read metadata from {}", contextMetadata), e);
return Collections.emptyMap();
}
}
}
Loading

0 comments on commit 770c27a

Please sign in to comment.