From e09ccbd473f2ed9fb9142e24a1a88472e5ad2997 Mon Sep 17 00:00:00 2001 From: msmol Date: Wed, 27 Jul 2016 15:55:34 -0400 Subject: [PATCH] Added basic Keystone authentication authenticate() is called outside the constructor so that the Keystone class plays nicely with Promises. (It would be weird for the constructor to return a Promise) Change-Id: I3f9008d4d954c340178c77ec0433c5a1ddc971d2 --- src/keystone.js | 52 +++++++++++++++++++++++++++ test/unit/keystoneTest.js | 74 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 src/keystone.js create mode 100644 test/unit/keystoneTest.js diff --git a/src/keystone.js b/src/keystone.js new file mode 100644 index 0000000..d87d1d9 --- /dev/null +++ b/src/keystone.js @@ -0,0 +1,52 @@ +import 'isomorphic-fetch'; +import log from 'loglevel'; + +log.setLevel('INFO'); + +export default class Keystone { + + constructor(cloudsConfig, cloudName) { + if (cloudsConfig.clouds.hasOwnProperty(cloudName)) { + this.cloudConfig = cloudsConfig.clouds[cloudName]; + } else { + throw new Error('Config for this cloud not found'); + } + + } + + authenticate() { + const headers = { + 'Content-Type': 'application/json' + }; + const body = { + auth: { + identity: { + methods: ['password'], + password: { + user: { + name: this.cloudConfig.auth.username, + password: this.cloudConfig.auth.password + } + } + } + } + }; + const init = { + method: 'POST', + headers: headers, + body: JSON.stringify(body) + }; + + return fetch(this.cloudConfig.auth.auth_url, init) + .then((res) => { + this.token = res.headers.get('X-Subject-Token'); + return res.json(); // This returns a promise... + }) + .then((body) => { + this.catalog = body.catalog || {}; + }) + .catch((reason) => { + return reason; + }); + } +} diff --git a/test/unit/keystoneTest.js b/test/unit/keystoneTest.js new file mode 100644 index 0000000..664dcf0 --- /dev/null +++ b/test/unit/keystoneTest.js @@ -0,0 +1,74 @@ +import Keystone from '../../src/keystone.js'; +import fetchMock from 'fetch-mock'; + +describe('Openstack connection test', () => { + it('should export a class', () => { + const keystone = new Keystone(aCloudsConfig('cloud1'), 'cloud1'); + expect(keystone).toBeDefined(); + }); + + it('should throw an error for an unknown cloud', () => { + const cloudsConfig = aCloudsConfig('cloud1'); + const cloudName = 'cloud2'; + + try { + const keystone = new Keystone(cloudsConfig, cloudName); + keystone.authenticate(); + } catch (e) { + expect(e.message).toEqual('Config for this cloud not found'); + } + }); + + it('should authenticate', (done) => { + const cloudsConfig = aCloudsConfig('cloud1'); + + const authUrl = cloudsConfig.clouds.cloud1.auth.auth_url; + + const cloudName = 'cloud1'; + + fetchMock + .post(authUrl, { + body: { + catalog: { + foo: 'bar' + } + }, + headers: { + 'X-Subject-Token': 'the-token' + } + }); + + const keystone = new Keystone(cloudsConfig, cloudName); + + keystone.authenticate() + .then(() => { + expect(fetchMock.called(authUrl)).toEqual(true); + expect(typeof keystone.token).toEqual('string'); + expect(keystone.token).toEqual('the-token'); + expect(keystone.catalog).toEqual({foo: 'bar'}); + done(); + }) + .catch((reason) => { + expect(reason).toBeUndefined(); + done(); + }); + }); + + function aCloudsConfig (name) { + const cloudsConfig = { + clouds: {} + }; + + cloudsConfig.clouds[name] = { + region_name: 'Region1', + auth: { + username: 'user', + password: 'pass', + project_name: 'js-openstack-lib', + auth_url: 'http://keystone' + } + }; + + return cloudsConfig; + } +});