From 4ef9254466bc7c7396e2e65a9fc926d6a10f3b60 Mon Sep 17 00:00:00 2001 From: Brendan Burns Date: Fri, 13 Jul 2018 22:01:08 -0700 Subject: [PATCH] Add tslint validation. --- node-client/package.json | 2 + node-client/src/attach.ts | 28 +-- node-client/src/auth-wrapper.ts | 3 +- node-client/src/config.ts | 259 +++++++++++++------------- node-client/src/config_test.ts | 111 ++++++----- node-client/src/config_types.ts | 86 ++++----- node-client/src/exec.ts | 26 +-- node-client/src/watch.ts | 30 +-- node-client/src/web-socket-handler.ts | 112 ++++++----- node-client/tslint.json | 19 ++ 10 files changed, 350 insertions(+), 326 deletions(-) create mode 100644 node-client/tslint.json diff --git a/node-client/package.json b/node-client/package.json index ee0e46f36b..4645edcecd 100644 --- a/node-client/package.json +++ b/node-client/package.json @@ -13,6 +13,7 @@ "main": "dist/index.js", "types": "dist/index.d.ts", "scripts": { + "lint": "tslint --project \".\"", "clean": "rm -Rf node_modules/ dist/", "build": "tsc", "watch": "tsc --watch", @@ -45,6 +46,7 @@ "jasmine": "^2.8.0", "mocha": "^3.4.2", "ts-node": "^3.1.0", + "tslint": "^5.10.0", "typescript": "^2.6.2" }, "bugs": { diff --git a/node-client/src/attach.ts b/node-client/src/attach.ts index f8af35a3e6..4c7d27df32 100644 --- a/node-client/src/attach.ts +++ b/node-client/src/attach.ts @@ -1,28 +1,30 @@ import querystring = require('querystring'); import stream = require('stream'); -import { WebSocketHandler } from './web-socket-handler'; import { KubeConfig } from './config'; +import { WebSocketHandler } from './web-socket-handler'; export class Attach { - 'handler': WebSocketHandler; + public 'handler': WebSocketHandler; public constructor(config: KubeConfig) { this.handler = new WebSocketHandler(config); - } + } - public attach(namespace: string, podName: string, containerName: string, stdout: stream.Writable | any, stderr: stream.Writable | any, stdin: stream.Readable | any, tty: boolean) { - var query = { - stdout: stdout != null, + public attach(namespace: string, podName: string, containerName: string, + stdout: stream.Writable | any, stderr: stream.Writable | any, stdin: stream.Readable | any, + tty: boolean) { + const query = { + container: containerName, stderr: stderr != null, stdin: stdin != null, - tty: tty, - container: containerName - } - var queryStr = querystring.stringify(query); - var path = `/api/v1/namespaces/${namespace}/pods/${podName}/attach?${queryStr}`; - this.handler.connect(path, null, (stream: number, buff: Buffer) => { - WebSocketHandler.handleStandardStreams(stream, buff, stdout, stderr); + stdout: stdout != null, + tty, + }; + const queryStr = querystring.stringify(query); + const path = `/api/v1/namespaces/${namespace}/pods/${podName}/attach?${queryStr}`; + this.handler.connect(path, null, (streamNum: number, buff: Buffer) => { + WebSocketHandler.handleStandardStreams(streamNum, buff, stdout, stderr); }); } } diff --git a/node-client/src/auth-wrapper.ts b/node-client/src/auth-wrapper.ts index 3a3c9cd80f..1959975250 100644 --- a/node-client/src/auth-wrapper.ts +++ b/node-client/src/auth-wrapper.ts @@ -3,6 +3,7 @@ import api = require('./api'); // These wrappers are needed until we update the swagger->TypeScript generator // Add the ability to extend auth. +/* tslint:disable: class-name */ export class Core_v1Api extends api.Core_v1Api { constructor(baseUri: string) { super(baseUri); @@ -12,6 +13,7 @@ export class Core_v1Api extends api.Core_v1Api { } } +/* tslint:disable: class-name */ export class Extensions_v1beta1Api extends api.Extensions_v1beta1Api { constructor(baseUri: string) { super(baseUri); @@ -22,4 +24,3 @@ export class Extensions_v1beta1Api extends api.Extensions_v1beta1Api { } // TODO: Add other API objects here - diff --git a/node-client/src/config.ts b/node-client/src/config.ts index 0e2b592a09..589b3fc3fe 100644 --- a/node-client/src/config.ts +++ b/node-client/src/config.ts @@ -1,40 +1,52 @@ import fs = require('fs'); +import https = require('https'); import os = require('os'); import path = require('path'); -import https = require('https'); import base64 = require('base-64'); +import yaml = require('js-yaml'); import jsonpath = require('jsonpath'); import request = require('request'); import shelljs = require('shelljs'); -import yaml = require('js-yaml'); import api = require('./api'); -import { Cluster, newClusters, User, newUsers, Context, newContexts } from './config_types'; +import { Cluster, Context, newClusters, newContexts, newUsers, User } from './config_types'; import client = require('./auth-wrapper'); export class KubeConfig { + + // Only really public for testing... + public static findObject(list: any[], name: string, key: string) { + for (const obj of list) { + if (obj.name === name) { + if (obj[key]) { + return obj[key]; + } + return obj; + } + } + return null; + } + /** * The list of all known clusters */ - 'clusters': Cluster[]; + public 'clusters': Cluster[]; /** * The list of all known users */ - 'users': User[]; + public 'users': User[]; /** * The list of all known contexts */ - 'contexts': Context[]; + public 'contexts': Context[]; /** * The name of the current context */ - 'currentContext': string; - - constructor() { } + public 'currentContext': string; public getContexts() { return this.contexts; @@ -56,29 +68,12 @@ export class KubeConfig { this.currentContext = context; } - // Only really public for testing... - public static findObject(list: Object[], name: string, key: string) { - for (let obj of list) { - if (obj['name'] == name) { - if (obj[key]) { - return obj[key]; - } - return obj; - } - } - return null; - } - - private getCurrentContextObject() { - return this.getContextObject(this.currentContext); - } - public getContextObject(name: string) { return KubeConfig.findObject(this.contexts, name, 'context'); } public getCurrentCluster() { - return this.getCluster(this.getCurrentContextObject()['cluster']); + return this.getCluster(this.getCurrentContextObject().cluster); } public getCluster(name: string): Cluster { @@ -86,7 +81,7 @@ export class KubeConfig { } public getCurrentUser() { - return this.getUser(this.getCurrentContextObject()['user']); + return this.getUser(this.getCurrentContextObject().user); } public getUser(name: string): User { @@ -97,6 +92,94 @@ export class KubeConfig { this.loadFromString(fs.readFileSync(file, 'utf8')); } + public applytoHTTPSOptions(opts: https.RequestOptions) { + const user = this.getCurrentUser(); + + this.applyOptions(opts); + + if (user.username) { + opts.auth = `${user.username}:${user.password}`; + } + } + + public applyToRequest(opts: request.Options) { + const cluster = this.getCurrentCluster(); + const user = this.getCurrentUser(); + + this.applyOptions(opts); + + if (cluster.skipTLSVerify) { + opts.strictSSL = false; + } + + if (user.username) { + opts.auth = { + password: user.password, + username: user.username, + }; + } + } + + public loadFromString(config: string) { + const obj = yaml.safeLoad(config) as any; + if (obj.apiVersion !== 'v1') { + throw new TypeError('unknown version: ' + obj.apiVersion); + } + this.clusters = newClusters(obj.clusters); + this.contexts = newContexts(obj.contexts); + this.users = newUsers(obj.users); + this.currentContext = obj['current-context']; + } + + public loadFromCluster() { + const host = process.env.KUBERNETES_SERVICE_HOST; + const port = process.env.KUBERNETES_SERVICE_PORT; + const clusterName = 'inCluster'; + const userName = 'inClusterUser'; + const contextName = 'inClusterContext'; + + let scheme = 'https'; + if (port === '80' || port === '8080' || port === '8001') { + scheme = 'http'; + } + + this.clusters = [ + { + name: clusterName, + caFile: Config.SERVICEACCOUNT_CA_PATH, + caData: null, + server: `${scheme}://${host}:${port}`, + skipTLSVerify: false, + }, + ]; + this.users = [ + { + name: userName, + token: fs.readFileSync(Config.SERVICEACCOUNT_TOKEN_PATH).toString(), + // empty defaults, fields are required... + certData: null, + certFile: null, + keyData: null, + keyFile: null, + authProvider: null, + username: null, + password: null, + }, + ]; + this.contexts = [ + { + cluster: clusterName, + name: contextName, + user: userName, + }, + ]; + this.currentContext = contextName; + } + + private getCurrentContextObject() { + return this.getContextObject(this.currentContext); + } + private bufferFromFileOrString(file: string, data: string) { if (file) { return fs.readFileSync(file); @@ -124,10 +207,10 @@ export class KubeConfig { const config = user.authProvider.config; // This should probably be extracted as auth-provider specific plugins... token = 'Bearer ' + config['access-token']; - const expiry = config['expiry']; + const expiry = config.expiry; if (expiry) { - let expiration = Date.parse(expiry); + const expiration = Date.parse(expiry); if (expiration < Date.now()) { if (config['cmd-path']) { let cmd = '"' + config['cmd-path'] + '"'; @@ -136,14 +219,14 @@ export class KubeConfig { } // TODO: Cache to file? const result = shelljs.exec(cmd, { silent: true }); - if (result['code'] != 0) { + if (result.code !== 0) { throw new Error('Failed to refresh token: ' + result.stderr); } - let resultObj = JSON.parse(result.stdout.toString()); + const resultObj = JSON.parse(result.stdout.toString()); - let path = config['token-key']; + let pathKey = config['token-key']; // Format in file is {}, so slice it out and add '$' - path = '$' + path.slice(1, -1); + pathKey = '$' + pathKey.slice(1, -1); config['access-token'] = jsonpath.query(resultObj, path); token = 'Bearer ' + config['access-token']; @@ -163,7 +246,7 @@ export class KubeConfig { if (!opts.headers) { opts.headers = []; } - opts.headers['Authorization'] = token; + opts.headers.Authorization = token; } } @@ -171,90 +254,6 @@ export class KubeConfig { this.applyHTTPSOptions(opts); this.applyAuthorizationHeader(opts); } - - public applytoHTTPSOptions(opts: https.RequestOptions) { - const user = this.getCurrentUser(); - - this.applyOptions(opts); - - if (user.username) { - opts.auth = `${user.username}:${user.password}`; - } - } - - public applyToRequest(opts: request.Options) { - const cluster = this.getCurrentCluster(); - const user = this.getCurrentUser(); - - this.applyOptions(opts); - - if (cluster.skipTLSVerify) { - opts.strictSSL = false - } - - if (user.username) { - opts.auth = { - username: user.username, - password: user.password - } - } - } - - public loadFromString(config: string) { - var obj = yaml.safeLoad(config); - if (obj['apiVersion'] != 'v1') { - throw new TypeError('unknown version: ' + obj['apiVersion']); - } - this.clusters = newClusters(obj['clusters']); - this.contexts = newContexts(obj['contexts']); - this.users = newUsers(obj['users']); - this.currentContext = obj['current-context']; - } - - public loadFromCluster() { - const host = process.env.KUBERNETES_SERVICE_HOST; - const port = process.env.KUBERNETES_SERVICE_PORT; - const clusterName = 'inCluster'; - const userName = 'inClusterUser'; - const contextName = 'inClusterContext'; - - let scheme = 'https'; - if (port === '80' || port === '8080' || port === '8001') { - scheme = 'http'; - } - - this.clusters = [ - { - name: clusterName, - caFile: Config.SERVICEACCOUNT_CA_PATH, - caData: null, - server: `${scheme}://${host}:${port}`, - skipTLSVerify: false - } - ] - this.users = [ - { - name: userName, - token: fs.readFileSync(Config.SERVICEACCOUNT_TOKEN_PATH).toString(), - // empty defaults, fields are required... - certData: null, - certFile: null, - keyData: null, - keyFile: null, - authProvider: null, - username: null, - password: null - } - ] - this.contexts = [ - { - cluster: clusterName, - user: userName, - name: contextName - } - ]; - this.currentContext = contextName; - } } export class Config { @@ -266,32 +265,32 @@ export class Config { Config.SERVICEACCOUNT_ROOT + '/token'; public static fromFile(filename: string): api.Core_v1Api { - let kc = new KubeConfig(); + const kc = new KubeConfig(); kc.loadFromFile(filename); - let k8sApi = new client.Core_v1Api(kc.getCurrentCluster()['server']); + const k8sApi = new client.Core_v1Api(kc.getCurrentCluster().server); k8sApi.setDefaultAuthentication(kc); return k8sApi; } public static fromCluster(): api.Core_v1Api { - let host = process.env.KUBERNETES_SERVICE_HOST - let port = process.env.KUBERNETES_SERVICE_PORT + const host = process.env.KUBERNETES_SERVICE_HOST; + const port = process.env.KUBERNETES_SERVICE_PORT; // TODO: better error checking here. - let caCert = fs.readFileSync(Config.SERVICEACCOUNT_CA_PATH); - let token = fs.readFileSync(Config.SERVICEACCOUNT_TOKEN_PATH); + const caCert = fs.readFileSync(Config.SERVICEACCOUNT_CA_PATH); + const token = fs.readFileSync(Config.SERVICEACCOUNT_TOKEN_PATH); - let k8sApi = new client.Core_v1Api('https://' + host + ':' + port); + const k8sApi = new client.Core_v1Api('https://' + host + ':' + port); k8sApi.setDefaultAuthentication({ - 'applyToRequest': (opts) => { + applyToRequest: (opts) => { opts.ca = caCert; if (!opts.headers) { opts.headers = []; } - opts.headers['Authorization'] = 'Bearer ' + token; - } + opts.headers.Authorization = 'Bearer ' + token; + }, }); return k8sApi; @@ -302,7 +301,7 @@ export class Config { return Config.fromFile(process.env.KUBECONFIG); } - let config = path.join(process.env.HOME, ".kube", "config"); + const config = path.join(process.env.HOME, '.kube', 'config'); if (fs.existsSync(config)) { return Config.fromFile(config); } diff --git a/node-client/src/config_test.ts b/node-client/src/config_test.ts index 2d9e9e4049..c25f563762 100644 --- a/node-client/src/config_test.ts +++ b/node-client/src/config_test.ts @@ -1,100 +1,99 @@ import 'mocha'; +import * as base64 from 'base-64'; import { expect } from 'chai'; import * as https from 'https'; -import * as base64 from 'base-64'; -import { KubeConfig, Config } from './config'; +import { Config, KubeConfig } from './config'; -const kcFileName = "testdata/kubeconfig.yaml"; +const kcFileName = 'testdata/kubeconfig.yaml'; -describe("Config", () => { -}); +/* tslint:disable: no-empty */ +describe('Config', () => {}); - -describe("KubeConfig", () => { - describe("findObject", () => { - it("should find objects", () => { - let list = [ +describe('KubeConfig', () => { + describe('findObject', () => { + it('should find objects', () => { + const list = [ { - name: "foo", - "cluster": { - some: "sub-object" + name: 'foo', + cluster: { + some: 'sub-object', }, - some: "object" + some: 'object', }, { - name: "bar", - some: "object", + name: 'bar', + some: 'object', cluster: { - sone: "sub-object" - } - } + sone: 'sub-object', + }, + }, ]; // Validate that if the named object ('cluster' in this case) is inside we pick it out - let obj1 = KubeConfig.findObject(list, "foo", "cluster"); - expect(obj1.some).to.equal("sub-object"); + const obj1 = KubeConfig.findObject(list, 'foo', 'cluster'); + expect(obj1.some).to.equal('sub-object'); // Validate that if the named object is missing, we just return the full object - let obj2 = KubeConfig.findObject(list, "bar", "context"); - expect(obj2.some).to.equal("object"); + const obj2 = KubeConfig.findObject(list, 'bar', 'context'); + expect(obj2.some).to.equal('object'); // validate that we do the right thing if it is missing - let obj3 = KubeConfig.findObject(list, "nonexistent", "context"); + const obj3 = KubeConfig.findObject(list, 'nonexistent', 'context'); expect(obj3).to.equal(null); }); }); - describe("loadFromFile", () => { - it("should load the kubeconfig file properly", () => { - let kc = new KubeConfig(); + describe('loadFromFile', () => { + it('should load the kubeconfig file properly', () => { + const kc = new KubeConfig(); kc.loadFromFile(kcFileName); // check clusters expect(kc.clusters.length).to.equal(2); - let cluster1 = kc.clusters[0]; - let cluster2 = kc.clusters[1]; - expect(cluster1.name).to.equal("cluster1"); - expect(cluster1.caData).to.equal("Q0FEQVRB"); - expect(cluster1.server).to.equal("http://example.com"); - expect(cluster2.name).to.equal("cluster2"); - expect(cluster2.caData).to.equal("Q0FEQVRBMg=="); - expect(cluster2.server).to.equal("http://example2.com"); + const cluster1 = kc.clusters[0]; + const cluster2 = kc.clusters[1]; + expect(cluster1.name).to.equal('cluster1'); + expect(cluster1.caData).to.equal('Q0FEQVRB'); + expect(cluster1.server).to.equal('http://example.com'); + expect(cluster2.name).to.equal('cluster2'); + expect(cluster2.caData).to.equal('Q0FEQVRBMg=='); + expect(cluster2.server).to.equal('http://example2.com'); expect(cluster2.skipTLSVerify).to.equal(true); // check users expect(kc.users.length).to.equal(2); - let user1 = kc.users[0]; - let user2 = kc.users[1]; - expect(user1.name).to.equal("user1"); - expect(user1.certData).to.equal("VVNFUl9DQURBVEE="); - expect(user1.keyData).to.equal("VVNFUl9DS0RBVEE="); - expect(user2.name).to.equal("user2"); - expect(user2.certData).to.equal("VVNFUjJfQ0FEQVRB"); - expect(user2.keyData).to.equal("VVNFUjJfQ0tEQVRB"); + const user1 = kc.users[0]; + const user2 = kc.users[1]; + expect(user1.name).to.equal('user1'); + expect(user1.certData).to.equal('VVNFUl9DQURBVEE='); + expect(user1.keyData).to.equal('VVNFUl9DS0RBVEE='); + expect(user2.name).to.equal('user2'); + expect(user2.certData).to.equal('VVNFUjJfQ0FEQVRB'); + expect(user2.keyData).to.equal('VVNFUjJfQ0tEQVRB'); // check contexts expect(kc.contexts.length).to.equal(2); - let context1 = kc.contexts[0]; - let context2 = kc.contexts[1]; - expect(context1.name).to.equal("context1"); - expect(context1.user).to.equal("user1"); - expect(context1.cluster).to.equal("cluster1"); - expect(context2.name).to.equal("context2"); - expect(context2.user).to.equal("user2"); - expect(context2.cluster).to.equal("cluster2") - - expect(kc.getCurrentContext()).to.equal("context2") + const context1 = kc.contexts[0]; + const context2 = kc.contexts[1]; + expect(context1.name).to.equal('context1'); + expect(context1.user).to.equal('user1'); + expect(context1.cluster).to.equal('cluster1'); + expect(context2.name).to.equal('context2'); + expect(context2.user).to.equal('user2'); + expect(context2.cluster).to.equal('cluster2'); + + expect(kc.getCurrentContext()).to.equal('context2'); }); - it("should fail to load a missing kubeconfig file", () => { + it('should fail to load a missing kubeconfig file', () => { // TODO: make the error check work // let kc = new KubeConfig(); // expect(kc.loadFromFile("missing.yaml")).to.throw(); }); }); - describe("applyHTTPSOptions", () => { - it("should apply cert configs", () => { + describe('applyHTTPSOptions', () => { + it('should apply cert configs', () => { const kc = new KubeConfig(); kc.loadFromFile(kcFileName); diff --git a/node-client/src/config_types.ts b/node-client/src/config_types.ts index c0f21d1e8d..cd040c35bc 100644 --- a/node-client/src/config_types.ts +++ b/node-client/src/config_types.ts @@ -1,5 +1,5 @@ -import * as u from 'underscore'; import * as fs from 'fs'; +import * as u from 'underscore'; export interface Cluster { readonly name: string; @@ -10,37 +10,37 @@ export interface Cluster { } export function newClusters(a: any): Cluster[] { - return u.map(a, clusterIterator()) + return u.map(a, clusterIterator()); } function clusterIterator(): u.ListIterator { - return function (elt: any, i: number, list: u.List): Cluster { - if (!elt['name']) { + return (elt: any, i: number, list: u.List): Cluster => { + if (!elt.name) { throw new Error(`clusters${i}.name is missing`); } - if (!elt.cluster['server']) { + if (!elt.cluster.server) { throw new Error(`clusters[${i}].cluster.server is missing`); } return { - name: elt['name'], caData: elt.cluster['certificate-authority-data'], caFile: elt.cluster['certificate-authority'], - server: elt.cluster['server'], - skipTLSVerify: elt.cluster['insecure-skip-tls-verify'] === true + name: elt.name, + server: elt.cluster.server, + skipTLSVerify: elt.cluster['insecure-skip-tls-verify'] === true, }; - } + }; } export interface User { - readonly name: string - readonly certData: string - readonly certFile: string - readonly keyData: string - readonly keyFile: string - readonly authProvider: any - readonly token: string - readonly username: string - readonly password: string + readonly name: string; + readonly certData: string; + readonly certFile: string; + readonly keyData: string; + readonly keyFile: string; + readonly authProvider: any; + readonly token: string; + readonly username: string; + readonly password: string; } export function newUsers(a: any): User[] { @@ -48,35 +48,35 @@ export function newUsers(a: any): User[] { } function userIterator(): u.ListIterator { - return function (elt: any, i: number, list: u.List): User { + return (elt: any, i: number, list: u.List): User => { if (!elt.name) { throw new Error(`users[${i}].name is missing`); } let token = null; - if (elt.user["token"]) { - token = elt.user["token"]; + if (elt.user.token) { + token = elt.user.token; } - if (elt.user["token-file"]) { - token = fs.readFileSync(elt.user["token-file"]); + if (elt.user['token-file']) { + token = fs.readFileSync(elt.user['token-file']); } return { + authProvider: elt.user['auth-provider'], + certData: elt.user['client-certificate-data'], + certFile: elt.user['client-certificate'], + keyData: elt.user['client-key-data'], + keyFile: elt.user['client-key'], name: elt.name, - certData: elt.user["client-certificate-data"], - certFile: elt.user["client-certificate"], - keyData: elt.user["client-key-data"], - keyFile: elt.user["client-key"], - authProvider: elt.user["auth-provider"], - token: token, - username: elt.user["username"], - password: elt.user["password"] - } - } + password: elt.user.password, + token, + username: elt.user.username, + }; + }; } export interface Context { - readonly cluster: string - readonly user: string - readonly name: string + readonly cluster: string; + readonly user: string; + readonly name: string; } export function newContexts(a: any): Context[] { @@ -84,20 +84,20 @@ export function newContexts(a: any): Context[] { } function contextIterator(): u.ListIterator { - return function (elt: any, i: number, list: u.List): Context { + return (elt: any, i: number, list: u.List): Context => { if (!elt.name) { throw new Error(`contexts[${i}].name is missing`); } - if (!elt.context["cluster"]) { + if (!elt.context.cluster) { throw new Error(`contexts[${i}].context.cluster is missing`); } - if (!elt.context["user"]) { + if (!elt.context.user) { throw new Error(`context[${i}].context.user is missing`); } return { - cluster: elt.context['cluster'], - user: elt.context["user"], - name: elt.name + cluster: elt.context.cluster, + name: elt.name, + user: elt.context.user, }; - } + }; } diff --git a/node-client/src/exec.ts b/node-client/src/exec.ts index 316bfde4f9..7957cfd8a4 100644 --- a/node-client/src/exec.ts +++ b/node-client/src/exec.ts @@ -2,30 +2,32 @@ import querystring = require('querystring'); import stream = require('stream'); import ws = require('websocket'); -import { WebSocketHandler } from './web-socket-handler'; import { KubeConfig } from './config'; +import { WebSocketHandler } from './web-socket-handler'; export class Exec { - 'handler': WebSocketHandler; + public 'handler': WebSocketHandler; public constructor(config: KubeConfig) { this.handler = new WebSocketHandler(config); } // TODO: make command an array and support multiple args - public async exec(namespace: string, podName: string, containerName: string, command: string, stdout: stream.Writable | any, stderr: stream.Writable | any, stdin: stream.Readable | any, tty: boolean): Promise { - var query = { + public async exec(namespace: string, podName: string, containerName: string, command: string, + stdout: stream.Writable | any, stderr: stream.Writable | any, stdin: stream.Readable | any, + tty: boolean): Promise { + const query = { stdout: stdout != null, stderr: stderr != null, stdin: stdin != null, - tty: tty, - command: command, - container: containerName - } - var queryStr = querystring.stringify(query); - var path = `/api/v1/namespaces/${namespace}/pods/${podName}/exec?${queryStr}`; - var conn = await this.handler.connect(path, null, (stream: number, buff: Buffer) => { - WebSocketHandler.handleStandardStreams(stream, buff, stdout, stderr); + tty, + command, + container: containerName, + }; + const queryStr = querystring.stringify(query); + const path = `/api/v1/namespaces/${namespace}/pods/${podName}/exec?${queryStr}`; + const conn = await this.handler.connect(path, null, (streamNum: number, buff: Buffer) => { + WebSocketHandler.handleStandardStreams(streamNum, buff, stdout, stderr); }); if (stdin != null) { WebSocketHandler.handleStandardInput(conn, stdin); diff --git a/node-client/src/watch.ts b/node-client/src/watch.ts index b52d3d70fb..1d37b5cc7c 100644 --- a/node-client/src/watch.ts +++ b/node-client/src/watch.ts @@ -1,31 +1,33 @@ -import request = require('request'); import { LineStream } from 'byline'; +import request = require('request'); import { KubeConfig } from './config'; export class Watch { - 'config': KubeConfig; + public 'config': KubeConfig; public constructor(config: KubeConfig) { this.config = config; } - public watch(path: string, queryParams: any, callback: (phase: string, obj: any) => void, done: (err: any) => void): any { - let url = this.config.getCurrentCluster().server + path; + public watch(path: string, queryParams: any, + callback: (phase: string, obj: any) => void, + done: (err: any) => void): any { + const url = this.config.getCurrentCluster().server + path; - queryParams['watch'] = true; - let headerParams: any = {}; + queryParams.watch = true; + const headerParams: any = {}; - let requestOptions: request.Options = { + const requestOptions: request.Options = { method: 'GET', qs: queryParams, headers: headerParams, uri: url, useQuerystring: true, - json: true + json: true, }; this.config.applyToRequest(requestOptions); - - let stream = new LineStream(); + + const stream = new LineStream(); stream.on('data', (data) => { let obj = null; if (data instanceof Buffer) { @@ -33,14 +35,14 @@ export class Watch { } else { obj = JSON.parse(data); } - if (obj['type'] && obj['object']) { - callback(obj['type'], obj['object']); + if (obj.type && obj.object) { + callback(obj.type, obj.object); } else { - console.log('unexpected object: ' + obj); + throw new Error(`unexpected object: ${obj}`); } }); - let req = request(requestOptions, (error, response, body) => { + const req = request(requestOptions, (error, response, body) => { if (error) { done(error); } diff --git a/node-client/src/web-socket-handler.ts b/node-client/src/web-socket-handler.ts index f58d04e162..e33ddd8a6a 100644 --- a/node-client/src/web-socket-handler.ts +++ b/node-client/src/web-socket-handler.ts @@ -1,26 +1,65 @@ -import stream = require('stream'); import https = require('https'); +import stream = require('stream'); import ws = require('websocket'); -import { KubeConfig } from './config'; import { V1Status } from './api'; - +import { KubeConfig } from './config'; const protocols = [ - "v4.channel.k8s.io", - "v3.channel.k8s.io", - "v2.channel.k8s.io", - "channel.k8s.io" -] + 'v4.channel.k8s.io', + 'v3.channel.k8s.io', + 'v2.channel.k8s.io', + 'channel.k8s.io', +]; export class WebSocketHandler { - 'config': KubeConfig; - public static readonly StdinStream = 0; public static readonly StdoutStream = 1; public static readonly StderrStream = 2; public static readonly StatusStream = 3; + public static handleStandardStreams(streamNum: number, buff: Buffer, stdout: any, stderr: any): V1Status { + if (buff.length < 1) { + return null; + } + if (streamNum === WebSocketHandler.StdoutStream) { + stdout.write(buff); + } else if (streamNum === WebSocketHandler.StderrStream) { + stderr.write(buff); + } else if (streamNum === WebSocketHandler.StatusStream) { + // stream closing. + if (stdout) { + stdout.end(); + } + if (stderr) { + stderr.end(); + } + return JSON.parse(buff.toString('utf8')) as V1Status; + } else { + throw new Error('Unknown stream: ' + stream); + } + return null; + } + + public static handleStandardInput(conn: ws.connection, stdin: stream.Readable | any) { + stdin.on('data', (data) => { + const buff = new Buffer(data.length + 1); + buff.writeInt8(0, 0); + if (data instanceof Buffer) { + data.copy(buff, 1); + } else { + buff.write(data, 1); + } + conn.send(buff); + }); + + stdin.on('end', () => { + conn.close(); + }); + } + + public 'config': KubeConfig; + public constructor(config: KubeConfig) { this.config = config; } @@ -33,23 +72,22 @@ export class WebSocketHandler { const target = server.startsWith('https://') ? server.substr(8) : server.substr(7); const uri = `wss://${target}${path}`; - const opts : https.RequestOptions = {}; - this.config.applytoHTTPSOptions(opts) + const opts: https.RequestOptions = {}; + this.config.applytoHTTPSOptions(opts); const client = new ws.client({ tlsOptions: opts } ); return new Promise((resolve, reject) => { client.on('connect', (connection) => { - connection.on('message', function(message) { + connection.on('message', (message) => { if (message.type === 'utf8') { if (textHandler) { textHandler(message.utf8Data); } - } - else if (message.type === 'binary') { + } else if (message.type === 'binary') { if (binaryHandler) { - let stream = message.binaryData.readInt8(0); - binaryHandler(stream, message.binaryData.slice(1)); + const streamNum = message.binaryData.readInt8(0); + binaryHandler(streamNum, message.binaryData.slice(1)); } } }); @@ -63,44 +101,4 @@ export class WebSocketHandler { client.connect(uri, protocols); }); } - - public static handleStandardStreams(stream: number, buff: Buffer, stdout: any, stderr: any): V1Status { - if (buff.length < 1) { - return null; - } - if (stream == WebSocketHandler.StdoutStream) { - stdout.write(buff); - } else if (stream == WebSocketHandler.StderrStream) { - stderr.write(buff); - } else if (stream == WebSocketHandler.StatusStream) { - // stream closing. - if (stdout) { - stdout.end(); - } - if (stderr) { - stderr.end(); - } - return JSON.parse(buff.toString('utf8')) as V1Status; - } else { - console.log("Unknown stream: " + stream); - } - return null; - } - - public static handleStandardInput(conn: ws.connection, stdin: stream.Readable | any) { - stdin.on('data', (data) => { - let buff = new Buffer(data.length + 1); - buff.writeInt8(0, 0); - if (data instanceof Buffer) { - data.copy(buff, 1); - } else { - buff.write(data, 1); - } - conn.send(buff); - }); - - stdin.on('end', () => { - conn.close(); - }); - } } diff --git a/node-client/tslint.json b/node-client/tslint.json new file mode 100644 index 0000000000..fee6086099 --- /dev/null +++ b/node-client/tslint.json @@ -0,0 +1,19 @@ +{ + "extends": "tslint:recommended", + "defaultSeverity": "error", + "linterOptions": { + "exclude": [ + "src/api.ts", + "dist", + "node_modules" + ] + }, + "jsRules": {}, + "rules": { + "quotemark": [true, "single", "avoid-escape", "avoid-template"], + "interface-name": [true, "never-prefix"], + "object-literal-sort-keys": false, + "max-classes-per-file": false + }, + "rulesDirectory": [] +}