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 bunyannoop = require('./bunyannoop');
var common = require('./common');
var errors = require('./errors');
var SaferJsonClient = require('./SaferJsonClient');
@ -199,21 +200,24 @@ CloudApi.prototype._path = function _path(subpath /* , qparams, ... */) {
/**
* 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} method - HTTP(s) request method
* - {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;
if (typeof (options) === 'string')
options = {path: options};
assert.object(options, 'options');
assert.func(callback, 'callback');
assert.optionalObject(options.data, 'options.data');
if (typeof (opts) === 'string')
opts = {path: opts};
assert.object(opts, 'opts');
assert.optionalObject(opts.data, 'opts.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,
'invalid method given');
switch (method) {
@ -226,17 +230,20 @@ CloudApi.prototype._request = function _request(options, callback) {
self._getAuthHeaders(function (err, headers) {
if (err) {
callback(err);
cb(err);
return;
}
var opts = {
path: options.path,
if (opts.headers) {
common.objMerge(headers, opts.headers);
}
var reqOpts = {
path: opts.path,
headers: headers
};
if (options.data)
self.client[method](opts, options.data, callback);
if (opts.data)
self.client[method](reqOpts, opts.data, cb);
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) {
var s = String(n);
assert.number(width, 'width');
@ -852,6 +878,7 @@ function tildeSync(s) {
module.exports = {
objCopy: objCopy,
deepObjCopy: deepObjCopy,
objMerge: objMerge,
zeroPad: zeroPad,
boolFromString: boolFromString,
jsonStream: jsonStream,

View File

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