Skip to content

Commit d5972d2

Browse files
committed
fix: allow multiple configs listed in KUBECONFIG (#779)
* save kube conf files when current ctx or ns changes * watch all config files (#779) Signed-off-by: Andre Dietisheim <[email protected]>
1 parent 653e6ca commit d5972d2

File tree

14 files changed

+3653
-432
lines changed

14 files changed

+3653
-432
lines changed

build.gradle

+11
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,17 @@ sourceSets {
1717
compileClasspath += sourceSets.main.output + configurations.runtimeClasspath
1818
runtimeClasspath += output + compileClasspath
1919
}
20+
main {
21+
java.srcDirs("src/main/java")
22+
kotlin.srcDirs("src/main/kotlin")
23+
}
24+
test {
25+
java.srcDirs("src/test/java")
26+
kotlin.srcDirs("src/test/kotlin")
27+
// #779: unit tests need to see kubernetes-client classes in src/main/java
28+
compileClasspath += sourceSets.main.output + configurations.runtimeClasspath
29+
runtimeClasspath += output + compileClasspath
30+
}
2031
}
2132

2233
task integrationTest(type: Test) {

gradle.properties

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ jetBrainsToken=invalid
88
jetBrainsChannel=stable
99
intellijPluginVersion=1.16.1
1010
kotlinJvmPluginVersion=1.8.0
11-
intellijCommonVersion=1.9.6
11+
intellijCommonVersion=1.9.7-SNAPSHOT
1212
telemetryPluginVersion=1.1.0.52
1313
kotlin.stdlib.default.dependency = false
1414
kotlinVersion=1.6.21

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

+1,902
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
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 io.fabric8.kubernetes.api.model.Config;
19+
import io.fabric8.kubernetes.client.internal.KubeConfigUtils;
20+
import org.slf4j.Logger;
21+
import org.slf4j.LoggerFactory;
22+
23+
import java.io.File;
24+
import java.io.IOException;
25+
26+
public class KubeConfigFile {
27+
28+
private static final Logger LOGGER = LoggerFactory.getLogger(KubeConfigFile.class);
29+
30+
private final File file;
31+
private boolean parsed = false;
32+
private Config config;
33+
34+
public KubeConfigFile(String file) {
35+
this(new File(file), null);
36+
}
37+
38+
public KubeConfigFile(File file) {
39+
this(file, null);
40+
}
41+
42+
private KubeConfigFile(File file, Config config) {
43+
this.file = file;
44+
this.config = config;
45+
}
46+
47+
public File getFile() {
48+
return file;
49+
}
50+
51+
public Config getConfig() {
52+
if (!parsed) {
53+
this.config = createConfig(file);
54+
this.parsed = true;
55+
}
56+
return config;
57+
}
58+
59+
private Config createConfig(File file) {
60+
Config config = null;
61+
try {
62+
if (isReadable(file)) {
63+
LOGGER.debug("Found for Kubernetes config at: [{}].", file.getPath());
64+
config = KubeConfigUtils.parseConfig(file);
65+
}
66+
} catch (IOException e) {
67+
LOGGER.debug("Kubernetes file at [{}] is not a valid config. Ignoring.", file.getPath(), e);
68+
}
69+
return config;
70+
}
71+
72+
public boolean isReadable() {
73+
return isReadable(file);
74+
}
75+
76+
private boolean isReadable(File file) {
77+
try {
78+
return file != null
79+
&& file.isFile();
80+
} catch (SecurityException e) {
81+
return false;
82+
}
83+
}
84+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
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.internal;
17+
18+
import io.fabric8.kubernetes.api.model.AuthInfo;
19+
import io.fabric8.kubernetes.api.model.Cluster;
20+
import io.fabric8.kubernetes.api.model.Config;
21+
import io.fabric8.kubernetes.api.model.ConfigBuilder;
22+
import io.fabric8.kubernetes.api.model.Context;
23+
import io.fabric8.kubernetes.api.model.NamedAuthInfo;
24+
import io.fabric8.kubernetes.api.model.NamedCluster;
25+
import io.fabric8.kubernetes.api.model.NamedContext;
26+
import io.fabric8.kubernetes.api.model.NamedExtension;
27+
import io.fabric8.kubernetes.api.model.PreferencesBuilder;
28+
import io.fabric8.kubernetes.client.utils.Serialization;
29+
import io.fabric8.kubernetes.client.utils.Utils;
30+
31+
import java.io.File;
32+
import java.io.FileWriter;
33+
import java.io.IOException;
34+
import java.nio.file.Files;
35+
import java.util.List;
36+
37+
/**
38+
* Helper class for working with the YAML config file thats located in
39+
* <code>~/.kube/config</code> which is updated when you use commands
40+
* like <code>osc login</code> and <code>osc project myproject</code>
41+
*/
42+
public class KubeConfigUtils {
43+
private KubeConfigUtils() {
44+
}
45+
46+
public static Config parseConfig(File file) throws IOException {
47+
return Serialization.unmarshal(Files.newInputStream(file.toPath()), Config.class);
48+
}
49+
50+
public static Config parseConfigFromString(String contents) {
51+
return Serialization.unmarshal(contents, Config.class);
52+
}
53+
54+
/**
55+
* Returns the current context in the given config
56+
*
57+
* @param config Config object
58+
* @return returns context in config if found, otherwise null
59+
*/
60+
public static NamedContext getCurrentContext(Config config) {
61+
String contextName = config.getCurrentContext();
62+
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) {
79+
List<NamedContext> contexts = config.getContexts();
80+
if (contexts != null) {
81+
context = contexts.stream()
82+
.filter(toInspect -> name.equals(toInspect.getName()))
83+
.findAny()
84+
.orElse(null);
85+
}
86+
}
87+
return context;
88+
}
89+
90+
/**
91+
* Returns the current user token for the config and current context
92+
*
93+
* @param config Config object
94+
* @param context Context object
95+
* @return returns current user based upon provided parameters.
96+
*/
97+
public static String getUserToken(Config config, Context context) {
98+
AuthInfo authInfo = getUserAuthInfo(config, context);
99+
if (authInfo != null) {
100+
return authInfo.getToken();
101+
}
102+
return null;
103+
}
104+
105+
/**
106+
* Returns the current {@link AuthInfo} for the current context and user
107+
*
108+
* @param config Config object
109+
* @param context Context object
110+
* @return {@link AuthInfo} for current context
111+
*/
112+
public static AuthInfo getUserAuthInfo(Config config, Context context) {
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);
134+
}
135+
}
136+
return authInfo;
137+
}
138+
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+
156+
/**
157+
* Returns the current {@link Cluster} for the current context
158+
*
159+
* @param config {@link Config} config object
160+
* @param context {@link Context} context object
161+
* @return current {@link Cluster} for current context
162+
*/
163+
public static Cluster getCluster(Config config, Context context) {
164+
Cluster cluster = null;
165+
if (config != null && context != null) {
166+
String clusterName = context.getCluster();
167+
if (clusterName != null) {
168+
List<NamedCluster> clusters = config.getClusters();
169+
if (clusters != null) {
170+
cluster = clusters.stream()
171+
.filter(c -> c.getName().equals(clusterName))
172+
.findAny()
173+
.map(NamedCluster::getCluster)
174+
.orElse(null);
175+
}
176+
}
177+
}
178+
return cluster;
179+
}
180+
181+
/**
182+
* Get User index from Config object
183+
*
184+
* @param config {@link io.fabric8.kubernetes.api.model.Config} Kube Config
185+
* @param userName username inside Config
186+
* @return index of user in users array
187+
*/
188+
public static int getNamedUserIndexFromConfig(Config config, String userName) {
189+
for (int i = 0; i < config.getUsers().size(); i++) {
190+
if (config.getUsers().get(i).getName().equals(userName)) {
191+
return i;
192+
}
193+
}
194+
return -1;
195+
}
196+
197+
/**
198+
* Modify KUBECONFIG file
199+
*
200+
* @param kubeConfig modified {@link io.fabric8.kubernetes.api.model.Config} object
201+
* @param kubeConfigPath path to KUBECONFIG
202+
* @throws IOException in case of failure while writing to file
203+
*/
204+
public static void persistKubeConfigIntoFile(Config kubeConfig, String kubeConfigPath) throws IOException {
205+
try (FileWriter writer = new FileWriter(kubeConfigPath)) {
206+
writer.write(Serialization.asYaml(kubeConfig));
207+
}
208+
}
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+
}
244+
}

0 commit comments

Comments
 (0)