Skip to content

Commit a8e8861

Browse files
committed
Fix a bug in attach where stdin wasn't copied. Add tests.
1 parent c1c0039 commit a8e8861

File tree

3 files changed

+123
-13
lines changed

3 files changed

+123
-13
lines changed

node-client/src/attach.ts

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,22 @@ import querystring = require('querystring');
22
import stream = require('stream');
33

44
import { KubeConfig } from './config';
5-
import { WebSocketHandler } from './web-socket-handler';
5+
import { WebSocketHandler, WebSocketInterface } from './web-socket-handler';
66

77
export class Attach {
8-
public 'handler': WebSocketHandler;
8+
public 'handler': WebSocketInterface;
99

10-
public constructor(config: KubeConfig) {
11-
this.handler = new WebSocketHandler(config);
10+
public constructor(config: KubeConfig, websocketInterface?: WebSocketInterface) {
11+
if (websocketInterface) {
12+
this.handler = websocketInterface;
13+
} else {
14+
this.handler = new WebSocketHandler(config);
15+
}
1216
}
1317

14-
public attach(namespace: string, podName: string, containerName: string,
15-
stdout: stream.Writable | any, stderr: stream.Writable | any, stdin: stream.Readable | any,
16-
tty: boolean): Promise<void> {
18+
public async attach(namespace: string, podName: string, containerName: string,
19+
stdout: stream.Writable | any, stderr: stream.Writable | any, stdin: stream.Readable | any,
20+
tty: boolean): Promise<void> {
1721
const query = {
1822
container: containerName,
1923
stderr: stderr != null,
@@ -23,13 +27,12 @@ export class Attach {
2327
};
2428
const queryStr = querystring.stringify(query);
2529
const path = `/api/v1/namespaces/${namespace}/pods/${podName}/attach?${queryStr}`;
26-
const promise = this.handler.connect(path, null, (streamNum: number, buff: Buffer): boolean => {
30+
const conn = await this.handler.connect(path, null, (streamNum: number, buff: Buffer): boolean => {
2731
WebSocketHandler.handleStandardStreams(streamNum, buff, stdout, stderr);
2832
return true;
2933
});
30-
const result = new Promise<void>((resolvePromise, reject) => {
31-
promise.then(() => resolvePromise(), (err) => reject(err));
32-
});
33-
return result;
34+
if (stdin != null) {
35+
WebSocketHandler.handleStandardInput(conn, stdin);
36+
}
3437
}
3538
}

node-client/src/attach_test.ts

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { expect } from 'chai';
2+
import { ReadableStreamBuffer, WritableStreamBuffer } from 'stream-buffers';
3+
import { anyFunction, anything, capture, instance, mock, verify, when } from 'ts-mockito';
4+
import ws = require('websocket');
5+
6+
import { Attach } from './attach';
7+
import { KubeConfig } from './config';
8+
import { WebSocketHandler, WebSocketInterface } from './web-socket-handler';
9+
10+
describe('Attach', () => {
11+
describe('basic', () => {
12+
it('should correctly attach to a url', async () => {
13+
const kc = new KubeConfig();
14+
const fakeWebSocket: WebSocketInterface = mock(WebSocketHandler);
15+
const attach = new Attach(kc, instance(fakeWebSocket));
16+
const osStream = new WritableStreamBuffer();
17+
const errStream = new WritableStreamBuffer();
18+
const isStream = new ReadableStreamBuffer();
19+
20+
const namespace = 'somenamespace';
21+
const pod = 'somepod';
22+
const container = 'somecontainer';
23+
24+
await attach.attach(
25+
namespace, pod, container, osStream, errStream, isStream, false);
26+
27+
const path = `/api/v1/namespaces/${namespace}/pods/${pod}/attach`;
28+
let args = `container=${container}&stderr=true&stdin=true&stdout=true&tty=false`;
29+
verify(fakeWebSocket.connect(`${path}?${args}`, null, anyFunction())).called();
30+
31+
await attach.attach(
32+
namespace, pod, container, null, null, null, false);
33+
args = `container=${container}&stderr=false&stdin=false&stdout=false&tty=false`;
34+
verify(fakeWebSocket.connect(`${path}?${args}`, null, anyFunction())).called();
35+
36+
await attach.attach(
37+
namespace, pod, container, osStream, null, null, false);
38+
args = `container=${container}&stderr=false&stdin=false&stdout=true&tty=false`;
39+
verify(fakeWebSocket.connect(`${path}?${args}`, null, anyFunction())).called();
40+
41+
await attach.attach(
42+
namespace, pod, container, osStream, errStream, null, false);
43+
args = `container=${container}&stderr=true&stdin=false&stdout=true&tty=false`;
44+
verify(fakeWebSocket.connect(`${path}?${args}`, null, anyFunction())).called();
45+
46+
await attach.attach(
47+
namespace, pod, container, osStream, errStream, null, true);
48+
args = `container=${container}&stderr=true&stdin=false&stdout=true&tty=true`;
49+
verify(fakeWebSocket.connect(`${path}?${args}`, null, anyFunction())).called();
50+
});
51+
52+
it('should correctly attach to streams', async () => {
53+
const kc = new KubeConfig();
54+
const fakeWebSocket: WebSocketInterface = mock(WebSocketHandler);
55+
const attach = new Attach(kc, instance(fakeWebSocket));
56+
const osStream = new WritableStreamBuffer();
57+
const errStream = new WritableStreamBuffer();
58+
const isStream = new ReadableStreamBuffer();
59+
60+
const namespace = 'somenamespace';
61+
const pod = 'somepod';
62+
const container = 'somecontainer';
63+
64+
const path = `/api/v1/namespaces/${namespace}/pods/${pod}/attach`;
65+
const args = `container=${container}&stderr=true&stdin=true&stdout=true&tty=false`;
66+
67+
const fakeConn: ws.connection = mock(ws.connection);
68+
when(fakeWebSocket.connect(`${path}?${args}`, null, anyFunction())).thenResolve(fakeConn);
69+
70+
await attach.attach(
71+
namespace, pod, container, osStream, errStream, isStream, false);
72+
const [, , outputFn] = capture(fakeWebSocket.connect).last();
73+
74+
/* tslint:disable:no-unused-expression */
75+
expect(outputFn).to.not.be.null;
76+
77+
// this is redundant but needed for the compiler, sigh...
78+
if (!outputFn) {
79+
return;
80+
}
81+
82+
let buffer = Buffer.alloc(1024, 10);
83+
84+
outputFn(WebSocketHandler.StdoutStream, buffer);
85+
expect(osStream.size()).to.equal(1024);
86+
let buff = osStream.getContents() as Buffer;
87+
for (let i = 0; i < 1024; i++) {
88+
expect(buff[i]).to.equal(10);
89+
}
90+
91+
buffer = Buffer.alloc(1024, 20);
92+
outputFn(WebSocketHandler.StderrStream, buffer);
93+
expect(errStream.size()).to.equal(1024);
94+
buff = errStream.getContents() as Buffer;
95+
for (let i = 0; i < 1024; i++) {
96+
expect(buff[i]).to.equal(20);
97+
}
98+
99+
const msg = 'This is test data';
100+
isStream.put(msg);
101+
verify(fakeConn.send(msg));
102+
103+
isStream.stop();
104+
verify(fakeConn.close());
105+
});
106+
});
107+
});

node-client/src/web-socket-handler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export class WebSocketHandler implements WebSocketInterface {
4343
}
4444
return JSON.parse(buff.toString('utf8')) as V1Status;
4545
} else {
46-
throw new Error('Unknown stream: ' + stream);
46+
throw new Error('Unknown stream: ' + streamNum);
4747
}
4848
return null;
4949
}

0 commit comments

Comments
 (0)