-
Notifications
You must be signed in to change notification settings - Fork 192
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(backends): @kubernetes/client-node (#394)
Experimental support for a @kubernetes/client-node based backend.
- Loading branch information
Silas Boyd-Wickizer
committed
May 19, 2019
1 parent
ec6cca3
commit 7faef3e
Showing
5 changed files
with
546 additions
and
33 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
/* eslint no-console:0 */ | ||
// | ||
// Demonstrate how to use @kubernetes/client-node as a backend. | ||
// | ||
const k8s = require('@kubernetes/client-node') | ||
|
||
const Client = require('..').Client | ||
const ClientNodeBackend = require('../lib/backends/kubernetes-client-node') | ||
|
||
const deploymentManifest = require('./nginx-deployment.json') | ||
|
||
async function main () { | ||
try { | ||
const kubeconfig = new k8s.KubeConfig() | ||
kubeconfig.loadFromDefault() | ||
|
||
const backend = new ClientNodeBackend({ client: k8s, kubeconfig }) | ||
const client = new Client({ backend, version: '1.10' }) | ||
|
||
// | ||
// Get all the Namespaces. | ||
// | ||
const namespaces = (await client.api.v1.namespaces.get()).body.items.map(namespace => ({ | ||
name: namespace.metadata.name, | ||
status: namespace.status | ||
})) | ||
console.log('Namespaces:', JSON.stringify(namespaces, null, 2)) | ||
|
||
// | ||
// Create a new Deployment. | ||
// | ||
const create = await client.apis.apps.v1.namespaces('default').deployments.post({ body: deploymentManifest }) | ||
console.log('Create:', create.body) | ||
|
||
// | ||
// Fetch the Deployment we just created. | ||
// | ||
const deployment = await client.apis.apps.v1.namespaces('default').deployments(deploymentManifest.metadata.name).get() | ||
console.log('Deployment: ', deployment.body) | ||
|
||
// | ||
// Change the Deployment Replica count to 10 | ||
// | ||
|
||
const replica = { | ||
spec: { | ||
replicas: 10 | ||
} | ||
} | ||
|
||
const replicaModify = await client.apis.apps.v1.namespaces('default').deployments(deploymentManifest.metadata.name).patch({ body: replica }) | ||
console.log('Replica Modification: ', replicaModify) | ||
|
||
// | ||
// Modify the image tag | ||
// | ||
const newImage = { | ||
spec: { | ||
template: { | ||
spec: { | ||
containers: [{ | ||
name: 'nginx', | ||
image: 'nginx:1.8.1' | ||
}] | ||
} | ||
} | ||
} | ||
} | ||
const imageSet = await client.apis.apps.v1.namespaces('default').deployments(deploymentManifest.metadata.name).patch({ body: newImage }) | ||
console.log('New Image: ', imageSet) | ||
|
||
// | ||
// Remove the Deployment we created. | ||
// | ||
const removed = await client.apis.apps.v1.namespaces('default').deployments(deploymentManifest.metadata.name).delete() | ||
console.log('Removed: ', removed) | ||
} catch (err) { | ||
console.error('Error:', err.message) | ||
} | ||
} | ||
|
||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
'use strict' | ||
|
||
const pascalcase = require('pascalcase') | ||
|
||
// | ||
// https://github.com/kubernetes-client/javascript | ||
// | ||
class ClientNodeBackend { | ||
constructor (options) { | ||
this.client = options.client | ||
this.kubeconfig = options.kubeconfig | ||
this.apiClients = { } | ||
} | ||
|
||
_getApiClient (tag) { | ||
// | ||
// API type is a snake_case CamelCase amalgamation. E.g., Core_v1Api | ||
// | ||
const apiType = tag.charAt(0).toUpperCase() + tag.slice(1) + 'Api' | ||
if (!(apiType in this.apiClients)) { | ||
this.apiClients[apiType] = this.kubeconfig.makeApiClient(this.client[apiType]) | ||
} | ||
return this.apiClients[apiType] | ||
} | ||
|
||
http (options) { | ||
const pathItemObject = options.pathItemObject | ||
const operationObject = pathItemObject[options.method.toLowerCase()] | ||
const tag = operationObject.tags[0] | ||
|
||
const apiClient = this._getApiClient(tag) | ||
|
||
// | ||
// In older Kubernetes API OpenAPI specifications the Operation IDs include | ||
// the tag, but in newer versions (including the ones used to generate | ||
// @kubernetes/client-node), the tag is absent. | ||
// | ||
// Support older versions of the Swagger specifications by removing the tag | ||
// part. | ||
// | ||
const method = operationObject.operationId.replace(pascalcase(tag), '') | ||
|
||
// | ||
// @kubernetes/client-node methods take parameters in the order the OpenAPI | ||
// specification declares them. | ||
// | ||
const parameterObjects = (pathItemObject.parameters || []).concat(operationObject.parameters || []) | ||
const orderedParameterObjects = parameterObjects | ||
.filter(parameterObject => parameterObject.required) | ||
.concat(parameterObjects | ||
.filter(parameterObject => !parameterObject.required)) | ||
|
||
// | ||
// Older versions of the Kubernetes API OpenAPI specifications requires body | ||
// for _some_ delete operations (e.g., deleteNamespacedDeployment). The API | ||
// does not actually require it and newer specifications remove the | ||
// requirement. Try to Workaround this issue by adding an empty body to | ||
// @kubernetes/client-node calls. | ||
// | ||
let body = options.body | ||
if (options.method.toLowerCase() === 'delete' && !body) { | ||
body = {} | ||
} | ||
|
||
const parameters = Object.assign( | ||
{ body }, | ||
options.pathnameParameters, | ||
options.qs, | ||
options.parameters) | ||
const args = orderedParameterObjects.reduce((acc, operationParameter) => { | ||
const name = operationParameter.name | ||
if (name in parameters) acc.push(parameters[name]) | ||
else acc.push(undefined) | ||
return acc | ||
}, []) | ||
|
||
return apiClient[method].apply(apiClient, args) | ||
.then(res => { | ||
res.statusCode = res.response.statusCode | ||
return res | ||
}) | ||
.catch(err => { | ||
if (!err.body) throw err | ||
const error = new Error(err.body.message) | ||
// .code is backwards compatible with pre-5.0.0 code. | ||
error.code = err.response.statusCode | ||
error.statusCode = err.response.statusCode | ||
throw error | ||
}) | ||
} | ||
} | ||
|
||
module.exports = ClientNodeBackend |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
'use strict' | ||
/* eslint-env mocha */ | ||
|
||
const { expect } = require('chai') | ||
const sinon = require('sinon') | ||
|
||
const ClientNodeBackend = require('./kubernetes-client-node') | ||
|
||
describe('lib.backends.kubernetes-client-node', () => { | ||
it('calls the expected method', done => { | ||
const fakeK8sClient = { Core_v1Api: 'foo' } | ||
const fakeKubeconfig = { | ||
makeApiClient: sinon.spy(() => { | ||
return { | ||
getStuff: (bar, foo) => { | ||
return new Promise((resolve, reject) => { | ||
expect(bar).to.equal('bar') | ||
expect(foo).to.equal('foo') | ||
resolve({ | ||
response: { | ||
statusCode: 200 | ||
} | ||
}) | ||
}) | ||
} | ||
} | ||
}) | ||
} | ||
|
||
const options = { | ||
method: 'GET', | ||
pathnameParameters: { | ||
foo: 'foo' | ||
}, | ||
parameters: { | ||
bar: 'bar' | ||
}, | ||
pathItemObject: { | ||
get: { | ||
operationId: 'getStuff', | ||
parameters: [{ | ||
name: 'foo' | ||
}], | ||
tags: ['core_v1'] | ||
}, | ||
parameters: [{ | ||
name: 'bar' | ||
}] | ||
} | ||
} | ||
|
||
const client = new ClientNodeBackend({ | ||
client: fakeK8sClient, | ||
kubeconfig: fakeKubeconfig | ||
}) | ||
|
||
client.http(options) | ||
.then(res => done()) | ||
.catch(done) | ||
}) | ||
}) |
Oops, something went wrong.