Skip to content

Commit

Permalink
only parse configs once (#6240)
Browse files Browse the repository at this point in the history
Signed-off-by: Andre Dietisheim <[email protected]>
  • Loading branch information
adietish committed Sep 4, 2024
1 parent 9a8857d commit e266e8e
Show file tree
Hide file tree
Showing 9 changed files with 270 additions and 102 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import io.fabric8.kubernetes.client.utils.IOHelpers;
import io.fabric8.kubernetes.client.utils.Serialization;
import io.fabric8.kubernetes.client.utils.Utils;
import lombok.Getter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -56,7 +57,6 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
Expand Down Expand Up @@ -197,6 +197,8 @@ public class Config {

private List<NamedContext> contexts;
private NamedContext currentContext = null;
@Getter
private List<KubeConfigFile> kubeConfigFiles = new ArrayList<>();

/**
* fields not used but needed for builder generation.
Expand Down Expand Up @@ -240,8 +242,6 @@ public class Config {

private Boolean autoConfigure;

private List<File> files = new ArrayList<>();

@JsonIgnore
protected Map<String, Object> additionalProperties = new HashMap<>();

Expand Down Expand Up @@ -422,7 +422,7 @@ public Config(String masterUrl, String apiVersion, String namespace, Boolean tru
String impersonateUsername, String[] impersonateGroups, Map<String, List<String>> impersonateExtras,
OAuthTokenProvider oauthTokenProvider, Map<String, String> customHeaders, Integer requestRetryBackoffLimit,
Integer requestRetryBackoffInterval, Integer uploadRequestTimeout, Boolean onlyHttpWatches, NamedContext currentContext,
List<NamedContext> contexts, Boolean autoConfigure, Boolean shouldSetDefaultValues, List<File> files) {
List<NamedContext> contexts, Boolean autoConfigure, Boolean shouldSetDefaultValues, List<KubeConfigFile> files) {
if (Boolean.TRUE.equals(shouldSetDefaultValues)) {
this.masterUrl = DEFAULT_MASTER_URL;
this.apiVersion = "v1";
Expand Down Expand Up @@ -605,9 +605,8 @@ public Config(String masterUrl, String apiVersion, String namespace, Boolean tru
this.customHeaders = customHeaders;
this.onlyHttpWatches = onlyHttpWatches;
if (Utils.isNotNullOrEmpty(files)) {
this.files = files;
this.kubeConfigFiles = files;
}

}

public static void configFromSysPropsOrEnvVars(Config config) {
Expand Down Expand Up @@ -853,8 +852,8 @@ public Config refresh() {
return Config.autoConfigure(currentContextName);
}
// if files is null there's nothing to refresh - the kubeconfigs were directly supplied
if (!Utils.isNullOrEmpty(files)) {
io.fabric8.kubernetes.api.model.Config mergedConfig = mergeKubeConfigs(files);
if (!Utils.isNullOrEmpty(kubeConfigFiles)) {
io.fabric8.kubernetes.api.model.Config mergedConfig = mergeKubeConfigs(kubeConfigFiles);
if (mergedConfig != null) {
loadFromKubeconfig(this, mergedConfig.getCurrentContext(), mergedConfig);
}
Expand All @@ -871,44 +870,34 @@ private static boolean tryKubeConfig(Config config, String context) {
if (Utils.isNullOrEmpty(kubeConfigFilenames)) {
return false;
}
List<File> allFiles = Arrays.stream(kubeConfigFilenames)

List<File> files = toFiles(kubeConfigFilenames);
config.setFiles(files);
io.fabric8.kubernetes.api.model.Config mergedConfig = mergeKubeConfigs(config.getKubeConfigFiles());
return loadFromKubeconfig(config, context, mergedConfig);
}

private static List<File> toFiles(String[] filenames) {
return Arrays.stream(filenames)
.map(File::new)
.collect(Collectors.toList());
config.files = allFiles;
io.fabric8.kubernetes.api.model.Config mergedConfig = mergeKubeConfigs(allFiles);
return loadFromKubeconfig(config, context, mergedConfig);
}

private static io.fabric8.kubernetes.api.model.Config mergeKubeConfigs(List<File> files) {
private static io.fabric8.kubernetes.api.model.Config mergeKubeConfigs(List<KubeConfigFile> files) {
if (Utils.isNullOrEmpty(files)) {
return null;
}
return files.stream()
.map(Config::createKubeconfig)
.map(KubeConfigFile::getConfig)
.reduce(null, (merged, additionalConfig) -> {
if (additionalConfig != null) {
return KubeConfigUtils.merge(additionalConfig, merged);
} else {
if (additionalConfig == null) {
return merged;
} else {
return KubeConfigUtils.merge(additionalConfig, merged);
}
});
}

private static io.fabric8.kubernetes.api.model.Config createKubeconfig(File file) {
io.fabric8.kubernetes.api.model.Config kubeConfig = null;
LOGGER.debug("Found for Kubernetes config at: [{}].", file.getPath());
try {
String content = getKubeconfigContents(file);
if (Utils.isNotNullOrEmpty(content)) {
kubeConfig = KubeConfigUtils.parseConfigFromString(content);
}
} catch (KubernetesClientException e) {
LOGGER.error("Could not load Kubernetes config [{}].", file.getPath(), e);
}

return kubeConfig;
}

/**
* @deprecated use {@link #getKubeconfigFilenames()} instead
*/
Expand All @@ -917,7 +906,7 @@ public static String getKubeconfigFilename() {
String fileName = null;
String[] fileNames = getKubeconfigFilenames();
// if system property/env var contains multiple files take the first one based on the environment
if (!Utils.isNullOrEmpty(fileNames)) {
if (Utils.isNotNullOrEmpty(fileNames)) {
fileName = fileNames[0];
if (fileNames.length > 1) {
LOGGER.info("Found multiple Kubernetes config files [{}], returning the first one. Use #getKubeconfigFilenames instead",
Expand All @@ -939,31 +928,6 @@ public static String[] getKubeconfigFilenames() {
return fileNames;
}

private static boolean isReadableKubeconfFile(File file) {
if (file == null) {
return false;
}
if (!file.isFile()) {
LOGGER.debug("Did not find Kubernetes config at: [{}]. Ignoring.", file.getPath());
return false;
}
return true;
}

private static String getKubeconfigContents(File kubeConfigFile) {
if (kubeConfigFile == null) {
return null;
}
String kubeconfigContents = null;
try (FileReader reader = new FileReader(kubeConfigFile)) {
kubeconfigContents = IOHelpers.readFully(reader);
} catch (IOException e) {
LOGGER.error("Could not load Kubernetes config file from {}", kubeConfigFile.getPath(), e);
return null;
}
return kubeconfigContents;
}

// Note: kubeconfigPath is optional
// It is only used to rewrite relative tls asset paths inside kubeconfig when a file is passed, and in the case that
// the kubeconfig references some assets via relative paths.
Expand Down Expand Up @@ -1748,44 +1712,23 @@ public void setCurrentContext(NamedContext context) {
*
* Returns the path to the file that this configuration was loaded from. Returns {@code null} if no file was used.
*
* @deprecated use {@link #getFiles} instead.
*
* @return the kubeConfig file
*/
public File getFile() {
if (Utils.isNotNullOrEmpty(files)) {
return files.get(0);
if (Utils.isNotNullOrEmpty(kubeConfigFiles)) {
return kubeConfigFiles.get(0).getFile();
} else {
return null;
}
}

/**
* Returns the kube config files that are used to configure this client.
* Returns the files that are listed in the KUBERNETES_KUBECONFIG_FILES env or system variables.
* Returns the default kube config file if it's not set'.
*
* @return
*/
public List<File> getFiles() {
return files;
}

public KubeConfigFile getFileWithAuthInfo(String name) {
if (Utils.isNullOrEmpty(name)
|| Utils.isNullOrEmpty(getFiles())) {
return null;
}
return getFiles().stream()
.filter(Config::isReadableKubeconfFile)
.map(file -> {
try {
return new KubeConfigFile(file, KubeConfigUtils.parseConfig(file));
} catch (IOException e) {
return null;
}
})
.filter(Objects::nonNull)
return kubeConfigFiles.stream()
.filter(KubeConfigFile::isReadable)
.filter(entry -> hasAuthInfoNamed(name, entry.getConfig()))
.findFirst()
.orElse(null);
Expand Down Expand Up @@ -1829,7 +1772,38 @@ public void setFile(File file) {
}

public void setFiles(List<File> files) {
this.files = files;
if (Utils.isNullOrEmpty(files)) {
this.kubeConfigFiles = Collections.emptyList();
} else {
this.kubeConfigFiles = files.stream()
.map(KubeConfigFile::new)
.collect(Collectors.toList());
}
}

public void setKubeConfigFiles(List<KubeConfigFile> files) {
if (Utils.isNullOrEmpty(files)) {
this.kubeConfigFiles = Collections.emptyList();
} else {
this.kubeConfigFiles = files;
}
}

/**
* Returns the kube config files that are used to configure this client.
* Returns the files that are listed in the KUBERNETES_KUBECONFIG_FILES env or system variables.
* Returns the default kube config file if it's not set'.
*
* @return
*/
public List<File> getFiles() {
if (this.kubeConfigFiles == null) {
return Collections.emptyList();
} else {
return kubeConfigFiles.stream()
.map(KubeConfigFile::getFile)
.collect(Collectors.toList());
}
}

public void setAutoConfigure(boolean autoConfigure) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,9 @@ public Config build() {
fluent.getOauthTokenProvider(), fluent.getCustomHeaders(), fluent.getRequestRetryBackoffLimit(),
fluent.getRequestRetryBackoffInterval(), fluent.getUploadRequestTimeout(), fluent.getOnlyHttpWatches(),
fluent.getCurrentContext(), fluent.getContexts(),
Optional.ofNullable(fluent.getAutoConfigure()).orElse(!disableAutoConfig()), true, fluent.getFiles());
Optional.ofNullable(fluent.getAutoConfigure()).orElse(!disableAutoConfig()), true, fluent.getKubeConfigFiles());
buildable.setAuthProvider(fluent.getAuthProvider());
return buildable;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
*/
package io.fabric8.kubernetes.client;

import java.io.File;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class ConfigFluent<A extends ConfigFluent<A>> extends SundrioConfigFluent<A> {
public ConfigFluent() {
super();
Expand Down Expand Up @@ -78,7 +83,7 @@ public void copyInstance(Config instance) {
this.withContexts(instance.getContexts());
this.withAutoConfigure(instance.getAutoConfigure());
this.withAuthProvider(instance.getAuthProvider());
this.withFiles(instance.getFiles());
this.withKubeConfigFiles(instance.getKubeConfigFiles());
}
}

Expand Down Expand Up @@ -148,4 +153,15 @@ public A withAutoConfigure(boolean autoConfigure) {
return this.withAutoConfigure(Boolean.valueOf(autoConfigure));
}

public A withFiles(File... files) {
if (files != null
&& files.length > 0) {
List<KubeConfigFile> configFiles = Arrays.stream(files)
.map(KubeConfigFile::new)
.collect(Collectors.toList());
withKubeConfigFiles(configFiles);
}
return (A) this;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,73 @@
*/
package io.fabric8.kubernetes.client;

import java.io.File;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.fabric8.kubernetes.api.model.Config;
import io.fabric8.kubernetes.client.internal.KubeConfigUtils;
import lombok.Getter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;

@Getter
public class KubeConfigFile {
private final File file;
private final Config config;

/** for testing purposes **/
public KubeConfigFile(File file, Config config) {
this.file = file;
this.config = config;
private static final Logger LOGGER = LoggerFactory.getLogger(KubeConfigFile.class);

@Getter
private final File file;
private boolean parsed = false;
private Config config;

@JsonCreator
public KubeConfigFile(@JsonProperty("file") String file) {
this(new File(file), null);
}

public KubeConfigFile(File file) {
this(file, null);
}

private KubeConfigFile(File file, Config config) {
this.file = file;
this.config = config;
}

public Config getConfig() {
if (!parsed) {
this.config = createConfig(file);
this.parsed = true;
}
return config;
}

private Config createConfig(File file) {
Config created = null;
try {
if (isReadable(file)) {
LOGGER.debug("Found for Kubernetes config at: [{}].", file.getPath());
created = KubeConfigUtils.parseConfig(file);
}
} catch (IOException e) {
LOGGER.debug("Kubernetes file at [{}] is not a valid config. Ignoring.", file.getPath(), e);
}
return created;
}

@JsonIgnore
public boolean isReadable() {
return isReadable(file);
}

private boolean isReadable(File file) {
try {
return file != null
&& file.isFile();
} catch (SecurityException e) {
return false;
}
}
}
}
Loading

0 comments on commit e266e8e

Please sign in to comment.