@yawetse/pkgcloud
Version:
An infrastructure-as-a-service agnostic cloud library for node.js
370 lines (323 loc) • 10.5 kB
JavaScript
/*
* stacks.js: Instance methods for working with stacks from OpenStack Orchestration
*
* (C) 2014 Rackspace
* Ken Perkins
* MIT LICENSE
*/
var pkgcloud = require('../../../../../lib/pkgcloud'),
errs = require('errs'),
urlJoin = require('url-join'),
util = require('util'),
_ = require('underscore'),
orchestration = pkgcloud.providers.openstack.orchestration;
var _urlPrefix = '/stacks';
/**
* validateProperties
*
* @description local helper function for validating arguments
*
* @param {Array} required The list of required properties
* @param {object} options The options object to validate
* @param {String} formatString String formatter for the error message
* @param {Function} callback
* @returns {boolean}
*/
function validateProperties(required, options, formatString, callback) {
return !required.some(function (item) {
if (typeof(options[item]) === 'undefined') {
errs.handle(
errs.create({ message: util.format(formatString, item) }),
callback
);
return true;
}
return false;
});
}
/**
* client.getStack
*
* @description Gets a stack from the account
*
* @param {String|object} stack The stack or stackName to fetch
* @param {Function} callback
* @returns {request|*}
*/
exports.getStack = function (stack, callback) {
var self = this,
path = stack instanceof orchestration.Stack
? urlJoin(_urlPrefix, stack.name, stack.id)
: urlJoin(_urlPrefix, stack);
return this._request({
path: path
}, function (err, body) {
if (err) {
return callback(err);
}
if (!body.stack) {
return new Error('Unexpected empty response');
}
else {
callback(null, new orchestration.Stack(self, body.stack));
}
});
};
/**
* client.getStacks
*
* @description get the list of stacks for the current account
*
* @param {object|Function} [options] A set of options for the getStacks call
* @param {function} callback f(err, stacks) where stacks is an array of Stack
* @returns {*}
*/
exports.getStacks = function getStacks(options, callback) {
var self = this;
if (typeof options === 'function') {
callback = options;
options = {};
}
var requestOptions = {
path: _urlPrefix
};
requestOptions.qs = _.pick(options,
'status',
'name',
'limit',
'marker',
'sort');
if (options.sortDir) {
requestOptions.qs['sort_dir'] = options.sortDir;
}
if (options.sortKeys) {
requestOptions.qs['sort_keys'] = options.sortKeys;
}
return this._request(requestOptions, function (err, body) {
if (err) {
callback(err);
return;
}
callback(err, body.stacks.map(function(stack) {
return new orchestration.Stack(self, stack);
}));
});
};
/**
* client.createStack
*
* @description Creates a stack with the specified options.
* @param {object} details the details to create this stack
* @param {String} details.name the name of the new stack
* @param {Number} details.timeout timeout in minutes for stack creation
* @param {String} [details.environment] the environment for the stack
* @param {Object} [details.template] template for the stack, required unless templateUrl is provided
* @param {String} [details.templateUrl] url for the template, required if no template
* @param {Object} [details.parameters] optional parameters configuration
* @param {Object} [details.files] optional files configuration
* @param callback
* @returns {request|null}
*/
exports.createStack = function (details, callback) {
if (typeof details === 'function') {
callback = details;
details = {};
}
details = details || {};
if (!validateProperties(['name', 'timeout'], details,
'options.%s is a required argument.', callback)) {
return;
}
if (!details.templateUrl && !details.template) {
callback(new Error('one of template or templateUrl are required'));
return;
}
var self = this,
createOptions = {
method: 'POST',
path: details.preview ? urlJoin(_urlPrefix, 'preview'): _urlPrefix,
body: {
stack_name: details.name,
// environment is required from the API, but may be empty
environment: details.environment ? JSON.stringify(details.environment) : JSON.stringify({}),
timeout_mins: typeof details.timeout === 'number' ? details.timeout : parseInt(details.timeout)
}
};
if (details.template) {
createOptions.body.template = details.template;
}
else if (details.templateUrl) {
createOptions.body['template_url'] = details.templateUrl;
}
if (details.parameters) {
createOptions.body.parameters = details.parameters;
}
if (details.files) {
createOptions.body.files = details.files;
}
// Adopt Stack options
if (details.stackData) {
createOptions.body['adopt_stack_data'] = JSON.stringify(details.stackData);
if (typeof details.disableRollback === 'boolean') {
createOptions.body['disable_rollback'] = details.disableRollback;
}
// if we're adopting a stack, copy the parameters (if any) from the stackData to
// the request payload
if (details.stackData.parameters) {
createOptions.body.parameters = details.stackData.parameters;
}
}
return self._request(createOptions, function (err, body) {
if (err) {
return callback(err);
}
if (!body || !body.stack) {
return new Error('Stack not passed back from OpenStack.');
}
// since createStack returns an href to the stack, lets go fetch it
self.getStack(body.stack.id, callback);
});
};
/**
* client.previewStack
*
* @description Preview a stack creation with the specified options.
* @param {object} details the details to create this stack
* @param {String} details.name the name of the new stack
* @param {String} details.environment the environment for the stack
* @param {Number} details.timeout timeout in minutes for stack creation
* @param {Object} [details.template] template for the stack, required unless templateUrl is provided
* @param {String} [details.templateUrl] url for the template, required if no template
* @param {Object} [details.parameters] optional parameters configuration
* @param {Object} [details.files] optional files configuration
* @param callback
* @returns {request|*}
*/
exports.previewStack = function(details, callback) {
return this.createStack(_.extend(details, { preview: true }), callback);
};
/**
* client.adoptStack
*
* @description adopt a stack from previously abandoned resources
* @param {object} details the details to create this stack
* @param {String} details.name the name of the new stack
* @param {String} details.environment the environment for the stack
* @param {Number} details.timeout timeout in minutes for stack creation
* @param {Object} details.stackData Object with stack data to adopt
* @param {Boolean} details.disableRollback Controls whetehr a failure during stack creation causes deletion
* @param {Object} [details.template] template for the stack, required unless templateUrl is provided
* @param {String} [details.templateUrl] url for the template, required if no template
* @param {Object} [details.parameters] optional parameters configuration
* @param {Object} [details.files] optional files configuration
* @param callback
* @returns {request|*}
*/
exports.adoptStack = function (details, callback) {
return this.createStack(details, callback);
};
/**
* client.updateStack
*
* @description Update a stack
*
* @param {String|object} stack The stack or stackName to update
* @param {Function} callback
* @returns {request|*}
*/
exports.updateStack = function (stack, callback) {
if (!(stack instanceof orchestration.Stack)) {
callback(new Error('you must provide a stack to update'));
return;
}
return this._request({
path: urlJoin(_urlPrefix, stack.name, stack.id),
body: {
template_url: stack.templateUrl,
template: stack.template,
files: stack.files,
environment: stack.environment,
parameters: stack.parameters,
timeout_mins: stack.timeout
},
method: 'PUT'
}, function (err) {
if (err) {
return callback(err);
}
callback(err, stack);
});
};
/**
* client.deleteStack
*
* @description Delete a stack from the account
*
* @param {String|object} stack The stack or stackName to delete
* @param {Function} callback
* @returns {request|*}
*/
exports.deleteStack = function (stack, callback) {
var self = this;
var deleteStack = function(stack) {
return self._request({
path: urlJoin(_urlPrefix, stack.name, stack.id),
method: 'DELETE'
}, function(err) {
if (err) {
return callback(err);
}
callback(err, true);
});
};
if (typeof stack === 'string') {
self.getStack(stack, function(err, stack) {
if (err) {
return callback(err);
}
return deleteStack(stack);
});
} else if (stack instanceof orchestration.Stack) {
return deleteStack(stack);
} else {
return callback(new Error('stack must be a string or stack instance'));
}
};
/**
* client.abandonStack
*
* @description Delete a stack, but preserve the created resources
*
* @param {String|object} stack The stack or stackName to abandon
* @param {Function} callback
* @returns {request|*}
*/
exports.abandonStack = function (stack, callback) {
var self = this;
function abandon(stack) {
return self._request({
path: urlJoin(_urlPrefix, stack.name, stack.id, 'abandon'),
method: 'DELETE'
}, function (err, abandonStackData) {
if (err) {
return callback(err);
}
callback(err, abandonStackData);
});
}
if (typeof stack === 'string') {
self.getStack(stack, function(err, stack) {
if (err) {
callback(err);
return;
}
abandon(stack);
});
return;
}
else if (!(stack instanceof orchestration.stack)) {
callback(new Error('stack must be a string or stack instance'));
return;
}
abandon(stack);
};