From 6a3aa8b7efce536e4c8cb6277e693ce3db889740 Mon Sep 17 00:00:00 2001 From: Silas Boyd-Wickizer Date: Tue, 28 Aug 2018 13:19:30 -0700 Subject: [PATCH] chore(4.X): remove old API code (#317) BREAKING CHANGE: This removes the kubernetes-client 4.X API. --- README-PRE-5.md | 374 ------------------------ README.md | 2 - lib/api-extensions.js | 22 -- lib/api-group.js | 141 --------- lib/api.js | 61 ---- lib/apps.js | 25 -- lib/base.js | 204 ------------- lib/batch.js | 23 -- lib/container-base-object.js | 49 ---- lib/core.js | 44 --- lib/custom-resource-definitions.js | 27 -- lib/deployments.js | 27 -- lib/doc.js | 19 -- lib/extensions.js | 32 -- lib/index.js | 27 +- lib/namespaces.js | 90 ------ lib/pods.js | 43 --- lib/rbac.js | 23 -- lib/replicationcontrollers.js | 48 --- lib/third-party-resources.js | 27 -- package-lock.json | 22 +- package.json | 3 - scripts/typings.js | 2 +- test/api-extensions.test.js | 58 ---- test/api-group.test.js | 183 ------------ test/api.test.js | 23 -- test/apps.test.js | 113 ------- test/base.test.js | 206 ------------- test/batch.test.js | 60 ---- test/common.js | 45 +-- test/container-base-object.test.js | 31 -- test/custom-resource-definition.test.js | 126 -------- test/deployments.test.js | 130 -------- test/extensions.test.js | 99 ------- test/namespaces.test.js | 31 -- test/objects.test.js | 156 ---------- test/pods.test.js | 215 -------------- test/promise.test.js | 41 --- test/rbac.test.js | 50 ---- test/third-party-resources.test.js | 114 -------- 40 files changed, 14 insertions(+), 3002 deletions(-) delete mode 100644 README-PRE-5.md delete mode 100644 lib/api-extensions.js delete mode 100644 lib/api-group.js delete mode 100644 lib/api.js delete mode 100644 lib/apps.js delete mode 100644 lib/base.js delete mode 100644 lib/batch.js delete mode 100644 lib/container-base-object.js delete mode 100644 lib/core.js delete mode 100644 lib/custom-resource-definitions.js delete mode 100644 lib/deployments.js delete mode 100644 lib/doc.js delete mode 100644 lib/extensions.js delete mode 100644 lib/namespaces.js delete mode 100644 lib/pods.js delete mode 100644 lib/rbac.js delete mode 100644 lib/replicationcontrollers.js delete mode 100644 lib/third-party-resources.js delete mode 100644 test/api-extensions.test.js delete mode 100644 test/api-group.test.js delete mode 100644 test/api.test.js delete mode 100644 test/apps.test.js delete mode 100644 test/base.test.js delete mode 100644 test/batch.test.js delete mode 100644 test/container-base-object.test.js delete mode 100644 test/custom-resource-definition.test.js delete mode 100644 test/deployments.test.js delete mode 100644 test/extensions.test.js delete mode 100644 test/namespaces.test.js delete mode 100644 test/objects.test.js delete mode 100644 test/pods.test.js delete mode 100644 test/promise.test.js delete mode 100644 test/rbac.test.js delete mode 100644 test/third-party-resources.test.js diff --git a/README-PRE-5.md b/README-PRE-5.md deleted file mode 100644 index 6d043e3a..00000000 --- a/README-PRE-5.md +++ /dev/null @@ -1,374 +0,0 @@ -# kubernetes-client - -[![Build Status][build]](https://travis-ci.org/godaddy/kubernetes-client) [![Greenkeeper badge][greenkeeper]](https://greenkeeper.io/) - -[greenkeeper]: https://badges.greenkeeper.io/godaddy/kubernetes-client.svg -[build]: https://travis-ci.org/godaddy/kubernetes-client.svg?branch=master - -Simplified [Kubernetes API](http://kubernetes.io/) client for Node.js. - -This kubernetes-client API is deprecated. See [README.md](README.md) -for the new API that has better support for all Kubernetes API -endpoints. - -## Installation - -Install via npm: - -```console -$ npm i kubernetes-client --save -``` - -## Examples - -kubernetes-client provides access to all the Kubernetes objects and -includes some niceties for writing simpler code. - -### Basics - -kubernetes-client maps the URI paths in the Kubernetes API to -sequences of objects chained together via properties and ending in a -function. For example, to GET the `ReplicationController` named -'http-rc' in the `Namespace` 'my-project': - -```js -const Api = require('kubernetes-client'); -const core = new Api.Core({ - url: 'http://my-k8s-api-server.com', - version: 'v1' // Defaults to 'v1' -}); - -function print(err, result) { - console.log(JSON.stringify(err || result, null, 2)); -} - -core.namespaces('my-project').replicationcontrollers('http-rc').get(print); -``` - -kubernetes-client supports the Extensions API group. For example, GET -the `Deployment` named `http-deployment`: - -```js -const ext = new Api.Extensions({ - url: 'http://my-k8s-api-server.com', - version: 'v1beta1' // Defaults to 'v1beta1' -}); - -ext.namespaces('my-project').deployments('http-deployment').get(print); -``` - -kubernetes-client provides a helper to get in-cluster config and accessing the API from a Pod: - -```js -const Api = require('kubernetes-client'); -const core = new Api.Core(Api.config.getInCluster()); -``` - -and a helper to get the current-context config from `~/.kube/config`: - -```js -const Api = require('kubernetes-client'); -const core = new Api.Core(Api.config.fromKubeconfig()); -``` - -### **Experimental** support for promises and async/await - -kubernetes-client has **experimental** support for promises. If you -omit callbacks, an HTTP method function (*e.g.*, `.get`), it will -return a promise. - -```js -core.namespaces('my-project').replicationcontrollers('http-rc').get() - .then(result => console.log(result)); -``` - -or with `async/await`: - -```js -console.log(await core.namespaces('my-project').replicationcontrollers('http-rc').get()); -``` - -### Creating and updating - -kubernetes-client objects expose `.post`, `.patch`, and `.put` -methods. Create the ReplicationController from the example above: - -```js -const manifestObject = require('./rc.json'); -core.namespaces('my-project').replicationcontrollers.post({ body: manifestObject }, print); -``` -or update the number of replicas: - -```js -const patch = { spec: { replicas: 10 } }; -core.namespaces('my-project').replicationcontrollers('http-rc').patch({ - body: patch -}, print); -``` - -### Using the correct API group and version - -kubernetes-client client includes functionality to help determine the -correct Kubernetes API group and version to use based on manifests: - -```js -const Api = require('kubernetes-client').Api; -const api = new Api({ url: 'http://my-k8s-api-server.com' }); - -const manifest0 = { - kind: 'Deployment', - apiVersion: 'extensions/v1beta1' - ... -}; -const manifest1 = { - kind: 'ReplicationController', - apiVersion: 'v1' - ... -}; - -api.group(manifest0).ns('my-project').kind(manifest0).post({ body: manifest0 }, print); -api.group(manifest1).ns('my-project').kind(manifest1).post({ body: manifest1 }, print); -``` - -### Object name aliases - -kubernetes-client supports the same aliases as -[`kubectl`](http://kubernetes.io/docs/user-guide/kubectl-overview/) -(*e.g.*, `ns` for `namespaces`) and the singular versions of the -resource name (*e.g.*, `namespace` for `namespaces`). We can shorten -the example above: - -```js -core.ns('my-project').rc('http-rc').get(print); -``` - -### Switching namespaces - -You can call the `namespace` object to specify the namespace: - -```js -core.ns('other-project').rc('http-rc').get(print); -``` - -### Query parameters - -You can optionally specify query string object `qs` to GET -endpoints. kubernetes-client passes `qs` directly to -[`request`](https://www.npmjs.com/package/request#requestoptions-callback). -For example to filter based on [label -selector](http://kubernetes.io/docs/user-guide/labels/): - -```js -core.ns('my-project').rc.get({ qs: { labelSelector: 'service=http,component=api' } }, print); -``` - -### Label selector filtering - -kubernetes-client has a shortcut, `matchLabels`, for filtering on label -selector equality: - -```js -core.ns('my-project').rc.matchLabels({ service: 'http' }).get(print); -``` - -and a more general `match` method based on Kubernetes Match Expressions: - -```js -core.ns('my-project').rc.match([{ - key: 'service', - operator: 'In', - values: ['http'] -}, { - key: 'deploy', - operator: 'NotIn', - values: ['production', 'staging'] -}]).get(print); -``` - -**Note:** The Kubernetes API supports label selector filtering [for GET -methods -only](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/). The -Kubernetes API does not support label selector filtering for POST, -PUT, PATCH, or DELETE. - -### ThirdPartyResources - -You can extend the Kubernetes API using a -[ThirdPartyResource](https://kubernetes.io/docs/user-guide/thirdpartyresources/) -and kubernetes-client: - -```js -const newResource = { - apiVersion: 'extensions/v1beta1', - kind: 'ThirdPartyResource', - metadata: { - name: 'new-resource.kubernetes-client.io' - }, - description: 'Example resource', - versions: [{ - name: 'v1' - }] -}; - -ext.thirdpartyresources.post({ body: newResource }, print); -``` - -and then extend an `ThirdPartyResource` API client with your new resources: - -```js -const thirdPartyResources = new Api.ThirdPartyResources({ - url: 'http://my-k8s-api-server.com', - group: 'kubernetes-client.io', - resources: ['customresources'] // Notice pluralization! -}); - -// Access `customresources` as if they were a regular Kubernetes object -thirdPartyResources.ns('my-project').customresources.get(print); -thirdPartyResources.addResource('newresources'); // Notice pluralization! -// Now access `newresources` -thirdPartyResources.ns('my-project').newresources.get(print); -``` - -### Watching and streaming - -You can call `.getStream` to stream results. This is useful for watching: - -```js -const JSONStream = require('json-stream'); -const jsonStream = new JSONStream(); - -const stream = core.ns('my-project').po.getStream({ qs: { watch: true } }); -stream.pipe(jsonStream); -jsonStream.on('data', object => { - console.log('Pod:', JSON.stringify(object, null, 2)); -}); -``` - -You can access logs in a similar fashion: -```js -const stream = core.ns('my-project').po('http-123').log.getStream({ qs: { follow: true } }); -stream.on('data', chunk => { - process.stdout.write(chunk.toString()); -}); -``` - -**Note:** the kube-apiserver eventually close watch connections -according to the -[`--min-request-timeout`](http://kubernetes.io/docs/admin/kube-apiserver/ -command line argument. kubernetes-client does not attempt to reconnect -when the kube-apiserver closes a connection. - -### Authentication - -kubernetes-client supports Kubernetes [apiserver -authentication](http://kubernetes.io/docs/admin/authentication/). - -Basic authentication (with optional certificate authority): - -```js -const core = new Api.Core({ - url: 'https://my-k8s-api-server.com', - ca: fs.readFileSync('cluster-ca.pem'), - auth: { - user: 'user', - pass: 'pass' - } -}); -``` - -or without a certificate authority: - -```js -const core = new Api.Core({ - url: 'https://my-k8s-api-server.com', - insecureSkipTlsVerify: true, - auth: { - user: 'user', - pass: 'pass' - } -}); -``` - -token authentication: - -```js -const core = new Api.Core({ - url: 'https://my-k8s-api-server.com', - auth: { - bearer: 'token' - } -}); -``` - -and client certificate authentication: - -```js -const core = new Api.Core({ - url: 'https://my-k8s-api-server.com', - ca: fs.readFileSync('cluster-ca.pem'), - cert: fs.readFileSync('my-user-cert.pem'), - key: fs.readFileSync('my-user-key.pem') -}); -``` - -### Passing options to `request` - -kubernetes-client uses -[`request`](https://github.com/request/request). You can specify -[`request` -options](https://github.com/request/request#requestoptions-callback) -for kubernetes-client to pass to `request`: - -```js -const core = new Api.Core({ - url: 'https://my-k8s-api-server.com', - request: { - timeout: 3000 - } -}); -``` - -### **Experimental** support for Swagger-based API clients - -**This API is going to change.** - -kubernetes-client has support for generating API clients from -Kubernetes Swagger documentation. - -```js -const kubernetes = require('kubernetes-client'); -const spec = require('./swagger-1.8.json'); -const http = new kubernetes.Request(kubernetes.config.fromKubeconfig()); -const client = new kubernetes.SwaggerClient({ spec, http }); - -console.log(await client.api.v1.namespaces.get()); -``` - -## Testing - -kubernetes-client includes unit tests and integration tests. -[Minikube](https://github.com/kubernetes/minikube) is a tool that -makes it easy to run integration tests locally. - -Run the unit tests: - -```console -$ npm test -``` - -The integration tests use a running Kubernetes server. You specify the -Kubernetes server context via the `CONTEXT` environment variable. For -example, run the integration tests with the `minikube` context: - -```console -$ CONTEXT=minikube npm run test-integration -``` - -## More Documentation - -* [Kubernetes Reference Documentation](http://kubernetes.io/docs/reference/) -* [Kubernetes Swagger Spec](http://kubernetes.io/kubernetes/third_party/swagger-ui/) - -## License - -[MIT](LICENSE) diff --git a/README.md b/README.md index 624658d1..eea126b3 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,6 @@ Simplified [Kubernetes API](http://kubernetes.io/) client for Node.js. -*For the v4.X documentation, go [HERE](README-PRE-5.md).* - ## Installation Install via npm: diff --git a/lib/api-extensions.js b/lib/api-extensions.js deleted file mode 100644 index 79ee5fbc..00000000 --- a/lib/api-extensions.js +++ /dev/null @@ -1,22 +0,0 @@ -'use strict'; - -const ApiGroup = require('./api-group'); -const CustomResourceDefinitons = require('./custom-resource-definitions'); - -class ApiExtensions extends ApiGroup { - constructor(options) { - options = Object.assign({}, options, { - path: 'apis/apiextensions.k8s.io', - version: options.version || 'v1beta1', - groupResources: [ - 'customresourcedefinitions' - ], - namespaceResources: [ - { name: 'customresourcedefinitions', Constructor: CustomResourceDefinitons } - ] - }); - super(options); - } -} - -module.exports = ApiExtensions; diff --git a/lib/api-group.js b/lib/api-group.js deleted file mode 100644 index a7546287..00000000 --- a/lib/api-group.js +++ /dev/null @@ -1,141 +0,0 @@ -'use strict'; - -const merge = require('lodash.merge'); -const aliasResources = require('./common').aliasResources; -const BaseObject = require('./base'); -const Namespaces = require('./namespaces'); -const Request = require('./request'); - -class ApiGroup { - /** - * API object - * @param {object} options - Options object - * @param {string} options.api - Kubernetes API URL - * @param {string} options.version - Kubernetes API version - * @param {string} options.namespace - Default namespace - * @param {string} options.ca - Certificate authority - * @param {string} options.cert - Client certificate - * @param {string} options.key - Client key - * @param {boolean} options.insecureSkipTlsVerify - Skip the validity check - * on the server's certificate. - */ - constructor(options) { - this.url = options.url; - this.version = options.version; - this.path = `/${ options.path }/${ this.version }`; - - const requestOptions = {}; - requestOptions.request = options.request || {}; - requestOptions.url = options.url; - requestOptions.ca = options.ca; - requestOptions.cert = options.cert; - requestOptions.key = options.key; - if ('insecureSkipTlsVerify' in options) { - requestOptions.insecureSkipTlsVerify = options.insecureSkipTlsVerify; - } - if (options.auth) { - requestOptions.auth = options.auth; - } - this.http = new Request(requestOptions); - - this.resourceConfig = { - promises: options.promises - }; - - // - // Create the default namespace so we have it directly on the API - // - this.namespaces = new Namespaces({ - api: this, - parentPath: this.path, - namespace: options.namespace, - resources: options.namespaceResources - }); - - // - // Create "group" resources that live at the root (e.g., /api/v1/nodes) - // - options.groupResources.forEach(type => this.addResource(type)); - - aliasResources(this); - } - - /** - * Add a resource (e.g., nodes) to the ApiGroup group - * @param {string|object} options - resource name or options object - * @param {string} options.name - resource name - * @param {fn} options.Constructor - constructor for new resource - */ - addResource(options) { - if (typeof options === 'string') { - options = { name: options, Constructor: BaseObject }; - } else if (!options.name || !options.Constructor) { - throw new RangeError( - 'ApiGroup.addResource: options requires .name and .Constructor'); - } - - if (this[options.name]) { - throw new RangeError( - `ApiGroup.addResource: .${ options.name } already exists`); - } - this[options.name] = new options.Constructor({ - api: this, - name: options.name, - parentPath: this.path - }); - } - - /** - * Invoke a GET request against the Kubernetes API server - * @param {ApiRequestOptions} options - Options object. - * @param {callback} cb - The callback that handles the response - * @returns {Stream} If cb is falsy, return a stream - */ - get(options, cb) { - return this.http.request('GET', options, cb); - } - - /** - * Invoke a DELETE request against the Kubernetes API server - * @param {ApiRequestOptions} options - Options object. - * @param {callback} cb - The callback that handles the response - */ - delete(options, cb) { - this.http.request('DELETE', options, cb); - } - - /** - * Invoke a PATCH request against the Kubernetes API server - * @param {ApiRequestOptions} options - Options object. - * @param {callback} cb - The callback that handles the response - */ - patch(options, cb) { - this.http.request('PATCH', merge({ - headers: { 'content-type': 'application/strategic-merge-patch+json' } - }, options), cb); - } - - /** - * Invoke a POST request against the Kubernetes API server - * @param {ApiRequestOptions} options - Options object. - * @param {callback} cb - The callback that handles the response - */ - post(options, cb) { - this.http.request('POST', merge({ - headers: { 'content-type': 'application/json' } - }, options), cb); - } - - /** - * Invoke a PUT request against the Kubernetes API server - * @param {ApiRequestOptions} options - Options object. - * @param {callback} cb - The callback that handles the response - */ - put(options, cb) { - this.http.request('PUT', merge({ - headers: { 'content-type': 'application/json' } - }, options), cb); - } -} - -module.exports = ApiGroup; diff --git a/lib/api.js b/lib/api.js deleted file mode 100644 index d203d912..00000000 --- a/lib/api.js +++ /dev/null @@ -1,61 +0,0 @@ -'use strict'; - -const Core = require('./core'); -const Extensions = require('./extensions'); -const Apps = require('./apps'); -const Batch = require('./batch'); -const Rbac = require('./rbac'); -const ApiExtensions = require('./api-extensions'); - -const groups = { - 'extensions': Extensions, - 'apps': Apps, - 'batch': Batch, - 'rbac.authorization.k8s.io': Rbac, - 'apiextensions.k8s.io': ApiExtensions -}; - -class Api { - /** - * Create an API client wrapper object. - * @param {object} options - Options to pass to client constructors - * @param {object} options.core - Optional default Core client - * @param {object} options.extensions - Optional default Extensions client - * @param {object} options.apps - Optional default Apps client - * @param {object} options.batch - Optional default Batch client - * @param {object} options.rbac - Optional default RBAC client - * @param {object} options.apiExtensions - Optional default ApiExtensions client - */ - constructor(options) { - this.options = options; - this.core = options.core || new Core(options); - this.extensions = options.extensions || new Extensions(options); - this.apps = options.apps || new Apps(options); - this.batch = options.batch || new Batch(options); - this.rbac = options.rbac || new Rbac(options); - this.apiExtensions = options.apiExtensions || new ApiExtensions(options); - } - - /** - * Return an API client for the given API group and version. - * @param {object|string} v - Kubernetes manifest object or a string - * @returns {ApiGroup} API client object. - */ - group(v) { - const groupVersion = v.apiVersion || v; - const pieces = groupVersion.split('/'); - let Group; - let version; - if (pieces.length > 1) { - Group = groups[pieces[0].toLowerCase()]; - version = pieces[1]; - } else { - Group = Core; - version = pieces[0]; - } - const options = Object.assign({}, this.options, { version }); - return new Group(options); - } -} - -module.exports = Api; diff --git a/lib/apps.js b/lib/apps.js deleted file mode 100644 index 697c0e7c..00000000 --- a/lib/apps.js +++ /dev/null @@ -1,25 +0,0 @@ -'use strict'; - -const ApiGroup = require('./api-group'); -const Deployments = require('./deployments'); - -class Apps extends ApiGroup { - constructor(options) { - const resources = [ - // Deprecated name of statefulsets in kubernetes 1.4 - 'petsets', - 'statefulsets' - ]; - options = Object.assign({}, options, { - path: 'apis/apps', - version: options.version || 'v1beta1', - groupResources: resources.concat(['deployments']), - namespaceResources: resources.concat([ - { name: 'deployments', Constructor: Deployments } - ]) - }); - super(options); - } -} - -module.exports = Apps; diff --git a/lib/base.js b/lib/base.js deleted file mode 100644 index e27ffe6d..00000000 --- a/lib/base.js +++ /dev/null @@ -1,204 +0,0 @@ -'use strict'; - -const promy = require('promy'); - -const matchExpression = require('./match-expression'); - -function cb200(cb) { - return function (err, result) { - if (err) return cb(err); - if (result.statusCode < 200 || result.statusCode > 299) { - const error = new Error(result.body.message || result.body); - error.code = result.body.code || result.statusCode; - return cb(error); - } - cb(null, result.body); - }; -} - -class CallableObject { - /** - * Create an object that invokes a function when called. - * @param {function} fn - The function to invoke. - */ - constructor(fn) { - function wrap() { - /* eslint-disable no-invalid-this */ - return fn.apply(this, arguments); - /* eslint-enable no-invalid-this */ - } - - if (fn) { - return Object.setPrototypeOf(wrap, Object.getPrototypeOf(this)); - } - } -} - -class BaseObject extends CallableObject { - /** - * Create generic Kubernetes API object. The object is callable (e.g., pod('foo')), - * which by default returns a new object of the same type with the parent path - * extended by the argument too the function - * (e.g., '/api/v1/namespace/default/pods/foo'). Users customize the callable - * behavior by passing an optional function to this constructor. - * - * @param {object} options - Options object - * @param {string} options.api - Kubernetes API URL - * @param {string} options.name - kubernetes resource name - * @param {string} options.parentPath - Kubernetes resource parent path - * @param {function} options.fn - Optional function to invoke when object is - * called - * @param {string} options.qs - Optional query string object - */ - constructor(options) { - const api = options.api; - const path = `${ options.parentPath }/${ options.name }`; - - let fn = options.fn; - if (!fn) { - fn = name => { - return new this.constructor({ - api: api, - name: name, - parentPath: path - }); - }; - } - - super(fn); - this.api = api; - this._name = options.name; - this.parentPath = options.parentPath; - this.fn = options.fn; - this.qs = options.qs || {}; - - this.path = path; - - const apiFunctions = ['delete', 'get', 'patch', 'post', 'put']; - apiFunctions.forEach(func => { - this[func] = promy(this[`_${ func }`].bind(this)); - }); - } - - /** - * Delete a Kubernetes resource - * @param {RequestOptions|string} options - DELETE options, or resource name - * @param {callback} cb - The callback that handles the response - */ - _delete(options, cb) { - if (typeof options === 'function') { - cb = options; - options = {}; - } else if (typeof options === 'string') { - options = { name: options }; - } else if (typeof options === 'undefined') { - options = {}; - } - this.api.delete({ path: this._path(options), qs: options.qs, body: options.body }, - cb200(cb)); - } - - _path(options) { - return [this.path].concat(options && options.name ? [options.name] : []); - } - - /** - * Get a Kubernetes resource - * @param {RequestOptions|string} options - GET options, or resource name - * @param {callback} cb - The callback that handles the response - * @returns {Stream} If cb is falsy, return a stream - */ - _get(options, cb) { - if (typeof options === 'function') { - cb = options; - options = {}; - } else if (typeof options === 'string') { - options = { name: options }; - } else if (typeof options === 'undefined') { - options = {}; - } - const qs = Object.assign({}, this.qs, options.qs || {}); - - return this.api.get({ - path: this._path(options), - qs: qs - }, cb && cb200(cb)); - } - - /** - * Get a Kubernetes resource - * @param {RequestOptions|string} options - GET options, or resource name - * @returns {Stream} Result stream - */ - getStream(options) { return this._get(options); } - - /** - * Patch a Kubernetes resource - * @param {RequestOptions} options - PATCH options - * @param {callback} cb - The callback that handle the response - */ - _patch(options, cb) { - const patchOptions = { - path: this._path(options), - body: options.body - }; - if ('headers' in options) patchOptions.headers = options.headers; - this.api.patch(patchOptions, cb200(cb)); - } - - /** - * Create a Kubernetes resource - * @param {RequestOptions} options - POST options - * @param {callback} cb - The callback that handle the response - */ - _post(options, cb) { - this.api.post({ path: this._path(options), body: options.body }, - cb200(cb)); - } - - /** - * Replace a Kubernetes resource - * @param {RequestOptions} options - PUT options - * @param {callback} cb - The callback that handle the response - */ - _put(options, cb) { - this.api.put({ path: this._path(options), body: options.body }, cb200(cb)); - } - - // - // Higher-level porcelain methods - // - - /** - * Return resources matching an array Match Expressions - * @param {MatchExpression[]} expressions - Array of expressions to match - * @returns {object} API object - */ - match(expressions) { - const qs = Object.assign({}, this.qs, { - labelSelector: matchExpression.stringify(expressions) - }); - return new this.constructor({ - api: this.api, - name: this._name, - parentPath: this.parentPath, - fn: this.fn, - qs - }); - } - - /** - * Return resources matching labels - * @param {object} labels - Object of label keys and values - * @returns {object} API object - */ - matchLabels(labels) { - return this.match(Object.keys(labels).map(key => ({ - key: key, - operator: 'In', - values: [labels[key]] - }))); - } -} - -module.exports = BaseObject; diff --git a/lib/batch.js b/lib/batch.js deleted file mode 100644 index 5bdf894e..00000000 --- a/lib/batch.js +++ /dev/null @@ -1,23 +0,0 @@ -'use strict'; - -const ApiGroup = require('./api-group'); - -class Batch extends ApiGroup { - constructor(options) { - const resources = [ - 'cronjobs', - 'jobs', - // Deprecated name for cronjobs in kubernetes 1.4 - 'scheduledjobs' - ]; - options = Object.assign({}, options, { - path: 'apis/batch', - version: options.version || 'v1', - groupResources: resources, - namespaceResources: resources - }); - super(options); - } -} - -module.exports = Batch; diff --git a/lib/container-base-object.js b/lib/container-base-object.js deleted file mode 100644 index 58fd0ec2..00000000 --- a/lib/container-base-object.js +++ /dev/null @@ -1,49 +0,0 @@ -'use strict'; - -const BaseObject = require('./base'); - -class ContainerBaseObject extends BaseObject { - /** - * Create generic Kubernetes API object that might contain other resources. - * For example, a named Pod contains .log resources (core.ns.pods('foo').log). - * - * @param {object} options - Options object - * @param {string} options.resources - Array of resources to add - */ - constructor(options) { - super(options); - if (options.resources) { - options.resources.forEach(resource => this.addResource(resource)); - } - } - - /** - * Add a resource to the container object. - * @param {string|object} options - resource name or options object - * @param {string} options.name - resource name - * @param {fn} options.Constructor - constructor for new resource - * @returns {object} returns this to facilitate chaining - */ - addResource(options) { - if (typeof options === 'string') { - options = { name: options, Constructor: BaseObject }; - } else if (!options.name || !options.Constructor) { - throw new RangeError( - 'NamedBaseObject.addResource: options requires .name and .Constructor'); - } - - if (this[options.name]) { - throw new RangeError( - `NamedBaseObject.addResource: .${ options.name } already exists`); - } - this[options.name] = new options.Constructor({ - api: this.api, - name: options.name, - parentPath: this.path - }); - - return this; - } -} - -module.exports = ContainerBaseObject; diff --git a/lib/core.js b/lib/core.js deleted file mode 100644 index ba7353ce..00000000 --- a/lib/core.js +++ /dev/null @@ -1,44 +0,0 @@ -'use strict'; - -const ApiGroup = require('./api-group'); -const ReplicationControllers = require('./replicationcontrollers'); -const Pods = require('./pods'); - -class Core extends ApiGroup { - constructor(options) { - const commonResources = [ - 'configmaps', - 'endpoints', - 'events', - 'limitranges', - 'persistentvolumes', - 'persistentvolumeclaims', - 'podtemplates', - 'resourcequotas', - 'secrets', - 'serviceaccounts', - 'services' - ]; - options = Object.assign({}, options, { - path: 'api', - version: options.version || 'v1', - groupResources: commonResources.concat([ - 'componentstatuses', - 'nodes', - 'pods', - 'replicationcontrollers' - ]), - // - // The custom Pods and ReplicationControllers objects implement - // functionality available only in namespaces. - // - namespaceResources: commonResources.concat([ - { name: 'replicationcontrollers', Constructor: ReplicationControllers }, - { name: 'pods', Constructor: Pods } - ]) - }); - super(options); - } -} - -module.exports = Core; diff --git a/lib/custom-resource-definitions.js b/lib/custom-resource-definitions.js deleted file mode 100644 index f48d793d..00000000 --- a/lib/custom-resource-definitions.js +++ /dev/null @@ -1,27 +0,0 @@ -'use strict'; - -const ApiGroup = require('./api-group'); - -class CustomResourceDefinition extends ApiGroup { - constructor(options) { - options = Object.assign({}, options, { - path: `apis/${ options.group }`, - version: options.version || 'v1', - groupResources: [], - namespaceResources: [] - }); - super(options); - - if (options.resources) { - options.resources.forEach(resource => this.addResource(resource)); - } - } - - addResource(resourceName) { - this.namespace.addNamedResource(resourceName); - super.addResource(resourceName); - return this; - } -} - -module.exports = CustomResourceDefinition; diff --git a/lib/deployments.js b/lib/deployments.js deleted file mode 100644 index 1b4ab31e..00000000 --- a/lib/deployments.js +++ /dev/null @@ -1,27 +0,0 @@ -'use strict'; - -const BaseObject = require('./base'); -const ContainerBaseObject = require('./container-base-object'); - -class NamedDeployments extends ContainerBaseObject { - constructor(options) { - super(Object.assign({ - resources: ['status', 'scale', 'rollback'] - }, options)); - } -} - -class Deployments extends BaseObject { - constructor(options) { - super(Object.assign({}, options, { - fn: name => new NamedDeployments({ - api: options.api, - name: name, - parentPath: this.path - }), - name: options.name || 'deployments' - })); - } -} - -module.exports = Deployments; diff --git a/lib/doc.js b/lib/doc.js deleted file mode 100644 index 9aa3c9b1..00000000 --- a/lib/doc.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Standard callback - * @callback callback - * @param {object} err - Node.js Error - * @param {object} result - Operations results - */ - -/** - * @typedef {object} RequestOptions - * @property {string} name - Resource name - * @property {object} qs - {@link https://www.npmjs.com/package/request#requestoptions-callback| - * request query parameter} - * @property {object} body - Request body - */ - -/** - * {@link https://www.npmjs.com/package/request#streaming|A stream from request} - * @typedef {object} Stream - */ diff --git a/lib/extensions.js b/lib/extensions.js deleted file mode 100644 index cb71ddea..00000000 --- a/lib/extensions.js +++ /dev/null @@ -1,32 +0,0 @@ -'use strict'; - -const ApiGroup = require('./api-group'); -const Deployments = require('./deployments'); - -class Extensions extends ApiGroup { - constructor(options) { - const commonResources = [ - 'daemonsets', - 'horizontalpodautoscalers', - 'ingresses', - 'jobs', - 'replicasets', - 'thirdpartyresources' - ]; - options = Object.assign({}, options, { - path: 'apis/extensions', - version: options.version || 'v1beta1', - groupResources: commonResources.concat(['deployments']), - // - // The custom Deployments objects implement functionality available only - // in namespaces. - // - namespaceResources: commonResources.concat([ - { name: 'deployments', Constructor: Deployments } - ]) - }); - super(options); - } -} - -module.exports = Extensions; diff --git a/lib/index.js b/lib/index.js index e6605a33..670718e5 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,24 +1,5 @@ -// -// Deprecated interface -// -const core = require('./core'); -module.exports = core; -module.exports.Api = require('./api'); -module.exports.ApiExtensions = require('./api-extensions'); -module.exports.Core = core; -module.exports.Extensions = require('./extensions'); -module.exports.ThirdPartyResources = require('./third-party-resources'); -module.exports.CustomResourceDefinitions = require('./custom-resource-definitions'); -module.exports.Apps = require('./apps'); -module.exports.Batch = require('./batch'); -module.exports.Rbac = require('./rbac'); -module.exports.testUtils = { - aliasResources: require('./common').aliasResources +module.exports = { + Client: require('./swagger-client').Client, + Client1_10: require('./swagger-client').Client1_10, + config: require('./config') }; - -// -// Current interface -// -module.exports.Client = require('./swagger-client').Client; -module.exports.Client1_10 = require('./swagger-client').Client1_10; -module.exports.config = require('./config'); diff --git a/lib/namespaces.js b/lib/namespaces.js deleted file mode 100644 index f31e3cd1..00000000 --- a/lib/namespaces.js +++ /dev/null @@ -1,90 +0,0 @@ -'use strict'; - -const async = require('async'); - -const aliasResources = require('./common').aliasResources; -const BaseObject = require('./base'); -const ContainerBaseObject = require('./container-base-object'); - -class NamedNamespaces extends ContainerBaseObject { - constructor(options) { - super(options); - aliasResources(this); - } - - /** - * Return the API object for the given Kubernetes kind - * @param {object|string} k - Kubernetes manifest object or a string - * @returns {BaseObject} Kubernetes API object. - */ - kind(k) { - return this[(k.kind || k).toLowerCase()]; - } -} - -class Namespaces extends BaseObject { - /** - * Create a Namespaces Kubernetes API object - * @extends BaseObject - * @param {object} options - Options object - * @param {Api} options.api - API object - * @param {string} options.parentPath - Optional path of parent resource - * @param {string} options.path - Optional path of this resource - */ - constructor(options) { - super(Object.assign({}, options, { - fn: name => new NamedNamespaces({ - api: options.api, - name: name, - parentPath: this.path, - resources: this.namedResources - }), - name: options.name || 'namespaces' - })); - this.namedResources = options.resources.slice(); - } - - addNamedResource(options) { - this.namedResources.push(options); - return this; - } - - _wait(options, cb) { - const interval = 1000; - const times = Math.ceil(options.timeout / interval); - async.retry({ times: times, interval: interval }, next => { - this.get(options.name, (err, result) => { - if (err) { - if (err.code === 404) return next(null); - return next(err); - } - if (result.metadata.uid !== options.uid) return next(null); - next(new Error('Waiting for namespace removal')); - }); - }, cb); - } - - /** - * Delete a Kubernetes resource - * @param {RequestOptions|string} options - DELETE options, or resource name - * @param {string} options.timeout - Optional timeout to wait for namespace - * deletion to complete - * @param {callback} cb - The callback that handles the response - */ - delete(options, cb) { - if (typeof options === 'string') options = { name: options }; - - super.delete(options, (err, result) => { - if (err) return cb(err); - if (!options.timeout) return cb(null, result); - - this._wait({ - timeout: options.timeout, - name: options.name, - uid: result.metadata.uid - }, waitErr => cb(waitErr, result)); - }); - } -} - -module.exports = Namespaces; diff --git a/lib/pods.js b/lib/pods.js deleted file mode 100644 index 45200816..00000000 --- a/lib/pods.js +++ /dev/null @@ -1,43 +0,0 @@ -'use strict'; - -const BaseObject = require('./base'); -const ContainerBaseObject = require('./container-base-object'); - -class NamedPods extends ContainerBaseObject { - /** - * Create a named Pod Kubernetes object with a log. - * @extends BaseObject - * @param {object} options - Options object - * @param {Api} options.api - API object - * @param {string} options.parentPath - Optional path of parent resource - * @param {string} options.path - Optional path of this resource - */ - constructor(options) { - super(Object.assign({ - resources: ['log'] - }, options)); - } -} - -class Pods extends BaseObject { - /** - * Create a Pods Kubernetes API object - * @extends BaseObject - * @param {object} options - Options object - * @param {Api} options.api - API object - * @param {string} options.parentPath - Optional path of parent resource - * @param {string} options.path - Optional path of this resource - */ - constructor(options) { - super(Object.assign({}, options, { - fn: name => new NamedPods({ - api: options.api, - name: name, - parentPath: this.path - }), - name: options.name || 'pods' - })); - } -} - -module.exports = Pods; diff --git a/lib/rbac.js b/lib/rbac.js deleted file mode 100644 index 3171e05c..00000000 --- a/lib/rbac.js +++ /dev/null @@ -1,23 +0,0 @@ -'use strict'; - -const ApiGroup = require('./api-group'); - -class Rbac extends ApiGroup { - constructor(options) { - const resources = [ - 'clusterroles', - 'clusterrolebindings', - 'roles', - 'rolebindings' - ]; - options = Object.assign({}, options, { - path: 'apis/rbac.authorization.k8s.io', - version: options.version || 'v1alpha1', - groupResources: resources, - namespaceResources: resources - }); - super(options); - } -} - -module.exports = Rbac; diff --git a/lib/replicationcontrollers.js b/lib/replicationcontrollers.js deleted file mode 100644 index ba9fd4a2..00000000 --- a/lib/replicationcontrollers.js +++ /dev/null @@ -1,48 +0,0 @@ -'use strict'; - -const BaseObject = require('./base'); - -class ReplicationControllers extends BaseObject { - /** - * Create a ReplicationControllers Kubernetes API object - * @extends BaseObject - * @property {ReplicationControllerPods} pods - Object representing the Pods - * selected by this ReplicationController. - * @param {object} options - Options object - * @param {Api} options.api - API object - * @param {string} options.parentPath - Optional path of parent resource - * @param {string} options.path - Optional path of this resource - */ - constructor(options) { - super(Object.assign({}, options, { - name: options.name || 'replicationcontrollers' })); - } - - /** - * Delete a Kubernetes ReplicationController and by default all its Pods - * @param {RequestOptions|string} options - DELETE options, or resource name - * @param {boolean} options.preservePods - If true, do not delete the Pods - * @param {callback} cb - The callback that handles the response - */ - _delete(options, cb) { - if (typeof options === 'string') { - options = { name: options }; - } - - if (options.preservePods) { - this.api.delete({ path: [this.path, options.name] }, cb); - return; - } - - const patch = { spec: { replicas: 0 }}; - this.api.patch({ - path: [this.path, options.name], - body: patch - }, err => { - if (err) return cb(err); - this.api.delete({ path: [this.path, options.name] }, cb); - }); - } -} - -module.exports = ReplicationControllers; diff --git a/lib/third-party-resources.js b/lib/third-party-resources.js deleted file mode 100644 index 3ef21c41..00000000 --- a/lib/third-party-resources.js +++ /dev/null @@ -1,27 +0,0 @@ -'use strict'; - -const ApiGroup = require('./api-group'); - -class ThirdPartyResource extends ApiGroup { - constructor(options) { - options = Object.assign({}, options, { - path: `apis/${ options.group }`, - version: options.version || 'v1', - groupResources: [], - namespaceResources: [] - }); - super(options); - - if (options.resources) { - options.resources.forEach(resource => this.addResource(resource)); - } - } - - addResource(resourceName) { - this.namespace.addNamedResource(resourceName); - super.addResource(resourceName); - return this; - } -} - -module.exports = ThirdPartyResource; diff --git a/package-lock.json b/package-lock.json index f2a9ae6e..49a31ca5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -205,14 +205,6 @@ } } }, - "async": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", - "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", - "requires": { - "lodash": "4.17.4" - } - }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -1600,9 +1592,9 @@ } }, "fluent-openapi": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/fluent-openapi/-/fluent-openapi-0.1.0.tgz", - "integrity": "sha512-e6yt5Zit0ANRd69rq3fzkf7GYyfLDqFTluJ6e4A+D/ICFKU6yC6q692jh7hEoA6XV34e0y4lBLgoER4+C7+K8g==", + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/fluent-openapi/-/fluent-openapi-0.1.1.tgz", + "integrity": "sha512-JTnCiuaAm66gGuizv4g46MqN1QVldPxySn9DxZ+hCjMaEGFx6ciJhz0iMfS5iv1YKqZo5pBW6qFJ9Zu0jaqvaw==", "requires": { "lodash.merge": "4.6.1" }, @@ -2561,7 +2553,8 @@ "lodash": { "version": "4.17.4", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", - "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=", + "dev": true }, "lodash._reinterpolate": { "version": "3.0.0", @@ -3347,11 +3340,6 @@ "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", "dev": true }, - "promy": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promy/-/promy-1.0.1.tgz", - "integrity": "sha512-A+/yskIBHx+ZMDzkv37ftC/QoDHI1luoxdDZ/4ryTg9fTbnU3YFL5sA4PYQhN5lU3tw3YECmI+H+Z7/r9BKSfw==" - }, "propagate": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/propagate/-/propagate-0.4.0.tgz", diff --git a/package.json b/package.json index 3edc9518..96420657 100644 --- a/package.json +++ b/package.json @@ -32,12 +32,9 @@ "typings" ], "dependencies": { - "async": "^2.6.0", "fluent-openapi": "0.1.1", "js-yaml": "^3.10.0", - "lodash.merge": "^4.6.0", "openid-client": "^2.0.0", - "promy": "^1.0.1", "request": "^2.83.0" }, "devDependencies": { diff --git a/scripts/typings.js b/scripts/typings.js index 713bcd0a..03cad105 100644 --- a/scripts/typings.js +++ b/scripts/typings.js @@ -79,7 +79,7 @@ function main(args) { const spec = JSON.parse(raw); let clientSuffix = ''; if (spec.info.version) { - clientSuffix = spec.info.version.replace(/v/, '').split('.').slice(0, 2).join('_') + clientSuffix = spec.info.version.replace(/v/, '').split('.').slice(0, 2).join('_'); } const interfaces = []; diff --git a/test/api-extensions.test.js b/test/api-extensions.test.js deleted file mode 100644 index 1bfb9f29..00000000 --- a/test/api-extensions.test.js +++ /dev/null @@ -1,58 +0,0 @@ -'use strict'; - -const assume = require('assume'); -const async = require('async'); -const nock = require('nock'); - -const common = require('./common'); -const beforeTesting = common.beforeTesting; - -const testApiExtensions = { - apiVersion: 'apiextensions.k8s.io/v1beta1', - kind: 'CustomResourceDefinition', - metadata: { - name: 'test.example.com' - }, - spec: { - group: 'example.com', - version: 'v1', - scope: 'Namespaced', - names: { - plural: 'tests', - singular: 'test', - kind: 'Test', - shortNames: [ - 't' - ] - } - } -}; - -describe('lib.apiextensions', () => { - describe('.ApiExtensions', () => { - const testCustomResourceName = testApiExtensions.metadata.name; - - beforeTesting('unit', () => { - nock(common.apiExtensions.url) - .post(`${ common.apiExtensions.path }/customresourcedefinitions`) - .reply(201, testApiExtensions) - .get(`${ common.apiExtensions.path }/customresourcedefinitions/${ testCustomResourceName }`) - .reply(200, testApiExtensions); - }); - - // NOTE: Running only unit tests. Setting up CRD is more involved, and it - // makes it cumbersome to run the other integration tests. We need - // improvements to our integration test harness to make this work well. - common.only('unit', 'can POST and GET', done => { - async.series([ - next => common.apiExtensions.customresourcedefinitions.post({ body: testApiExtensions }, next), - next => common.apiExtensions.customresourcedefinitions.get(testCustomResourceName, next) - ], (err, results) => { - assume(err).is.falsy(); - const getResult = results[1]; - assume(getResult.metadata.name).is.equal(testCustomResourceName); - done(); - }); - }); - }); -}); diff --git a/test/api-group.test.js b/test/api-group.test.js deleted file mode 100644 index bcefcf31..00000000 --- a/test/api-group.test.js +++ /dev/null @@ -1,183 +0,0 @@ -'use strict'; - -const assume = require('assume'); -const async = require('async'); -const nock = require('nock'); - -const ApiGroup = require('../lib/api-group'); -const common = require('./common'); -const beforeTesting = common.beforeTesting; - -function ingress() { - return { - kind: 'Ingress', - apiVersion: 'extensions/v1beta1' - }; -} - -describe('lib.api-group', () => { - describe('.constructor', () => { - it('passes request options correctly', () => { - const apiGroup = new ApiGroup({ - namespaceResources: [], - groupResources: [], - url: '127.0.0.1', - ca: 'testCA', - cert: 'testCert', - key: 'testKey', - insecureSkipTlsVerify: false, - request: { - timeout: 1000 - }, - auth: { - user: 'testUser', - pass: 'testPass' - } - }); - assume(apiGroup).hasOwn('http'); - assume(apiGroup.http).hasOwn('requestOptions'); - assume(apiGroup.http.requestOptions).hasOwn('baseUrl', '127.0.0.1'); - assume(apiGroup.http.requestOptions).hasOwn('ca', 'testCA'); - assume(apiGroup.http.requestOptions).hasOwn('cert', 'testCert'); - assume(apiGroup.http.requestOptions).hasOwn('key', 'testKey'); - assume(apiGroup.http.requestOptions).hasOwn('strictSSL', true); - assume(apiGroup.http.requestOptions).hasOwn('timeout', 1000); - assume(apiGroup.http.requestOptions).hasOwn('auth'); - assume(apiGroup.http.requestOptions.auth).hasOwn('user', 'testUser'); - assume(apiGroup.http.requestOptions.auth).hasOwn('pass', 'testPass'); - }); - }); - - describe('.ns', () => { - - let nameForTest; - beforeTesting('integration', done => { - nameForTest = common.newName(); - common.changeName(done); - }); - - beforeTesting('unit', () => { - nameForTest = common.newName(); - const mockNamespace = { - kind: 'Namespace', - metadata: { - name: nameForTest - } - }; - nock(common.api.url) - .post('/api/v1/namespaces') - .reply(201, mockNamespace) - .get(`/api/v1/namespaces/${ nameForTest }`) - .reply(200, mockNamespace) - .delete(`/api/v1/namespaces/${ nameForTest }`) - .reply(200, mockNamespace) - .get(`/api/v1/namespaces/${ nameForTest }`) - .reply(404, { message: 'An error', code: 404 }); - }); - - it('POSTs, GETs, and DELETEs', done => { - async.series([ - next => { - common.api.ns.post({ body: { - kind: 'Namespace', - metadata: { - name: nameForTest - } - }}, next); - }, - next => common.api.ns.get(nameForTest, next), - next => common.api.ns.delete({ - name: nameForTest, - timeout: common.defaultTimeout - }, next) - ], (err, results) => { - assume(err).is.falsy(); - const namespace = results[1]; - assume(namespace.metadata.name).is.equal(nameForTest); - done(); - }); - }); - }); - - describe('.addResource', () => { - afterEach(() => delete common.api.foo); - - it('adds new group resources', () => { - common.api.addResource('foo'); - assume(common.api.foo).is.truthy(); - }); - it('throws an error for missing .name', () => { - const fn = () => common.api.addResource({ Constructor: 'does not matter' }); - assume(fn).throws(); - }); - it('throws an error for missing .Constructor', () => { - const fn = () => common.api.addResource({ name: 'does not matter' }); - assume(fn).throws(); - }); - it('throws an error for same resource', () => { - const fn = () => common.api.addResource('foo'); - fn(); - assume(fn).throws(); - }); - }); - - describe('.ingresses', () => { - beforeTesting('unit', () => { - nock(common.api.url) - .get(`/apis/extensions/v1beta1/namespaces/${ common.currentName }/ingresses`) - .reply(200); - }); - - it('GETs', done => { - async.series([ - next => { common.apiGroup.group(ingress()).ns(common.currentName).kind(ingress()).get(next); } - ], err => { - assume(err).is.falsy(); - done(); - }); - }); - }); - - describe('.nodes', () => { - beforeTesting('unit', () => { - nock(common.api.url) - .get('/api/v1/nodes') - .reply(200, { - kind: 'NodeList', - items: [{ - kind: 'Node' - }] - }); - }); - - it('returns some nodes', done => { - common.api.no.get((err, results) => { - assume(err).is.falsy(); - assume(results.kind).is.equal('NodeList'); - assume(results.items.length).is.above(0); - done(); - }); - }); - }); - - describe('.resourcequotas', () => { - beforeTesting('unit', () => { - nock(common.api.url) - .get('/api/v1/resourcequotas') - .reply(200, { - kind: 'ResourceQuotaList', - items: [] - }); - }); - - it('returns ResourceQuotaList', done => { - common.api.resourcequotas.get((err, results) => { - assume(err).is.falsy(); - assume(results.kind).is.equal('ResourceQuotaList'); - done(); - }); - }); - }); - - common.afterTesting('int', common.cleanupName); -}); diff --git a/test/api.test.js b/test/api.test.js deleted file mode 100644 index 68584eb6..00000000 --- a/test/api.test.js +++ /dev/null @@ -1,23 +0,0 @@ -'use strict'; - -const assume = require('assume'); - -const Api = require('../lib/api'); -const Core = require('../lib/core'); -const Extensions = require('../lib/extensions'); - -describe('lib.api', () => { - describe('Api', () => { - it('returns Core', () => { - const api = new Api({}); - assume(api.group('v1').constructor).equals(Core); - assume(api.group({ apiVersion: 'v1' }).constructor).equals(Core); - }); - it('returns Extensions', () => { - const api = new Api({}); - const result = api.group('extensions/v1beta1'); - assume(result.constructor).equals(Extensions); - assume(result.version).equals('v1beta1'); - }); - }); -}); diff --git a/test/apps.test.js b/test/apps.test.js deleted file mode 100644 index 72d12946..00000000 --- a/test/apps.test.js +++ /dev/null @@ -1,113 +0,0 @@ -'use strict'; - -const assume = require('assume'); -const async = require('async'); -const nock = require('nock'); - -const common = require('./common'); -const beforeTesting = common.beforeTesting; - -const testStatefulSet = { - apiVersion: 'apps/v1beta1', - kind: 'StatefulSet', - metadata: { - name: 'web' - }, - spec: { - serviceName: 'nginx', - template: { - spec: { - containers: [ - { - image: 'fake-image-kubernetes-client', - name: 'nginx' - } - ] - }, - metadata: { - labels: { - app: 'nginx' - } - } - }, - replicas: 1 - } -}; - -const testDeployment = { - apiVersion: 'apps/v1beta1', - kind: 'Deployment', - metadata: { - name: 'web' - }, - spec: { - template: { - spec: { - containers: [ - { - image: 'fake-image-kubernetes-client', - name: 'nginx' - } - ] - }, - metadata: { - labels: { - app: 'nginx' - } - }, - replicas: 1 - } - } -}; - -describe('lib.apps', () => { - describe('.statefulsets', () => { - const testStatefuleSetName = testStatefulSet.metadata.name; - - beforeTesting('int', common.changeName); - beforeTesting('unit', () => { - nock(common.apps.url) - .post(`${ common.apps.path }/namespaces/${ common.currentName }/statefulsets`) - .reply(201, testStatefulSet) - .get(`${ common.apps.path }/namespaces/${ common.currentName }/statefulsets/${ testStatefuleSetName }`) - .reply(200, testStatefulSet); - }); - - it('can POST and GET', done => { - async.series([ - next => common.apps.ns(common.currentName).statefulsets.post({ body: testStatefulSet }, next), - next => common.apps.ns(common.currentName).statefulsets.get(testStatefuleSetName, next) - ], (err, results) => { - assume(err).is.falsy(); - const getResult = results[1]; - assume(getResult.metadata.name).is.equal(testStatefuleSetName); - done(); - }); - }); - }); - - describe('.deployments', () => { - const testDeploymentName = testDeployment.metadata.name; - - beforeTesting('int', common.changeName); - beforeTesting('unit', () => { - nock(common.apps.url) - .post(`${ common.apps.path }/namespaces/${ common.currentName }/deployments`) - .reply(201, testDeployment) - .get(`${ common.apps.path }/namespaces/${ common.currentName }/deployments/${ testDeploymentName }`) - .reply(200, testDeployment); - }); - - it('can POST and GET', done => { - async.series([ - next => common.apps.ns(common.currentName).deployments.post({ body: testDeployment }, next), - next => common.apps.ns(common.currentName).deployments.get(testDeploymentName, next) - ], (err, results) => { - assume(err).is.falsy(); - const getResult = results[1]; - assume(getResult.metadata.name).is.equal(testDeploymentName); - done(); - }); - }); - }); -}); diff --git a/test/base.test.js b/test/base.test.js deleted file mode 100644 index 424dd21d..00000000 --- a/test/base.test.js +++ /dev/null @@ -1,206 +0,0 @@ -/* eslint max-nested-callbacks:0 */ -'use strict'; - -const assume = require('assume'); -const async = require('async'); -const nock = require('nock'); - -const common = require('./common'); -const beforeTesting = common.beforeTesting; - -function pod(name) { - return { - kind: 'Pod', - metadata: { - name: name, - labels: { - name: name, - service: 'service1' - } - }, - spec: { - containers: [{ - name: name, - image: 'doesnotmatter', - imagePullPolicy: 'IfNotPresent' - }] - } - }; -} - -const testPvc = { - kind: 'PersistentVolumeClaim', - apiVersion: 'v1', - metadata: { - name: 'test-claim', - labels: { - app: 'test' - }, - annotations: { - 'volume.beta.kubernetes.io/storage-class': 'slow' - } - }, - spec: { - accessModes: ['ReadWriteOnce'], - resources: { - requests: { - storage: '1Mi' - } - } - } -}; - -describe('lib.base', () => { - describe('.BaseObject', () => { - - describe('.match', () => { - beforeTesting('int', done => { - common.changeName(err => { - assume(err).is.falsy(); - const po = common.api.ns(common.currentName).po; - async.each([ - { body: pod('pod0') }, - { body: pod('pod1') } - ], po.post.bind(po), done); - }); - }); - beforeTesting('unit', () => { - nock(common.api.url) - .get(`/api/v1/namespaces/${ common.currentName }/pods`) - .query({ labelSelector: 'name in (pod0),service notin (service0)' }) - .reply(200, { - kind: 'PodList', - items: [{ - kind: 'Pod' - }] - }); - }); - - it('GETs with labelSelector', done => { - common.api.ns(common.currentName).po.match([{ - key: 'name', - operator: 'In', - values: ['pod0'] - }, { - key: 'service', - operator: 'NotIn', - values: ['service0'] - }]).get((err, pods) => { - assume(err).is.falsy(); - assume(pods.kind).is.equal('PodList'); - assume(pods.items).has.length(1); - done(); - }); - }); - }); - - describe('.delete', () => { - const podName = 'pod-name'; - const query = { pretty: true }; - const body = { - apiVersion: 'v1', - kind: 'DeleteOptions', - propagationPolicy: 'Foreground' - }; - beforeTesting('unit', () => { - nock(common.api.url) - .delete(`/api/v1/namespaces/${ common.currentName }/pods/${ podName }`, body) - .query(query) - .reply(200, { - kind: 'Pod', - metadata: { - name: podName, - finalizers: ['foregroundDeletion'] - }, - spec: { - - } - }); - }); - beforeTesting('int', done => { - common.api.ns(common.currentName).po.post({ body: pod(podName) }, done); - }); - - it('should bypass query string and body from arguments into request', done => { - common.api.ns(common.currentName).po.delete({ name: podName, qs: query, body: body }, (err, result) => { - assume(err).is.falsy(); - assume(result.kind).is.eql('Pod'); - assume(result.metadata).is.truthy(); - assume(result.spec).is.truthy(); - assume(result.metadata.name).is.eql(podName); - assume(result.metadata.finalizers).is.eql(['foregroundDeletion']); - done(); - }); - }); - }); - - describe('.po.matchLabels', () => { - beforeTesting('int', done => { - common.changeName(err => { - assume(err).is.falsy(); - const po = common.api.ns(common.currentName).po; - async.each([ - { body: pod('pod0') }, - { body: pod('pod1') } - ], po.post.bind(po), done); - }); - }); - beforeTesting('unit', () => { - nock(common.api.url) - .get(`/api/v1/namespaces/${ common.currentName }/pods`) - .query({ labelSelector: 'name in (pod0),service in (service1)' }) - .reply(200, { - kind: 'PodList', - items: [{ - kind: 'Pod' - }] - }); - }); - - it('GETs with labelSelector', done => { - common.api.ns(common.currentName).po.matchLabels({ - name: 'pod0', - service: 'service1' - }).get((err, pods) => { - assume(err).is.falsy(); - assume(pods.kind).is.equal('PodList'); - assume(pods.items).has.length(1); - done(); - }); - }); - }); - }); - - describe('.pvc.matchLabels', () => { - beforeTesting('int', done => { - common.changeName(err => { - assume(err).is.falsy(); - common.api.ns(common.currentName).pvc.post({ body: testPvc }, done); - }); - }); - beforeTesting('unit', () => { - nock(common.api.url) - .get(`/api/v1/namespaces/${ common.currentName }/persistentvolumeclaims`) - .query({ labelSelector: 'app in (test)' }) - .reply(200, { - kind: 'PersistentVolumeClaimList', - items: [{ - kind: 'PersistentVolumeClaim' - }] - }); - }); - - it('GETs with labelSelector', done => { - common.api.ns(common.currentName).pvc.matchLabels({ - app: 'test' - }).get((err, pvcs) => { - assume(err).is.falsy(); - assume(pvcs.kind).is.equal('PersistentVolumeClaimList'); - assume(pvcs.items).has.length(1); - done(); - }); - }); - }); - - common.afterTesting('int', common.cleanupName); -}); diff --git a/test/batch.test.js b/test/batch.test.js deleted file mode 100644 index b78adaa4..00000000 --- a/test/batch.test.js +++ /dev/null @@ -1,60 +0,0 @@ -'use strict'; - -const assume = require('assume'); -const async = require('async'); -const nock = require('nock'); - -const common = require('./common'); -const beforeTesting = common.beforeTesting; - -const testJob = { - apiVersion: 'batch/v1', - kind: 'Job', - metadata: { - name: 'pi' - }, - spec: { - template: { - spec: { - restartPolicy: 'Never', - containers: [ - { - image: 'perl', - command: ['perl', '-Mbignum=bpi', '-wle', 'print bpi(2000)'], - name: 'pi' - } - ] - }, - metadata: { - name: 'pi' - } - } - } -}; - -describe('lib.batch', () => { - describe('.jobs', () => { - const testJobName = testJob.metadata.name; - - beforeTesting('int', common.changeName); - beforeTesting('unit', () => { - nock(common.batch.url) - .post(`${ common.batch.path }/namespaces/${ common.currentName }/jobs`) - .reply(201, testJob) - .get(`${ common.batch.path }/namespaces/${ common.currentName }/jobs/${ testJobName }`) - .reply(200, testJob); - }); - - it('can POST and GET', done => { - async.series([ - next => common.batch.ns(common.currentName).jobs.post({ body: testJob }, next), - next => common.batch.ns(common.currentName).jobs.get(testJobName, next) - ], (err, results) => { - assume(err).is.falsy(); - const getResult = results[1]; - assume(getResult.metadata.name).is.equal(testJobName); - done(); - }); - }); - }); -}); diff --git a/test/common.js b/test/common.js index 5465ceb1..a0baac3b 100644 --- a/test/common.js +++ b/test/common.js @@ -1,22 +1,11 @@ /* eslint no-process-env:0, no-sync:0, max-statements:0 */ 'use strict'; -const async = require('async'); const crypto = require('crypto'); const fs = require('fs'); const path = require('path'); const yaml = require('js-yaml'); -const Api = require('../lib/api'); -const ApiExtensions = require('../lib/api-extensions'); -const Apps = require('../lib/apps'); -const Batch = require('../lib/batch'); -const Core = require('../lib/core'); -const Extensions = require('../lib/extensions'); -const Rbac = require('../lib/rbac'); -const ThirdPartyResources = require('../lib/third-party-resources'); -const CustomResourceDefinitions = require('../lib/custom-resource-definitions'); - const defaultName = process.env.NAMESPACE || 'integration-tests'; const defaultTimeout = process.env.TIMEOUT || 30000; @@ -68,26 +57,9 @@ function newName() { } function injectApis(options) { - const apis = { - api: { Constructor: Core }, - apiExtensions: { Constructor: ApiExtensions }, - apiGroup: { Constructor: Api }, - apps: { Constructor: Apps }, - batch: { Constructor: Batch }, - core: { Constructor: Core }, - extensions: { Constructor: Extensions }, - rbac: { Constructor: Rbac }, - thirdPartyResources: { - Constructor: ThirdPartyResources, options: { group: 'kubernetes-client.com' } - }, - customResourceDefinitions: { - Constructor: CustomResourceDefinitions, options: { group: 'kubernetes-client.com' } - } + module.exports.api = { + url: options.url }; - Object.keys(apis).forEach(apiName => { - const api = apis[apiName]; - module.exports[apiName] = new (api.Constructor)(Object.assign({}, options, api.options)); - }); } function changeNameInt(cb) { @@ -143,17 +115,8 @@ function changeNameInt(cb) { } }, err => { if (err) return cb(err); - const times = Math.ceil(defaultTimeout / 1000); - const interval = 1000; - async.retry({ times: times, interval: interval }, next => { - module.exports.api.ns(currentName).serviceaccounts.get('default', (saErr, sa) => { - if (saErr) return next(saErr); - if (!sa.secrets) { - return next(new Error('Waiting for servicesaccount secrets')); - } - cb(); - }); - }); + // TODO(sbw): We need to delay until we're sure namespace is ready for action. + cb(); }); } diff --git a/test/container-base-object.test.js b/test/container-base-object.test.js deleted file mode 100644 index d1fce3ca..00000000 --- a/test/container-base-object.test.js +++ /dev/null @@ -1,31 +0,0 @@ -'use strict'; - -const assume = require('assume'); - -const ContainerBaseObject = require('../lib/container-base-object'); -const api = { resourceConfig: {}}; - -describe('lib.container-base-object', () => { - describe('.ContainerBaseObject', () => { - it('adds resources specified in the constructor', () => { - const fake = new ContainerBaseObject({ api, resources: ['foo'] }); - assume(fake.foo).is.a('function'); - }); - - it('throws an error if missing resource name', () => { - const fn = () => new ContainerBaseObject({ api, resources: [{ Constructor: 'fake' }] }); - assume(fn).throws(); - }); - - it('throws an error if missing resource Constructor', () => { - const fn = () => new ContainerBaseObject({ api, resources: [{ name: 'fake' }] }); - assume(fn).throws(); - }); - - it('throws an error for adding the resource', () => { - const fake = new ContainerBaseObject({ api, resources: ['foo'] }); - const fn = () => fake.addResource('foo'); - assume(fn).throws(); - }); - }); -}); diff --git a/test/custom-resource-definition.test.js b/test/custom-resource-definition.test.js deleted file mode 100644 index ff72016a..00000000 --- a/test/custom-resource-definition.test.js +++ /dev/null @@ -1,126 +0,0 @@ -/* eslint max-nested-callbacks:0 */ -'use strict'; - -const assume = require('assume'); -const async = require('async'); -const nock = require('nock'); - -const common = require('./common'); -const afterTesting = common.afterTesting; -const beforeTesting = common.beforeTesting; -const only = common.only; - -const newResource = { - apiVersion: 'apiextensions.k8s.io/v1beta1', - kind: 'CustomResourceDefinition', - metadata: { - name: `newresources.${ common.customResourceDomain }` - }, - spec: { - group: common.customResourceDomain, - version: 'v1', - scope: 'Namespaced', - names: { - plural: 'newresources', - singular: 'newresource', - kind: 'NewResource', - shortNames: [ - 'nr' - ] - } - } -}; - -const testManifest = { - apiVersion: `${ common.customResourceDomain }/v1`, - kind: 'NewResource', - metadata: { - name: 'test' - }, - testProperty: 'hello world' -}; - -function createNewResource(cb) { - common.customResourceDefinitions.addResource('newresources'); - common.apiExtensions.customresourcedefinition.delete(newResource.metadata.name, () => { - common.apiExtensions.customresourcedefinition.post({ body: newResource }, postErr => { - if (postErr) return cb(postErr); - // - // Creating the API endpoints for a 3rd party resource appears to be - // asynchronous with respect to creating the resource. - // - const times = common.defaultTimeout / 1000; - async.retry({ times: times, interval: 1000 }, next => { - common.customResourceDefinitions.ns(common.currentName).newresources.get(err => { - if (err) return next(err); - cb(); - }); - }); - }); - }); -} - -describe('lib.CustomResourceDefinition', () => { - - beforeTesting('int', common.changeName); - - describe('.addResource', () => { - only('unit', 'adds a BaseObject globally and to default namespace', () => { - common.customResourceDefinitions.addResource('newresources'); - assume(common.customResourceDefinitions.newresources).is.truthy(); - assume(common.customResourceDefinitions.ns(common.currentName).newresources).is.truthy(); - }); - }); - - describe('.newresources', () => { - beforeTesting('int', done => { - createNewResource(done); - }); - afterTesting('int', done => { - common.apiExtensions.customresourcedefinition.delete(newResource.metadata.name, done); - }); - - describe('.get', () => { - beforeTesting('unit', () => { - nock(common.customResourceDefinitions.url) - .get(`${ common.customResourceDefinitions.path }/newresources`) - .reply(200, { kind: 'NewResourceList' }); - }); - - it('returns NewSourceList', done => { - common.customResourceDefinitions.newresources.get((err, results) => { - assume(err).is.falsy(); - assume(results.kind).is.equal('NewResourceList'); - done(); - }); - }); - }); - - describe('.post', () => { - beforeTesting('unit', () => { - nock(common.customResourceDefinitions.url) - .post(`/apis/${ common.customResourceDomain }/v1/namespaces/${ common.currentName }/newresources`) - .reply(200, {}) - .get(`/apis/${ common.customResourceDomain }/v1/namespaces/${ common.currentName }/newresources/test`) - .reply(200, { metadata: { name: 'test' }}); - }); - - it('creates a resources', done => { - common.customResourceDefinitions - .ns(common.currentName) - .newresources - .post({ body: testManifest }, postErr => { - assume(postErr).is.falsy(); - common.customResourceDefinitions - .ns(common.currentName) - .newresources - .get('test', (err, result) => { - assume(err).is.falsy(); - assume(result.metadata.name).is.equal('test'); - done(); - }); - }); - }); - }); - }); -}); diff --git a/test/deployments.test.js b/test/deployments.test.js deleted file mode 100644 index ab50e56a..00000000 --- a/test/deployments.test.js +++ /dev/null @@ -1,130 +0,0 @@ -'use strict'; - -const assume = require('assume'); -const async = require('async'); -const nock = require('nock'); - -const common = require('./common'); -const beforeTesting = common.beforeTesting; - -const resourceName = 'test'; - -describe('lib.deployments', () => { - const path = `/apis/extensions/v1beta1/namespaces/${ common.currentName }/deployments`; - const resourcePath = `${ path }/${ resourceName }`; - const deploymentObj = { - kind: 'Deployment', - metadata: { - name: resourceName - }, - spec: { - selector: [ - { app: 'test' } - ], - template: { - metadata: { - labels: { - app: 'test' - } - }, - spec: { - containers: [{ - name: 'test', - imagePullPolicy: 'Never', - image: 'test.com:5000/test:v0' - }] - } - } - } - }; - - beforeTesting('int', common.changeName); - beforeTesting('unit', () => { - const mockDeployment = { - kind: 'Deployment', - metadata: { - name: resourceName - } - }; - nock(common.extensions.url) - .post(path) - .reply(201, mockDeployment) - .get(resourcePath) - .reply(200, mockDeployment) - .delete(resourcePath) - .reply(200, mockDeployment); - }); - - it('POSTs, GETs, and DELETEs', done => { - async.series([ - next => { - common.extensions.ns(common.currentName).deployments.post({ body: deploymentObj }, next); - }, - next => common.extensions.ns(common.currentName).deployments.get(resourceName, next), - next => common.extensions.ns(common.currentName).deployments.delete(resourceName, next) - ], (err, results) => { - assume(err).is.falsy(); - const deployments = results[0]; - assume(deployments.metadata.name).is.equal(resourceName); - done(); - }); - }); - - describe('lists', () => { - beforeTesting('int', common.changeName); - beforeTesting('unit', () => { - const mockDeploymentList = { - kind: 'DeploymentList', - items: [] - }; - nock(common.extensions.url) - .get(path) - .reply(200, mockDeploymentList); - }); - - it('returns DeploymentList', done => { - async.series([ - next => common.extensions.ns(common.currentName).deployments.get(next) - ], (err, results) => { - assume(err).is.falsy(); - const deploymentList = results[0]; - assume(deploymentList.kind).is.equal('DeploymentList'); - done(); - }); - }); - }); - - describe('.status', () => { - beforeTesting('unit', () => { - const mockStatus = { - kind: 'Deployment', - status: { - observedGeneration: 1, - replicas: 2 - } - }; - nock(common.extensions.url) - .get(`${ resourcePath }/status`) - .reply(200, mockStatus); - }); - - beforeTesting('int', done => { - common.changeName(err => { - assume(err).is.falsy(); - common.extensions.ns(common.currentName).deployments.post({ body: deploymentObj }, postErr => { - assume(postErr).is.falsy(); - done(); - }); - }); - }); - - it('returns Deployment with status', done => { - common.extensions.ns(common.currentName).deployments(resourceName).status.get((err, deployment) => { - assume(err).is.falsy(); - assume(deployment.kind).is.equal('Deployment'); - assume(deployment.status).is.a('object'); - done(); - }); - }); - }); -}); diff --git a/test/extensions.test.js b/test/extensions.test.js deleted file mode 100644 index 2bffb6d6..00000000 --- a/test/extensions.test.js +++ /dev/null @@ -1,99 +0,0 @@ -'use strict'; - -const assume = require('assume'); -const async = require('async'); -const nock = require('nock'); - -const common = require('./common'); -const beforeTesting = common.beforeTesting; -const resourceName = 'test'; - -describe('lib.extensions', () => { - describe('.ds', () => { - const path = `/apis/extensions/v1beta1/namespaces/${ common.currentName }/daemonsets`; - const resourcePath = `${ path }/${ resourceName }`; - const daemonSetObj = { - kind: 'DaemonSet', - metadata: { - name: resourceName - }, - spec: { - template: { - metadata: { - labels: { - app: 'test' - } - }, - spec: { - containers: [ - { - name: 'test', - imagePullPolicy: 'Never', - image: 'test.com:5000/test:v0' - } - ] - } - } - } - }; - - beforeTesting('int', common.changeName); - beforeTesting('unit', () => { - const mockDs = { - kind: 'DaemonSet', - metadata: { - name: resourceName - } - }; - nock(common.extensions.url) - .post(path) - .reply(201, mockDs) - .get(resourcePath) - .reply(200, mockDs) - .delete(resourcePath) - .reply(200, mockDs); - }); - - it('POSTs, GETs, and DELETEs', done => { - async.series([ - next => { - common.extensions.ns(common.currentName).ds.post({ body: daemonSetObj }, next); - }, - next => common.extensions.ns(common.currentName).ds.get(resourceName, next), - next => common.extensions.ns(common.currentName).ds.delete(resourceName, next) - ], (err, results) => { - assume(err).is.falsy(); - const ds = results[0]; - assume(ds.metadata.name).is.equal(resourceName); - done(); - }); - }); - - describe('lists', () => { - beforeTesting('int', common.changeName); - beforeTesting('unit', () => { - const mockDsList = { - kind: 'DaemonSetList', - items: [] - }; - - nock(common.extensions.url) - .get(path) - .reply(200, mockDsList); - }); - - it('returns DaemonSetList', done => { - async.series([ - next => common.extensions.ns(common.currentName).ds.get(next) - ], (err, results) => { - assume(err).is.falsy(); - const dsList = results[0]; - assume(dsList.kind).is.equal('DaemonSetList'); - done(); - }); - }); - }); - }); - - common.afterTesting('int', common.cleanupName); -}); diff --git a/test/namespaces.test.js b/test/namespaces.test.js deleted file mode 100644 index 3325c40b..00000000 --- a/test/namespaces.test.js +++ /dev/null @@ -1,31 +0,0 @@ -'use strict'; - -const assume = require('assume'); - -const Namespaces = require('../lib/namespaces'); - -const api = { resourceConfig: {}}; - -describe('lib.namespaces', () => { - describe('.addResource', () => { - it('adds a new resource object', () => { - const parentPath = '/apis/foo.com/v1'; - const namespace = 'notdefault'; - const namespaces = new Namespaces({ api, parentPath, namespace, resources: [] }); - namespaces - .addNamedResource('balonies') - .addNamedResource('ducks'); - assume(namespaces('foo').balonies.constructor.name).is.equal('BaseObject'); - assume(namespaces('foo').ducks.constructor.name).is.equal('BaseObject'); - }); - - it('ensures named namespaces inherit resources', () => { - const parentPath = '/apis/foo.com/v1'; - const namespace = 'notdefault'; - const namespaces = new Namespaces({ api, parentPath, namespace, resources: [] }); - namespaces.addNamedResource('balonies'); - const namespacesFoo = namespaces('foo'); - assume(namespacesFoo.balonies.constructor.name).is.equal('BaseObject'); - }); - }); -}); diff --git a/test/objects.test.js b/test/objects.test.js deleted file mode 100644 index f1a970ff..00000000 --- a/test/objects.test.js +++ /dev/null @@ -1,156 +0,0 @@ -/* eslint max-nested-callbacks:0 */ -'use strict'; -const assume = require('assume'); -const nock = require('nock'); -const ReplicationControllers = require('../lib/replicationcontrollers'); -const Core = require('../lib/core'); - -const common = require('./common'); -const only = common.only; -const beforeTesting = common.beforeTesting; - -const testReplicationController = { - kind: 'ReplicationController', - metadata: { - name: 'test-rc' - }, - spec: { - replicas: 1, - selector: { - name: 'test-rc' - }, - template: { - metadata: { - labels: { - name: 'test-rc' - } - }, - spec: { - containers: [ - { - image: 'does-not-matter:latest', - name: 'test' - } - ] - } - } - } -}; - -describe('objects', function () { - const _url = 'http://mock.kube.api'; - const _ns = '/api/v1/namespaces/default'; - const _rcs = `${ _ns }/replicationcontrollers`; - - function rcs() { - return new ReplicationControllers({ - api: new Core({ url: _url }), - parentPath: _ns - }); - } - - describe('.ReplicationControllers.get', function () { - function nock200() { - return nock(_url) - .get(`${ _rcs }/foo`) - .reply(200, { - kind: 'replicationcontroller', - metadata: {} - }); - } - only('unit', 'returns replication controller', function (done) { - nock200(); - rcs().get('foo', (err, rc) => { - assume(err).is.falsy(); - assume(rc.kind).is.equal('replicationcontroller'); - done(); - }); - }); - only('unit', 'handles object argument', function (done) { - nock200(); - rcs().get({ name: 'foo' }, (err, rc) => { - assume(err).is.falsy(); - assume(rc.kind).is.equal('replicationcontroller'); - done(); - }); - }); - function nock400() { - return nock(_url) - .get(`${ _rcs }/foo`) - .reply(400, { message: 'an error message' }); - } - only('unit', 'handles errors', function (done) { - nock400(); - rcs().get('foo', err => { - assume(err).is.truthy(); - done(); - }); - }); - }); - - describe('.ReplicationControllers.delete', function () { - function nock200() { - return nock(_url) - .patch(`${ _rcs }/foo`) - .reply(200, {}) - .delete(`${ _rcs }/foo`) - .reply(200, {}); - } - only('unit', 'deletes Pods and RC', function (done) { - const scope = nock200(); - rcs().delete('foo', err => { - assume(err).is.falsy(); - assume(scope.isDone()).true(); - done(); - }); - }); - only('unit', 'preserves Pods if specified', function (done) { - const scope = nock(_url).delete(`${ _rcs }/foo`).reply(200, {}); - rcs().delete({ name: 'foo', preservePods: true }, err => { - assume(err).is.falsy(); - assume(scope.isDone()).true(); - done(); - }); - }); - }); - - describe('.ReplicationControllers.post', function () { - beforeTesting('int', common.changeName); - beforeTesting('unit', () => { - nock(common.api.url) - .post(`/api/v1/namespaces/${ common.currentName }/replicationcontrollers`) - .reply(200, testReplicationController); - }); - - it('creates a ReplicationController', function (done) { - common.api.ns(common.currentName).rc.post({ body: testReplicationController }, (err, result) => { - assume(err).is.falsy(); - assume(result.metadata.name).is.equal('test-rc'); - done(); - }); - }); - }); - - describe('.ReplicationControllers.put', function () { - beforeTesting('int', done => { - common.changeName(err => { - assume(err).is.falsy(); - common.api.ns(common.currentName).rc.post({ body: testReplicationController }, done); - }); - }); - beforeTesting('unit', () => { - nock(common.api.url) - .put(`/api/v1/namespaces/${ common.currentName }/replicationcontrollers/test-rc`) - .reply(200, testReplicationController); - }); - it('PUTs the new manifest', function (done) { - common.api.ns(common.currentName).rc.put({ name: 'test-rc', body: testReplicationController }, (err, result) => { - assume(err).is.falsy(); - assume(result.metadata.name).is.equal('test-rc'); - done(); - }); - }); - }); - - common.afterTesting('int', common.cleanupName); -}); diff --git a/test/pods.test.js b/test/pods.test.js deleted file mode 100644 index c146a582..00000000 --- a/test/pods.test.js +++ /dev/null @@ -1,215 +0,0 @@ -/* eslint max-nested-callbacks:0 */ -'use strict'; - -const assume = require('assume'); -const nock = require('nock'); - -const common = require('./common'); -const only = common.only; -const beforeTesting = common.beforeTesting; -const beforeTestingEach = common.beforeTestingEach; - -const testPod = { - kind: 'Pod', - metadata: { - name: 'test-pod' - }, - spec: { - containers: [ - { - image: 'does-not-matter:latest', - name: 'test' - } - ] - } -}; - -const testStrategicMergePatch = { - metadata: { - name: 'test-pod' - }, - spec: { - containers: [ - { - image: 'still-does-not-matter:latest', - name: 'test' - } - ] - } -}; - -const testMergePatch = { - spec: { - activeDeadlineSeconds: 100 - } -}; - -describe('lib.pods', () => { - - describe('.post', () => { - beforeTesting('int', common.changeName); - beforeTestingEach('unit', () => { - nock(common.api.url) - .post(`/api/v1/namespaces/${ common.currentName }/pods`) - .reply(200, { - kind: 'Pod', - metadata: { name: 'test-pod' } - }); - }); - - it('succeeds creating a new pod', done => { - common.api.ns(common.currentName).pods.post({ body: testPod }, (err, pod) => { - assume(err).is.falsy(); - assume(pod.metadata.name).is.equal('test-pod'); - done(); - }); - }); - }); - - describe('.patch', () => { - describe('.strategic-merge-patch+json', () => { - beforeTesting('int', done => { - common.changeName(err => { - assume(err).is.falsy(); - common.api.ns(common.currentName).pods.post({ body: testPod }, postErr => { - assume(postErr).is.falsy(); - done(); - }); - }); - }); - beforeTestingEach('unit', () => { - nock(common.api.url) - .patch(`/api/v1/namespaces/${ common.currentName }/pods/test-pod`) - .reply(200, Object.assign({ kind: 'Pod' }, testStrategicMergePatch)); - }); - - it('succeeds at updating a pod', done => { - common.api.ns(common.currentName).pods('test-pod').patch({ body: testStrategicMergePatch }, (err, pod) => { - assume(err).is.falsy(); - assume(pod.metadata.name).is.equal('test-pod'); - assume(pod.spec.containers[0].image).is.equal('still-does-not-matter:latest'); - done(); - }); - }); - }); - - describe('.merge-patch+json', () => { - beforeTesting('int', done => { - common.changeName(err => { - assume(err).is.falsy(); - common.api.ns(common.currentName).pods.post({ body: testPod }, postErr => { - assume(postErr).is.falsy(); - done(); - }); - }); - }); - beforeTestingEach('unit', () => { - nock(common.api.url, { 'content-type': 'application/merge-patch+json' }) - .patch(`/api/v1/namespaces/${ common.currentName }/pods/test-pod`) - .reply(200, Object.assign({ kind: 'Pod' }, { - metadata: { - name: 'test-pod' - }, - spec: { - activeDeadlineSeconds: 100 - } - })); - }); - - it('succeeds at updating a pod', done => { - common.api.ns(common.currentName).pods('test-pod').patch({ - body: testMergePatch, - headers: { 'content-type': 'application/merge-patch+json' } - }, (err, pod) => { - assume(err).is.falsy(); - assume(pod.metadata.name).is.equal('test-pod'); - assume(pod.spec.activeDeadlineSeconds).is.equal(100); - done(); - }); - }); - - only('int', 'fails at updating a pod if the patch is strategic', done => { - common.api.ns(common.currentName).pods('test-pod').patch({ - body: testStrategicMergePatch, - headers: { 'content-type': 'application/merge-patch+json' } - }, err => { - assume(err).is.truthy(); - done(); - }); - }); - }); - }); - - describe('.get', () => { - beforeTesting('int', done => { - common.changeName(err => { - assume(err).is.falsy(); - common.api.ns(common.currentName).pods.post({ body: testPod }, done); - }); - }); - beforeTestingEach('unit', () => { - nock(common.api.url) - .get(`/api/v1/namespaces/${ common.currentName }/pods/test-pod`) - .reply(200, { - kind: 'Pod', - metadata: { name: 'test-pod' } - }); - }); - it('returns the Pod', done => { - common.api.ns(common.currentName).pods('test-pod').get((err, pod) => { - assume(err).is.falsy(); - assume(pod.kind).is.equal('Pod'); - done(); - }); - }); - it('returns the Pod via a stream', done => { - const stream = common.api.ns(common.currentName).pods('test-pod').getStream(); - const pieces = []; - stream.on('data', data => pieces.push(data.toString())); - stream.on('error', err => assume(err).is.falsy()); - stream.on('end', () => { - const object = JSON.parse(pieces.join('')); - assume(object.kind).is.equal('Pod'); - done(); - }); - }); - }); - - describe('.delete', () => { - beforeTesting('int', done => { - common.changeName(err => { - assume(err).is.falsy(); - common.api.ns(common.currentName).pods.post({ body: testPod }, done); - }); - }); - beforeTestingEach('unit', () => { - nock(common.api.url) - .delete(`/api/v1/namespaces/${ common.currentName }/pods/test-pod`) - .reply(200, { kind: 'Pod' }); - }); - it('deletes the Pod', done => { - common.api.ns(common.currentName).pods('test-pod').delete((err, pod) => { - assume(err).is.falsy(); - assume(pod.kind).is.equal('Pod'); - done(); - }); - }); - }); - - describe('.log', () => { - beforeTestingEach('unit', () => { - nock(common.api.url) - .get(`/api/v1/namespaces/${ common.currentName }/pods/test-pod/log`) - .reply(200, 'some log contents'); - }); - only('unit', 'returns log contents', done => { - common.api.ns(common.currentName).pods('test-pod').log.get((err, contents) => { - assume(err).is.falsy(); - assume(contents).is.equal('some log contents'); - done(); - }); - }); - }); - - common.afterTesting('int', common.cleanupName); -}); diff --git a/test/promise.test.js b/test/promise.test.js deleted file mode 100644 index f69ebdb2..00000000 --- a/test/promise.test.js +++ /dev/null @@ -1,41 +0,0 @@ -'use strict'; - -const assume = require('assume'); -const nock = require('nock'); - -const common = require('./common'); -const only = common.only; -const beforeTestingEach = common.beforeTestingEach; - -describe('lib.promise', () => { - describe('Core', () => { - beforeTestingEach('unit', () => { - nock(common.api.url) - .get(`/api/v1/namespaces/${ common.currentName }/pods/test-pod`) - .reply(200, { - kind: 'Pod', - metadata: { name: 'test-pod' } - }); - }); - - only('unit', '.get returns a Pod via a promise', done => { - const pods = common.core.ns(common.currentName).po('test-pod').get(); - pods.then(object => { - assume(object.kind).is.equal('Pod'); - assume(object.metadata.name).is.equal('test-pod'); - done(); - }); - }); - only('unit', '.getStream returns the Pod via a stream', done => { - const stream = common.core.ns(common.currentName).po('test-pod').getStream(); - const pieces = []; - stream.on('data', data => pieces.push(data.toString())); - stream.on('error', err => assume(err).is.falsy()); - stream.on('end', () => { - const object = JSON.parse(pieces.join('')); - assume(object.kind).is.equal('Pod'); - done(); - }); - }); - }); -}); diff --git a/test/rbac.test.js b/test/rbac.test.js deleted file mode 100644 index d1200a48..00000000 --- a/test/rbac.test.js +++ /dev/null @@ -1,50 +0,0 @@ -'use strict'; - -const assume = require('assume'); -const async = require('async'); -const nock = require('nock'); - -const common = require('./common'); -const beforeTesting = common.beforeTesting; - -const testRbac = { - kind: 'Role', - apiVersion: 'rbac.authorization.k8s.io/v1alpha1', - metadata: { - name: 'pod-reader' - }, - rules: [{ - apiGroups: [''], - resources: ['pods'], - verbs: ['get', 'watch', 'list'] - }] -}; - -describe('lib.rbac', () => { - describe('.Rbac', () => { - const testRbacName = testRbac.metadata.name; - - beforeTesting('unit', () => { - nock(common.rbac.url) - .post(`${ common.rbac.path }/namespaces/${ common.currentName }/roles`) - .reply(201, testRbac) - .get(`${ common.rbac.path }/namespaces/${ common.currentName }/roles/${ testRbacName }`) - .reply(200, testRbac); - }); - - // NOTE: Running only unit tests. Setting up RBAC is more involved, and it - // makes it cumbersome to run the other integration tests. We need - // improvements to our integration test harness to make this work well. - common.only('unit', 'can POST and GET', done => { - async.series([ - next => common.rbac.ns(common.currentName).roles.post({ body: testRbac }, next), - next => common.rbac.ns(common.currentName).roles.get(testRbacName, next) - ], (err, results) => { - assume(err).is.falsy(); - const getResult = results[1]; - assume(getResult.metadata.name).is.equal(testRbacName); - done(); - }); - }); - }); -}); diff --git a/test/third-party-resources.test.js b/test/third-party-resources.test.js deleted file mode 100644 index 8270e3bf..00000000 --- a/test/third-party-resources.test.js +++ /dev/null @@ -1,114 +0,0 @@ -/* eslint max-nested-callbacks:0 */ -'use strict'; - -const assume = require('assume'); -const async = require('async'); -const nock = require('nock'); - -const common = require('./common'); -const afterTesting = common.afterTesting; -const beforeTesting = common.beforeTesting; -const only = common.only; - -const newResource = { - apiVersion: 'extensions/v1beta1', - kind: 'ThirdPartyResource', - metadata: { - name: `new-resource.${ common.thirdPartyDomain }` - }, - description: 'Resource for testing', - versions: [{ - name: 'v1' - }] -}; - -const testManifest = { - apiVersion: `${ common.thirdPartyDomain }/v1`, - kind: 'NewResource', - metadata: { - name: 'test' - }, - testProperty: 'hello world' -}; - -function createNewResource(cb) { - common.thirdPartyResources.addResource('newresources'); - common.extensions.thirdpartyresources.delete(newResource.metadata.name, () => { - common.extensions.thirdpartyresources.post({ body: newResource }, postErr => { - if (postErr) return cb(postErr); - // - // Creating the API endpoints for a 3rd party resource appears to be - // asynchronous with respect to creating the resource. - // - const times = common.defaultTimeout / 1000; - async.retry({ times: times, interval: 1000 }, next => { - common.thirdPartyResources.newresources.get(err => { - if (err) return next(err); - cb(); - }); - }); - }); - }); -} - -describe('lib.ThirdPartyResource', () => { - describe('.addResource', () => { - only('unit', 'adds a BaseObject globally and to default namespace', () => { - common.thirdPartyResources.addResource('newresources'); - assume(common.thirdPartyResources.newresources).is.truthy(); - assume(common.thirdPartyResources.ns(common.currentName).newresources).is.truthy(); - }); - }); - - describe('.newresources', () => { - beforeTesting('int', done => { - createNewResource(done); - }); - afterTesting('int', done => { - common.extensions.thirdpartyresources.delete(newResource.metadata.name, done); - }); - - describe('.get', () => { - beforeTesting('unit', () => { - nock(common.thirdPartyResources.url) - .get(`${ common.thirdPartyResources.path }/newresources`) - .reply(200, { kind: 'NewResourceList' }); - }); - - it('returns NewSourceList', done => { - common.thirdPartyResources.newresources.get((err, results) => { - assume(err).is.falsy(); - assume(results.kind).is.equal('NewResourceList'); - done(); - }); - }); - }); - - describe('.post', () => { - beforeTesting('unit', () => { - nock(common.thirdPartyResources.url) - .post(`/apis/${ common.thirdPartyDomain }/v1/namespaces/${ common.currentName }/newresources`) - .reply(200, {}) - .get(`/apis/${ common.thirdPartyDomain }/v1/namespaces/${ common.currentName }/newresources/test`) - .reply(200, { metadata: { name: 'test' }}); - }); - - it('creates a resources', done => { - common.thirdPartyResources - .ns(common.currentName) - .newresources - .post({ body: testManifest }, postErr => { - assume(postErr).is.falsy(); - common.thirdPartyResources - .ns(common.currentName) - .newresources - .get('test', (err, result) => { - assume(err).is.falsy(); - assume(result.metadata.name).is.equal('test'); - done(); - }); - }); - }); - }); - }); -});