diff --git a/README.md b/README.md index 2b8f9ca..ea71136 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,16 @@ const openshiftRestClient = require('openshift-rest-client').OpenshiftClient; To see more examples of how to customize your config, check out the [kubernetes-client Initializing section](https://www.npmjs.com/package/kubernetes-client#initializing) +#### Load API from a Remote Cluster + +By default, the openshift-rest-client, will load a swagger spec file that is included with the module. This has all the basic API's that come with Openshift and Kubernetes. If you are using operators to extend your cluster, the openshift-rest-client, by default, won't know about them. + +To fix this, you can tell the openshift-rest-client to load the spec file from your remote cluster using the `loadSpecFromCluster` option. Setting this to true, will try to load the spec file from your clusters `/openapi/v2` endpoint. If that doesn't exist, it will also try, `/swagger.json` + +If the remote spec cannot be loaded, a warning will be output to the console and the default spec will be loaded. + +In a future version of this client, this might become the default. + #### Changes in 2.0 diff --git a/lib/openshift-rest-client.js b/lib/openshift-rest-client.js index aa8bf88..90fdf36 100644 --- a/lib/openshift-rest-client.js +++ b/lib/openshift-rest-client.js @@ -61,6 +61,7 @@ const spec = JSON.parse(zlib.gunzipSync(fs.readFileSync(path.join(__dirname, 'sp * Builds the rest client based on provided or default kubernetes configuration. * * @param {object} [settings] - settings object for the openshiftClient function + * @param {boolean} [settings.loadSpecFromCluster] - load the api spec from a remote cluster. Defaults to false * @param {object|string} [settings.config] - custom config object. String value will assume a config file location * @param {string} [settings.config.url] - Openshift cluster url * @param {string} [settings.config.authUrl] - Openshift Basic auth url @@ -72,7 +73,13 @@ const spec = JSON.parse(zlib.gunzipSync(fs.readFileSync(path.join(__dirname, 'sp */ async function openshiftClient (settings = {}) { let config = settings.config; - const clientConfig = { backend: null, spec, getNames }; + + const clientConfig = { backend: null, /* spec, */ getNames }; + + if (!settings.loadSpecFromCluster) { + clientConfig.spec = spec; + } + const kubeconfig = new KubeConfig(); let fullyUserDefined = false; @@ -143,7 +150,18 @@ async function openshiftClient (settings = {}) { clientConfig.backend = new Request({ kubeconfig }); } - const client = new Client(clientConfig); + let client = new Client(clientConfig); + if (settings.loadSpecFromCluster) { + try { + await client.loadSpec(); + } catch (err) { + // Warn the user there was an error and loading the other spec + console.warn('Warning: Remote client spec unable to load', err.message); + console.warn('Warning: Loading default spec instead'); + clientConfig.spec = spec; + client = new Client(clientConfig); + } + } // CRD with the service instance stuff, but only to this client, not the cluster client.addCustomResourceDefinition(serviceCatalogCRD); diff --git a/test/openshift-client-test.js b/test/openshift-client-test.js index cc96a54..5275697 100644 --- a/test/openshift-client-test.js +++ b/test/openshift-client-test.js @@ -2,6 +2,7 @@ const test = require('tape'); const proxyquire = require('proxyquire'); +const nock = require('nock'); const userDefinedConfig = require('./test-config.json'); @@ -189,3 +190,85 @@ test('test different config - different location as a string', async (t) => { t.equal(kubeconfig.currentContext, 'for-node-client-testing/192-168-99-100:8443/developer', 'current context is correctly loaded'); t.end(); }); + +test('openshift client tests - loadSpecFromCluster', async (t) => { + const openshiftRestClient = require('../'); + + openshiftRestClient.config.loadFromString(JSON.stringify(userDefinedConfig)); + const settings = { + loadSpecFromCluster: true, + config: openshiftRestClient.config + }; + + nock('https://192.168.99.100:8443') + .matchHeader('authorization', 'Bearer zVBd1ZFeJqEAILJgimm4-gZJauaw3PW4EVqV_peEZ3U') + .get('/openapi/v2') + .reply(201, { paths: {} }); + + const osClient = await openshiftRestClient.OpenshiftClient(settings); + + t.pass('client created using the loadSpec function and hitting the /openapi/v2'); + + t.ok(osClient.kubeconfig, 'client should have the kubeconfig object'); + t.end(); +}); + +test('openshift client tests - loadSpecFromCluster - Fail to load remote and load default spec', async (t) => { + const openshiftRestClient = require('../'); + + openshiftRestClient.config.loadFromString(JSON.stringify(userDefinedConfig)); + const settings = { + loadSpecFromCluster: true, + config: openshiftRestClient.config + }; + + nock('https://192.168.99.100:8443') + .matchHeader('authorization', 'Bearer zVBd1ZFeJqEAILJgimm4-gZJauaw3PW4EVqV_peEZ3U') + .get('/openapi/v2') + .reply(404, { message: 'Nope' }) + .get('/swagger.json') + .reply(404, { message: 'Nope' }); + + const osClient = await openshiftRestClient.OpenshiftClient(settings); + + t.pass('Failing client load should load default spec'); + + t.ok(osClient.apis['build.openshift.io'], 'client object should have a build object'); + t.ok(osClient.apis.build, 'build object is aliased'); + + t.ok(osClient.apis['apps.openshift.io'], 'client object should have a apps object'); + t.ok(osClient.apis.app, 'apps object is aliased to app'); + + t.ok(osClient.apis['authorization.openshift.io'], 'client object should have a authorization object'); + t.ok(osClient.apis.authorization, 'authorization object is aliased to authorization'); + + t.ok(osClient.apis['image.openshift.io'], 'client object should have a image object'); + t.ok(osClient.apis.image, 'image object is aliased to image'); + + t.ok(osClient.apis['network.openshift.io'], 'osClient object should have a network object'); + t.ok(osClient.apis.network, 'network object is aliased to network'); + + t.ok(osClient.apis['oauth.openshift.io'], 'osClient object should have a oauth object'); + t.ok(osClient.apis.oauth, 'oauth object is aliased to oauth'); + + t.ok(osClient.apis['project.openshift.io'], 'osClient object should have a project object'); + t.ok(osClient.apis.project, 'project object is aliased to project'); + + t.ok(osClient.apis['quota.openshift.io'], 'osClient object should have a quota object'); + t.ok(osClient.apis.quota, 'quota object is aliased to quota'); + + t.ok(osClient.apis['route.openshift.io'], 'osClient object should have a route object'); + t.ok(osClient.apis.route, 'route object is aliased to route'); + + t.ok(osClient.apis['security.openshift.io'], 'osClient object should have a security object'); + t.ok(osClient.apis.security, 'security object is aliased to security'); + + t.ok(osClient.apis['template.openshift.io'], 'osClient object should have a template object'); + t.ok(osClient.apis.template, 'template object is aliased to template'); + + t.ok(osClient.apis['user.openshift.io'], 'osClient object should have a user object'); + t.ok(osClient.apis.user, 'user object is aliased to user'); + + t.ok(osClient.kubeconfig, 'client should have the kubeconfig object'); + t.end(); +});