Skip to content

Commit 0c5ff29

Browse files
adietishmanusa
authored andcommitted
use kubeconfigs listed in KUBECONFIG env var (fabric8io#6240)
* update token in file listed in KUBECONFIG env var (fabric8io#6240) * only parse configs once (fabric8io#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]>
1 parent a6cefc1 commit 0c5ff29

18 files changed

+975
-179
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
* Fix #6281: use GitHub binary repo for Kube API Tests
1616
* Fix #6282: Allow annotated types with Pattern, Min, and Max with Lists and Maps and CRD generation
1717
* Fix #5480: Move `io.fabric8:zjsonpatch` to KubernetesClient project
18+
* Fix #6240: Use kubeconfig files listed in the KUBECONFIG env var
1819

1920
#### Dependency Upgrade
2021
* Fix #2632: Bumped OkHttp from 3.12.12 to 4.12.0

kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/Config.java

Lines changed: 233 additions & 100 deletions
Large diffs are not rendered by default.

kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/ConfigBuilder.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,9 @@ public Config build() {
5757
fluent.getOauthTokenProvider(), fluent.getCustomHeaders(), fluent.getRequestRetryBackoffLimit(),
5858
fluent.getRequestRetryBackoffInterval(), fluent.getUploadRequestTimeout(), fluent.getOnlyHttpWatches(),
5959
fluent.getCurrentContext(), fluent.getContexts(),
60-
Optional.ofNullable(fluent.getAutoConfigure()).orElse(!disableAutoConfig()), true);
60+
Optional.ofNullable(fluent.getAutoConfigure()).orElse(!disableAutoConfig()), true, fluent.getKubeConfigFiles());
6161
buildable.setAuthProvider(fluent.getAuthProvider());
62-
buildable.setFile(fluent.getFile());
6362
return buildable;
6463
}
64+
6565
}

kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/ConfigFluent.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@
1515
*/
1616
package io.fabric8.kubernetes.client;
1717

18+
import java.io.File;
19+
import java.util.Arrays;
20+
import java.util.List;
21+
import java.util.stream.Collectors;
22+
1823
public class ConfigFluent<A extends ConfigFluent<A>> extends SundrioConfigFluent<A> {
1924
public ConfigFluent() {
2025
super();
@@ -78,7 +83,7 @@ public void copyInstance(Config instance) {
7883
this.withContexts(instance.getContexts());
7984
this.withAutoConfigure(instance.getAutoConfigure());
8085
this.withAuthProvider(instance.getAuthProvider());
81-
this.withFile(instance.getFile());
86+
this.withKubeConfigFiles(instance.getKubeConfigFiles());
8287
}
8388
}
8489

@@ -148,4 +153,15 @@ public A withAutoConfigure(boolean autoConfigure) {
148153
return this.withAutoConfigure(Boolean.valueOf(autoConfigure));
149154
}
150155

156+
public A withFiles(File... files) {
157+
if (files != null
158+
&& files.length > 0) {
159+
List<KubeConfigFile> configFiles = Arrays.stream(files)
160+
.map(KubeConfigFile::new)
161+
.collect(Collectors.toList());
162+
withKubeConfigFiles(configFiles);
163+
}
164+
return (A) this;
165+
}
166+
151167
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Copyright (C) 2015 Red Hat, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.fabric8.kubernetes.client;
17+
18+
import com.fasterxml.jackson.annotation.JsonCreator;
19+
import com.fasterxml.jackson.annotation.JsonIgnore;
20+
import com.fasterxml.jackson.annotation.JsonProperty;
21+
import io.fabric8.kubernetes.api.model.Config;
22+
import io.fabric8.kubernetes.client.internal.KubeConfigUtils;
23+
import lombok.Getter;
24+
import org.slf4j.Logger;
25+
import org.slf4j.LoggerFactory;
26+
27+
import java.io.File;
28+
import java.io.IOException;
29+
30+
public class KubeConfigFile {
31+
32+
private static final Logger LOGGER = LoggerFactory.getLogger(KubeConfigFile.class);
33+
34+
@Getter
35+
private final File file;
36+
private boolean parsed = false;
37+
private Config config;
38+
39+
@JsonCreator
40+
public KubeConfigFile(@JsonProperty("file") String file) {
41+
this(new File(file), null);
42+
}
43+
44+
public KubeConfigFile(File file) {
45+
this(file, null);
46+
}
47+
48+
private KubeConfigFile(File file, Config config) {
49+
this.file = file;
50+
this.config = config;
51+
}
52+
53+
public Config getConfig() {
54+
if (!parsed) {
55+
this.config = createConfig(file);
56+
this.parsed = true;
57+
}
58+
return config;
59+
}
60+
61+
private Config createConfig(File file) {
62+
Config created = null;
63+
try {
64+
if (isReadable(file)) {
65+
LOGGER.debug("Found for Kubernetes config at: [{}].", file.getPath());
66+
created = KubeConfigUtils.parseConfig(file);
67+
}
68+
} catch (IOException e) {
69+
LOGGER.debug("Kubernetes file at [{}] is not a valid config. Ignoring.", file.getPath(), e);
70+
}
71+
return created;
72+
}
73+
74+
@JsonIgnore
75+
public boolean isReadable() {
76+
return isReadable(file);
77+
}
78+
79+
private boolean isReadable(File file) {
80+
try {
81+
return file != null
82+
&& file.isFile();
83+
} catch (SecurityException e) {
84+
return false;
85+
}
86+
}
87+
}

kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/SundrioConfig.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,15 @@ public SundrioConfig(String masterUrl, String apiVersion, String namespace, Bool
5050
String impersonateUsername, String[] impersonateGroups, Map<String, List<String>> impersonateExtras,
5151
OAuthTokenProvider oauthTokenProvider, Map<String, String> customHeaders, Integer requestRetryBackoffLimit,
5252
Integer requestRetryBackoffInterval, Integer uploadRequestTimeout, Boolean onlyHttpWatches, NamedContext currentContext,
53-
List<NamedContext> contexts, Boolean autoConfigure) {
53+
List<NamedContext> contexts, Boolean autoConfigure, List<KubeConfigFile> kubeConfigFiles) {
5454
super(masterUrl, apiVersion, namespace, trustCerts, disableHostnameVerification, caCertFile, caCertData,
5555
clientCertFile, clientCertData, clientKeyFile, clientKeyData, clientKeyAlgo, clientKeyPassphrase, username,
5656
password, oauthToken, autoOAuthToken, watchReconnectInterval, watchReconnectLimit, connectionTimeout, requestTimeout,
5757
scaleTimeout, loggingInterval, maxConcurrentRequests, maxConcurrentRequestsPerHost, http2Disable,
5858
httpProxy, httpsProxy, noProxy, userAgent, tlsVersions, websocketPingInterval, proxyUsername, proxyPassword,
5959
trustStoreFile, trustStorePassphrase, keyStoreFile, keyStorePassphrase, impersonateUsername, impersonateGroups,
6060
impersonateExtras, oauthTokenProvider, customHeaders, requestRetryBackoffLimit, requestRetryBackoffInterval,
61-
uploadRequestTimeout, onlyHttpWatches, currentContext, contexts, autoConfigure, true);
61+
uploadRequestTimeout, onlyHttpWatches, currentContext, contexts, autoConfigure, true, kubeConfigFiles);
6262
}
63+
6364
}

kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/internal/KubeConfigUtils.java

Lines changed: 100 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,20 @@
1818
import io.fabric8.kubernetes.api.model.AuthInfo;
1919
import io.fabric8.kubernetes.api.model.Cluster;
2020
import io.fabric8.kubernetes.api.model.Config;
21+
import io.fabric8.kubernetes.api.model.ConfigBuilder;
2122
import io.fabric8.kubernetes.api.model.Context;
2223
import io.fabric8.kubernetes.api.model.NamedAuthInfo;
2324
import io.fabric8.kubernetes.api.model.NamedCluster;
2425
import io.fabric8.kubernetes.api.model.NamedContext;
26+
import io.fabric8.kubernetes.api.model.NamedExtension;
27+
import io.fabric8.kubernetes.api.model.PreferencesBuilder;
2528
import io.fabric8.kubernetes.client.utils.Serialization;
29+
import io.fabric8.kubernetes.client.utils.Utils;
2630

2731
import java.io.File;
28-
import java.io.FileInputStream;
2932
import java.io.FileWriter;
3033
import java.io.IOException;
34+
import java.nio.file.Files;
3135
import java.util.List;
3236

3337
/**
@@ -40,7 +44,7 @@ private KubeConfigUtils() {
4044
}
4145

4246
public static Config parseConfig(File file) throws IOException {
43-
return Serialization.unmarshal(new FileInputStream(file), Config.class);
47+
return Serialization.unmarshal(Files.newInputStream(file.toPath()), Config.class);
4448
}
4549

4650
public static Config parseConfigFromString(String contents) {
@@ -56,16 +60,31 @@ public static Config parseConfigFromString(String contents) {
5660
public static NamedContext getCurrentContext(Config config) {
5761
String contextName = config.getCurrentContext();
5862
if (contextName != null) {
63+
return getContext(config, contextName);
64+
}
65+
return null;
66+
}
67+
68+
/**
69+
* Returns the {@link NamedContext} with the given name.
70+
* Returns {@code null} otherwise
71+
*
72+
* @param config the config to search
73+
* @param name the context name to match
74+
* @return the context with the the given name
75+
*/
76+
public static NamedContext getContext(Config config, String name) {
77+
NamedContext context = null;
78+
if (config != null && name != null) {
5979
List<NamedContext> contexts = config.getContexts();
6080
if (contexts != null) {
61-
for (NamedContext context : contexts) {
62-
if (contextName.equals(context.getName())) {
63-
return context;
64-
}
65-
}
81+
context = contexts.stream()
82+
.filter(toInspect -> name.equals(toInspect.getName()))
83+
.findAny()
84+
.orElse(null);
6685
}
6786
}
68-
return null;
87+
return context;
6988
}
7089

7190
/**
@@ -91,23 +110,49 @@ public static String getUserToken(Config config, Context context) {
91110
* @return {@link AuthInfo} for current context
92111
*/
93112
public static AuthInfo getUserAuthInfo(Config config, Context context) {
94-
AuthInfo authInfo = null;
95-
if (config != null && context != null) {
96-
String user = context.getUser();
97-
if (user != null) {
98-
List<NamedAuthInfo> users = config.getUsers();
99-
if (users != null) {
100-
authInfo = users.stream()
101-
.filter(u -> u.getName().equals(user))
102-
.findAny()
103-
.map(NamedAuthInfo::getUser)
104-
.orElse(null);
105-
}
113+
NamedAuthInfo namedAuthInfo = getAuthInfo(config, context.getUser());
114+
return (namedAuthInfo != null) ? namedAuthInfo.getUser() : null;
115+
}
116+
117+
/**
118+
* Returns the {@link NamedAuthInfo} with the given name.
119+
* Returns {@code null} otherwise
120+
*
121+
* @param config the config to search
122+
* @param name
123+
* @return
124+
*/
125+
public static NamedAuthInfo getAuthInfo(Config config, String name) {
126+
NamedAuthInfo authInfo = null;
127+
if (config != null && name != null) {
128+
List<NamedAuthInfo> users = config.getUsers();
129+
if (users != null) {
130+
authInfo = users.stream()
131+
.filter(toInspect -> name.equals(toInspect.getName()))
132+
.findAny()
133+
.orElse(null);
106134
}
107135
}
108136
return authInfo;
109137
}
110138

139+
/**
140+
* Returns {@code true} if the given {@link Config} has a {@link NamedAuthInfo} with the given name.
141+
* Returns {@code false} otherwise.
142+
*
143+
* @param name the name of the NamedAuthInfo that we are looking for
144+
* @param config the Config to search
145+
* @return true if it contains a NamedAuthInfo with the given name
146+
*/
147+
public static boolean hasAuthInfoNamed(Config config, String name) {
148+
if (Utils.isNullOrEmpty(name)
149+
|| config == null
150+
|| config.getUsers() == null) {
151+
return false;
152+
}
153+
return getAuthInfo(config, name) != null;
154+
}
155+
111156
/**
112157
* Returns the current {@link Cluster} for the current context
113158
*
@@ -161,4 +206,39 @@ public static void persistKubeConfigIntoFile(Config kubeConfig, String kubeConfi
161206
writer.write(Serialization.asYaml(kubeConfig));
162207
}
163208
}
209+
210+
public static Config merge(Config thisConfig, Config thatConfig) {
211+
if (thisConfig == null) {
212+
return thatConfig;
213+
}
214+
ConfigBuilder builder = new ConfigBuilder(thatConfig);
215+
if (thisConfig.getClusters() != null) {
216+
builder.addAllToClusters(thisConfig.getClusters());
217+
}
218+
if (thisConfig.getContexts() != null) {
219+
builder.addAllToContexts(thisConfig.getContexts());
220+
}
221+
if (thisConfig.getUsers() != null) {
222+
builder.addAllToUsers(thisConfig.getUsers());
223+
}
224+
if (thisConfig.getExtensions() != null) {
225+
builder.addAllToExtensions(thisConfig.getExtensions());
226+
}
227+
if (!builder.hasCurrentContext()
228+
&& Utils.isNotNullOrEmpty(thisConfig.getCurrentContext())) {
229+
builder.withCurrentContext(thisConfig.getCurrentContext());
230+
}
231+
Config merged = builder.build();
232+
mergePreferences(thisConfig, merged);
233+
return merged;
234+
}
235+
236+
public static void mergePreferences(io.fabric8.kubernetes.api.model.Config source,
237+
io.fabric8.kubernetes.api.model.Config destination) {
238+
if (source.getPreferences() != null) {
239+
PreferencesBuilder builder = new PreferencesBuilder(destination.getPreferences());
240+
builder.addToExtensions(source.getExtensions().toArray(new NamedExtension[] {}));
241+
destination.setPreferences(builder.build());
242+
}
243+
}
164244
}

0 commit comments

Comments
 (0)