@@ -92,7 +92,8 @@ public void setApiClient(ApiClient apiClient) {
92
92
}
93
93
94
94
/**
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
96
97
* while the websocket communication is happening.
97
98
*
98
99
* @return The {@link Consumer<Throwable>} that will be used.
@@ -102,7 +103,8 @@ public Consumer<Throwable> getOnUnhandledError() {
102
103
}
103
104
104
105
/**
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
106
108
* while the websocket communication is happening.
107
109
*
108
110
* @param onUnhandledError The new {@link Consumer<Throwable>} to use.
@@ -115,77 +117,82 @@ public void setOnUnhandledError(Consumer<Throwable> onUnhandledError) {
115
117
* Setup a Builder for the given namespace, name and command
116
118
*
117
119
* @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
120
122
*/
121
123
public ExecutionBuilder newExecutionBuilder (String namespace , String name , String [] command ) {
122
124
return new ExecutionBuilder (namespace , name , command );
123
125
}
124
126
125
127
/**
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
127
130
* container in the Pod.
128
131
*
129
132
* @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
133
136
*/
134
137
public Process exec (String namespace , String name , String [] command , boolean stdin )
135
138
throws ApiException , IOException {
136
139
return exec (namespace , name , command , null , stdin , false );
137
140
}
138
141
139
142
/**
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
141
145
* container in the Pod.
142
146
*
143
- * @param pod The pod where the command is run.
147
+ * @param pod The pod where the command is run.
144
148
* @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
146
150
*/
147
151
public Process exec (V1Pod pod , String [] command , boolean stdin ) throws ApiException , IOException {
148
152
return exec (pod , command , pod .getSpec ().getContainers ().get (0 ).getName (), stdin , false );
149
153
}
150
154
151
155
/**
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
153
158
* container in the Pod.
154
159
*
155
160
* @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.
160
165
*/
161
166
public Process exec (String namespace , String name , String [] command , boolean stdin , boolean tty )
162
167
throws ApiException , IOException {
163
168
return exec (namespace , name , command , null , stdin , tty );
164
169
}
165
170
166
171
/**
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
168
174
* container in the Pod.
169
175
*
170
- * @param pod The pod where the command is run.
176
+ * @param pod The pod where the command is run.
171
177
* @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.
174
180
*/
175
181
public Process exec (V1Pod pod , String [] command , boolean stdin , boolean tty )
176
182
throws ApiException , IOException {
177
183
return exec (pod , command , pod .getSpec ().getContainers ().get (0 ).getName (), stdin , tty );
178
184
}
179
185
180
186
/**
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
182
189
* container in the Pod.
183
190
*
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
186
193
* @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)
189
196
*/
190
197
public Process exec (V1Pod pod , String [] command , String container , boolean stdin , boolean tty )
191
198
throws ApiException , IOException {
@@ -202,15 +209,16 @@ public Process exec(V1Pod pod, String[] command, String container, boolean stdin
202
209
}
203
210
204
211
/**
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
206
214
* container in the Pod.
207
215
*
208
216
* @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
211
219
* @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)
214
222
*/
215
223
public Process exec (
216
224
String namespace , String name , String [] command , String container , boolean stdin , boolean tty )
@@ -224,32 +232,46 @@ public Process exec(
224
232
}
225
233
226
234
/**
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
228
237
* execution. The monitored events are: <br>
229
238
* - connection established (onOpen) <br>
230
239
* - connection closed (onClosed) <br>
231
240
* - 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
233
243
* 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>
235
246
*
236
247
* @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}.
253
275
* @throws IOException
254
276
*/
255
277
public Future <Integer > exec (
@@ -265,12 +287,11 @@ public Future<Integer> exec(
265
287
CompletableFuture <Integer > future = new CompletableFuture <>();
266
288
IOTrio io = new IOTrio ();
267
289
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
+ };
274
295
try {
275
296
Process process = exec (namespace , podName , command , null , true , tty );
276
297
@@ -289,9 +310,8 @@ public Future<Integer> exec(
289
310
Supplier <Integer > returnCode = process ::exitValue ;
290
311
try {
291
312
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 ));
295
315
if (!beforeTimeout ) {
296
316
returnCode = () -> Integer .MAX_VALUE ;
297
317
}
@@ -368,6 +388,14 @@ private ExecutionBuilder(String namespace, String name, String[] command) {
368
388
this .stderr = true ;
369
389
}
370
390
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
+
371
399
public String getName () {
372
400
return name ;
373
401
}
@@ -478,7 +506,8 @@ public Process execute() throws ApiException, IOException {
478
506
479
507
static int parseExitCode (ApiClient client , InputStream inputStream ) {
480
508
try {
481
- Type returnType = new TypeToken <V1Status >() {}.getType ();
509
+ Type returnType = new TypeToken <V1Status >() {
510
+ }.getType ();
482
511
String body ;
483
512
try (final Reader reader = new InputStreamReader (inputStream )) {
484
513
body = Streams .toString (reader );
@@ -488,7 +517,8 @@ static int parseExitCode(ApiClient client, InputStream inputStream) {
488
517
if (status == null ) {
489
518
return -1 ;
490
519
}
491
- if (V1STATUS_SUCCESS .equals (status .getStatus ())) return 0 ;
520
+ if (V1STATUS_SUCCESS .equals (status .getStatus ()))
521
+ return 0 ;
492
522
493
523
if (V1STATUS_REASON_NONZEROEXITCODE .equals (status .getReason ())) {
494
524
V1StatusDetails details = status .getDetails ();
@@ -529,58 +559,57 @@ public ExecProcess(final ApiClient apiClient) throws IOException {
529
559
530
560
public ExecProcess (final ApiClient apiClient , final Consumer <Throwable > onUnhandledError )
531
561
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
561
570
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 ;
566
572
isAlive = false ;
567
- ExecProcess .this .latch .countDown ();
568
573
}
569
574
}
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
+ }
570
584
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
+ }
580
599
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 ();
582
607
}
583
- };
608
+ }
609
+
610
+ super .close ();
611
+ }
612
+ };
584
613
}
585
614
586
615
// Protected to facilitate unit testing.
@@ -632,7 +661,8 @@ public boolean waitFor(long timeout, TimeUnit unit) throws InterruptedException
632
661
633
662
@ Override
634
663
public synchronized int exitValue () {
635
- if (isAlive ) throw new IllegalThreadStateException ();
664
+ if (isAlive )
665
+ throw new IllegalThreadStateException ();
636
666
return statusCode ;
637
667
}
638
668
0 commit comments