Add '-d,--data DATA' option to triton cloudapi ...

Also fix '-H,--header' option to `triton cloudapi`. It never worked.
This commit is contained in:
Trent Mick 2015-12-09 11:59:47 -08:00
parent 6e44318f00
commit d25df7c011
3 changed files with 97 additions and 37 deletions

View File

@ -43,6 +43,7 @@ var vasync = require('vasync');
var auth = require('smartdc-auth'); var auth = require('smartdc-auth');
var bunyannoop = require('./bunyannoop'); var bunyannoop = require('./bunyannoop');
var common = require('./common');
var errors = require('./errors'); var errors = require('./errors');
var SaferJsonClient = require('./SaferJsonClient'); var SaferJsonClient = require('./SaferJsonClient');
@ -199,21 +200,24 @@ CloudApi.prototype._path = function _path(subpath /* , qparams, ... */) {
/** /**
* Cloud API request wrapper - modeled after http.request * Cloud API request wrapper - modeled after http.request
* *
* @param {Object|String} options - object or string for endpoint * @param {Object|String} opts - object or string for endpoint
* - {String} path - URL endpoint to hit * - {String} path - URL endpoint to hit
* - {String} method - HTTP(s) request method * - {String} method - HTTP(s) request method
* - {Object} data - data to be passed * - {Object} data - data to be passed
* @param {Function} callback passed via the restify client * - {Object} headers - optional additional request headers
* @param {Function} cb passed via the restify client
*/ */
CloudApi.prototype._request = function _request(options, callback) { CloudApi.prototype._request = function _request(opts, cb) {
var self = this; var self = this;
if (typeof (options) === 'string') if (typeof (opts) === 'string')
options = {path: options}; opts = {path: opts};
assert.object(options, 'options'); assert.object(opts, 'opts');
assert.func(callback, 'callback'); assert.optionalObject(opts.data, 'opts.data');
assert.optionalObject(options.data, 'options.data'); assert.optionalString(opts.method, 'opts.method');
assert.optionalObject(opts.headers, 'opts.headers');
assert.func(cb, 'cb');
var method = (options.method || 'GET').toLowerCase(); var method = (opts.method || 'GET').toLowerCase();
assert.ok(['get', 'post', 'put', 'delete', 'head'].indexOf(method) >= 0, assert.ok(['get', 'post', 'put', 'delete', 'head'].indexOf(method) >= 0,
'invalid method given'); 'invalid method given');
switch (method) { switch (method) {
@ -226,17 +230,20 @@ CloudApi.prototype._request = function _request(options, callback) {
self._getAuthHeaders(function (err, headers) { self._getAuthHeaders(function (err, headers) {
if (err) { if (err) {
callback(err); cb(err);
return; return;
} }
var opts = { if (opts.headers) {
path: options.path, common.objMerge(headers, opts.headers);
}
var reqOpts = {
path: opts.path,
headers: headers headers: headers
}; };
if (options.data) if (opts.data)
self.client[method](opts, options.data, callback); self.client[method](reqOpts, opts.data, cb);
else else
self.client[method](opts, callback); self.client[method](reqOpts, cb);
}); });
}; };

View File

@ -46,6 +46,32 @@ function deepObjCopy(obj) {
} }
/*
* Merge given objects into the given `target` object. Last one wins.
* The `target` is modified in place.
*
* var foo = {bar: 32};
* objMerge(foo, {bar: 42}, {bling: 'blam'});
*
* Adapted from tunnel-agent `mergeOptions`.
*/
function objMerge(target) {
for (var i = 1, len = arguments.length; i < len; ++i) {
var overrides = arguments[i];
if (typeof (overrides) === 'object') {
var keys = Object.keys(overrides);
for (var j = 0, keyLen = keys.length; j < keyLen; ++j) {
var k = keys[j];
if (overrides[k] !== undefined) {
target[k] = overrides[k];
}
}
}
}
return target;
}
function zeroPad(n, width) { function zeroPad(n, width) {
var s = String(n); var s = String(n);
assert.number(width, 'width'); assert.number(width, 'width');
@ -852,6 +878,7 @@ function tildeSync(s) {
module.exports = { module.exports = {
objCopy: objCopy, objCopy: objCopy,
deepObjCopy: deepObjCopy, deepObjCopy: deepObjCopy,
objMerge: objMerge,
zeroPad: zeroPad, zeroPad: zeroPad,
boolFromString: boolFromString, boolFromString: boolFromString,
jsonStream: jsonStream, jsonStream: jsonStream,

View File

@ -12,6 +12,9 @@
var http = require('http'); var http = require('http');
var errors = require('./errors');
function do_cloudapi(subcmd, opts, args, callback) { function do_cloudapi(subcmd, opts, args, callback) {
if (opts.help) { if (opts.help) {
this.do_help('help', {}, [subcmd], callback); this.do_help('help', {}, [subcmd], callback);
@ -21,34 +24,50 @@ function do_cloudapi(subcmd, opts, args, callback) {
return; return;
} }
var path = args[0]; // Get `reqOpts` from given options.
var method = opts.method;
var reqopts = { if (!method) {
method: opts.method.toLowerCase(), if (opts.data) {
method = 'PUT';
} else {
method = 'GET';
}
}
var reqOpts = {
method: method.toLowerCase(),
headers: {}, headers: {},
path: path path: args[0]
}; };
if (opts.header) {
// parse -H headers
for (var i = 0; i < opts.header.length; i++) { for (var i = 0; i < opts.header.length; i++) {
var raw = opts.header[i]; var raw = opts.header[i];
var j = raw.indexOf(':'); var j = raw.indexOf(':');
if (j < 0) { if (j < 0) {
callback(new Error('failed to parse header: ' + raw)); callback(new errors.TritonError(
'failed to parse header: ' + raw));
return; return;
} }
var header = raw.substr(0, j); var header = raw.substr(0, j);
var value = raw.substr(j + 1).leftTrim(); var value = raw.substr(j + 1).trimLeft();
reqOpts.headers[header] = value;
reqopts.headers[header] = value; }
}
if (opts.data) {
try {
reqOpts.data = JSON.parse(opts.data);
} catch (parseErr) {
callback(new errors.TritonError(parseErr,
'given <data> is not valid JSON: ' + parseErr.message));
return;
}
} }
this.tritonapi.cloudapi._request(reqopts, function (err, req, res, body) { this.tritonapi.cloudapi._request(reqOpts, function (err, req, res, body) {
if (err) { if (err) {
callback(err); callback(err);
return; return;
} }
if (opts.headers || reqopts.method === 'head') { if (opts.headers || reqOpts.method === 'head') {
console.error('%s/%s %d %s', console.error('%s/%s %d %s',
req.connection.encrypted ? 'HTTPS' : 'HTTP', req.connection.encrypted ? 'HTTPS' : 'HTTP',
res.httpVersion, res.httpVersion,
@ -60,7 +79,7 @@ function do_cloudapi(subcmd, opts, args, callback) {
console.error(); console.error();
} }
if (reqopts.method !== 'head') if (reqOpts.method !== 'head')
console.log(JSON.stringify(body, null, 4)); console.log(JSON.stringify(body, null, 4));
callback(); callback();
}); });
@ -75,26 +94,33 @@ do_cloudapi.options = [
{ {
names: ['method', 'X'], names: ['method', 'X'],
type: 'string', type: 'string',
default: 'GET', helpArg: '<method>',
help: 'Request method to use. Default is "GET".' help: 'Request method to use. Default is "GET".'
}, },
{ {
names: ['header', 'H'], names: ['header', 'H'],
type: 'arrayOfString', type: 'arrayOfString',
default: [], helpArg: '<header>',
help: 'Headers to send with request.' help: 'Headers to send with request.'
}, },
{ {
names: ['headers', 'i'], names: ['headers', 'i'],
type: 'bool', type: 'bool',
help: 'Print response headers to stderr.' help: 'Print response headers to stderr.'
},
{
names: ['data', 'd'],
type: 'string',
helpArg: '<data>',
help: 'Add POST data. This must be valid JSON.'
} }
]; ];
do_cloudapi.help = ( do_cloudapi.help = (
'Raw cloudapi request.\n' 'Raw cloudapi request.\n'
+ '\n' + '\n'
+ 'Usage:\n' + 'Usage:\n'
+ ' {{name}} cloudapi [-X method] [-H header=value] <endpoint>\n' + ' {{name}} cloudapi [-X <method>] [-H <header=value>] \\\n'
+ ' [-d <data>] <endpoint>\n'
+ '\n' + '\n'
+ '{{options}}' + '{{options}}'
); );