Skip to content

Commit f385aba

Browse files
committed
autodetect resizable streams
1 parent 748eb62 commit f385aba

File tree

11 files changed

+81
-101
lines changed

11 files changed

+81
-101
lines changed

examples/typescript/attach/attach-resize-example.ts

Lines changed: 0 additions & 29 deletions
This file was deleted.

examples/typescript/exec/exec-resize-example.ts

Lines changed: 0 additions & 39 deletions
This file was deleted.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
"@types/chai": "^4.1.6",
6868
"@types/mocha": "^5.2.5",
6969
"@types/mock-fs": "^3.6.30",
70+
"@types/stream-buffers": "^3.0.3",
7071
"chai": "^4.2.0",
7172
"jasmine": "^3.3.0",
7273
"mocha": "^5.2.0",

src/attach.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ import querystring = require('querystring');
33
import stream = require('stream');
44

55
import { KubeConfig } from './config';
6-
import { TerminalSizeQueue } from './terminal-size-queue';
6+
import { isResizable, ResizableStream, TerminalSizeQueue } from './terminal-size-queue';
77
import { WebSocketHandler, WebSocketInterface } from './web-socket-handler';
88

99
export class Attach {
1010
public 'handler': WebSocketInterface;
1111

12+
private terminalSizeQueue?: TerminalSizeQueue;
13+
1214
public constructor(config: KubeConfig, websocketInterface?: WebSocketInterface) {
1315
if (websocketInterface) {
1416
this.handler = websocketInterface;
@@ -25,7 +27,6 @@ export class Attach {
2527
stderr: stream.Writable | any,
2628
stdin: stream.Readable | any,
2729
tty: boolean,
28-
terminalSizeQueue?: TerminalSizeQueue,
2930
): Promise<WebSocket> {
3031
const query = {
3132
container: containerName,
@@ -47,9 +48,10 @@ export class Attach {
4748
if (stdin != null) {
4849
WebSocketHandler.handleStandardInput(conn, stdin, WebSocketHandler.StdinStream);
4950
}
50-
51-
if (terminalSizeQueue != null) {
52-
WebSocketHandler.handleStandardInput(conn, terminalSizeQueue, WebSocketHandler.ResizeStream);
51+
if (isResizable(stdout)) {
52+
this.terminalSizeQueue = new TerminalSizeQueue();
53+
WebSocketHandler.handleStandardInput(conn, this.terminalSizeQueue, WebSocketHandler.ResizeStream);
54+
this.terminalSizeQueue.handleResizes((stdout as any) as ResizableStream);
5355
}
5456
return conn;
5557
}

src/attach_test.ts

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import { expect } from 'chai';
2-
import { EventEmitter } from 'events';
32
import WebSocket = require('isomorphic-ws');
43
import { ReadableStreamBuffer, WritableStreamBuffer } from 'stream-buffers';
54
import { anyFunction, anything, capture, instance, mock, verify, when } from 'ts-mockito';
6-
import { CallAwaiter, matchBuffer } from '../test';
75

6+
import { CallAwaiter, matchBuffer, ResizableWriteableStreamBuffer } from '../test';
87
import { Attach } from './attach';
98
import { KubeConfig } from './config';
10-
import { TerminalSize, TerminalSizeQueue } from './terminal-size-queue';
9+
import { TerminalSize } from './terminal-size-queue';
1110
import { WebSocketHandler, WebSocketInterface } from './web-socket-handler';
1211

1312
describe('Attach', () => {
@@ -53,10 +52,9 @@ describe('Attach', () => {
5352
const fakeWebSocket: WebSocket = mock(WebSocket);
5453
const callAwaiter: CallAwaiter = new CallAwaiter();
5554
const attach = new Attach(kc, instance(fakeWebSocketInterface));
56-
const osStream = new WritableStreamBuffer();
55+
const osStream = new ResizableWriteableStreamBuffer();
5756
const errStream = new WritableStreamBuffer();
5857
const isStream = new ReadableStreamBuffer();
59-
const terminalSizeQueue = new TerminalSizeQueue();
6058

6159
const namespace = 'somenamespace';
6260
const pod = 'somepod';
@@ -72,16 +70,7 @@ describe('Attach', () => {
7270
when(fakeWebSocket.send(anything())).thenCall(callAwaiter.resolveCall('send'));
7371
when(fakeWebSocket.close()).thenCall(callAwaiter.resolveCall('close'));
7472

75-
await attach.attach(
76-
namespace,
77-
pod,
78-
container,
79-
osStream,
80-
errStream,
81-
isStream,
82-
false,
83-
terminalSizeQueue,
84-
);
73+
await attach.attach(namespace, pod, container, osStream, errStream, isStream, false);
8574
const [, , outputFn] = capture(fakeWebSocketInterface.connect).last();
8675

8776
/* tslint:disable:no-unused-expression */
@@ -109,6 +98,14 @@ describe('Attach', () => {
10998
expect(buff[i]).to.equal(20);
11099
}
111100

101+
const initialTerminalSize: TerminalSize = { height: 0, width: 0 };
102+
await callAwaiter.awaitCall('send');
103+
verify(
104+
fakeWebSocket.send(
105+
matchBuffer(WebSocketHandler.ResizeStream, JSON.stringify(initialTerminalSize)),
106+
),
107+
).called();
108+
112109
const msg = 'This is test data';
113110
const inputPromise = callAwaiter.awaitCall('send');
114111
isStream.put(msg);
@@ -117,7 +114,9 @@ describe('Attach', () => {
117114

118115
const terminalSize: TerminalSize = { height: 80, width: 120 };
119116
const resizePromise = callAwaiter.awaitCall('send');
120-
terminalSizeQueue.resize(terminalSize);
117+
osStream.rows = terminalSize.height;
118+
osStream.columns = terminalSize.width;
119+
osStream.emit('resize');
121120
await resizePromise;
122121
verify(
123122
fakeWebSocket.send(matchBuffer(WebSocketHandler.ResizeStream, JSON.stringify(terminalSize))),

src/exec.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ import stream = require('stream');
44

55
import { V1Status } from './api';
66
import { KubeConfig } from './config';
7-
import { TerminalSizeQueue } from './terminal-size-queue';
7+
import { isResizable, ResizableStream, TerminalSizeQueue } from './terminal-size-queue';
88
import { WebSocketHandler, WebSocketInterface } from './web-socket-handler';
99

1010
export class Exec {
1111
public 'handler': WebSocketInterface;
1212

13+
private terminalSizeQueue?: TerminalSizeQueue;
14+
1315
public constructor(config: KubeConfig, wsInterface?: WebSocketInterface) {
1416
if (wsInterface) {
1517
this.handler = wsInterface;
@@ -41,7 +43,6 @@ export class Exec {
4143
stdin: stream.Readable | null,
4244
tty: boolean,
4345
statusCallback?: (status: V1Status) => void,
44-
terminalSizeQueue?: TerminalSizeQueue,
4546
): Promise<WebSocket> {
4647
const query = {
4748
stdout: stdout != null,
@@ -70,8 +71,10 @@ export class Exec {
7071
if (stdin != null) {
7172
WebSocketHandler.handleStandardInput(conn, stdin, WebSocketHandler.StdinStream);
7273
}
73-
if (terminalSizeQueue != null) {
74-
WebSocketHandler.handleStandardInput(conn, terminalSizeQueue, WebSocketHandler.ResizeStream);
74+
if (isResizable(stdout)) {
75+
this.terminalSizeQueue = new TerminalSizeQueue();
76+
WebSocketHandler.handleStandardInput(conn, this.terminalSizeQueue, WebSocketHandler.ResizeStream);
77+
this.terminalSizeQueue.handleResizes((stdout as any) as ResizableStream);
7578
}
7679
return conn;
7780
}

src/exec_test.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ import WebSocket = require('isomorphic-ws');
33
import { ReadableStreamBuffer, WritableStreamBuffer } from 'stream-buffers';
44
import { anyFunction, anything, capture, instance, mock, verify, when } from 'ts-mockito';
55

6-
import { CallAwaiter, matchBuffer } from '../test';
6+
import { CallAwaiter, matchBuffer, ResizableWriteableStreamBuffer } from '../test';
77
import { V1Status } from './api';
88
import { KubeConfig } from './config';
99
import { Exec } from './exec';
10-
import { TerminalSize, TerminalSizeQueue } from './terminal-size-queue';
10+
import { TerminalSize } from './terminal-size-queue';
1111
import { WebSocketHandler, WebSocketInterface } from './web-socket-handler';
1212

1313
describe('Exec', () => {
@@ -61,10 +61,9 @@ describe('Exec', () => {
6161
const fakeWebSocket: WebSocket = mock(WebSocket);
6262
const callAwaiter: CallAwaiter = new CallAwaiter();
6363
const exec = new Exec(kc, instance(fakeWebSocketInterface));
64-
const osStream = new WritableStreamBuffer();
64+
const osStream = new ResizableWriteableStreamBuffer();
6565
const errStream = new WritableStreamBuffer();
6666
const isStream = new ReadableStreamBuffer();
67-
const terminalSizeQueue = new TerminalSizeQueue();
6867

6968
const namespace = 'somenamespace';
7069
const pod = 'somepod';
@@ -95,7 +94,6 @@ describe('Exec', () => {
9594
(status: V1Status) => {
9695
statusOut = status;
9796
},
98-
terminalSizeQueue,
9997
);
10098

10199
const [, , outputFn] = capture(fakeWebSocketInterface.connect).last();
@@ -125,6 +123,14 @@ describe('Exec', () => {
125123
expect(buff[i]).to.equal(20);
126124
}
127125

126+
const initialTerminalSize: TerminalSize = { height: 0, width: 0 };
127+
await callAwaiter.awaitCall('send');
128+
verify(
129+
fakeWebSocket.send(
130+
matchBuffer(WebSocketHandler.ResizeStream, JSON.stringify(initialTerminalSize)),
131+
),
132+
).called();
133+
128134
const msg = 'This is test data';
129135
const inputPromise = callAwaiter.awaitCall('send');
130136
isStream.put(msg);
@@ -133,7 +139,9 @@ describe('Exec', () => {
133139

134140
const terminalSize: TerminalSize = { height: 80, width: 120 };
135141
const resizePromise = callAwaiter.awaitCall('send');
136-
terminalSizeQueue.resize(terminalSize);
142+
osStream.rows = terminalSize.height;
143+
osStream.columns = terminalSize.width;
144+
osStream.emit('resize');
137145
await resizePromise;
138146
verify(
139147
fakeWebSocket.send(matchBuffer(WebSocketHandler.ResizeStream, JSON.stringify(terminalSize))),

src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,3 @@ export * from './portforward';
88
export * from './types';
99
export * from './yaml';
1010
export * from './log';
11-
export * from './terminal-size-queue';

src/terminal-size-queue.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import { Readable, ReadableOptions } from 'stream';
22

3+
export interface ResizableStream {
4+
columns: number;
5+
rows: number;
6+
on(event: 'resize', cb: () => void);
7+
}
8+
39
export interface TerminalSize {
410
height: number;
511
width: number;
@@ -14,7 +20,30 @@ export class TerminalSizeQueue extends Readable {
1420
});
1521
}
1622

17-
public resize(size: TerminalSize) {
23+
public handleResizes(writeStream: ResizableStream) {
24+
// Set initial size
25+
this.resize(getTerminalSize(writeStream));
26+
27+
// Handle future size updates
28+
writeStream.on('resize', () => this.resize(getTerminalSize(writeStream)));
29+
}
30+
31+
private resize(size: TerminalSize) {
1832
this.push(JSON.stringify(size));
1933
}
2034
}
35+
36+
export function isResizable(stream: any) {
37+
if (stream == null) {
38+
return false;
39+
}
40+
41+
const hasRows = 'rows' in stream;
42+
const hasColumns = 'columns' in stream;
43+
const hasOn = typeof stream.on === 'function';
44+
return hasRows && hasColumns && hasOn;
45+
}
46+
47+
function getTerminalSize(writeStream: ResizableStream): TerminalSize {
48+
return { height: writeStream.rows!, width: writeStream.columns! };
49+
}

test/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './call-awaiter';
22
export * from './match-buffer';
3+
export * from './resizable-writeable-stream-buffer';

0 commit comments

Comments
 (0)