Skip to content

Commit

Permalink
use kubeconfigs listed in KUBECONFIG env var (#6240)
Browse files Browse the repository at this point in the history
* update token in file listed in KUBECONFIG env var (#6240)
* only parse configs once (#6240)
* update file with auth info when merging authinfos
* parametrized KubeConfigUtilsTest for #hasAuthInfoNamed
* expose Config#getFileWithCurrentContext & #getFileWithContext to consumers

Signed-off-by: Andre Dietisheim <[email protected]>
  • Loading branch information
adietish committed Oct 28, 2024
1 parent 2286540 commit 33083fc
Show file tree
Hide file tree
Showing 20 changed files with 1,004 additions and 187 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
* Fix #6281: use GitHub binary repo for Kube API Tests
* Fix #6282: Allow annotated types with Pattern, Min, and Max with Lists and Maps and CRD generation
* Fix #5480: Move `io.fabric8:zjsonpatch` to KubernetesClient project
* Fix #6240: Use kubeconfig files listed in the KUBECONFIG env var

#### Dependency Upgrade
* Fix #2632: Bumped OkHttp from 3.12.12 to 4.12.0
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +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);
Optional.ofNullable(fluent.getAutoConfigure()).orElse(!disableAutoConfig()), true, fluent.getKubeConfigFiles());
buildable.setAuthProvider(fluent.getAuthProvider());
buildable.setFile(fluent.getFile());
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.withFile(instance.getFile());
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
@@ -0,0 +1,87 @@
/*
* Copyright (C) 2015 Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.fabric8.kubernetes.client;

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;

public class KubeConfigFile {

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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,15 @@ public SundrioConfig(String masterUrl, String apiVersion, String namespace, Bool
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) {
List<NamedContext> contexts, Boolean autoConfigure, List<KubeConfigFile> kubeConfigFiles) {
super(masterUrl, apiVersion, namespace, trustCerts, disableHostnameVerification, caCertFile, caCertData,
clientCertFile, clientCertData, clientKeyFile, clientKeyData, clientKeyAlgo, clientKeyPassphrase, username,
password, oauthToken, autoOAuthToken, watchReconnectInterval, watchReconnectLimit, connectionTimeout, requestTimeout,
scaleTimeout, loggingInterval, maxConcurrentRequests, maxConcurrentRequestsPerHost, http2Disable,
httpProxy, httpsProxy, noProxy, userAgent, tlsVersions, websocketPingInterval, proxyUsername, proxyPassword,
trustStoreFile, trustStorePassphrase, keyStoreFile, keyStorePassphrase, impersonateUsername, impersonateGroups,
impersonateExtras, oauthTokenProvider, customHeaders, requestRetryBackoffLimit, requestRetryBackoffInterval,
uploadRequestTimeout, onlyHttpWatches, currentContext, contexts, autoConfigure, true);
uploadRequestTimeout, onlyHttpWatches, currentContext, contexts, autoConfigure, true, kubeConfigFiles);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,20 @@
import io.fabric8.kubernetes.api.model.AuthInfo;
import io.fabric8.kubernetes.api.model.Cluster;
import io.fabric8.kubernetes.api.model.Config;
import io.fabric8.kubernetes.api.model.ConfigBuilder;
import io.fabric8.kubernetes.api.model.Context;
import io.fabric8.kubernetes.api.model.NamedAuthInfo;
import io.fabric8.kubernetes.api.model.NamedCluster;
import io.fabric8.kubernetes.api.model.NamedContext;
import io.fabric8.kubernetes.api.model.NamedExtension;
import io.fabric8.kubernetes.api.model.PreferencesBuilder;
import io.fabric8.kubernetes.client.utils.Serialization;
import io.fabric8.kubernetes.client.utils.Utils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.util.List;

/**
Expand All @@ -40,7 +44,7 @@ private KubeConfigUtils() {
}

public static Config parseConfig(File file) throws IOException {
return Serialization.unmarshal(new FileInputStream(file), Config.class);
return Serialization.unmarshal(Files.newInputStream(file.toPath()), Config.class);
}

public static Config parseConfigFromString(String contents) {
Expand All @@ -56,16 +60,31 @@ public static Config parseConfigFromString(String contents) {
public static NamedContext getCurrentContext(Config config) {
String contextName = config.getCurrentContext();
if (contextName != null) {
return getContext(config, contextName);
}
return null;
}

/**
* Returns the {@link NamedContext} with the given name.
* Returns {@code null} otherwise
*
* @param config the config to search
* @param name the context name to match
* @return the context with the the given name
*/
public static NamedContext getContext(Config config, String name) {
NamedContext context = null;
if (config != null && name != null) {
List<NamedContext> contexts = config.getContexts();
if (contexts != null) {
for (NamedContext context : contexts) {
if (contextName.equals(context.getName())) {
return context;
}
}
context = contexts.stream()
.filter(toInspect -> name.equals(toInspect.getName()))
.findAny()
.orElse(null);
}
}
return null;
return context;
}

/**
Expand All @@ -91,23 +110,49 @@ public static String getUserToken(Config config, Context context) {
* @return {@link AuthInfo} for current context
*/
public static AuthInfo getUserAuthInfo(Config config, Context context) {
AuthInfo authInfo = null;
if (config != null && context != null) {
String user = context.getUser();
if (user != null) {
List<NamedAuthInfo> users = config.getUsers();
if (users != null) {
authInfo = users.stream()
.filter(u -> u.getName().equals(user))
.findAny()
.map(NamedAuthInfo::getUser)
.orElse(null);
}
NamedAuthInfo namedAuthInfo = getAuthInfo(config, context.getUser());
return (namedAuthInfo != null) ? namedAuthInfo.getUser() : null;
}

/**
* Returns the {@link NamedAuthInfo} with the given name.
* Returns {@code null} otherwise
*
* @param config the config to search
* @param name
* @return
*/
public static NamedAuthInfo getAuthInfo(Config config, String name) {
NamedAuthInfo authInfo = null;
if (config != null && name != null) {
List<NamedAuthInfo> users = config.getUsers();
if (users != null) {
authInfo = users.stream()
.filter(toInspect -> name.equals(toInspect.getName()))
.findAny()
.orElse(null);
}
}
return authInfo;
}

/**
* Returns {@code true} if the given {@link Config} has a {@link NamedAuthInfo} with the given name.
* Returns {@code false} otherwise.
*
* @param name the name of the NamedAuthInfo that we are looking for
* @param config the Config to search
* @return true if it contains a NamedAuthInfo with the given name
*/
public static boolean hasAuthInfoNamed(Config config, String name) {
if (Utils.isNullOrEmpty(name)
|| config == null
|| config.getUsers() == null) {
return false;
}
return getAuthInfo(config, name) != null;
}

/**
* Returns the current {@link Cluster} for the current context
*
Expand Down Expand Up @@ -161,4 +206,39 @@ public static void persistKubeConfigIntoFile(Config kubeConfig, String kubeConfi
writer.write(Serialization.asYaml(kubeConfig));
}
}

public static Config merge(Config thisConfig, Config thatConfig) {
if (thisConfig == null) {
return thatConfig;
}
ConfigBuilder builder = new ConfigBuilder(thatConfig);
if (thisConfig.getClusters() != null) {
builder.addAllToClusters(thisConfig.getClusters());
}
if (thisConfig.getContexts() != null) {
builder.addAllToContexts(thisConfig.getContexts());
}
if (thisConfig.getUsers() != null) {
builder.addAllToUsers(thisConfig.getUsers());
}
if (thisConfig.getExtensions() != null) {
builder.addAllToExtensions(thisConfig.getExtensions());
}
if (!builder.hasCurrentContext()
&& Utils.isNotNullOrEmpty(thisConfig.getCurrentContext())) {
builder.withCurrentContext(thisConfig.getCurrentContext());
}
Config merged = builder.build();
mergePreferences(thisConfig, merged);
return merged;
}

public static void mergePreferences(io.fabric8.kubernetes.api.model.Config source,
io.fabric8.kubernetes.api.model.Config destination) {
if (source.getPreferences() != null) {
PreferencesBuilder builder = new PreferencesBuilder(destination.getPreferences());
builder.addToExtensions(source.getExtensions().toArray(new NamedExtension[] {}));
destination.setPreferences(builder.build());
}
}
}
Loading

0 comments on commit 33083fc

Please sign in to comment.