Skip to content
Browse files

moved to base minimalistic implementation to provide support for all …

…api endpoints
  • Loading branch information...
1 parent 8cd4cf1 commit c964a79d2a5039fc60106b9eb6d31e4bfff54463 @timsavery timsavery committed Jul 12, 2012
Showing with 103 additions and 429 deletions.
  1. +1 −1 .travis.yml
  2. +9 −16 README.md
  3. +0 −40 lib/asyncJobPoller.js
  4. +62 −3 lib/cloudstack.js
  5. +0 −157 lib/cloudstackClient.js
  6. +0 −43 lib/querystring.js
  7. +0 −55 lib/request.js
  8. +31 −17 package.json
  9. +0 −25 test/client-test.js
  10. +0 −10 test/live/clientargs.js
  11. +0 −10 test/nonlive/clientargs.js
  12. +0 −20 test/nonlive/mocks.js
  13. +0 −32 test/querystring-test.js
View
2 .travis.yml
@@ -2,7 +2,7 @@ language: node_js
node_js:
- 0.4
- 0.6
- - 0.7 # development version of 0.8, may be unstable
+ - 0.8
notifications:
email:
View
25 README.md
@@ -1,6 +1,6 @@
-# node-cloudstack
+# cloudstack
-`node-cloudstack` is a CloudStack client implementation for Node.js.
+`cloudstack` is a minimalistic wrapper for the CloudStack API in Node.js.
## Build Status
@@ -9,21 +9,14 @@
## Usage
```javascript
-var cloudstack = require('cloudstack')
- , templateId = 1
- , serviceOfferingId = 1
- , zoneId = 1;
-
-client.deployVirtualMachine(templateId, serviceOfferingId, zoneId, function(result) {
- var virtualMachineId = result.vmid;
-
- result.emitter.on('success', function() {
- console.log('Machine deployed successfully and it is ready to use!');
- });
+var cloudstack = new (require('./cloudstack'))({
+ apiUri: config.api_uri, // overrides process.env.CLOUDSTACK_API_URI
+ apiKey: config.api_key, // overrides process.env.CLOUDSTACK_API_KEY
+ apiSecret: config.api_secret // overrides process.env.CLOUDSTACK_API_SECRET
+});
- result.emitter.on('fail', function() {
- console.log('Machine failed to deploy.');
- });
+cloudstack.exec('listVirtualMachines', {}, function(error, result) {
+ console.log(result);
});
```
View
40 lib/asyncJobPoller.js
@@ -1,40 +0,0 @@
-function AsyncJobPoller(client, emitter, jobId) {
- this._client = client;
- this._emitter = emitter;
- this._jobId = jobId;
- this._interval = null;
-};
-
-AsyncJobPoller.prototype.poll = function(jobid, callback) {
- this._client.queryAsyncJobResult(jobid, function(res) {
- if (res.status == 'error') {
- callback(res.data);
- } else if (res.status == 'ok') {
- callback(null, res.data);
- }
- });
-};
-
-AsyncJobPoller.prototype.start = function() {
- this._interval = setInterval(function(poller) {
- poller.poll(poller._jobId, function(err, status) {
- if (err) {
- poller._emitter.emit('error', err);
- } else {
- if (status.jobstatus == 1) {
- poller.stop();
- poller._emitter.emit('success', poller._jobId);
- } else if (status.jobstatus == 2) {
- poller.stop();
- poller._emitter.emit('fail', poller._jobId);
- }
- }
- });
- }, 2000, this);
-};
-
-AsyncJobPoller.prototype.stop = function() {
- clearInterval(this._interval);
-};
-
-module.exports = AsyncJobPoller;
View
65 lib/cloudstack.js
@@ -1,7 +1,66 @@
-exports.createClient = function(options) {
+var request = require('request')
+ , crypto = require('crypto');
+
+module.exports = function cloudstack(options) {
if (!options) {
- throw Error('Missing parameter "options".')
+ options = {};
}
- return new (require('./cloudstackClient'))(options);
+ var apiUri = options.apiUri || process.env.CLOUDSTACK_API_URI
+ , apiKey = options.apiKey || process.env.CLOUDSTACK_API_KEY
+ , apiSecret = options.apiSecret || process.env.CLOUDSTACK_API_SECRET;
+
+ this.exec = function(cmd, params, callback) {
+ var paramString = genSignedParamString(
+ apiKey,
+ apiSecret,
+ cmd,
+ params
+ );
+
+ var uri = apiUri + '?' + paramString;
+ request(uri, function(err, res, body) {
+ if (err) {
+ return callback(err);
+ }
+
+ var parsedBody = JSON.parse(body);
+
+ if (res.statusCode == 200) {
+ var result = parsedBody[cmd.toLowerCase() + 'response'];
+
+ return callback(null, result);
+ }
+
+ // TODO: need all the error condition here
+ callback(parsedBody);
+ });
+ };
+
+ var genSignedParamString = function(apiKey, apiSecret, cmd, params) {
+ params.apiKey = apiKey;
+ params.command = cmd;
+ params.response = 'json';
+
+ var paramKeys = [];
+ for(var key in params) {
+ if(params.hasOwnProperty(key)){
+ paramKeys.push(key);
+ };
+ };
+
+ paramKeys.sort();
+
+ var qsParameters = [];
+ for(var i = 0; i < paramKeys.length; i++) {
+ key = paramKeys[i];
+ qsParameters.push(key + '=' + encodeURIComponent(params[key]));
+ }
+
+ var queryString = qsParameters.join('&')
+ , cryptoAlg = crypto.createHmac('sha1', apiSecret)
+ , signature = cryptoAlg.update(queryString.toLowerCase()).digest('base64');
+
+ return queryString + '&signature=' + encodeURIComponent(signature);
+ };
};
View
157 lib/cloudstackClient.js
@@ -1,157 +0,0 @@
-var request = require('./request')
- , AsyncJobPoller = require('./asyncJobPoller')
- , EventEmitter = require('events').EventEmitter;
-
-function CloudStackClient(options) {
- this._options = options;
-};
-
-CloudStackClient.prototype.buildRequestArgs = function(command, params) {
- var args = {
- command: command
- };
-
- for (var o in this._options) {
- args[o] = this._options[o];
- }
-
- if (params) {
- args.params = params;
- }
-
- return(args);
-};
-
-CloudStackClient.prototype.exec = function(command, requiredParams, optionalParams, callback) {
- var params = {};
-
- if (requiredParams) {
- for (p in requiredParams) {
- params[p] = requiredParams[p];
- }
- }
-
- if (optionalParams) {
- for (p in optionalParams) {
- params[p] = optionalParams[p];
- }
- }
-
- var args = this.buildRequestArgs(command, params);
-
- new request(args).exec(callback);
-};
-
-CloudStackClient.prototype.listVirtualMachines = function(params, callback) {
- this.exec('listVirtualMachines', {}, params, callback);
-};
-
-CloudStackClient.prototype.deployVirtualMachine = function(templateId, serviceOfferingId, zoneId/*, params, callback*/) {
- var self = this;
-
- var params, callback;
- if (typeof arguments[3] === 'function') {
- params = {};
- callback = arguments[3];
- } else {
- params = arguments[3];
- callback = arguments[4];
- }
-
- var requiredParams = {
- templateId: templateId,
- serviceOfferingId: serviceOfferingId,
- zoneId: zoneId
- };
-
- this.exec('deployVirtualMachine', requiredParams, params, function(res) {
- if (res.status == 'error') {
- console.log(res);
- }
-
- var vmid = res.data.id;
- var jobid = res.data.jobid;
- var emitter = new EventEmitter();
-
- new AsyncJobPoller(self, emitter, jobid).start();
-
- callback({
- vmid: vmid,
- emitter: emitter
- });
- });
-};
-
-CloudStackClient.prototype.destroyVirtualMachine = function(id, callback) {
- this.exec('destroyVirtualMachine', { id: id }, {}, callback);
-};
-
-CloudStackClient.prototype.stopVirtualMachine = function(id, callback) {
- this.exec('stopVirtualMachine', { id: id }, {}, callback);
-};
-
-CloudStackClient.prototype.startVirtualMachine = function(id, callback) {
- this.exec('startVirtualMachine', { id: id }, {}, callback);
-};
-
-CloudStackClient.prototype.rebootVirtualMachine = function(id, callback) {
- this.exec('rebootVirtualMachine', { id: id }, {}, callback);
-};
-
-CloudStackClient.prototype.updateVirtualMachine = function(id/*, params, callback*/) {
- var params, callback;
- if (typeof arguments[1] === 'function') {
- params = {};
- callback = arguments[1];
- } else {
- params = arguments[1];
- callback = arguments[2];
- }
-
- this.exec('updateVirtualMachine', { id: id }, params, callback);
-};
-
-CloudStackClient.prototype.queryAsyncJobResult = function(jobid, callback) {
- this.exec('queryAsyncJobResult', { jobid: jobid }, {}, callback);
-};
-
-CloudStackClient.prototype.listTemplates = function(templateFilter/*, params, callback*/) {
- var params, callback;
- if (typeof arguments[1] === 'function') {
- params = {};
- callback = arguments[1];
- } else {
- params = arguments[1];
- callback = arguments[2];
- }
-
- this.exec('listTemplates', { templateFilter: templateFilter }, params, callback);
-};
-
-CloudStackClient.prototype.listServiceOfferings = function(/*params, callback*/) {
- var params, callback;
- if (typeof arguments[0] === 'function') {
- params = {};
- callback = arguments[0];
- } else {
- params = arguments[0];
- callback = arguments[1];
- }
-
- this.exec('listServiceOfferings', {}, params, callback);
-};
-
-CloudStackClient.prototype.listZones = function(/*params, callback*/) {
- var params, callback;
- if (typeof arguments[0] === 'function') {
- params = {};
- callback = arguments[0];
- } else {
- params = arguments[0];
- callback = arguments[1];
- }
-
- this.exec('listZones', {}, params, callback);
-};
-
-module.exports = CloudStackClient;
View
43 lib/querystring.js
@@ -1,43 +0,0 @@
-var crypto = require('crypto')
- , qs = require('querystring');
-
-function QueryString(options) {
- this.options = options || {};
-
- this.params = {};
- this.params['apiKey'] = this.options.apiKey;
-}
-
-QueryString.prototype.addParam = function(key, value) {
- if (!key) { throw Error('Missing parameter "key"'); }
- if (key.length == 0) { throw Error('Value for parameter "key" cannot be empty.'); }
-
- if (!value) { throw Error('Missing value for key "' + key + '"'); }
- if (value.length == 0) { throw Error('Value for key "' + key + '" cannot be empty.'); }
-
- this.params[key] = encodeURIComponent(value);
-};
-
-QueryString.prototype.toString = function() {
- // sort keys as required by documentation to sign
- var keys = [];
- for(var key in this.params) {
- if(this.params.hasOwnProperty(key)){
- keys.push(key);
- };
- };
- keys.sort();
-
- var components = [];
- for(var i=0; i<keys.length; i++) {
- key = keys[i];
- components.push(key + '=' + this.params[key]);
- }
- var str = components.join('&');
-
- var sig = crypto.createHmac('sha1', this.options.apiSecret).update(str.toLowerCase()).digest('base64');
- str += '&signature=' + encodeURIComponent(sig);
- return str;
-};
-
-module.exports = QueryString;
View
55 lib/request.js
@@ -1,55 +0,0 @@
-var HttpRequest = require('request')
- , QueryString = require('./querystring');
-
-function Request(options) {
- this.options = options;
-
- this.qs = new (QueryString)({
- apiKey: this.options.apiKey || '',
- apiSecret: this.options.apiSecret || ''
- });
-
- this.qs.addParam('command', this.options.command || '');
- this.qs.addParam('response', this.options.response || 'json');
-
- // add additional parameters if specified
- if (options.params) {
- for (var p in options.params) {
- this.qs.addParam(p, options.params[p]);
- }
- }
-
- // generate the uri
- this.uri = 'http://#host:#port/client/api?#qs'
- .replace(/#host/, this.options.host || 'localhost')
- .replace(/#port/, this.options.port || 8080)
- .replace(/#qs/, this.qs.toString());
-};
-
-Request.prototype.exec = function(callback) {
- var self = this;
-
- if (!callback) callback = function() {};
-
- HttpRequest(this.uri, function(err, res, body) {
- if (err) {
- return callback({ status: 'error', data: err });
- }
-
- var data = JSON.parse(body)[self.options.command.toLowerCase() + 'response'];
-
- if (res.statusCode == 200) {
- return callback({
- status: 'ok',
- data: data
- });
- } else {
- return callback({
- status: 'error',
- data: data
- });
- }
- });
-};
-
-module.exports = Request;
View
48 package.json
@@ -1,18 +1,32 @@
{
- "name" : "cloudstack"
- , "version" : "0.1.1"
- , "author" : "Chatham <oss@chathamfinancial.com>"
- , "repository" : { "type": "git",
- "url": "http://github.com/chatham/node-cloudstack.git" }
- , "description" : "An API wrapper for CloudStack."
- , "contributors" : [{ "name": "Chris Hoffman", "email": "choffman@chathamfinancial.com" },
- { "name": "Tim Savery" , "email": "tsavery@chathamfinancial.com" }]
- , "scripts" : { "test": "vows" }
- , "keywords" : [ "cloudstack", "api", "cloud" ]
- , "dependencies" : { "request": "2.2.x" }
- , "devDependencies" : { "nodeunit": "0.7.x", "nock": "0.10.x" }
- , "license" : "MIT"
- , "main" : "./lib/cloudstack"
- , "scripts" : { "test": "nodeunit test" }
- , "engines" : { "node": ">= 0.4.0" }
-}
+ "name": "cloudstack",
+ "version": "0.2.0",
+ "author": "Chatham Financial <oss@chathamfinancial.com>",
+ "repository": {
+ "type": "git",
+ "url": "http://github.com/chatham/node-cloudstack.git"
+ },
+ "description": "An API wrapper for CloudStack.",
+ "contributors": [{
+ "name": "Tim Savery",
+ "email": "tsavery@chathamfinancial.com"
+ },{
+ "name": "Chris Hoffman",
+ "email": "choffman@chathamfinancial.com"
+ }
+ ],
+ "keywords": [
+ "cloudstack",
+ "api",
+ "cloud"
+ ],
+ "dependencies": {
+ "crypto": "0.0.3",
+ "request": "2.9.203"
+ },
+ "license": "MIT",
+ "main": "./lib/cloudstack",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+}
View
25 test/client-test.js
@@ -1,25 +0,0 @@
-var client = require('../lib/cloudstackClient');
-
-var args = (process.env.LIVE)
- ? require('./live/clientargs')
- : require('./nonlive/clientargs');
-
-if (!process.env.LIVE) {
- var mocks = require('./nonlive/mocks');
- (new mocks()).engage();
-}
-
-var client = new client(args);
-
-exports['deployVirtualMachine'] = function(test) {
- client.deployVirtualMachine(211, 1, 1, { domainId:1, account:"TestUser" }, function(result) {
- result.emitter.on('success', function() {
- test.done();
- });
-
- result.emitter.on('fail', function() {
- test.fail();
- test.done();
- });
- });
-};
View
10 test/live/clientargs.js
@@ -1,10 +0,0 @@
-var config = {
- "host": "xxx.xxx.xxx.xxx",
- "account": "xxx",
- "apiKey": "xxx",
- "apiSecret": "xxx",
- "domainId": 1,
- "response": "json"
-}
-
-module.exports = config;
View
10 test/nonlive/clientargs.js
@@ -1,10 +0,0 @@
-var config = {
- "host": "Host",
- "account": "TestUser",
- "apiKey": "TestUserApiKey",
- "apiSecret": "TestUserApiSecret",
- "domainId": 1,
- "response": "json"
-};
-
-module.exports = config;
View
20 test/nonlive/mocks.js
@@ -1,20 +0,0 @@
-var nock = require('nock');
-
-module.exports = function mocks() {
- this.engage = function() {
- nock("http://host:8080")
- .get('/client/api?account=TestUser&apiKey=TestUserApiKey&command=deployVirtualMachine&domainId=1&response=json&serviceOfferingId=1&templateId=211&zoneId=1&signature=7wdCP6pJhVHL4ULQJzyh60Xd9Nw%3D')
- .reply(200, {
- deployvirtualmachineresponse: {
- jobid: 123,
- id: 456
- }
- })
- .get('/client/api?apiKey=TestUserApiKey&command=queryAsyncJobResult&jobid=123&response=json&signature=7%2BzLnDw0TvYezkZovSYda2AABxY%3D')
- .reply(200, {
- queryasyncjobresultresponse: {
- jobstatus: 1
- }
- });
- };
-};
View
32 test/querystring-test.js
@@ -1,32 +0,0 @@
-var querystring = require('../lib/querystring');
-
-exports['when constructed with an "apiKey" value in the options'] = function(test) {
- var qs = new querystring({
- apiKey: 'TestApiKey'
- });
-
- test.equal(qs.params.apiKey, 'TestApiKey');
-
- test.done();
-};
-
-exports['when a parameter is added'] = function(test) {
- var qs = new querystring();
- qs.addParam('Property', 'Property Value');
-
- test.equal(qs.params.Property, 'Property%20Value');
-
- test.done();
-};
-
-exports['when converted to a string'] = function(test) {
- var qs = new querystring({
- apiKey: 'ApiKeyString',
- apiSecret: 'ApiSecretString'
- }).toString();
-
- test.ok(qs.match(/apiKey=ApiKeyString/));
- test.ok(qs.match(/signature=9MQbwfKzCXVlfuNV80wmrXnzL%2B4%3D/));
-
- test.done();
-};

0 comments on commit c964a79

Please sign in to comment.
Something went wrong with that request. Please try again.