Skip to content

Commit ef580a6

Browse files
committed
Added a fuction/method setTerminalsize in Exec.ExecBuilber
1 parent 65ade94 commit ef580a6

File tree

1 file changed

+136
-106
lines changed
  • util/src/main/java/io/kubernetes/client

1 file changed

+136
-106
lines changed

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

Lines changed: 136 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@ public void setApiClient(ApiClient apiClient) {
9292
}
9393

9494
/**
95-
* Get a {@link Consumer<Throwable>} that will be accepted if there is any unhandled exception
95+
* Get a {@link Consumer<Throwable>} that will be accepted if there is any
96+
* unhandled exception
9697
* while the websocket communication is happening.
9798
*
9899
* @return The {@link Consumer<Throwable>} that will be used.
@@ -102,7 +103,8 @@ public Consumer<Throwable> getOnUnhandledError() {
102103
}
103104

104105
/**
105-
* Set the {@link Consumer<Throwable>} that will be accepted if there is any unhandled exception
106+
* Set the {@link Consumer<Throwable>} that will be accepted if there is any
107+
* unhandled exception
106108
* while the websocket communication is happening.
107109
*
108110
* @param onUnhandledError The new {@link Consumer<Throwable>} to use.
@@ -115,77 +117,82 @@ public void setOnUnhandledError(Consumer<Throwable> onUnhandledError) {
115117
* Setup a Builder for the given namespace, name and command
116118
*
117119
* @param namespace The namespace of the Pod
118-
* @param name The name of the Pod
119-
* @param command The command to run
120+
* @param name The name of the Pod
121+
* @param command The command to run
120122
*/
121123
public ExecutionBuilder newExecutionBuilder(String namespace, String name, String[] command) {
122124
return new ExecutionBuilder(namespace, name, command);
123125
}
124126

125127
/**
126-
* Execute a command in a container. If there are multiple containers in the pod, uses the first
128+
* Execute a command in a container. If there are multiple containers in the
129+
* pod, uses the first
127130
* container in the Pod.
128131
*
129132
* @param namespace The namespace of the Pod
130-
* @param name The name of the Pod
131-
* @param command The command to run
132-
* @param stdin If true, pass a stdin stream into the container
133+
* @param name The name of the Pod
134+
* @param command The command to run
135+
* @param stdin If true, pass a stdin stream into the container
133136
*/
134137
public Process exec(String namespace, String name, String[] command, boolean stdin)
135138
throws ApiException, IOException {
136139
return exec(namespace, name, command, null, stdin, false);
137140
}
138141

139142
/**
140-
* Execute a command in a container. If there are multiple containers in the pod, uses the first
143+
* Execute a command in a container. If there are multiple containers in the
144+
* pod, uses the first
141145
* container in the Pod.
142146
*
143-
* @param pod The pod where the command is run.
147+
* @param pod The pod where the command is run.
144148
* @param command The command to run
145-
* @param stdin If true, pass a stdin stream into the container
149+
* @param stdin If true, pass a stdin stream into the container
146150
*/
147151
public Process exec(V1Pod pod, String[] command, boolean stdin) throws ApiException, IOException {
148152
return exec(pod, command, pod.getSpec().getContainers().get(0).getName(), stdin, false);
149153
}
150154

151155
/**
152-
* Execute a command in a container. If there are multiple containers in the pod, uses the first
156+
* Execute a command in a container. If there are multiple containers in the
157+
* pod, uses the first
153158
* container in the Pod.
154159
*
155160
* @param namespace The namespace of the Pod
156-
* @param name The name of the Pod
157-
* @param command The command to run
158-
* @param stdin If true, pass a stdin stream into the container
159-
* @param tty If true, stdin is a tty.
161+
* @param name The name of the Pod
162+
* @param command The command to run
163+
* @param stdin If true, pass a stdin stream into the container
164+
* @param tty If true, stdin is a tty.
160165
*/
161166
public Process exec(String namespace, String name, String[] command, boolean stdin, boolean tty)
162167
throws ApiException, IOException {
163168
return exec(namespace, name, command, null, stdin, tty);
164169
}
165170

166171
/**
167-
* Execute a command in a container. If there are multiple containers in the pod, uses the first
172+
* Execute a command in a container. If there are multiple containers in the
173+
* pod, uses the first
168174
* container in the Pod.
169175
*
170-
* @param pod The pod where the command is run.
176+
* @param pod The pod where the command is run.
171177
* @param command The command to run
172-
* @param stdin If true, pass a stdin stream into the container
173-
* @param tty If true, stdin is a tty.
178+
* @param stdin If true, pass a stdin stream into the container
179+
* @param tty If true, stdin is a tty.
174180
*/
175181
public Process exec(V1Pod pod, String[] command, boolean stdin, boolean tty)
176182
throws ApiException, IOException {
177183
return exec(pod, command, pod.getSpec().getContainers().get(0).getName(), stdin, tty);
178184
}
179185

180186
/**
181-
* Execute a command in a container. If there are multiple containers in the pod, uses the first
187+
* Execute a command in a container. If there are multiple containers in the
188+
* pod, uses the first
182189
* container in the Pod.
183190
*
184-
* @param pod The pod where the command is run.
185-
* @param command The command to run
191+
* @param pod The pod where the command is run.
192+
* @param command The command to run
186193
* @param container The container in the Pod where the command is run.
187-
* @param stdin If true, pass a stdin stream into the container.
188-
* @param tty If true, stdin is a TTY (only applies if stdin is true)
194+
* @param stdin If true, pass a stdin stream into the container.
195+
* @param tty If true, stdin is a TTY (only applies if stdin is true)
189196
*/
190197
public Process exec(V1Pod pod, String[] command, String container, boolean stdin, boolean tty)
191198
throws ApiException, IOException {
@@ -202,15 +209,16 @@ public Process exec(V1Pod pod, String[] command, String container, boolean stdin
202209
}
203210

204211
/**
205-
* Execute a command in a container. If there are multiple containers in the pod, uses the first
212+
* Execute a command in a container. If there are multiple containers in the
213+
* pod, uses the first
206214
* container in the Pod.
207215
*
208216
* @param namespace The namespace of the Pod
209-
* @param name The name of the Pod
210-
* @param command The command to run
217+
* @param name The name of the Pod
218+
* @param command The command to run
211219
* @param container The container in the Pod where the command is run.
212-
* @param stdin If true, pass a stdin stream into the container.
213-
* @param tty If true, stdin is a TTY (only applies if stdin is true)
220+
* @param stdin If true, pass a stdin stream into the container.
221+
* @param tty If true, stdin is a TTY (only applies if stdin is true)
214222
*/
215223
public Process exec(
216224
String namespace, String name, String[] command, String container, boolean stdin, boolean tty)
@@ -224,32 +232,46 @@ public Process exec(
224232
}
225233

226234
/**
227-
* A convenience method. Executes a command remotely on a pod and monitors for events in that
235+
* A convenience method. Executes a command remotely on a pod and monitors for
236+
* events in that
228237
* execution. The monitored events are: <br>
229238
* - connection established (onOpen) <br>
230239
* - connection closed (onClosed) <br>
231240
* - execution error occurred (onError) <br>
232-
* This method also allows to specify a MAX timeout for the execution and returns a future in
241+
* This method also allows to specify a MAX timeout for the execution and
242+
* returns a future in
233243
* order to monitor the execution flow. <br>
234-
* onError and onClosed callbacks are invoked asynchronously, in a separate thread. <br>
244+
* onError and onClosed callbacks are invoked asynchronously, in a separate
245+
* thread. <br>
235246
*
236247
* @param namespace a namespace the target pod "lives" in
237-
* @param podName a name of the pod to exec the command on
238-
* @param onOpen a callback invoked upon the connection established event.
239-
* @param onClosed a callback invoked upon the process termination. Return code might not always
240-
* be there. N.B. this callback is invoked before the returned {@link Future} is completed.
241-
* @param onError a callback to handle k8s errors (NOT the command errors/stderr!)
242-
* @param timeoutMs timeout in milliseconds for the execution. I.e. the execution will take this
243-
* many ms or less. If the timeout command is running longer than the allowed timeout, the
244-
* command will be "asked" to terminate gracefully. If the command is still running after the
245-
* grace period, the sigkill will be issued. If null is passed, the timeout will not be used
246-
* and will wait for process to exit itself.
247-
* @param tty whether you need tty to pipe the data. TTY mode will trim some binary data in order
248-
* to make it possible to show on screen (tty)
249-
* @param command a tokenized command to run on the pod
250-
* @return a {@link Future} promise representing this execution. Unless something goes south, the
251-
* promise will contain the process return exit code. If the timeoutMs is non-null and the
252-
* timeout expires before the process exits, promise will contain {@link Integer#MAX_VALUE}.
248+
* @param podName a name of the pod to exec the command on
249+
* @param onOpen a callback invoked upon the connection established event.
250+
* @param onClosed a callback invoked upon the process termination. Return code
251+
* might not always
252+
* be there. N.B. this callback is invoked before the returned
253+
* {@link Future} is completed.
254+
* @param onError a callback to handle k8s errors (NOT the command
255+
* errors/stderr!)
256+
* @param timeoutMs timeout in milliseconds for the execution. I.e. the
257+
* execution will take this
258+
* many ms or less. If the timeout command is running longer
259+
* than the allowed timeout, the
260+
* command will be "asked" to terminate gracefully. If the
261+
* command is still running after the
262+
* grace period, the sigkill will be issued. If null is passed,
263+
* the timeout will not be used
264+
* and will wait for process to exit itself.
265+
* @param tty whether you need tty to pipe the data. TTY mode will trim
266+
* some binary data in order
267+
* to make it possible to show on screen (tty)
268+
* @param command a tokenized command to run on the pod
269+
* @return a {@link Future} promise representing this execution. Unless
270+
* something goes south, the
271+
* promise will contain the process return exit code. If the timeoutMs
272+
* is non-null and the
273+
* timeout expires before the process exits, promise will contain
274+
* {@link Integer#MAX_VALUE}.
253275
* @throws IOException
254276
*/
255277
public Future<Integer> exec(
@@ -265,12 +287,11 @@ public Future<Integer> exec(
265287
CompletableFuture<Integer> future = new CompletableFuture<>();
266288
IOTrio io = new IOTrio();
267289
String cmdStr = Arrays.toString(command);
268-
BiConsumer<Throwable, IOTrio> errHandler =
269-
(err, errIO) -> {
270-
if (onError != null) {
271-
onError.accept(err, errIO);
272-
}
273-
};
290+
BiConsumer<Throwable, IOTrio> errHandler = (err, errIO) -> {
291+
if (onError != null) {
292+
onError.accept(err, errIO);
293+
}
294+
};
274295
try {
275296
Process process = exec(namespace, podName, command, null, true, tty);
276297

@@ -289,9 +310,8 @@ public Future<Integer> exec(
289310
Supplier<Integer> returnCode = process::exitValue;
290311
try {
291312
log.debug("Waiting for process to close in {} ms: {}", timeoutMs, cmdStr);
292-
boolean beforeTimeout =
293-
waitForProcessToExit(
294-
process, timeoutMs, cmdStr, err -> errHandler.accept(err, io));
313+
boolean beforeTimeout = waitForProcessToExit(
314+
process, timeoutMs, cmdStr, err -> errHandler.accept(err, io));
295315
if (!beforeTimeout) {
296316
returnCode = () -> Integer.MAX_VALUE;
297317
}
@@ -368,6 +388,14 @@ private ExecutionBuilder(String namespace, String name, String[] command) {
368388
this.stderr = true;
369389
}
370390

391+
public void setTerminalSize(int widthColumns, int heightColumns) throws IOException, ApiException {
392+
Exec.ExecProcess execProcess = (Exec.ExecProcess) execute(); // Ensure execute() is called to get the process
393+
OutputStream resizeOutStream = execProcess.getResizeStream();
394+
String resize = "{\"Width\":" + widthColumns + ",\"Height\":" + heightColumns + "}";
395+
resizeOutStream.write(resize.getBytes());
396+
resizeOutStream.flush(); // Optional: flush to ensure immediate sending
397+
}
398+
371399
public String getName() {
372400
return name;
373401
}
@@ -478,7 +506,8 @@ public Process execute() throws ApiException, IOException {
478506

479507
static int parseExitCode(ApiClient client, InputStream inputStream) {
480508
try {
481-
Type returnType = new TypeToken<V1Status>() {}.getType();
509+
Type returnType = new TypeToken<V1Status>() {
510+
}.getType();
482511
String body;
483512
try (final Reader reader = new InputStreamReader(inputStream)) {
484513
body = Streams.toString(reader);
@@ -488,7 +517,8 @@ static int parseExitCode(ApiClient client, InputStream inputStream) {
488517
if (status == null) {
489518
return -1;
490519
}
491-
if (V1STATUS_SUCCESS.equals(status.getStatus())) return 0;
520+
if (V1STATUS_SUCCESS.equals(status.getStatus()))
521+
return 0;
492522

493523
if (V1STATUS_REASON_NONZEROEXITCODE.equals(status.getReason())) {
494524
V1StatusDetails details = status.getDetails();
@@ -529,58 +559,57 @@ public ExecProcess(final ApiClient apiClient) throws IOException {
529559

530560
public ExecProcess(final ApiClient apiClient, final Consumer<Throwable> onUnhandledError)
531561
throws IOException {
532-
this.onUnhandledError =
533-
Optional.ofNullable(onUnhandledError).orElse(Throwable::printStackTrace);
534-
this.streamHandler =
535-
new WebSocketStreamHandler() {
536-
@Override
537-
protected void handleMessage(int stream, InputStream inStream) throws IOException {
538-
if (stream == 3) {
539-
int exitCode = parseExitCode(apiClient, inStream);
540-
if (exitCode >= 0) {
541-
// notify of process completion
542-
synchronized (ExecProcess.this) {
543-
statusCode = exitCode;
544-
isAlive = false;
545-
}
546-
}
547-
inStream.close();
548-
// Stream ID of `3` delivers the status of exec connection from
549-
// kubelet,
550-
// closing the connection upon 0 exit-code.
551-
this.close();
552-
ExecProcess.this.latch.countDown();
553-
} else super.handleMessage(stream, inStream);
554-
}
555-
556-
@Override
557-
public void failure(Throwable ex) {
558-
super.failure(ex);
559-
ExecProcess.this.onUnhandledError.accept(ex);
560-
562+
this.onUnhandledError = Optional.ofNullable(onUnhandledError).orElse(Throwable::printStackTrace);
563+
this.streamHandler = new WebSocketStreamHandler() {
564+
@Override
565+
protected void handleMessage(int stream, InputStream inStream) throws IOException {
566+
if (stream == 3) {
567+
int exitCode = parseExitCode(apiClient, inStream);
568+
if (exitCode >= 0) {
569+
// notify of process completion
561570
synchronized (ExecProcess.this) {
562-
// Try for a pretty unique error code, so if someone searches
563-
// they'll find this
564-
// code.
565-
statusCode = -1975219;
571+
statusCode = exitCode;
566572
isAlive = false;
567-
ExecProcess.this.latch.countDown();
568573
}
569574
}
575+
inStream.close();
576+
// Stream ID of `3` delivers the status of exec connection from
577+
// kubelet,
578+
// closing the connection upon 0 exit-code.
579+
this.close();
580+
ExecProcess.this.latch.countDown();
581+
} else
582+
super.handleMessage(stream, inStream);
583+
}
570584

571-
@Override
572-
public void close() {
573-
// notify of process completion
574-
synchronized (ExecProcess.this) {
575-
if (isAlive) {
576-
isAlive = false;
577-
ExecProcess.this.latch.countDown();
578-
}
579-
}
585+
@Override
586+
public void failure(Throwable ex) {
587+
super.failure(ex);
588+
ExecProcess.this.onUnhandledError.accept(ex);
589+
590+
synchronized (ExecProcess.this) {
591+
// Try for a pretty unique error code, so if someone searches
592+
// they'll find this
593+
// code.
594+
statusCode = -1975219;
595+
isAlive = false;
596+
ExecProcess.this.latch.countDown();
597+
}
598+
}
580599

581-
super.close();
600+
@Override
601+
public void close() {
602+
// notify of process completion
603+
synchronized (ExecProcess.this) {
604+
if (isAlive) {
605+
isAlive = false;
606+
ExecProcess.this.latch.countDown();
582607
}
583-
};
608+
}
609+
610+
super.close();
611+
}
612+
};
584613
}
585614

586615
// Protected to facilitate unit testing.
@@ -632,7 +661,8 @@ public boolean waitFor(long timeout, TimeUnit unit) throws InterruptedException
632661

633662
@Override
634663
public synchronized int exitValue() {
635-
if (isAlive) throw new IllegalThreadStateException();
664+
if (isAlive)
665+
throw new IllegalThreadStateException();
636666
return statusCode;
637667
}
638668

0 commit comments

Comments
 (0)