Skip to content

Commit 2f6bacc

Browse files
committed
Add support for exec
1 parent 380928a commit 2f6bacc

File tree

6 files changed

+357
-6
lines changed

6 files changed

+357
-6
lines changed

examples/pom.xml

+8-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@
1919
<artifactId>client-java-util</artifactId>
2020
<version>1.0-SNAPSHOT</version>
2121
</dependency>
22+
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
23+
<dependency>
24+
<groupId>com.google.guava</groupId>
25+
<artifactId>guava</artifactId>
26+
<version>22.0</version>
27+
</dependency>
28+
2229
<!-- test dependencies -->
2330
<dependency>
2431
<groupId>junit</groupId>
@@ -39,4 +46,4 @@
3946
<junit-version>4.12</junit-version>
4047
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
4148
</properties>
42-
</project>
49+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package io.kubernetes.client.examples;
2+
3+
import io.kubernetes.client.ApiClient;
4+
import io.kubernetes.client.ApiException;
5+
import io.kubernetes.client.Configuration;
6+
import io.kubernetes.client.Exec;
7+
import io.kubernetes.client.apis.CoreV1Api;
8+
import io.kubernetes.client.models.V1Pod;
9+
import io.kubernetes.client.models.V1PodList;
10+
import io.kubernetes.client.util.Config;
11+
12+
import com.google.common.io.ByteStreams;
13+
14+
import java.io.BufferedReader;
15+
import java.io.InputStreamReader;
16+
import java.io.IOException;
17+
import java.io.OutputStream;
18+
19+
/**
20+
* A simple example of how to use the Java API
21+
*
22+
* Requires kubectl proxy running
23+
*
24+
* Easiest way to run this:
25+
* mvn exec:java -Dex.mainClass="io.kubernetes.client.examples.Example"
26+
*
27+
* From inside $REPO_DIR/kubernetes
28+
*/
29+
public class ExecExample {
30+
public static void main(String[] args) throws IOException, ApiException, InterruptedException {
31+
ApiClient client = Config.defaultClient();
32+
Configuration.setDefaultApiClient(client);
33+
34+
Exec exec = new Exec();
35+
final Process proc = exec.exec("default", "nginx-2371676037-czqx3", new String[] {"sh", "-c", "echo foo"}, true);
36+
37+
new Thread(new Runnable() {
38+
public void run() {
39+
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
40+
OutputStream output = proc.getOutputStream();
41+
try {
42+
while (true) {
43+
String line = in.readLine();
44+
output.write(line.getBytes());
45+
output.write('\n');
46+
output.flush();
47+
}
48+
} catch (IOException ex) {
49+
ex.printStackTrace();
50+
}
51+
}
52+
}).start();
53+
54+
new Thread(new Runnable() {
55+
public void run() {
56+
try {
57+
ByteStreams.copy(proc.getInputStream(), System.out);
58+
} catch (IOException ex) {
59+
ex.printStackTrace();
60+
}
61+
}
62+
}).start();
63+
64+
proc.waitFor();
65+
66+
System.exit(0);
67+
}
68+
}

examples/src/main/java/io/kubernetes/client/examples/WebSocketsExample.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import io.kubernetes.client.util.WebSockets;
1919

2020
import java.io.BufferedReader;
21+
import java.io.Closeable;
2122
import java.io.IOException;
2223
import java.io.InputStream;
2324
import java.io.Reader;
@@ -26,7 +27,7 @@ public class WebSocketsExample {
2627
public static void main(String... args) throws ApiException, IOException {
2728
final ApiClient client = Config.defaultClient();
2829
WebSockets.stream(args[0], "GET", client, new WebSockets.SocketListener() {
29-
public void open() {}
30+
public void open(String protocol, Closeable close) {}
3031
public void close() {
3132
// Trigger shutdown of the dispatcher's executor so this process can exit cleanly.
3233
client.getHttpClient().getDispatcher().getExecutorService().shutdown();

util/pom.xml

+5
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@
2424
<artifactId>commons-codec</artifactId>
2525
<version>1.10</version>
2626
</dependency>
27+
<dependency>
28+
<groupId>commons-lang</groupId>
29+
<artifactId>commons-lang</artifactId>
30+
<version>2.6</version>
31+
</dependency>
2732
<dependency>
2833
<groupId>com.squareup.okhttp</groupId>
2934
<artifactId>okhttp-ws</artifactId>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
package io.kubernetes.client;
2+
3+
import io.kubernetes.client.Configuration;
4+
import io.kubernetes.client.models.V1Pod;
5+
import io.kubernetes.client.util.WebSockets;
6+
7+
import java.io.ByteArrayInputStream;
8+
import java.io.Closeable;
9+
import java.io.InputStream;
10+
import java.io.IOException;
11+
import java.io.OutputStream;
12+
import java.io.PipedInputStream;
13+
import java.io.PipedOutputStream;
14+
import java.io.Reader;
15+
16+
import org.apache.commons.lang.StringUtils;
17+
18+
public class Exec {
19+
private ApiClient apiClient;
20+
21+
/**
22+
* Simple Exec API constructor, uses default configuration
23+
*/
24+
public Exec() {
25+
this(Configuration.getDefaultApiClient());
26+
}
27+
28+
/**
29+
* Exec API Constructor
30+
* @param apiClient The api client to use.
31+
*/
32+
public Exec(ApiClient apiClient) {
33+
this.apiClient = apiClient;
34+
}
35+
36+
/**
37+
* Get the API client for these exec operations.
38+
* @returns The API client that will be used.
39+
*/
40+
public ApiClient getApiClient() {
41+
return apiClient;
42+
}
43+
44+
/**
45+
* Set the API client for subsequent exec operations.
46+
* @param apiClient The new API client to use.
47+
*/
48+
public void setApiClient(ApiClient apiClient) {
49+
this.apiClient = apiClient;
50+
}
51+
52+
private String makePath(String namespace, String name, String[] command, String container, boolean stdin, boolean tty) {
53+
String path = "/api/v1/namespaces/" +
54+
namespace +
55+
"/pods/" +
56+
name +
57+
"/exec?" +
58+
"stdin=" + stdin +
59+
"&tty=" + tty +
60+
(container != null ? "&container=" + container : "") +
61+
"&command=" + StringUtils.join(command, "&command=");
62+
return path;
63+
}
64+
65+
/**
66+
* Execute a command in a container. If there are multiple containers in the pod, uses
67+
* the first container in the Pod.
68+
*
69+
* @param namespace The namespace of the Pod
70+
* @param name The name of the Pod
71+
* @param command The command to run
72+
* @param stdin If true, pass a stdin stream into the container
73+
*/
74+
public Process exec(String namespace, String name, String[] command, boolean stdin) throws ApiException, IOException {
75+
return exec(namespace, name, command, null, stdin, false);
76+
}
77+
78+
/**
79+
* Execute a command in a container. If there are multiple containers in the pod, uses
80+
* the first container in the Pod.
81+
*
82+
* @param pod The pod where the command is run.
83+
* @param command The command to run
84+
* @param stdin If true, pass a stdin stream into the container
85+
*/
86+
public Process exec(V1Pod pod, String[] command, boolean stdin) throws ApiException, IOException {
87+
return exec(pod, command, null, stdin, false);
88+
}
89+
90+
91+
/**
92+
* Execute a command in a container. If there are multiple containers in the pod, uses
93+
* the first container in the Pod.
94+
*
95+
* @param pod The pod where the command is run.
96+
* @param command The command to run
97+
* @param stdin If true, pass a stdin stream into the container
98+
* @param tty If true, stdin is a tty.
99+
*/
100+
public Process exec(V1Pod pod, String[] command, boolean stdin, boolean tty) throws ApiException, IOException {
101+
return exec(pod, command, null, stdin, tty);
102+
}
103+
104+
/**
105+
* Execute a command in a container. If there are multiple containers in the pod, uses
106+
* the first container in the Pod.
107+
*
108+
* @param pod The pod where the command is run.
109+
* @param command The command to run
110+
* @param container The container in the Pod where the command is run.
111+
* @param stdin If true, pass a stdin stream into the container.
112+
* @param tty If true, stdin is a TTY (only applies if stdin is true)
113+
*/
114+
public Process exec(V1Pod pod, String[] command, String container, boolean stdin, boolean tty) throws ApiException, IOException {
115+
return exec(pod.getMetadata().getNamespace(), pod.getMetadata().getName(), command, container, stdin, tty);
116+
}
117+
118+
/**
119+
* Execute a command in a container. If there are multiple containers in the pod, uses
120+
* the first container in the Pod.
121+
*
122+
* @param namespace The namespace of the Pod
123+
* @param name The name of the Pod
124+
* @param command The command to run
125+
* @param container The container in the Pod where the command is run.
126+
* @param stdin If true, pass a stdin stream into the container.
127+
* @param tty If true, stdin is a TTY (only applies if stdin is true)
128+
*/
129+
public Process exec(String namespace, String name, String[] command, String container, boolean stdin, boolean tty) throws ApiException, IOException {
130+
String path = makePath(namespace, name, command, container, stdin, tty);
131+
132+
ExecProcess exec = new ExecProcess();
133+
WebSockets.stream(path, "GET", apiClient, exec);
134+
135+
return exec;
136+
}
137+
138+
private static class ExecProcess extends Process implements WebSockets.SocketListener {
139+
private PipedInputStream pipeIn;
140+
private PipedOutputStream pipeOut;
141+
private PipedOutputStream pipeErr;
142+
private InputStream err;
143+
private OutputStream output;
144+
private InputStream input;
145+
private int statusCode;
146+
private Closeable closer;
147+
148+
public ExecProcess() throws IOException {
149+
this.pipeIn = new PipedInputStream();
150+
this.pipeOut = new PipedOutputStream();
151+
this.pipeErr = new PipedOutputStream();
152+
this.input = new PipedInputStream(pipeOut);
153+
this.output = new PipedOutputStream(pipeIn);
154+
this.err = new PipedInputStream(pipeErr);
155+
this.statusCode = -1;
156+
}
157+
158+
public OutputStream getOutputStream() {
159+
return output;
160+
}
161+
162+
public InputStream getInputStream() {
163+
return input;
164+
}
165+
166+
public InputStream getErrorStream() {
167+
return err;
168+
}
169+
170+
public int waitFor() throws InterruptedException {
171+
synchronized(this) {
172+
this.wait();
173+
}
174+
return statusCode;
175+
}
176+
177+
public int exitValue() {
178+
return statusCode;
179+
}
180+
181+
public void destroy() {
182+
if (this.closer != null) {
183+
this.close();
184+
}
185+
}
186+
187+
@Override
188+
public void open(String protocol, Closeable closer) {
189+
this.closer = closer;
190+
}
191+
192+
private OutputStream findOutputStream(int val) {
193+
if (val == 1) {
194+
return pipeOut;
195+
}
196+
if (val == 2) {
197+
return pipeErr;
198+
}
199+
System.err.println("Unknown stream: " + val);
200+
return pipeOut;
201+
}
202+
203+
@Override
204+
public void bytesMessage(InputStream in) {
205+
try {
206+
int val = in.read();
207+
OutputStream out = findOutputStream(val);
208+
209+
byte[] buffer = new byte[4096];
210+
for (int read = in.read(buffer); read != -1; read = in.read(buffer)) {
211+
out.write(buffer, 0, read);
212+
}
213+
out.flush();
214+
} catch (IOException ex) {
215+
// TODO use a logger here.
216+
ex.printStackTrace();
217+
}
218+
}
219+
220+
@Override
221+
public void textMessage(Reader in) {
222+
try {
223+
int val = in.read();
224+
OutputStream out = findOutputStream(val);
225+
// TODO: there has to be a better way to do this...
226+
char[] buffer = new char[4096];
227+
for (int read = in.read(buffer); read != -1; read = in.read(buffer)) {
228+
String data = new String(buffer, 0, read);
229+
out.write(data.getBytes("UTF-8"));
230+
}
231+
out.flush();
232+
} catch (IOException ex) {
233+
// TODO use a logger here
234+
ex.printStackTrace();
235+
}
236+
}
237+
238+
@Override
239+
public void close() {
240+
try {
241+
pipeIn.close();
242+
pipeOut.close();
243+
output.close();
244+
} catch (IOException ex) {
245+
// TODO use a logger here
246+
ex.printStackTrace();
247+
}
248+
// TODO: get status code here
249+
synchronized(this) {
250+
this.notifyAll();
251+
}
252+
}
253+
}
254+
}

0 commit comments

Comments
 (0)