├── .gitignore ├── LICENSE ├── README.md ├── index.ts ├── lib ├── kubectl.ts └── request.ts ├── package.json ├── test ├── kubeapi.js ├── kubeapi_kubeconf.js ├── kubectl_node.js ├── kubectl_pods.js ├── kubectl_rc.js ├── kubectl_service.js ├── nodes │ └── node3.json ├── pods │ └── nginx.yaml ├── rc │ ├── helloworld-v1.yaml │ ├── helloworld-v2.yaml │ └── nginx-rc.json └── service │ └── helloworld-service.yaml ├── tsconfig.json └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | typings 3 | npm-debug.log 4 | .idea 5 | 6 | # Generated JS files 7 | index.js 8 | lib/*.js* 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2009-2014 TJ Holowaychuk 4 | Copyright (c) 2013-2014 Roman Shtylman 5 | Copyright (c) 2014-2015 Douglas Christopher Wilson 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining 8 | a copy of this software and associated documentation files (the 9 | 'Software'), to deal in the Software without restriction, including 10 | without limitation the rights to use, copy, modify, merge, publish, 11 | distribute, sublicense, and/or sell copies of the Software, and to 12 | permit persons to whom the Software is furnished to do so, subject to 13 | the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Nodejs Kubernetes client 4 | 5 | 6 | Node.js client library for Google's Kubernetes Kubectl And API 7 | 8 | 9 | # build 10 | ``` 11 | git clone https://github.com/Goyoo/node-k8s-client.git 12 | npm install 13 | npm run build 14 | ``` 15 | #test 16 | for test please install [minikube](https://github.com/kubernetes/minikube/releases) 17 | ``` 18 | 19 | mocha test 20 | ``` 21 | 22 | # Install: 23 | ``` 24 | npm install k8s 25 | ``` 26 | # Usage 27 | 28 | ## Create client 29 | 30 | ```js 31 | var K8s = require('k8s') 32 | 33 | // use kubectl 34 | 35 | var kubectl = K8s.kubectl({ 36 | endpoint: 'http://192.168.10.10:8080' 37 | , namespace: 'namespace' 38 | , binary: '/usr/local/bin/kubectl' 39 | }) 40 | 41 | //use restful api 42 | var kubeapi = K8s.api({ 43 | endpoint: 'http://192.168.10.10:8080' 44 | , version: '/api/v1' 45 | }) 46 | 47 | // Configure using kubeconfig 48 | var kubeapi = K8s.api({ 49 | kubeconfig: '/etc/cluster1.yaml' 50 | ,version: '/api/v1' 51 | }) 52 | 53 | var kube = K8s.kubectl({ 54 | binary: '/bin/kubectl' 55 | ,kubeconfig: '/etc/cluster1.yaml' 56 | ,version: '/api/v1' 57 | }); 58 | 59 | ``` 60 | 61 | ### Options 62 | 63 | endpoint 64 | : URL for API 65 | 66 | version 67 | : API Version 68 | 69 | binary 70 | : Path to binary file 71 | 72 | kubeconfig 73 | : Path to kubeconfig 74 | 75 | :auth 76 | See below authentication section 77 | 78 | :strictSSL 79 | If set to false, use of the API will not validate SSL certificate. Defualt is true. 80 | 81 | #### Authentication 82 | 83 | Authentication to REST API is done via the `auth` option. Currently supported authentication method types are username/password, token and client certificate. Presence of authentication details is checked in this order so if a token is specified as well as a client certificate then a token will be used. 84 | 85 | Username/password: 86 | 87 | ``` 88 | { 89 | "auth": { 90 | "username": "admin", 91 | "password": "123123" 92 | } 93 | } 94 | ``` 95 | 96 | Token: 97 | 98 | ``` 99 | { 100 | "auth": { 101 | "token": "hcc927ndkcka12" 102 | } 103 | } 104 | ``` 105 | 106 | Client certificate: 107 | 108 | ``` 109 | { 110 | "auth": { 111 | "clientKey": fs.readFileSync('k8s-client-key.pem'), 112 | "clientCert": fs.readFileSync('k8s-client-cert.pem'), 113 | "caCert": fs.readFileSync('k8s-ca-crt.pem') 114 | } 115 | } 116 | ``` 117 | 118 | # kubeAPI 119 | 120 | #### using callback 121 | ```js 122 | // method GET 123 | kubeapi.get('namespaces/default/replicationcontrollers', function(err, data){}) 124 | 125 | // method POST 126 | kubeapi.post('namespaces/default/replicationcontrollers', require('./rc/nginx-rc.json'), function(err, data){}) 127 | // method PUT 128 | kubeapi.put('namespaces/default/replicationcontrollers/nginx', require('./rc/nginx-rc.json'), function(err, data){}) 129 | // method PATCH 130 | kubeapi.patch('namespaces/default/replicationcontrollers/nginx', [{ op: 'replace', path: '/spec/replicas', value: 2 }], function(err, data){}) 131 | // method DELETE 132 | kubeapi.delete('namespaces/default/replicationcontrollers/nginx', function(err, data){}) 133 | 134 | ``` 135 | #### using promise 136 | ```js 137 | // method GET 138 | kubeapi.get('namespaces/default/replicationcontrollers').then(function(data){}).catch(function(err){}) 139 | // method POST 140 | kubeapi.post('namespaces/default/replicationcontrollers', require('./rc/nginx-rc.json')).then(function(data){}).catch(function(err){}) 141 | // method PUT 142 | kubeapi.put('namespaces/default/replicationcontrollers/nginx', require('./rc/nginx-rc.json')).then(function(data){}).catch(function(err){}) 143 | // method PATCH 144 | kubeapi.patch('namespaces/default/replicationcontrollers/nginx', [{ op: 'replace', path: '/spec/replicas', value: 2 }]).then(function(data){}).catch(function(err){}) 145 | // method DELETE 146 | kubeapi.delete('namespaces/default/replicationcontrollers/nginx').then(function(data){}).catch(function(err){}) 147 | 148 | ``` 149 | #### using async/await 150 | ```js 151 | 152 | !async function() 153 | { 154 | try 155 | { 156 | // method GET 157 | const data1 = await kubeapi.get('namespaces/default/replicationcontrollers') 158 | // method POST 159 | const data2 = await kubeapi.post('namespaces/default/replicationcontrollers', require('./rc/nginx-rc.json')) 160 | // method PUT 161 | const data3 = await kubeapi.put('namespaces/default/replicationcontrollers/nginx', require('./rc/nginx-rc.json')) 162 | // method PATCH 163 | const data4 = await kubeapi.patch('namespaces/default/replicationcontrollers/nginx', [{ op: 'replace', path: '/spec/replicas', value: 2 }]) 164 | // method DELETE 165 | const data5 = await kubeapi.delete('namespaces/default/replicationcontrollers/nginx') 166 | } 167 | catch(err){ 168 | console.log(err) 169 | } 170 | }() 171 | 172 | ``` 173 | 174 | #### method GET -> watch 175 | ###### using callback 176 | ```js 177 | var res = kubeapi.watch('watch/namespaces/default/pods', function(data){ 178 | // message 179 | }, function(err){ 180 | // exit 181 | }, [timeout]) 182 | 183 | ``` 184 | 185 | ###### using rxjs 186 | ```js 187 | kubeapi.watch('watch/namespaces/default/pods', [timeout]).subscribe(data=>{ 188 | // message 189 | }, err=>{ 190 | // exit 191 | }) 192 | ``` 193 | 194 | # kubectl (callback, promise, async/await) 195 | 196 | ### example 197 | ```js 198 | //kubectl['type']['action]([arguments], [flags], [callback]): Promise 199 | 200 | //callback 201 | kubect.pod.delete('pod_name', function(err, data){}) 202 | kubect.pod.delete('pod_name', ['--grace-period=0'], function(err, data){}) 203 | //promise 204 | kubect.pod.delete('pod_name').then() 205 | kubect.pod.delete('pod_name', ['--grace-period=0']).then() 206 | //async/await 207 | const data = await kubect.pod.delete('pod_name') 208 | const data = await kubect.pod.delete('pod_name',['--grace-period=0']) 209 | ``` 210 | 211 | ### excute command 212 | ```js 213 | kubectl.command('get pod pod_name --output=json', function(err, data){}) 214 | kubectl.command('get pod pod_name --output=json').then() 215 | const data = await kubectl.command('get pod pod_name --output=json') 216 | ``` 217 | 218 | ## Pods 219 | 220 | ### get pod list 221 | 222 | ```js 223 | kubectl.pod.list(function(err, pods){}) 224 | 225 | //selector 226 | var label = { name: nginx } 227 | kubectl.pod.list(label, function(err, pods){}) 228 | ``` 229 | 230 | ### get pod 231 | 232 | ```js 233 | kubectl.pod.get('nginx', function(err, pod){}) 234 | 235 | // label selector 236 | kubectl.pod.list({ app: 'nginx' }, function(err, pods){}) 237 | ``` 238 | 239 | ### create a pod 240 | 241 | ```js 242 | kubectl.pod.create('/:path/pods/nginx.yaml'), function(err, data){}) 243 | ``` 244 | 245 | ### delete a pod 246 | 247 | ```js 248 | kubectl.pod.delete('nginx', function(err, data){}) 249 | ``` 250 | 251 | ### log 252 | 253 | ```js 254 | kubectl.pod.log('pod_id1 pod_id2 pod_id3', function(err, log){}) 255 | ``` 256 | 257 | ## ReplicationController 258 | 259 | ### get rc list 260 | 261 | ```js 262 | kubectl.rc.list(function(err, pods){}) 263 | ``` 264 | 265 | ### get a rc 266 | 267 | ```js 268 | kubectl.rc.get('nginx', function(err, pod){}) 269 | ``` 270 | 271 | ### create a rc 272 | 273 | ```js 274 | kubectl.rc.create('/:path/pods/nginx.yaml'), function(err, data){}) 275 | ``` 276 | 277 | ### delete a rc 278 | 279 | ```js 280 | kubectl.rc.delete('nginx', function(err, data){}) 281 | 282 | ``` 283 | 284 | ### rolling-update by image name 285 | 286 | ```js 287 | kubectl.rc.rollingUpdate('nginx', 'nginx:vserion', function(err, data){}) 288 | ``` 289 | 290 | ### rolling-update by file 291 | 292 | ```js 293 | kubectl.rc.rollingUpdateByFile('nginx', '/:path/rc/nginx-v2.yaml', function(err, data){}) 294 | ``` 295 | 296 | ### change replicas 297 | 298 | ```js 299 | kubectl.rc.scale('nginx', 3, function(err, data){}) 300 | ``` 301 | 302 | ## Service 303 | 304 | ### get service list 305 | 306 | ```js 307 | kubectl.service.list(function(err, pods){}) 308 | ``` 309 | 310 | ### get a service 311 | 312 | ```js 313 | kubectl.service.get('nginx', function(err, pod){}) 314 | ``` 315 | 316 | ### create a service 317 | 318 | ```js 319 | kubectl.service.create('/:path/service/nginx.yaml'), function(err, data){}) 320 | ``` 321 | 322 | ### delete a service 323 | 324 | ```js 325 | kubectl.service.delete('nginx', function(err, data){}) 326 | ``` 327 | 328 | 329 | ## Node 330 | 331 | ### get node list 332 | 333 | ```js 334 | kubectl.node.list(function(err, nodes){}) 335 | ``` 336 | 337 | ### get a node 338 | 339 | ```js 340 | kubectl.node.get('node1', function(err, node){}) 341 | ``` 342 | 343 | ### create a node 344 | 345 | ```js 346 | kubectl.node.create('/:path/nodes/node1.yaml'), function(err, node){}) 347 | ``` 348 | 349 | ### delete a node 350 | 351 | ```js 352 | kubectl.node.delete('node1', function(err, node){}) 353 | 354 | 355 | ``` 356 | 357 | ## Namespace 358 | 359 | ```js 360 | kubectl.namespace['fn'] 361 | 362 | ``` 363 | ## Daemonset 364 | 365 | ```js 366 | kubectl.daemonset['fn'] 367 | 368 | ``` 369 | ## Deployment 370 | 371 | ```js 372 | kubectl.deployment['fn'] 373 | 374 | ``` 375 | ## Secrets 376 | 377 | ```js 378 | kubectl.secrets['fn'] 379 | 380 | ``` 381 | ## endpoint 382 | 383 | ```js 384 | kubectl.endpoint['fn'] 385 | 386 | ``` 387 | ## ingress 388 | 389 | ```js 390 | kubectl.ingress['fn'] 391 | 392 | ``` 393 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | import { Request } from './lib/request' 2 | 3 | namespace K8s { 4 | export type K8sRequest = Request 5 | export var api = (conf: any) => { 6 | return new Request(conf) 7 | } 8 | export var kubectl = require('./lib/kubectl') 9 | } 10 | 11 | export = K8s 12 | -------------------------------------------------------------------------------- /lib/kubectl.ts: -------------------------------------------------------------------------------- 1 | const spawn = require('child_process').spawn 2 | const _ = require('underscore') 3 | 4 | class Kubectl 5 | { 6 | private type: any 7 | private binary: any 8 | private kubeconfig: any 9 | private namespace: any 10 | private endpoint: any 11 | private context: any 12 | 13 | constructor(type: any, conf: any) 14 | { 15 | this.type = type 16 | this.binary = conf.binary || 'kubectl' 17 | this.kubeconfig = conf.kubeconfig || '' 18 | this.namespace = conf.namespace || '' 19 | this.endpoint = conf.endpoint || '' 20 | this.context = conf.context || '' 21 | } 22 | 23 | private spawn(args: any, done: any) 24 | { 25 | const ops: any = new Array() 26 | 27 | if( this.kubeconfig ){ 28 | ops.push('--kubeconfig='+this.kubeconfig) 29 | } 30 | else { 31 | ops.push('-s') 32 | ops.push(this.endpoint) 33 | } 34 | 35 | if (this.namespace) { 36 | ops.push('--namespace='+this.namespace) 37 | } 38 | 39 | if (this.context) { 40 | ops.push('--context='+this.context) 41 | } 42 | 43 | const kube: any = spawn(this.binary, ops.concat(args)) 44 | , stdout: any[] = [] 45 | , stderr: any[] = [] 46 | 47 | kube.stdout.on('data', function (data: any) { 48 | stdout.push(data.toString()) 49 | }) 50 | 51 | kube.stderr.on('data', function (data: any) { 52 | stderr.push(data.toString()) 53 | }) 54 | 55 | kube.on('close', function () 56 | { 57 | if( !stderr.length ) 58 | return done(null, stdout.join('')) 59 | 60 | done(stderr.join('')) 61 | }) 62 | } 63 | 64 | private callbackFunction(promise: any, callback: Function) 65 | { 66 | if( _.isFunction(callback) ) 67 | { 68 | promise.then((data: any)=>{ 69 | callback(null, data) 70 | }).catch((err: any)=>{ 71 | callback(err) 72 | }) 73 | } 74 | } 75 | 76 | public command(cmd: any, callback: Function): Promise 77 | { 78 | if( _.isString(cmd) ) 79 | cmd = cmd.split(' ') 80 | 81 | const promise = new Promise((resolve, reject) => 82 | { 83 | this.spawn(cmd, function(err: any, data: any) 84 | { 85 | if( err ) 86 | return reject(err || data) 87 | 88 | resolve(cmd.join(' ').indexOf('--output=json') > -1 ? JSON.parse(data): data) 89 | }) 90 | }) 91 | 92 | this.callbackFunction(promise, callback) 93 | 94 | return promise 95 | } 96 | 97 | public list(selector: any, flags: any, done: any) 98 | { 99 | if( !this.type ) 100 | throw new Error('not a function') 101 | 102 | if( typeof selector === 'object') 103 | { 104 | var args: any = '--selector=' 105 | 106 | for( var key in selector ) 107 | args += (key + '=' + selector[key]) 108 | 109 | selector = args + '' 110 | } 111 | else{ 112 | done = selector 113 | selector = '--output=json' 114 | } 115 | 116 | if( _.isFunction(flags) ){ 117 | done = flags 118 | flags = null 119 | } 120 | 121 | flags = flags || [] 122 | 123 | const action: any = ['get', this.type , selector, '--output=json'].concat(flags) 124 | 125 | return this.command(action, done) 126 | } 127 | 128 | public get(name: string, flags: any, done: (err: any, data: any)=>void) 129 | { 130 | if( !this.type ) 131 | throw new Error('not a function') 132 | 133 | 134 | if( _.isFunction(flags) ){ 135 | done = flags 136 | flags = null 137 | } 138 | 139 | flags = flags || [] 140 | 141 | const action = ['get', this.type, name, '--output=json'].concat(flags) 142 | 143 | return this.command(action, done) 144 | 145 | } 146 | 147 | public create(filepath: string, flags: any, done: (err: any, data: any)=>void) 148 | { 149 | if( !this.type ) 150 | throw new Error('not a function') 151 | 152 | if( _.isFunction(flags) ){ 153 | done = flags 154 | flags = null 155 | } 156 | 157 | flags = flags || [] 158 | 159 | const action: any = ['create', '-f', filepath].concat(flags) 160 | 161 | return this.command(action, done) 162 | } 163 | 164 | public delete(id: string, flags: any, done: (err: any, data: any)=>void) 165 | { 166 | if( !this.type ) 167 | throw new Error('not a function') 168 | 169 | if( _.isFunction(flags) ){ 170 | done = flags 171 | flags = null 172 | } 173 | 174 | flags = flags || [] 175 | 176 | const action: any = ['delete', this.type, id].concat(flags) 177 | 178 | return this.command(action, done) 179 | } 180 | 181 | public update(filepath: string, flags: any, done: (err: any, data: any)=>void) 182 | { 183 | if( !this.type ) 184 | throw new Error('not a function') 185 | 186 | if( _.isFunction(flags) ){ 187 | done = flags 188 | flags = null 189 | } 190 | 191 | flags = flags || [] 192 | 193 | const action: any = ['update', '-f', filepath].concat(flags) 194 | 195 | return this.command(action, done) 196 | } 197 | 198 | public apply(name: string, json: Object, flags: any, done: (err: any, data: any)=>void) 199 | { 200 | if( !this.type ) 201 | throw new Error('not a function') 202 | 203 | if( _.isFunction(flags) ){ 204 | done = flags 205 | flags = null 206 | } 207 | 208 | flags = flags || [] 209 | const action: any = ['update', this.type, name, '--patch='+ JSON.stringify(json)].concat(flags) 210 | 211 | return this.command(action, done) 212 | } 213 | 214 | public rollingUpdateByFile(name: string, filepath: string, flags: any, done: (err: any, data: any)=>void) 215 | { 216 | if( this.type !== 'replicationcontrollers' ) 217 | throw new Error('not a function') 218 | 219 | 220 | if( _.isFunction(flags) ){ 221 | done = flags 222 | flags = null 223 | } 224 | 225 | flags = flags || [] 226 | const action: any = ['rolling-update', name, '-f', filepath, '--update-period=0s'].concat(flags) 227 | 228 | return this.command(action, done) 229 | } 230 | 231 | 232 | public rollingUpdate(name: string, image: string, flags: any, done: (err: any, data: any)=>void) 233 | { 234 | if( this.type !== 'replicationcontrollers' ) 235 | throw new Error('not a function') 236 | 237 | 238 | if( _.isFunction(flags) ){ 239 | done = flags 240 | flags = null 241 | } 242 | 243 | flags = flags || [] 244 | 245 | const action: any = ['rolling-update', name, '--image=' + image, '--update-period=0s'].concat(flags) 246 | 247 | return this.command(action, done) 248 | } 249 | 250 | public scale(name: string, replicas: string, flags: any, done: (err: any, data: any)=>void) 251 | { 252 | if( this.type !== 'replicationcontrollers' && this.type !== 'deployments' ) 253 | throw new Error('not a function') 254 | 255 | if( _.isFunction(flags) ){ 256 | done = flags 257 | flags = null 258 | } 259 | 260 | flags = flags || [] 261 | const action: any = ['scale', '--replicas=' + replicas, 'replicationcontrollers', name].concat(flags) 262 | 263 | return this.command(action, done) 264 | } 265 | 266 | public logs(name: string, flags: any, done: (err: any, data: any)=>void) 267 | { 268 | if( this.type !== 'pods' ) 269 | throw new Error('not a function') 270 | 271 | var action: any = new Array('logs') 272 | 273 | if (name.indexOf(' ') > -1) { 274 | var names: any = name.split(/ /) 275 | action.push(names[0]) 276 | action.push(names[1]) 277 | } else { 278 | action.push(name) 279 | } 280 | 281 | 282 | if( _.isFunction(flags) ){ 283 | done = flags 284 | flags = null 285 | } 286 | 287 | flags = flags || [] 288 | 289 | return this.command(action.concat(flags), done) 290 | } 291 | 292 | public describe(name: string, flags: any, done: (err: any, data: any)=>void) 293 | { 294 | if( !this.type ) 295 | throw new Error('not a function') 296 | 297 | var action: any = new Array('describe', this.type) 298 | 299 | if ( name === null ) { 300 | action.push(name) 301 | } 302 | 303 | if( _.isFunction(flags) ){ 304 | done = flags 305 | flags = null 306 | } 307 | 308 | flags = flags || [] 309 | 310 | return this.command(action.concat(flags), done) 311 | } 312 | 313 | public portForward(name: string, portString: string, done: (err: any, data: any)=>void) 314 | { 315 | if( this.type !== 'pods' ) 316 | throw new Error('not a function') 317 | 318 | var action: any = new Array('port-forward', name, portString) 319 | 320 | return this.command(action, done) 321 | } 322 | 323 | public useContext(context: string, done: (err: any, data: any)=>void) 324 | { 325 | var action: any = new Array('config', 'use-context', context) 326 | 327 | return this.command(action, done) 328 | } 329 | 330 | public viewContext(done: (err: any, data: any)=>void) 331 | { 332 | var action: any = new Array('config', '--output=json', 'view') 333 | 334 | this.command(action, done) 335 | } 336 | } 337 | 338 | declare function require(name:string): any 339 | 340 | export = (conf: any):any=> 341 | { 342 | return { 343 | // short names are just aliases to longer names 344 | pod: new Kubectl('pods', conf) 345 | , po: new Kubectl('pods', conf) 346 | , replicationcontroller: new Kubectl('replicationcontrollers', conf) 347 | , rc: new Kubectl('replicationcontrollers', conf) 348 | , service: new Kubectl('services', conf) 349 | , svc: new Kubectl('services', conf) 350 | , node: new Kubectl('nodes', conf) 351 | , no: new Kubectl('nodes', conf) 352 | , namespace: new Kubectl('namespaces', conf) 353 | , ns: new Kubectl('namespaces', conf) 354 | , deployment: new Kubectl('deployments', conf) 355 | , daemonset: new Kubectl('daemonsets', conf) 356 | , ds: new Kubectl('daemonsets', conf) 357 | , secrets: new Kubectl('secrets', conf) 358 | , endpoint: new Kubectl('endpoints', conf) 359 | , ep: new Kubectl('endpoints', conf) 360 | , ingress: new Kubectl('ingress', conf) 361 | , ing: new Kubectl('ingress', conf) 362 | , job: new Kubectl('job', conf) 363 | , context: new Kubectl('context', conf) 364 | , command: function(){ 365 | arguments[0] = arguments[0].split(' ') 366 | return this.pod.command.apply(this.pod, arguments) 367 | } 368 | } 369 | } 370 | -------------------------------------------------------------------------------- /lib/request.ts: -------------------------------------------------------------------------------- 1 | const request = require('request') 2 | const Rx = require('rx') 3 | const _ = require('underscore') 4 | const Observable = Rx.Observable 5 | const fs = require('fs') 6 | const jsyaml = require('js-yaml') 7 | 8 | declare var Buffer: any 9 | 10 | export class Request 11 | { 12 | private strictSSL: any 13 | private domain: any 14 | private auth: any 15 | 16 | constructor(conf: any) 17 | { 18 | if (conf.kubeconfig) { 19 | var kubeconfig: any = jsyaml.safeLoad(fs.readFileSync(conf.kubeconfig)) 20 | var context: any = conf.context || this.readContext(kubeconfig) 21 | var cluster: any = this.readCluster(kubeconfig, context) 22 | var user: any = this.readUser(kubeconfig, context) 23 | } 24 | 25 | this.auth = conf.auth || {} 26 | 27 | this.auth.caCert = this.auth.caCert || this.readCaCert(cluster) 28 | this.auth.clientKey = this.auth.clientKey || this.readClientKey(user) 29 | this.auth.clientCert = this.auth.clientCert || this.readClientCert(user) 30 | this.auth.token = this.auth.token || this.readUserToken(user) 31 | this.auth.username = this.auth.username || this.readUsername(user) 32 | this.auth.password = this.auth.password || this.readPassword(user) 33 | 34 | // only set to false if explictly false in the config 35 | this.strictSSL = (conf.strictSSL !== false) 36 | 37 | var endpoint = conf.endpoint || this.readEndpoint(cluster) 38 | this.domain = `${endpoint}${conf.version}/` 39 | } 40 | 41 | // Returns Context JSON from kubeconfig 42 | private readContext(kubeconfig: any) 43 | { 44 | if (!kubeconfig) return 45 | return kubeconfig.contexts.find((x: any) => x.name === kubeconfig['current-context']) 46 | } 47 | 48 | // Returns Cluster JSON from context at kubeconfig 49 | private readCluster(kubeconfig: any, context: any) 50 | { 51 | if (!kubeconfig || !context) return 52 | return kubeconfig.clusters.find((x: any) => x.name === context.context.cluster) 53 | } 54 | 55 | // Returns Cluster JSON from context at kubeconfig 56 | private readUser(kubeconfig: any, context: any) 57 | { 58 | if (!kubeconfig) return 59 | return kubeconfig.users.find((x: any) => x.name === context.context.user) 60 | } 61 | 62 | // Returns CaCert from kubeconfig 63 | private readCaCert(cluster: any) 64 | { 65 | if (!cluster) return 66 | var certificate_authority: any = cluster.cluster['certificate-authority'] 67 | if (certificate_authority) { 68 | return fs.readFileSync(certificate_authority).toString() 69 | } 70 | var certificate_authority_data: any = cluster.cluster['certificate-authority-data'] 71 | if (certificate_authority_data) { 72 | return Buffer.from(certificate_authority_data, 'base64').toString("ascii") 73 | } 74 | } 75 | 76 | // Returns CaCert from kubeconfig 77 | private readClientKey(user: any) 78 | { 79 | if (!user) return 80 | var client_key: any = user.user['client-key'] 81 | if (client_key) { 82 | return fs.readFileSync(client_key).toString() 83 | } 84 | var client_key_data: any = user.user['client-key-data'] 85 | if (client_key_data) { 86 | return Buffer.from(client_key_data, 'base64').toString("ascii") 87 | } 88 | } 89 | 90 | // Returns CaCert from kubeconfig 91 | private readClientCert(user: any) 92 | { 93 | if (!user) return 94 | var client_certificate = user.user['client-certificate'] 95 | if (client_certificate) { 96 | return fs.readFileSync(client_certificate).toString() 97 | } 98 | var client_certificate_data = user.user['client-certificate-data'] 99 | if (client_certificate_data) { 100 | return Buffer.from(client_certificate_data, 'base64').toString("ascii") 101 | } 102 | } 103 | 104 | // Returns User token from kubeconfig 105 | private readUserToken(user: any) 106 | { 107 | if (!user) return 108 | return user.user['token'] 109 | } 110 | 111 | // Returns User token from kubeconfig 112 | private readUsername(user: any) 113 | { 114 | if (!user) return 115 | return user.user['username'] 116 | } 117 | 118 | private readPassword(user: any) 119 | { 120 | if (!user) return 121 | return user.user['password'] 122 | } 123 | 124 | private readEndpoint(cluster: any) 125 | { 126 | if (!cluster) return 127 | return cluster.cluster['server'] 128 | } 129 | 130 | private callbackFunction(promise: any, callback: Function) 131 | { 132 | if( _.isFunction(callback) ) 133 | { 134 | promise.then((data: any)=>{ 135 | callback(null, data) 136 | }).catch((err: any)=>{ 137 | callback(err) 138 | }) 139 | } 140 | } 141 | 142 | private getRequestOptions(path: string, opts?: any) 143 | { 144 | const options: any = opts || {} 145 | 146 | options.url = this.domain + path 147 | 148 | options.headers = { 149 | 'Content-Type': 'application/json' 150 | } 151 | 152 | options.strictSSL = this.strictSSL 153 | 154 | if (this.auth) { 155 | if (this.auth.caCert) { 156 | options.ca = this.auth.caCert 157 | } 158 | 159 | if (this.auth.username && this.auth.password) { 160 | const authstr: string = new Buffer(this.auth.username + ':' + this.auth.password).toString('base64') 161 | options.headers.Authorization = `Basic ${authstr}` 162 | } else if (this.auth.token) { 163 | options.headers.Authorization = `Bearer ${this.auth.token}` 164 | } else if (this.auth.clientCert && this.auth.clientKey) { 165 | options.cert = this.auth.clientCert 166 | options.key = this.auth.clientKey 167 | } 168 | } 169 | 170 | return options 171 | } 172 | 173 | public async get(url: string, done?: any): Promise 174 | { 175 | const promise = new Promise((resolve, reject) => 176 | { 177 | request.get(this.getRequestOptions(url), function(err: any, res: any, data: any) 178 | { 179 | if( err || res.statusCode < 200 || res.statusCode >= 300 ) 180 | return reject(err || data) 181 | 182 | resolve(JSON.parse(data)) 183 | }) 184 | }) 185 | 186 | this.callbackFunction(promise, done) 187 | 188 | return promise 189 | } 190 | 191 | 192 | public async log(url: string, done?: any): Promise 193 | { 194 | const promise = new Promise((resolve, reject) => 195 | { 196 | request.get(this.getRequestOptions(url), function(err: any, res: any, data: any) 197 | { 198 | if( err || res.statusCode < 200 || res.statusCode >= 300 ) 199 | return reject(err || data) 200 | 201 | resolve(data) 202 | }) 203 | }) 204 | 205 | this.callbackFunction(promise, done) 206 | 207 | return promise 208 | } 209 | 210 | public post(url: string, body: any, done?: any): Promise 211 | { 212 | const promise = new Promise((resolve, reject) => 213 | { 214 | request.post(this.getRequestOptions(url, {json: body}), function(err: any, res: any, data: any) 215 | { 216 | if( err || res.statusCode < 200 || res.statusCode >= 300 ) 217 | return reject(err || data) 218 | 219 | resolve(data) 220 | }) 221 | }) 222 | 223 | this.callbackFunction(promise, done) 224 | 225 | return promise 226 | } 227 | 228 | public put(url: string, body: any, done?: any): Promise 229 | { 230 | const promise = new Promise((resolve, reject) => 231 | { 232 | request.put(this.getRequestOptions(url, {json: body}), function(err: any, res: any, data: any) 233 | { 234 | if( err || res.statusCode < 200 || res.statusCode >= 300 ) 235 | return reject(err || data) 236 | 237 | resolve(data) 238 | }) 239 | }) 240 | 241 | this.callbackFunction(promise, done) 242 | 243 | return promise 244 | } 245 | 246 | public patch(url: string, body: any, _options?: any, done?: any): Promise 247 | { 248 | if( typeof (_options) === 'function' ){ 249 | done = _options 250 | _options = undefined 251 | } 252 | 253 | const promise = new Promise((resolve, reject) => 254 | { 255 | const options = this.getRequestOptions(url, { json: body }) 256 | 257 | options.headers['Content-Type'] = 'application/json-patch+json' 258 | 259 | if( _options && _options.headers ) 260 | { 261 | for( let key in _options.headers ){ 262 | options.headers[key] = _options.headers[key] 263 | } 264 | } 265 | 266 | request.patch(options, function(err: any, res: any, data: any) 267 | { 268 | if( err || res.statusCode < 200 || res.statusCode >= 300 ) 269 | return reject(err || data) 270 | 271 | resolve(data) 272 | }) 273 | }) 274 | 275 | this.callbackFunction(promise, done) 276 | 277 | return promise 278 | } 279 | 280 | public delete(url: string, json?: any, done?: any): Promise 281 | { 282 | if( _.isFunction(json) ){ 283 | done = json 284 | json = undefined 285 | } 286 | 287 | const promise = new Promise((resolve, reject) => 288 | { 289 | request.del(this.getRequestOptions(url, json), function(err: any, res: any, data: any) 290 | { 291 | if( err || res.statusCode < 200 || res.statusCode >= 300 ) 292 | return reject(err || data) 293 | 294 | resolve(data) 295 | }) 296 | }) 297 | 298 | this.callbackFunction(promise, done) 299 | 300 | return promise 301 | } 302 | 303 | public watch(url: string, message?: any, exit?: any, timeout?: any) 304 | { 305 | if( _.isNumber(message) ){ 306 | timeout = message 307 | message = undefined 308 | } 309 | var res: any 310 | 311 | const source = Observable.create((observer: any)=> 312 | { 313 | var jsonStr = '' 314 | res = request.get(this.getRequestOptions(url, { timeout: timeout }),function(){ }).on('data', function(data: any) 315 | { 316 | if (res.response.headers['content-type']==='text/plain') { 317 | observer.onNext(data.toString()) 318 | } else { 319 | jsonStr += data.toString() 320 | 321 | if (!/\n$/.test(jsonStr)) 322 | return 323 | jsonStr = jsonStr.replace('\n$', '') 324 | try { 325 | jsonStr.split('\n').forEach(function(jsonStr){ 326 | if( !jsonStr ) 327 | return 328 | const json = JSON.parse(jsonStr) 329 | observer.onNext(json) 330 | }) 331 | jsonStr = '' 332 | } 333 | catch(err){ 334 | observer.onError(err) 335 | } 336 | } 337 | }).on('error', function(err: any){ 338 | observer.onError(err) 339 | }).on('close', function(){ 340 | observer.onError() 341 | }) 342 | }) 343 | 344 | if( _.isFunction(message) ) 345 | { 346 | source.subscribe((data: any)=>{ 347 | message(data) 348 | }, (err: any)=>{ 349 | if( _.isFunction(exit) ) 350 | exit(err) 351 | }) 352 | 353 | return res 354 | } 355 | 356 | return source 357 | } 358 | } 359 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "k8s", 3 | "version": "0.4.16", 4 | "author": "junjun16818", 5 | "description": "Node.js client library for Google's Kubernetes Kubectl And API", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/goyoo/node-k8s-client.git" 9 | }, 10 | "bugs": { 11 | "url": "https://github.com/goyoo/node-k8s-client/issues" 12 | }, 13 | "main": "index.js", 14 | "typings": "typings", 15 | "scripts": { 16 | "test": "mocha test", 17 | "build": "webpack", 18 | "watch": "npm run build -- --watch", 19 | "start": "webpack --watch" 20 | }, 21 | "dependencies": { 22 | "request": "^2.72.0", 23 | "rx": "^4.1.0", 24 | "js-yaml": "^3.8.2", 25 | "underscore": "^1.8.3" 26 | }, 27 | "devDependencies": { 28 | "@types/node": "^6.0.32", 29 | "chai": "^3.5.0", 30 | "concurrently": "^2.0.0", 31 | "fast-json-patch": "^0.5.7", 32 | "json-loader": "^0.5.4", 33 | "mocha": "^3.2.0", 34 | "temp": "^0.8.3", 35 | "ts-loader": "^0.8.2", 36 | "typescript": "^2.0.0", 37 | "webpack": "^1.13.1" 38 | }, 39 | "license": "MIT" 40 | } 41 | -------------------------------------------------------------------------------- /test/kubeapi.js: -------------------------------------------------------------------------------- 1 | const expect = require('chai').expect 2 | , K8s = require('../index.js') 3 | , fs = require('fs') 4 | , assert = require('chai').assert 5 | , jsonpatch = require('fast-json-patch') 6 | 7 | var kubeapi = K8s.api({ 8 | endpoint: 'https://192.168.99.100:8443', 9 | version: '/api/v1', 10 | auth: { 11 | clientCert: fs.readFileSync(`${process.env.HOME}/.minikube/apiserver.crt`).toString(), 12 | clientKey: fs.readFileSync(`${process.env.HOME}/.minikube/apiserver.key`).toString() , 13 | caCert: fs.readFileSync(`${process.env.HOME}/.minikube/ca.crt`).toString() 14 | } 15 | }) 16 | 17 | var res = kubeapi.watch('watch/namespaces/default/pods', function(data){ 18 | console.log('watch pod log: ' + data.type) 19 | }, function(err){ 20 | console.log(err) 21 | }) 22 | 23 | 24 | describe('kubeapi ',function() 25 | { 26 | this.timeout(1000000) 27 | 28 | it('test api GET -> get rc list', function(done) 29 | { 30 | kubeapi.get('namespaces/default/replicationcontrollers', function(err, data){ 31 | // assert(data.items.length===0) 32 | done(err) 33 | }) 34 | }) 35 | 36 | // it('test api log -> get pod log', function(done) 37 | // { 38 | // kubeapi.log('namespaces/default/pods/helloworld-v1-6q0cr/log', function(err, data){ 39 | // console.log(data) 40 | // done(err) 41 | // }) 42 | // }) 43 | // return 44 | // it('test api GET by watch -> get rc list', function(done) 45 | // { 46 | // var res = kubeapi.watch('watch/namespaces/default/pods', function(data) 47 | // { 48 | // console.log('watch pod log: ' + data.type) 49 | // res.emit('close') 50 | 51 | // }, function(err){ 52 | // console.log('exit',err) 53 | // }, 20000) 54 | 55 | // }) 56 | 57 | it('test api POST -> create rc', function(done){ 58 | var rc = require('./rc/nginx-rc.json') 59 | kubeapi.post('namespaces/default/replicationcontrollers', rc, function(err, data){ 60 | assert(data.spec.replicas === 3) 61 | // console.log(data) 62 | return done() 63 | }) 64 | }) 65 | 66 | it('test api PATCH -> update rc replicas', function(done) 67 | { 68 | 69 | var json = [{ op: 'replace', path: '/spec/replicas', value: 2 }] 70 | 71 | kubeapi.patch('namespaces/default/replicationcontrollers/nginx', json, function(err, data) 72 | { 73 | kubeapi.get('namespaces/default/replicationcontrollers/nginx', function(err, data){ 74 | assert(data.spec.replicas === 2) 75 | done() 76 | }) 77 | }) 78 | }) 79 | 80 | it('test api PATCH -> update rc replicas', function(done) 81 | { 82 | 83 | var json = [{ op: 'replace', path: '/spec/replicas', value: 0 }] 84 | 85 | kubeapi.patch('namespaces/default/replicationcontrollers/nginx', json, function(err, data) 86 | { 87 | kubeapi.get('namespaces/default/replicationcontrollers/nginx', function(err, data){ 88 | assert(data.spec.replicas === 0) 89 | done() 90 | }) 91 | }) 92 | }) 93 | 94 | it('test api DELETE -> delete rc', function(done) 95 | { 96 | kubeapi.delete('namespaces/default/replicationcontrollers/nginx', {gracePeriodSeconds: 1}, function(err, data){ 97 | done() 98 | }) 99 | }) 100 | }) 101 | -------------------------------------------------------------------------------- /test/kubeapi_kubeconf.js: -------------------------------------------------------------------------------- 1 | const expect = require('chai').expect 2 | , K8s = require('../index.js') 3 | , fs = require('fs') 4 | , assert = require('chai').assert 5 | , _ = require('underscore') 6 | , jsyaml = require('js-yaml') 7 | , temp = require("temp") 8 | 9 | temp.track() // Cleanup files 10 | 11 | // https://kubernetes.io/docs/concepts/cluster-administration/authenticate-across-clusters-kubeconfig/ 12 | 13 | // Use embedded base64-data inside kubeconfig 14 | var kubeconfigEmbedded = { 15 | 'current-context': 'minikube', 16 | contexts: [{ 17 | name: 'minikube', 18 | context:{ 19 | cluster: 'minikube', 20 | user: 'minikube' 21 | } 22 | }], 23 | clusters: [{ 24 | name: 'minikube', 25 | cluster: { 26 | 'certificate-authority-data': fs.readFileSync(`${process.env.HOME}/.minikube/ca.crt`).toString('base64'), 27 | server: 'https://192.168.99.100:8443' 28 | } 29 | }], 30 | users: [{ 31 | name: 'minikube', 32 | user: { 33 | 'client-certificate-data': fs.readFileSync(`${process.env.HOME}/.minikube/apiserver.crt`).toString('base64'), 34 | 'client-key-data': fs.readFileSync(`${process.env.HOME}/.minikube/apiserver.key`).toString('base64'), 35 | } 36 | }] 37 | } 38 | 39 | // Use file-references inside kubeconfig 40 | var kubeconfigKeyReference = { 41 | 'current-context': 'minikube', 42 | contexts: [{ 43 | name: 'minikube', 44 | context:{ 45 | cluster: 'minikube', 46 | user: 'minikube' 47 | } 48 | }], 49 | clusters: [{ 50 | name: 'minikube', 51 | cluster: { 52 | 'certificate-authority': `${process.env.HOME}/.minikube/ca.crt`, 53 | server: 'https://192.168.99.100:8443' 54 | } 55 | }], 56 | users: [{ 57 | name: 'minikube', 58 | user: { 59 | 'client-certificate': `${process.env.HOME}/.minikube/apiserver.crt`, 60 | 'client-key': `${process.env.HOME}/.minikube/apiserver.key` 61 | } 62 | }] 63 | } 64 | 65 | // Use Insecure connection inside kubeconfig 66 | var kubeconfigInsecure = { 67 | 'current-context': 'minikube', 68 | contexts: [{ 69 | name: 'minikube', 70 | context:{ 71 | cluster: 'minikube', 72 | user: 'minikube' 73 | } 74 | }], 75 | clusters: [{ 76 | name: 'minikube', 77 | cluster: { 78 | 'certificate-authority': `${process.env.HOME}/.minikube/ca.crt`, 79 | server: 'https://192.168.99.100:8443', 80 | 'insecure-skip-tls-verify': false 81 | } 82 | }], 83 | users: [{ 84 | name: 'minikube', 85 | user: { 86 | 'client-certificate': `${process.env.HOME}/.minikube/apiserver.crt`, 87 | 'client-key': `${process.env.HOME}/.minikube/apiserver.key` 88 | } 89 | }] 90 | } 91 | 92 | var kubeconfigToken = { 93 | 'current-context': 'minikube', 94 | contexts: [{ 95 | name: 'minikube', 96 | context:{ 97 | cluster: 'minikube', 98 | user: 'minikube' 99 | } 100 | }], 101 | clusters: [{ 102 | name: 'minikube', 103 | cluster: { 104 | 'certificate-authority-data': fs.readFileSync(`${process.env.HOME}/.minikube/ca.crt`).toString('base64'), 105 | server: 'https://192.168.99.100:8443' 106 | } 107 | }], 108 | users: [{ 109 | name: 'minikube', 110 | user: { 111 | token: 'replaced_by_actual_token' 112 | } 113 | }] 114 | } 115 | 116 | var kubeconfigUserPassword = { 117 | version: '/api/v1', 118 | kubeconfig: { 119 | 'current-context': 'minikube', 120 | contexts: [{ 121 | name: 'minikube', 122 | context:{ 123 | cluster: 'minikube', 124 | user: 'minikube' 125 | } 126 | }], 127 | clusters: [{ 128 | name: 'minikube', 129 | cluster: { 130 | 'certificate-authority-data': fs.readFileSync(`${process.env.HOME}/.minikube/ca.crt`).toString('base64'), 131 | server: 'https://192.168.99.100:8443' 132 | } 133 | }], 134 | users: [{ 135 | name: 'minikube', 136 | user: { 137 | username: 'admin', 138 | password: 'some_password' 139 | } 140 | }] 141 | } 142 | } 143 | 144 | describe.only('kubeapi read kubeconfig',function() 145 | { 146 | this.timeout(1000) 147 | 148 | it('test kubeconfig with embedded data', function(done) 149 | { 150 | var api = K8s.api({version: '/api/v1', kubeconfig: createTmpFS(kubeconfigEmbedded)}) 151 | api.get('namespaces/default/replicationcontrollers', function(err, data){ 152 | done(err) 153 | }) 154 | }) 155 | 156 | it('test Kubeconfig with file references', function(done) 157 | { 158 | var api = K8s.api({version: '/api/v1', kubeconfig: createTmpFS(kubeconfigKeyReference)}) 159 | api.get('namespaces/default/replicationcontrollers', function(err, data){ 160 | done(err) 161 | }) 162 | }) 163 | 164 | it('test Kubeconfig with strictSSL set to false', function(done) 165 | { 166 | var api = K8s.api({version: '/api/v1', kubeconfig: createTmpFS(kubeconfigInsecure)}) 167 | api.get('namespaces/default/replicationcontrollers', function(err, data){ 168 | done(err) 169 | }) 170 | }) 171 | 172 | it('test Kubeconfig with User token', function(done) 173 | { 174 | // Get a real token for minikube to authenticate 175 | K8s.api({version: '/api/v1', kubeconfig: createTmpFS(kubeconfigKeyReference)}).get('namespaces/default/secrets', function(err, data){ 176 | secret = data.items[0].data.token 177 | 178 | var config = kubeconfigToken 179 | config.users = [{ 180 | name: 'minikube', 181 | user: { 182 | token: Buffer.from(secret, 'base64').toString("ascii") 183 | } 184 | }] 185 | var api = K8s.api({version: '/api/v1', kubeconfig: createTmpFS(config)}) 186 | api.get('namespaces/default/replicationcontrollers', function(err, data){ 187 | done(err) 188 | }) 189 | }); 190 | }) 191 | 192 | // it('test Kubeconfig with username/password', function(done) 193 | // { 194 | // // Read real kube config (No way to use minikube) 195 | // var api = K8s.api({ 196 | // version: '/api/v1', 197 | // kubeconfig: `${process.env.HOME}/.kube/config` 198 | // }) 199 | // api.get('namespaces/default/replicationcontrollers', function(err, data){ 200 | // done(err) 201 | // }) 202 | // }) 203 | 204 | // Puts data in a temporal file 205 | createTmpFS = function(data) { 206 | var stream = temp.createWriteStream() 207 | fs.writeFileSync(stream.path, jsyaml.safeDump(data)) 208 | return stream.path 209 | } 210 | 211 | after(function() { 212 | temp.cleanup() // Cleanup files 213 | }) 214 | }) 215 | -------------------------------------------------------------------------------- /test/kubectl_node.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect 2 | , K8s = require('../index.js') 3 | , fs = require('fs') 4 | , assert = require('chai').assert 5 | , path = require('path') 6 | 7 | 8 | var kubectl = K8s.kubectl({ 9 | endpoint: 'https://192.168.99.100:8443' 10 | , binary: 'kubectl' 11 | }) 12 | 13 | describe('kubectl nodes',function() 14 | { 15 | this.timeout(1000000) 16 | 17 | it('get node list', function(done) 18 | { 19 | kubectl.node.list(function(err, data){ 20 | assert(!err && data) 21 | done(err) 22 | }) 23 | }) 24 | 25 | it('create a node', function(done){ 26 | kubectl.node.create(path.join(__dirname, '/nodes/node3.json'), function(err, data){ 27 | done(err) 28 | }) 29 | }) 30 | 31 | it('get a node', function(done){ 32 | kubectl.node.get('192.168.10.13', function(err, data){ 33 | done(err) 34 | }) 35 | }) 36 | 37 | it('delete a node', function(done){ 38 | kubectl.node.delete('192.168.10.13', function(err, data){ 39 | done(err) 40 | }) 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /test/kubectl_pods.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect 2 | , K8s = require('../index.js') 3 | , fs = require('fs') 4 | , assert = require('chai').assert 5 | , path = require('path') 6 | 7 | 8 | var kubectl = K8s.kubectl({ 9 | endpoint: 'https://192.168.99.100:8443' 10 | , binary: 'kubectl' 11 | }) 12 | 13 | describe('kubectl pod',function() 14 | { 15 | this.timeout(1000000) 16 | 17 | it('get pods list', function(done) 18 | { 19 | // kubectl.command('get pods --output=json').then(function(a){ 20 | // console.log(a) 21 | // }) 22 | kubectl.pod.list(function(err, data){ 23 | done(err) 24 | }) 25 | }) 26 | 27 | it('create a pod', function(done) 28 | { 29 | kubectl.pod.create(path.join(__dirname, '/pods/nginx.yaml'), function(err, data){ 30 | done(err) 31 | }) 32 | }) 33 | 34 | it('get a pod', function(done) 35 | { 36 | kubectl.pod.get('nginx', function(err, data){ 37 | done(err) 38 | }) 39 | }) 40 | 41 | it('get pods by selector', function(done) 42 | { 43 | kubectl.pod.list({ app: 'nginx'}, function(err, data){ 44 | done(err) 45 | }) 46 | }) 47 | 48 | it('delete a pod', function(done) 49 | { 50 | kubectl.pod.delete('nginx', function(err, data){ 51 | done(err) 52 | }) 53 | }) 54 | }) 55 | -------------------------------------------------------------------------------- /test/kubectl_rc.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect 2 | , K8s = require('../index.js') 3 | , fs = require('fs') 4 | , assert = require('chai').assert 5 | , path = require('path') 6 | 7 | 8 | var kubectl = K8s.kubectl({ 9 | endpoint: 'https://192.168.99.100:8443' 10 | , binary: 'kubectl' 11 | }) 12 | 13 | describe('kubectl rc',function() 14 | { 15 | this.timeout(1000000) 16 | 17 | it('get rc list', function(done) 18 | { 19 | kubectl.rc.list(function(err, data){ 20 | done() 21 | }) 22 | }) 23 | 24 | it('create a rc', function(done) 25 | { 26 | kubectl.rc.create(path.join(__dirname, '/rc/helloworld-v1.yaml'), function(err, data){ 27 | assert(!err) 28 | done() 29 | }) 30 | }) 31 | 32 | it('get a rc helloworld-v1', function(done) 33 | { 34 | kubectl.rc.get('helloworld-v1', function(err, data){ 35 | assert(!err && data.metadata.name === 'helloworld-v1') 36 | done() 37 | }) 38 | }) 39 | 40 | it('scale rc', function(done){ 41 | kubectl.rc.scale('helloworld-v1', 1, function(err, data){ 42 | done(err) 43 | }) 44 | }) 45 | 46 | it('rollingUpdate rc by file', function(done){ 47 | kubectl.rc.rollingUpdateByFile('helloworld-v1', path.join(__dirname, '/rc/helloworld-v2.yaml'), function(err, data){ 48 | assert(!err) 49 | done(err) 50 | }) 51 | }) 52 | 53 | it('get a rc helloworld-v2', function(done) 54 | { 55 | kubectl.rc.get('helloworld-v2', function(err, data){ 56 | assert(!err && data.metadata.name === 'helloworld-v2') 57 | done(err) 58 | }) 59 | }) 60 | 61 | it('rollingUpdate rc by image', function(done) 62 | { 63 | kubectl.rc.rollingUpdate('helloworld-v2', 'junjun16818/hello-world:v1', function(err, data){ 64 | assert(!err) 65 | done(err) 66 | }) 67 | }) 68 | 69 | it('delete a rc', function(done) 70 | { 71 | kubectl.rc.delete('helloworld-v2',['--grace-period=0'], function(err, data){ 72 | assert(!err) 73 | done(err) 74 | }) 75 | }) 76 | }) 77 | -------------------------------------------------------------------------------- /test/kubectl_service.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect 2 | , K8s = require('../index.js') 3 | , fs = require('fs') 4 | , assert = require('chai').assert 5 | , path = require('path') 6 | 7 | var kubectl = K8s.kubectl({ 8 | endpoint: 'https://192.168.99.100:8443' 9 | , binary: 'kubectl' 10 | }) 11 | 12 | describe('kubectl service',function() 13 | { 14 | this.timeout(1000000) 15 | 16 | it('get service list', function(done) 17 | { 18 | kubectl.service.list(function(err, data){ 19 | done(err) 20 | }) 21 | }) 22 | 23 | it('create a service', function(done) 24 | { 25 | kubectl.service.create(path.join(__dirname, '/service/helloworld-service.yaml'), function(err, data){ 26 | done(err) 27 | }) 28 | }) 29 | 30 | it('get service helloworld', function(done) 31 | { 32 | kubectl.service.get('helloworld', function(err, data){ 33 | done(err) 34 | }) 35 | }) 36 | 37 | it('delete a service', function(done) 38 | { 39 | kubectl.service.delete('helloworld', function(err, data){ 40 | done(err) 41 | }) 42 | }) 43 | }) 44 | -------------------------------------------------------------------------------- /test/nodes/node3.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "Node", 3 | "apiVersion": "v1", 4 | "metadata": { 5 | "name": "192.168.10.13", 6 | "labels": { 7 | "kubernetes.io/hostname": "192.168.10.13" 8 | } 9 | }, 10 | "spec": { 11 | "externalID": "192.168.10.13" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/pods/nginx.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: nginx 5 | labels: 6 | app: nginx 7 | spec: 8 | containers: 9 | - name: nginx 10 | image: dhub.yunpro.cn/junjun16818/nginx 11 | ports: 12 | - containerPort: 80 13 | -------------------------------------------------------------------------------- /test/rc/helloworld-v1.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ReplicationController 3 | metadata: 4 | name: helloworld-v1 5 | spec: 6 | replicas: 3 7 | selector: 8 | app: helloworld 9 | deployment: v1 10 | template: 11 | metadata: 12 | name: helloworld 13 | labels: 14 | app: helloworld 15 | deployment: v1 16 | spec: 17 | containers: 18 | - name: nodejs 19 | image: junjun16818/hello-world:v1 20 | ports: 21 | - containerPort: 80 22 | -------------------------------------------------------------------------------- /test/rc/helloworld-v2.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ReplicationController 3 | metadata: 4 | name: helloworld-v2 5 | spec: 6 | replicas: 3 7 | selector: 8 | app: helloworld 9 | deployment: v2 10 | template: 11 | metadata: 12 | name: helloworld 13 | labels: 14 | app: helloworld 15 | deployment: v2 16 | spec: 17 | containers: 18 | - name: nodejs 19 | image: junjun16818/hello-world:v2 20 | ports: 21 | - containerPort: 80 22 | -------------------------------------------------------------------------------- /test/rc/nginx-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "v1", 3 | "kind": "ReplicationController", 4 | "metadata":{ 5 | "name": "nginx" 6 | }, 7 | "spec":{ 8 | "replicas": 3, 9 | "selector":{ 10 | "app": "nginx" 11 | }, 12 | "template":{ 13 | "metadata":{ 14 | "name": "nginx", 15 | "labels":{ 16 | "app": "nginx" 17 | } 18 | }, 19 | "spec":{ 20 | "containers":[{ 21 | "name": "nginx", 22 | "image": "nginx", 23 | "ports":[ 24 | { "containerPort": 80 } 25 | ] 26 | }] 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /test/service/helloworld-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: helloworld 5 | labels: 6 | name: helloworld-service 7 | spec: 8 | ports: 9 | - port: 80 10 | type: NodePort 11 | selector: 12 | app: helloworld 13 | 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6", 4 | "module": "commonjs", 5 | "sourceMap": true, 6 | "noImplicitAny": true, 7 | "noImplicitThis": true, 8 | "noUnusedLocals": true, 9 | "noUnusedParameters": true, 10 | "strictNullChecks": true, 11 | "emitDecoratorMetadata": true, 12 | "experimentalDecorators": true, 13 | "removeComments": false, 14 | "declaration": true, 15 | "declarationDir": "typings" 16 | }, 17 | "exclude": [ 18 | "node_modules" 19 | ], 20 | "files": [ 21 | "node_modules/@types/node/index.d.ts" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack') 2 | var path = require('path') 3 | var fs = require('fs') 4 | 5 | var nodeModules = {} 6 | 7 | fs.readdirSync('node_modules').filter(function(x) { 8 | return ['.bin'].indexOf(x) === -1 9 | }).forEach(function(mod) { 10 | nodeModules[mod] = 'commonjs ' + mod 11 | }) 12 | 13 | module.exports = { 14 | entry: './index.ts', 15 | output: { 16 | path: __dirname, 17 | filename: 'index.js', 18 | libraryTarget: 'this' 19 | }, 20 | target: 'node', 21 | module: { 22 | loaders: [ 23 | { test: /\.ts$/, loader: 'ts-loader' }, 24 | { test: /\.json$/, loader: 'json-loader' } 25 | ] 26 | }, 27 | resolve: { 28 | extensions: ['', '.ts', 'js'] 29 | }, 30 | externals: [nodeModules] 31 | } 32 | --------------------------------------------------------------------------------