Skip to content

Commit ba42f45

Browse files
committed
Add an auto-magic APIClient function.
And some initial tests.
1 parent b04093e commit ba42f45

File tree

5 files changed

+258
-12
lines changed

5 files changed

+258
-12
lines changed

examples/pom.xml

+19
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,24 @@
1919
<artifactId>client-java-util</artifactId>
2020
<version>1.0-SNAPSHOT</version>
2121
</dependency>
22+
<!-- test dependencies -->
23+
<dependency>
24+
<groupId>junit</groupId>
25+
<artifactId>junit</artifactId>
26+
<version>${junit-version}</version>
27+
<scope>test</scope>
28+
</dependency>
2229
</dependencies>
30+
<properties>
31+
<java.version>1.7</java.version>
32+
<maven.compiler.source>${java.version}</maven.compiler.source>
33+
<maven.compiler.target>${java.version}</maven.compiler.target>
34+
<swagger-core-version>1.5.12</swagger-core-version>
35+
<okhttp-version>2.7.5</okhttp-version>
36+
<gson-version>2.6.2</gson-version>
37+
<jodatime-version>2.9.3</jodatime-version>
38+
<maven-plugin-version>1.0.0</maven-plugin-version>
39+
<junit-version>4.12</junit-version>
40+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
41+
</properties>
2342
</project>

util/pom.xml

+29
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,19 @@
2424
<artifactId>commons-codec</artifactId>
2525
<version>1.10</version>
2626
</dependency>
27+
<!-- test dependencies -->
28+
<dependency>
29+
<groupId>junit</groupId>
30+
<artifactId>junit</artifactId>
31+
<version>4.12</version>
32+
<scope>test</scope>
33+
</dependency>
34+
<!-- https://mvnrepository.com/artifact/com.github.stefanbirkner/system-rules -->
35+
<dependency>
36+
<groupId>com.github.stefanbirkner</groupId>
37+
<artifactId>system-rules</artifactId>
38+
<version>1.16.1</version>
39+
</dependency>
2740
</dependencies>
2841
<build>
2942
<plugins>
@@ -36,6 +49,22 @@
3649
<target>1.7</target>
3750
</configuration>
3851
</plugin>
52+
<plugin>
53+
<groupId>org.apache.maven.plugins</groupId>
54+
<artifactId>maven-surefire-plugin</artifactId>
55+
<version>2.12</version>
56+
<configuration>
57+
<systemProperties>
58+
<property>
59+
<name>loggerPath</name>
60+
<value>conf/log4j.properties</value>
61+
</property>
62+
</systemProperties>
63+
<argLine>-Xms512m -Xmx1500m</argLine>
64+
<parallel>methods</parallel>
65+
<forkMode>pertest</forkMode>
66+
</configuration>
67+
</plugin>
3968
</plugins>
4069
</build>
4170
</project>

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

+43-5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import java.io.BufferedReader;
66
import java.io.ByteArrayInputStream;
7+
import java.io.File;
78
import java.io.FileReader;
89
import java.io.FileInputStream;
910
import java.io.FileNotFoundException;
@@ -18,16 +19,21 @@
1819
import javax.net.ssl.KeyManagerFactory;
1920

2021
public class Config {
21-
private static final String SERVICEACCOUNT_ROOT =
22+
public static final String SERVICEACCOUNT_ROOT =
2223
"/var/run/secrets/kubernetes.io/serviceaccount";
23-
private static final String SERVICEACCOUNT_CA_PATH =
24+
public static final String SERVICEACCOUNT_CA_PATH =
2425
SERVICEACCOUNT_ROOT + "/ca.crt";
25-
private static final String SERVICEACCOUNT_TOKEN_PATH =
26+
public static final String SERVICEACCOUNT_TOKEN_PATH =
2627
SERVICEACCOUNT_ROOT + "/token";
28+
public static final String ENV_KUBECONFIG = "KUBECONFIG";
29+
public static final String ENV_SERVICE_HOST = "KUBERNETES_SERVICE_HOST";
30+
public static final String ENV_SERVICE_PORT = "KUBERNETES_SERVICE_PORT";
31+
// The last resort host to try
32+
public static final String DEFAULT_FALLBACK_HOST = "http://localhost:8080";
2733

2834
public static ApiClient fromCluster() throws IOException {
29-
String host = System.getenv("KUBERNETES_SERVICE_HOST");
30-
String port = System.getenv("KUBERNETES_SERVICE_PORT");
35+
String host = System.getenv(ENV_SERVICE_HOST);
36+
String port = System.getenv(ENV_SERVICE_PORT);
3137

3238
FileInputStream caCert = new FileInputStream(SERVICEACCOUNT_CA_PATH);
3339
BufferedReader tokenReader = new BufferedReader(new FileReader(SERVICEACCOUNT_TOKEN_PATH));
@@ -137,4 +143,36 @@ public static ApiClient fromConfig(Reader input) {
137143

138144
return client;
139145
}
146+
147+
/**
148+
* Easy client creation, follows this plan
149+
* <ul>
150+
* <li>If $KUBECONFIG is defined, use that config file
151+
* <li>If $HOME/.kube/confg can be found, use that.
152+
* <li>If the in-cluster service account can be found, assume in cluster config.
153+
* <li>Default to localhost:8080 as a last resort.
154+
* <ul>
155+
*
156+
* @returns The best APIClient given the previously described rules
157+
*/
158+
public static ApiClient defaultClient() throws IOException {
159+
String kubeConfig = System.getenv(ENV_KUBECONFIG);
160+
if (kubeConfig != null) {
161+
return fromConfig(new FileReader(kubeConfig));
162+
}
163+
File config = new File(
164+
new File(System.getenv(KubeConfig.ENV_HOME),
165+
KubeConfig.KUBEDIR),
166+
KubeConfig.KUBECONFIG);
167+
if (config.exists()) {
168+
return fromConfig(new FileReader(config));
169+
}
170+
File clusterCA = new File(SERVICEACCOUNT_CA_PATH);
171+
if (clusterCA.exists()) {
172+
return fromCluster();
173+
}
174+
ApiClient client = new ApiClient();
175+
client.setBasePath(DEFAULT_FALLBACK_HOST);
176+
return client;
177+
}
140178
}

util/src/main/java/io/kubernetes/client/util/KubeConfig.java

+30-7
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@
1414
* KubeConfig represents a kubernetes client configuration
1515
*/
1616
public class KubeConfig {
17+
// Defaults for where to find a kubeconfig file
18+
public static final String ENV_HOME = "HOME";
19+
public static final String KUBEDIR = ".kube";
20+
public static final String KUBECONFIG = "config";
21+
1722
// Note to the reader: I considered creating a Config object
1823
// and parsing into that instead of using Maps, but honestly
1924
// this seemed cleaner than a bunch of boilerplate classes
@@ -29,8 +34,10 @@ public class KubeConfig {
2934
* Load a Kubernetes config from the default location
3035
*/
3136
public static KubeConfig loadDefaultKubeConfig() throws FileNotFoundException {
32-
File homeDir = new File(System.getenv("HOME"));
33-
File config = new File(new File(homeDir, ".kube"), "config");
37+
File config = new File(new File(System.getenv(ENV_HOME), KUBEDIR), KUBECONFIG);
38+
if (!config.exists()) {
39+
return null;
40+
}
3441
return loadKubeConfig(new FileReader(config));
3542
}
3643

@@ -61,22 +68,35 @@ public KubeConfig(ArrayList<Object> contexts,
6168
this.users = users;
6269
}
6370

64-
public void setContext(String context) {
71+
public boolean setContext(String context) {
6572
currentCluster = null;
6673
currentUser = null;
67-
currentContext = (Map<String, Object>) findObject(contexts, context).get("context");
74+
Map<String, Object> ctx = findObject(contexts, context);
75+
if (ctx == null) {
76+
System.err.println("Failed to find context: " + context + " in " + contexts);
77+
return false;
78+
}
79+
currentContext = (Map<String, Object>) ctx.get("context");
6880
if (currentContext == null) {
69-
return;
81+
System.err.println("Failed to find 'context' in " + ctx);
82+
return false;
7083
}
7184
String cluster = (String) currentContext.get("cluster");
7285
String user = (String) currentContext.get("user");
7386

7487
if (cluster != null) {
75-
currentCluster = (Map<String, Object>) findObject(clusters, cluster).get("cluster");
88+
Map<String, Object> obj = findObject(clusters, cluster);
89+
if (obj != null) {
90+
currentCluster = (Map<String, Object>) obj.get("cluster");
91+
}
7692
}
7793
if (user != null) {
78-
currentUser = (Map<String, Object>) findObject(users, user).get("user");
94+
Map<String, Object> obj = findObject(users, user);
95+
if (obj != null) {
96+
currentUser = (Map<String, Object>) obj.get("user");
97+
}
7998
}
99+
return true;
80100
}
81101

82102
public String getServer() {
@@ -138,6 +158,9 @@ private static String getData(Map<String, Object> obj, String key) {
138158
}
139159

140160
private static Map<String, Object> findObject(ArrayList<Object> list, String name) {
161+
if (list == null) {
162+
return null;
163+
}
141164
for (Object obj : list) {
142165
Map<String, Object> map = (Map<String, Object>)obj;
143166
if (name.equals((String)map.get("name"))) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package io.kubernetes.client.util;
2+
3+
import io.kubernetes.client.ApiClient;
4+
5+
import java.io.File;
6+
import java.io.FileWriter;
7+
import java.io.IOException;
8+
9+
import static org.junit.Assert.*;
10+
import org.junit.After;
11+
import org.junit.Before;
12+
import org.junit.Ignore;
13+
import org.junit.Rule;
14+
import org.junit.Test;
15+
import org.junit.contrib.java.lang.system.EnvironmentVariables;
16+
17+
/**
18+
* Tests for the Config helper class
19+
*/
20+
public class ConfigTest {
21+
@Rule
22+
public final EnvironmentVariables environmentVariables
23+
= new EnvironmentVariables();
24+
25+
@Test
26+
public void testDefaultClientNothingPresent() {
27+
environmentVariables.set("HOME", "/non-existent");
28+
try {
29+
ApiClient client = Config.defaultClient();
30+
assertEquals("http://localhost:8080", client.getBasePath());
31+
} catch (IOException ex) {
32+
fail("Unexpected exception: " + ex);
33+
}
34+
}
35+
36+
public static String HOME_CONFIG =
37+
"apiVersion: v1\n" +
38+
"clusters:\n" +
39+
"- cluster:\n" +
40+
" server: http://home.dir.com\n" +
41+
" name: foo\n" +
42+
"contexts:\n" +
43+
"- context:\n" +
44+
" cluster: foo\n" +
45+
" name: foo-context\n" +
46+
"current-context: foo-context\n";
47+
48+
public static String KUBECONFIG =
49+
"apiVersion: v1\n" +
50+
"clusters:\n" +
51+
"- cluster:\n" +
52+
" server: http://kubeconfig.dir.com\n" +
53+
" name: foo\n" +
54+
"contexts:\n" +
55+
"- context:\n" +
56+
" cluster: foo\n" +
57+
" name: foo-context\n" +
58+
"current-context: foo-context\n";
59+
60+
File config = null;
61+
File dir = null;
62+
File kubedir = null;
63+
File configFile = null;
64+
65+
@Before
66+
public void setUp() throws IOException {
67+
dir = new File("/tmp/tester");
68+
dir.mkdir();
69+
kubedir = new File(dir, ".kube");
70+
kubedir.mkdir();
71+
config = new File(kubedir, "config");
72+
FileWriter writer = new FileWriter(config);
73+
writer.write(HOME_CONFIG);
74+
writer.flush();
75+
writer.close();
76+
77+
configFile = File.createTempFile("config", "");
78+
writer = new FileWriter(configFile);
79+
writer.write(KUBECONFIG);
80+
writer.flush();
81+
writer.close();
82+
}
83+
84+
@After
85+
public void tearDown() throws IOException {
86+
if (config != null) {
87+
config.delete();
88+
}
89+
if (kubedir != null) {
90+
kubedir.delete();
91+
}
92+
if (dir != null) {
93+
dir.delete();
94+
}
95+
if (configFile != null) {
96+
configFile.delete();
97+
}
98+
}
99+
100+
@Test
101+
public void testDefaultClientHomeDir() {
102+
try {
103+
environmentVariables.set("HOME", dir.getCanonicalPath());
104+
ApiClient client = Config.defaultClient();
105+
assertEquals("http://home.dir.com", client.getBasePath());
106+
} catch (Exception ex) {
107+
ex.printStackTrace();
108+
fail("Unexpected exception: " + ex);
109+
}
110+
}
111+
112+
@Test
113+
public void testDefaultClientKubeConfig() {
114+
try {
115+
environmentVariables.set("KUBECONFIG", configFile.getCanonicalPath());
116+
ApiClient client = Config.defaultClient();
117+
assertEquals("http://kubeconfig.dir.com", client.getBasePath());
118+
} catch (Exception ex) {
119+
ex.printStackTrace();
120+
fail("Unexpected exception: " + ex);
121+
}
122+
}
123+
124+
@Test
125+
public void testDefaultClientPrecedence() {
126+
try {
127+
environmentVariables.set("HOME", dir.getCanonicalPath());
128+
environmentVariables.set("KUBECONFIG", configFile.getCanonicalPath());
129+
ApiClient client = Config.defaultClient();
130+
// $KUBECONFIG should take precedence over $HOME/.kube/config
131+
assertEquals("http://kubeconfig.dir.com", client.getBasePath());
132+
} catch (Exception ex) {
133+
ex.printStackTrace();
134+
fail("Unexpected exception: " + ex);
135+
}
136+
}
137+
}

0 commit comments

Comments
 (0)