add packages, remove tabulate, put stuff in common
This commit is contained in:
parent
8ff2fcb53c
commit
7afaadbd29
@ -107,6 +107,11 @@ CLI.prototype.do_create = require('./do_create');
|
|||||||
CLI.prototype.do_instances = require('./do_instances');
|
CLI.prototype.do_instances = require('./do_instances');
|
||||||
CLI.prototype.do_instance_audit = require('./do_instance_audit');
|
CLI.prototype.do_instance_audit = require('./do_instance_audit');
|
||||||
|
|
||||||
|
// Packages
|
||||||
|
CLI.prototype.do_packages = require('./do_packages');
|
||||||
|
|
||||||
|
// Row Cloud API
|
||||||
|
CLI.prototype.do_cloudapi = require('./do_cloudapi');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -153,25 +153,18 @@ CloudAPI.prototype._getAuthHeaders = function _getAuthHeaders(callback) {
|
|||||||
* fields. If any of the field values are undefined or null, then they will
|
* fields. If any of the field values are undefined or null, then they will
|
||||||
* be excluded.
|
* be excluded.
|
||||||
*/
|
*/
|
||||||
CloudAPI.prototype._qs = function _qs(fields, fields2) {
|
CloudAPI.prototype._qs = function _qs(/* fields1, ...*/) {
|
||||||
assert.object(fields, 'fields');
|
var fields = Array.prototype.slice.call(arguments);
|
||||||
assert.optionalObject(fields2, 'fields2'); // can be handy to pass in 2 objs
|
|
||||||
|
|
||||||
var query = {};
|
var query = {};
|
||||||
Object.keys(fields).forEach(function (key) {
|
fields.forEach(function (field) {
|
||||||
var value = fields[key];
|
Object.keys(field).forEach(function (key) {
|
||||||
if (value !== undefined && value !== null) {
|
var value = field[key];
|
||||||
query[key] = value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (fields2) {
|
|
||||||
Object.keys(fields2).forEach(function (key) {
|
|
||||||
var value = fields2[key];
|
|
||||||
if (value !== undefined && value !== null) {
|
if (value !== undefined && value !== null) {
|
||||||
query[key] = value;
|
query[key] = value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
|
|
||||||
if (Object.keys(query).length === 0) {
|
if (Object.keys(query).length === 0) {
|
||||||
return '';
|
return '';
|
||||||
@ -189,20 +182,47 @@ CloudAPI.prototype._qs = function _qs(fields, fields2) {
|
|||||||
* Optionally an object of query params can be passed in to include a query
|
* Optionally an object of query params can be passed in to include a query
|
||||||
* string. This just calls `this._qs(...)`.
|
* string. This just calls `this._qs(...)`.
|
||||||
*/
|
*/
|
||||||
CloudAPI.prototype._path = function _path(subpath, qparams, qparams2) {
|
CloudAPI.prototype._path = function _path(subpath /*, qparams, ... */) {
|
||||||
assert.string(subpath, 'subpath');
|
assert.string(subpath, 'subpath');
|
||||||
assert.ok(subpath[0] === '/');
|
assert.ok(subpath[0] === '/');
|
||||||
assert.optionalObject(qparams, 'qparams');
|
|
||||||
assert.optionalObject(qparams2, 'qparams2'); // can be handy to pass in 2
|
|
||||||
|
|
||||||
var path = subpath;
|
var path = subpath;
|
||||||
if (qparams) {
|
var qparams = Array.prototype.slice.call(arguments, 1);
|
||||||
path += this._qs(qparams, qparams2);
|
path += this._qs.apply(this, qparams);
|
||||||
}
|
|
||||||
return path;
|
return path;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cloud API requset wrapper - modeled after http.request
|
||||||
|
*
|
||||||
|
* @param {Object|String} options - object or string for endpoint
|
||||||
|
* - {String} path - URL endpoint to hit
|
||||||
|
* - {String} method - HTTP(s) request method
|
||||||
|
* @param {Function} callback passed via the restify client
|
||||||
|
*/
|
||||||
|
CloudAPI.prototype.request = function _request(options, callback) {
|
||||||
|
var self = this;
|
||||||
|
if (typeof options === 'string')
|
||||||
|
options = {path: options};
|
||||||
|
assert.object(options, 'options');
|
||||||
|
assert.func(callback, 'callback');
|
||||||
|
|
||||||
|
var method = (options.method || 'GET').toLowerCase();
|
||||||
|
assert.ok(['get', 'post', 'delete', 'head'].indexOf(method) >= 0,
|
||||||
|
'invalid method given');
|
||||||
|
self._getAuthHeaders(function (err, headers) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var opts = {
|
||||||
|
path: options.path,
|
||||||
|
headers: headers
|
||||||
|
};
|
||||||
|
self.client[method](opts, callback);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// ---- accounts
|
// ---- accounts
|
||||||
|
|
||||||
@ -408,9 +428,25 @@ CloudAPI.prototype.listMachines = function listMachines(options, callback) {
|
|||||||
callback(null, machines, responses);
|
callback(null, machines, responses);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
CloudAPI.prototype.listPackages = function listPackages(options, callback) {
|
||||||
|
var self = this;
|
||||||
|
if (typeof (options) === 'function') {
|
||||||
|
callback = options;
|
||||||
|
options = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
var endpoint = self._path(format('/%s/packages', self.user), options);
|
||||||
|
self.request(endpoint, function (err, req, res, body) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callback(null, body);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
151
lib/common.js
151
lib/common.js
@ -70,127 +70,41 @@ function boolFromString(value, default_, errName) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Print a table of the given items.
|
* given an array return a string with each element
|
||||||
*
|
* JSON-stringifed separated by newlines
|
||||||
* @params items {Array} of row objects.
|
|
||||||
* @params options {Object}
|
|
||||||
* - `columns` {String} of comma-separated field names for columns
|
|
||||||
* - `skipHeader` {Boolean} Default false.
|
|
||||||
* - `sort` {String} of comma-separate fields on which to alphabetically
|
|
||||||
* sort the rows. Optional.
|
|
||||||
* - `validFields` {String} valid fields for `columns` and `sort`
|
|
||||||
*/
|
*/
|
||||||
function tabulate(items, options) {
|
function jsonStream(arr) {
|
||||||
assert.arrayOfObject(items, 'items');
|
return arr.map(function (elem) {
|
||||||
assert.object(options, 'options');
|
return JSON.stringify(elem);
|
||||||
assert.string(options.columns, 'options.columns');
|
}).join('\n');
|
||||||
assert.optionalBool(options.skipHeader, 'options.skipHeader');
|
|
||||||
assert.optionalString(options.sort, 'options.sort');
|
|
||||||
assert.optionalString(options.validFields, 'options.validFields');
|
|
||||||
|
|
||||||
if (items.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate.
|
|
||||||
var validFields = options.validFields && options.validFields.split(',');
|
|
||||||
var columns = options.columns.split(',');
|
|
||||||
var sort = options.sort ? options.sort.split(',') : [];
|
|
||||||
if (validFields) {
|
|
||||||
columns.forEach(function (c) {
|
|
||||||
if (validFields.indexOf(c) === -1) {
|
|
||||||
throw new TypeError(sprintf('invalid output field: "%s"', c));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
sort.forEach(function (s) {
|
|
||||||
if (s[0] === '-') s = s.slice(1);
|
|
||||||
if (validFields && validFields.indexOf(s) === -1) {
|
|
||||||
throw new TypeError(sprintf('invalid sort field: "%s"', s));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Function to lookup each column field in a row.
|
|
||||||
var colFuncs = columns.map(function (lookup) {
|
|
||||||
return new Function(
|
|
||||||
'try { return (this["' + lookup + '"]); } catch (e) {}');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Determine columns and widths.
|
|
||||||
var widths = {};
|
|
||||||
columns.forEach(function (c) { widths[c] = c.length; });
|
|
||||||
items.forEach(function (item) {
|
|
||||||
for (var j = 0; j < columns.length; j++) {
|
|
||||||
var col = columns[j];
|
|
||||||
var cell = colFuncs[j].call(item);
|
|
||||||
if (cell === null || cell === undefined) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
widths[col] = Math.max(
|
|
||||||
widths[col], (cell ? String(cell).length : 0));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var template = '';
|
|
||||||
for (var i = 0; i < columns.length; i++) {
|
|
||||||
if (i === columns.length - 1) {
|
|
||||||
// Last column, don't have trailing whitespace.
|
|
||||||
template += '%s';
|
|
||||||
} else {
|
|
||||||
template += '%-' + String(widths[columns[i]]) + 's ';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function cmp(a, b) {
|
|
||||||
for (var j = 0; j < sort.length; j++) {
|
|
||||||
var field = sort[j];
|
|
||||||
var invert = false;
|
|
||||||
if (field[0] === '-') {
|
|
||||||
invert = true;
|
|
||||||
field = field.slice(1);
|
|
||||||
}
|
|
||||||
assert.ok(field.length, 'zero-length sort field: ' + options.sort);
|
|
||||||
var a_cmp = Number(a[field]);
|
|
||||||
var b_cmp = Number(b[field]);
|
|
||||||
if (isNaN(a_cmp) || isNaN(b_cmp)) {
|
|
||||||
a_cmp = a[field] || '';
|
|
||||||
b_cmp = b[field] || '';
|
|
||||||
}
|
|
||||||
if (a_cmp < b_cmp) {
|
|
||||||
return (invert ? 1 : -1);
|
|
||||||
} else if (a_cmp > b_cmp) {
|
|
||||||
return (invert ? -1 : 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if (sort.length) {
|
|
||||||
items.sort(cmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!options.skipHeader) {
|
|
||||||
var header = columns.map(function (c) { return c.toUpperCase(); });
|
|
||||||
header.unshift(template);
|
|
||||||
console.log(sprintf.apply(null, header));
|
|
||||||
}
|
|
||||||
items.forEach(function (item) {
|
|
||||||
var row = [];
|
|
||||||
for (var j = 0; j < colFuncs.length; j++) {
|
|
||||||
var cell = colFuncs[j].call(item);
|
|
||||||
if (cell === null || cell === undefined) {
|
|
||||||
row.push('-');
|
|
||||||
} else {
|
|
||||||
row.push(String(cell));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
row.unshift(template);
|
|
||||||
console.log(sprintf.apply(null, row));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* given an array of key=value pairs, break them into an object
|
||||||
|
*
|
||||||
|
* @param {Array} kvs - an array of key=value pairs
|
||||||
|
* @param {Array} valid (optional) - an array to validate pairs
|
||||||
|
*/
|
||||||
|
function kvToObj(kvs, valid) {
|
||||||
|
var o = {};
|
||||||
|
for (var i = 0; i < kvs.length; i++) {
|
||||||
|
var kv = kvs[i];
|
||||||
|
var idx = kv.indexOf('=');
|
||||||
|
if (idx === -1)
|
||||||
|
throw new errors.UsageError(format(
|
||||||
|
'invalid filter: "%s" (must be of the form "field=value")',
|
||||||
|
kv));
|
||||||
|
var k = kv.slice(0, idx);
|
||||||
|
var v = kv.slice(idx + 1);
|
||||||
|
if (valid.indexOf(k) === -1)
|
||||||
|
throw new errors.UsageError(format(
|
||||||
|
'invalid filter name: "%s" (must be one of "%s")',
|
||||||
|
k, valid.join('", "')));
|
||||||
|
o[k] = v;
|
||||||
|
}
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
//---- exports
|
//---- exports
|
||||||
|
|
||||||
@ -199,6 +113,7 @@ module.exports = {
|
|||||||
deepObjCopy: deepObjCopy,
|
deepObjCopy: deepObjCopy,
|
||||||
zeroPad: zeroPad,
|
zeroPad: zeroPad,
|
||||||
boolFromString: boolFromString,
|
boolFromString: boolFromString,
|
||||||
tabulate: tabulate
|
jsonStream: jsonStream,
|
||||||
|
kvToObj: kvToObj
|
||||||
};
|
};
|
||||||
// vim: set softtabstop=4 shiftwidth=4:
|
// vim: set softtabstop=4 shiftwidth=4:
|
||||||
|
68
lib/do_cloudapi.js
Normal file
68
lib/do_cloudapi.js
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 Joyent Inc.
|
||||||
|
*
|
||||||
|
* `triton cloudapi ...`
|
||||||
|
*/
|
||||||
|
|
||||||
|
var http = require('http');
|
||||||
|
|
||||||
|
function do_cloudapi (subcmd, opts, args, callback) {
|
||||||
|
if (opts.help) {
|
||||||
|
this.do_help('help', {}, [subcmd], callback);
|
||||||
|
return;
|
||||||
|
} else if (args.length !== 2) {
|
||||||
|
callback(new Error('invalid arguments'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var reqopts = {
|
||||||
|
method: args[0].toLowerCase(),
|
||||||
|
path: args[1]
|
||||||
|
};
|
||||||
|
|
||||||
|
this.triton.cloudapi.request(reqopts, function (err, req, res, body) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (opts.headers || reqopts.method === 'head') {
|
||||||
|
console.error('%s/%s %d %s',
|
||||||
|
req.connection.encrypted ? 'HTTPS' : 'HTTP',
|
||||||
|
res.httpVersion,
|
||||||
|
res.statusCode,
|
||||||
|
http.STATUS_CODES[res.statusCode]);
|
||||||
|
Object.keys(res.headers).forEach(function (key) {
|
||||||
|
console.error('%s: %s', key, res.headers[key]);
|
||||||
|
});
|
||||||
|
console.error();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reqopts.method !== 'head')
|
||||||
|
console.log(JSON.stringify(body, null, 4));
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
do_cloudapi.options = [
|
||||||
|
{
|
||||||
|
names: ['help', 'h'],
|
||||||
|
type: 'bool',
|
||||||
|
help: 'Show this help.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
names: ['headers', 'i'],
|
||||||
|
type: 'bool',
|
||||||
|
help: 'Print response headers to stderr.'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
do_cloudapi.help = (
|
||||||
|
'Raw cloudapi request.\n'
|
||||||
|
+ '\n'
|
||||||
|
+ 'Usage:\n'
|
||||||
|
+ ' {{name}} <method> <endpoint>\n'
|
||||||
|
+ '\n'
|
||||||
|
+ '{{options}}'
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = do_cloudapi;
|
@ -7,6 +7,7 @@
|
|||||||
var format = require('util').format;
|
var format = require('util').format;
|
||||||
var tabula = require('tabula');
|
var tabula = require('tabula');
|
||||||
|
|
||||||
|
var common = require('./common');
|
||||||
var errors = require('./errors');
|
var errors = require('./errors');
|
||||||
|
|
||||||
|
|
||||||
@ -21,26 +22,15 @@ function do_images(subcmd, opts, args, callback) {
|
|||||||
/* JSSTYLED */
|
/* JSSTYLED */
|
||||||
var sort = opts.s.trim().split(/\s*,\s*/g);
|
var sort = opts.s.trim().split(/\s*,\s*/g);
|
||||||
|
|
||||||
var listOpts = {};
|
|
||||||
var validFilters = [
|
var validFilters = [
|
||||||
'name', 'os', 'version', 'public', 'state', 'owner', 'type'
|
'name', 'os', 'version', 'public', 'state', 'owner', 'type'
|
||||||
];
|
];
|
||||||
for (var i = 0; i < args.length; i++) {
|
var listOpts;
|
||||||
var arg = args[i];
|
try {
|
||||||
var idx = arg.indexOf('=');
|
listOpts = common.kvToObj(args, validFilters);
|
||||||
if (idx === -1) {
|
} catch (e) {
|
||||||
return callback(new errors.UsageError(format(
|
callback(e);
|
||||||
'invalid filter: "%s" (must be of the form "field=value")',
|
return;
|
||||||
arg)));
|
|
||||||
}
|
|
||||||
var k = arg.slice(0, idx);
|
|
||||||
var v = arg.slice(idx + 1);
|
|
||||||
if (validFilters.indexOf(k) === -1) {
|
|
||||||
return callback(new errors.UsageError(format(
|
|
||||||
'invalid filter name: "%s" (must be one of "%s")',
|
|
||||||
k, validFilters.join('", "'))));
|
|
||||||
}
|
|
||||||
listOpts[k] = v;
|
|
||||||
}
|
}
|
||||||
if (opts.all) {
|
if (opts.all) {
|
||||||
listOpts.state = 'all';
|
listOpts.state = 'all';
|
||||||
@ -53,12 +43,9 @@ function do_images(subcmd, opts, args, callback) {
|
|||||||
|
|
||||||
if (opts.json) {
|
if (opts.json) {
|
||||||
// XXX we should have a common method for all these:
|
// XXX we should have a common method for all these:
|
||||||
// XXX json stream
|
|
||||||
// XXX sorting
|
// XXX sorting
|
||||||
// XXX if opts.o is given, then filter to just those fields?
|
// XXX if opts.o is given, then filter to just those fields?
|
||||||
for (var i = 0; i < imgs.length; i++) {
|
console.log(common.jsonStream(imgs));
|
||||||
console.log(JSON.stringify(imgs[i]));
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Add some convenience fields
|
// Add some convenience fields
|
||||||
// Added fields taken from imgapi-cli.git.
|
// Added fields taken from imgapi-cli.git.
|
||||||
|
90
lib/do_packages.js
Normal file
90
lib/do_packages.js
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 Joyent Inc.
|
||||||
|
*
|
||||||
|
* `triton packages ...`
|
||||||
|
*/
|
||||||
|
|
||||||
|
var tabula = require('tabula');
|
||||||
|
|
||||||
|
var common = require('./common');
|
||||||
|
|
||||||
|
function do_packages (subcmd, opts, args, callback) {
|
||||||
|
if (opts.help) {
|
||||||
|
this.do_help('help', {}, [subcmd], callback);
|
||||||
|
return;
|
||||||
|
} else if (args.length > 1) {
|
||||||
|
callback(new Error('too many args: ' + args));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var columns = opts.o.trim().split(',');
|
||||||
|
var sort = opts.s.trim().split(',');
|
||||||
|
|
||||||
|
var validFilters = [
|
||||||
|
'name', 'memory', 'disk', 'swap', 'lwps', 'version', 'vcpus', 'group'
|
||||||
|
];
|
||||||
|
var listOpts;
|
||||||
|
try {
|
||||||
|
listOpts = common.kvToObj(args, validFilters);
|
||||||
|
} catch (e) {
|
||||||
|
callback(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.triton.cloudapi.listPackages(listOpts, function (err, packages) {
|
||||||
|
if (opts.json) {
|
||||||
|
console.log(common.jsonStream(packages));
|
||||||
|
} else {
|
||||||
|
tabula(packages, {
|
||||||
|
skipHeader: opts.H,
|
||||||
|
columns: columns,
|
||||||
|
sort: sort,
|
||||||
|
validFields: 'name,memory,disk,swap,vcpus,lwps,default,id,version'.split(',')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
do_packages.options = [
|
||||||
|
{
|
||||||
|
names: ['help', 'h'],
|
||||||
|
type: 'bool',
|
||||||
|
help: 'Show this help.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
names: ['H'],
|
||||||
|
type: 'bool',
|
||||||
|
help: 'Omit table header row.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
names: ['o'],
|
||||||
|
type: 'string',
|
||||||
|
default: 'id,name,version,memory,disk',
|
||||||
|
help: 'Specify fields (columns) to output.',
|
||||||
|
helpArg: 'field1,...'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
names: ['s'],
|
||||||
|
type: 'string',
|
||||||
|
default: 'name',
|
||||||
|
help: 'Sort on the given fields. Default is "name".',
|
||||||
|
helpArg: 'field1,...'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
names: ['json', 'j'],
|
||||||
|
type: 'bool',
|
||||||
|
help: 'JSON output.'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
do_packages.help = (
|
||||||
|
'List packgaes.\n'
|
||||||
|
+ '\n'
|
||||||
|
+ 'Usage:\n'
|
||||||
|
+ ' {{name}} packages\n'
|
||||||
|
+ '\n'
|
||||||
|
+ '{{options}}'
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = do_packages;
|
Reference in New Issue
Block a user