auto-kubernetes-client
Advanced tools
+2
-1
| { | ||
| "name": "auto-kubernetes-client", | ||
| "version": "0.3.1", | ||
| "version": "0.4.0", | ||
| "description": "NodeJS Kubernetes Client with automatic API discoveryEdit", | ||
@@ -29,2 +29,3 @@ "main": "src/index.js", | ||
| "check-node-version": "^2.0.1", | ||
| "deepmerge": "^1.3.2", | ||
| "flatmap": "0.0.3", | ||
@@ -31,0 +32,0 @@ "request": "^2.81.0", |
+9
-3
| # auto-kubernetes-client [](https://travis-ci.org/Collaborne/auto-kubernetes-client) [](https://greenkeeper.io/) | ||
| NodeJS Kubernetes Client with automatic API discovery | ||
| NodeJS Kubernetes Client with automatic API discovery. | ||
| See this [blog post](https://medium.com/collaborne-engineering/keep-pace-with-kubernetes-nodejs-client-b87a8b175b7b) for further information. | ||
| ## Installation | ||
@@ -50,7 +52,10 @@ | ||
| represents the "pod" resources for the "pod1" pod. | ||
| Single resources offer resource methods `get`, `update`, `patch` and `delete`. | ||
| Single resources offer resource methods `get`, `create`, `update`, `patch` and `delete`. | ||
| - Resource methods typically have the signature `method([qs])`, where `qs` is a hash for additional query parameters, | ||
| and return a promise for the parsed response entity. | ||
| - The `watch` resource method has the signature `watch([resourceVersion[, qs]])`, and returns an object stream for the observed changes. | ||
| Each object has a `type` field ('ADDED', 'DELETED', 'MODIFIED'), and the actual object that was modified. | ||
| Each object has a `type` field ('ADDED', 'DELETED', 'MODIFIED', 'ERROR'), and the actual object that was modified. | ||
| - By default the client interprets 'Status' responses from the server with a 'Failure' status as error responses, and translates | ||
| them into actual promise rejections. This can be disabled by using '.options({ rawResponse: true}).resourceMethod(...)' on the resource collection | ||
| or resource. | ||
@@ -62,2 +67,3 @@ ## Examples | ||
| |[examples/list-pods](./examples/list-pods)|List all pods in the cluster | ||
| |[examples/watch-pods](./examples/watch-pods)|Watch all pods in a specific namespace | ||
@@ -64,0 +70,0 @@ ## License |
+94
-21
@@ -7,2 +7,3 @@ 'use strict'; | ||
| const through2 = require('through2'); | ||
| const deepMerge = require('deepmerge'); | ||
@@ -69,2 +70,3 @@ /** | ||
| function k8sRequest(path, extraOptions = {}) { | ||
| const cooked = !extraOptions.rawResponse; | ||
| const options = Object.assign({}, configOptions, { json: true }, extraOptions); | ||
@@ -77,2 +79,6 @@ | ||
| } else { | ||
| if (cooked && data.kind === 'Status' && data.status === 'Failure') { | ||
| // Synthesize an error from the status | ||
| return reject(Object.assign(data, new Error(data.message))); | ||
| } | ||
| return resolve(data); | ||
@@ -94,3 +100,3 @@ } | ||
| // set on the thing. | ||
| function createResourceCollection(resource, pathPrefix = '') { | ||
| function createResourceCollection(resource, pathPrefix = '', extraOptions = {}) { | ||
| let resourcePath = groupPath + '/'; | ||
@@ -103,2 +109,6 @@ if (pathPrefix) { | ||
| return { | ||
| options: function(options) { | ||
| return createResourceCollection(resource, pathPrefix, Object.assign({}, extraOptions, options)); | ||
| }, | ||
| watch: function(resourceVersion = '', qs = {}) { | ||
@@ -146,3 +156,3 @@ let buffer = Buffer.alloc(0); | ||
| return streamK8sRequest(resourcePath, { method: 'GET', json: false, qs: Object.assign({}, qs, { watch: 'true', resourceVersion }) }) | ||
| return streamK8sRequest(resourcePath, Object.assign({}, extraOptions, { method: 'GET', json: false, qs: Object.assign({}, qs, { watch: 'true', resourceVersion }) })) | ||
| .pipe(parseJSONStream); | ||
@@ -152,11 +162,11 @@ }, | ||
| list: function(qs = {}) { | ||
| return k8sRequest(resourcePath, { qs, method: 'GET' }); | ||
| return k8sRequest(resourcePath, Object.assign({}, extraOptions, { qs, method: 'GET' })); | ||
| }, | ||
| create: function(object, qs = {}) { | ||
| return k8sRequest(resourcePath, { qs, method: 'POST', body: object }); | ||
| return k8sRequest(resourcePath, Object.assign({}, extraOptions, { qs, method: 'POST', body: object })); | ||
| }, | ||
| deletecollection: function(qs = {}) { | ||
| return k8sRequest(resourcePath, { qs, method: 'DELETE' }); | ||
| return k8sRequest(resourcePath, Object.assign({}, extraOptions, { qs, method: 'DELETE' })); | ||
| }, | ||
@@ -166,3 +176,3 @@ } | ||
| function createResource(resource, name, pathPrefix = '') { | ||
| function createResource(resource, name, pathPrefix = '', extraOptions = {}) { | ||
| let resourcePath = groupPath + '/'; | ||
@@ -176,17 +186,54 @@ if (pathPrefix) { | ||
| return { | ||
| options: function(options) { | ||
| return createResource(resource, name, pathPrefix, Object.assign({}, extraOptions, options)); | ||
| }, | ||
| get: function(qs = {}) { | ||
| return k8sRequest(resourcePath, { qs, method: 'GET' }); | ||
| return k8sRequest(resourcePath, Object.assign({}, extraOptions, { qs, method: 'GET' })); | ||
| }, | ||
| create: function(object, qs = {}) { | ||
| const createObject = deepMerge({ metadata: { name }}, object); | ||
| // Creating happens by posting to the list: | ||
| let listPath = groupPath + '/'; | ||
| if (pathPrefix) { | ||
| listPath += pathPrefix + '/'; | ||
| } | ||
| listPath += resource.name; | ||
| return k8sRequest(listPath, Object.assign({}, extraOptions, { qs, method: 'POST', body: createObject })); | ||
| }, | ||
| update: function(object, qs = {}) { | ||
| const updateObject = Object.assign({ metadata: { name }}, object); | ||
| return k8sRequest(resourcePath, { qs, method: 'PUT', body: updateObject }); | ||
| const updateObject = deepMerge({ metadata: { name }}, object); | ||
| return k8sRequest(resourcePath, Object.assign({}, extraOptions, { qs, method: 'PUT', body: updateObject })); | ||
| }, | ||
| patch: function(object, qs = {}) { | ||
| return k8sRequest(resourcePath, { qs, method: 'PATCH', body: object }); | ||
| /** | ||
| * Patch the resource. | ||
| * | ||
| * The 'contentType' parameter describes how to process the given object: | ||
| * 'application/strategic-merge-patch+json': (default) object is a partial representation | ||
| * 'application/merge-patch+json': RFC7386 "Merge Patch" | ||
| * 'application/json-patch+json': RFC6902 "JSON Patch" (object is an array of operations to apply) | ||
| * | ||
| * See https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#patch-operations for details. | ||
| * | ||
| * @param {any} object the patch to apply | ||
| * @param {String} [contentType] the content type | ||
| * @param {Object} [qs] additional query parameters | ||
| */ | ||
| patch: function(object, contentType = 'application/strategic-merge-patch+json', qs = {}) { | ||
| // Handle cases where qs is given but not contentType | ||
| if (typeof contentType === 'object') { | ||
| qs = contentType; | ||
| contentType = 'application/strategic-merge-patch+json'; | ||
| } | ||
| return k8sRequest(resourcePath, Object.assign({}, extraOptions, { qs, method: 'PATCH', headers: { 'content-type': contentType }, body: object })); | ||
| }, | ||
| delete: function(qs = {}) { | ||
| return k8sRequest(resourcePath, { qs, method: 'DELETE' }); | ||
| return k8sRequest(resourcePath, Object.assign({}, extraOptions, { qs, method: 'DELETE' })); | ||
| }, | ||
@@ -216,2 +263,11 @@ }; | ||
| }, | ||
| /** | ||
| * Get information about the resource with the given kind | ||
| * | ||
| * @param {String} kind | ||
| */ | ||
| // XXX: Should this instead exist on the collection or on the single resource via an 'info'/'explain' method? | ||
| resource: function(kind) { | ||
| return apiResources.resources.find(resource => kind === resource.kind); | ||
| }, | ||
@@ -253,6 +309,7 @@ // other properties here represent non-namespaced resources | ||
| return apis.reduce(function(result, api) { | ||
| result[api.name] = result[api.name] || {}; | ||
| result[api.name][api.version] = api; | ||
| // Build a compatible name for this API. Note that both api.name and api.version can end up empty here. | ||
| const apiNamePrefix = api.name ? `${api.name}/` : ''; | ||
| result[`${apiNamePrefix}${api.version}`] = api; | ||
| if (api.preferred) { | ||
| result[api.name][''] = api; | ||
| result[api.name] = api; | ||
| } | ||
@@ -262,15 +319,31 @@ return result; | ||
| }).then(function(apis) { | ||
| const coreApi = Object.assign({}, apis[''][coreVersion]); | ||
| const coreApi = Object.assign({}, apis[coreVersion]); | ||
| // Remove the 'name' field from the root object | ||
| delete coreApi.name; | ||
| return Object.assign({}, coreApi, { | ||
| /** | ||
| * Get the API group with the given name and version | ||
| * | ||
| * @param {String} groupName name of the group, may optionally contain a '/version' specification | ||
| * @param {String} [versionName] version of the group, if not given defaults to the "preferred" version as reported by the server | ||
| */ | ||
| group: function(groupName, versionName) { | ||
| const apiGroup = apis[groupName]; | ||
| if (!apiGroup) { | ||
| throw new Error(`No API group ${groupName} available`); | ||
| // Calculate a full API name from groupName and version name. | ||
| let apiName; | ||
| const slashIndex = groupName.indexOf('/'); | ||
| if (slashIndex === -1) { | ||
| apiName = versionName ? `${groupName}/${versionName}` : groupName; | ||
| } else if (versionName) { | ||
| // Version given in both the groupName and as parameters, use the one from the parameter. | ||
| const realGroupName = groupName.substring(0, slashIndex); | ||
| apiName = `${realGroupName}/${versionName}`; | ||
| } else { | ||
| apiName = groupName; | ||
| } | ||
| const api = apiGroup[versionName || '']; | ||
| const api = apis[apiName]; | ||
| if (!api) { | ||
| throw new Error(`No version ${versionName} for API group ${groupName} available`); | ||
| // FIXME: APIs might appear later on, we should do a query again here to check | ||
| throw new Error(`No API group ${apiName} available`); | ||
| } | ||
@@ -277,0 +350,0 @@ |
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
32222
14.67%368
22.26%85
7.59%5
25%+ Added
+ Added