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_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
|
||||
* be excluded.
|
||||
*/
|
||||
CloudAPI.prototype._qs = function _qs(fields, fields2) {
|
||||
assert.object(fields, 'fields');
|
||||
assert.optionalObject(fields2, 'fields2'); // can be handy to pass in 2 objs
|
||||
CloudAPI.prototype._qs = function _qs(/* fields1, ...*/) {
|
||||
var fields = Array.prototype.slice.call(arguments);
|
||||
|
||||
var query = {};
|
||||
Object.keys(fields).forEach(function (key) {
|
||||
var value = fields[key];
|
||||
fields.forEach(function (field) {
|
||||
Object.keys(field).forEach(function (key) {
|
||||
var value = field[key];
|
||||
if (value !== undefined && value !== null) {
|
||||
query[key] = value;
|
||||
}
|
||||
});
|
||||
if (fields2) {
|
||||
Object.keys(fields2).forEach(function (key) {
|
||||
var value = fields2[key];
|
||||
if (value !== undefined && value !== null) {
|
||||
query[key] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (Object.keys(query).length === 0) {
|
||||
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
|
||||
* 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.ok(subpath[0] === '/');
|
||||
assert.optionalObject(qparams, 'qparams');
|
||||
assert.optionalObject(qparams2, 'qparams2'); // can be handy to pass in 2
|
||||
|
||||
var path = subpath;
|
||||
if (qparams) {
|
||||
path += this._qs(qparams, qparams2);
|
||||
}
|
||||
var qparams = Array.prototype.slice.call(arguments, 1);
|
||||
path += this._qs.apply(this, qparams);
|
||||
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
|
||||
|
||||
@ -408,9 +428,25 @@ CloudAPI.prototype.listMachines = function listMachines(options, callback) {
|
||||
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.
|
||||
*
|
||||
* @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`
|
||||
* given an array return a string with each element
|
||||
* JSON-stringifed separated by newlines
|
||||
*/
|
||||
function tabulate(items, options) {
|
||||
assert.arrayOfObject(items, 'items');
|
||||
assert.object(options, 'options');
|
||||
assert.string(options.columns, 'options.columns');
|
||||
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));
|
||||
});
|
||||
function jsonStream(arr) {
|
||||
return arr.map(function (elem) {
|
||||
return JSON.stringify(elem);
|
||||
}).join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
@ -199,6 +113,7 @@ module.exports = {
|
||||
deepObjCopy: deepObjCopy,
|
||||
zeroPad: zeroPad,
|
||||
boolFromString: boolFromString,
|
||||
tabulate: tabulate
|
||||
jsonStream: jsonStream,
|
||||
kvToObj: kvToObj
|
||||
};
|
||||
// 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 tabula = require('tabula');
|
||||
|
||||
var common = require('./common');
|
||||
var errors = require('./errors');
|
||||
|
||||
|
||||
@ -21,26 +22,15 @@ function do_images(subcmd, opts, args, callback) {
|
||||
/* JSSTYLED */
|
||||
var sort = opts.s.trim().split(/\s*,\s*/g);
|
||||
|
||||
var listOpts = {};
|
||||
var validFilters = [
|
||||
'name', 'os', 'version', 'public', 'state', 'owner', 'type'
|
||||
];
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
var arg = args[i];
|
||||
var idx = arg.indexOf('=');
|
||||
if (idx === -1) {
|
||||
return callback(new errors.UsageError(format(
|
||||
'invalid filter: "%s" (must be of the form "field=value")',
|
||||
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;
|
||||
var listOpts;
|
||||
try {
|
||||
listOpts = common.kvToObj(args, validFilters);
|
||||
} catch (e) {
|
||||
callback(e);
|
||||
return;
|
||||
}
|
||||
if (opts.all) {
|
||||
listOpts.state = 'all';
|
||||
@ -53,12 +43,9 @@ function do_images(subcmd, opts, args, callback) {
|
||||
|
||||
if (opts.json) {
|
||||
// XXX we should have a common method for all these:
|
||||
// XXX json stream
|
||||
// XXX sorting
|
||||
// XXX if opts.o is given, then filter to just those fields?
|
||||
for (var i = 0; i < imgs.length; i++) {
|
||||
console.log(JSON.stringify(imgs[i]));
|
||||
}
|
||||
console.log(common.jsonStream(imgs));
|
||||
} else {
|
||||
// Add some convenience fields
|
||||
// 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