From eaf93e619bcf172dc5a3a147684215c548bab976 Mon Sep 17 00:00:00 2001 From: Dave Eddy Date: Tue, 25 Aug 2015 19:46:14 -0400 Subject: [PATCH] triton instances --- lib/cloudapi2.js | 124 ++++++++++++++++++-------------------------- lib/do_cloudapi.js | 15 ++++-- lib/do_instances.js | 111 +++++++++++++++++++++++++-------------- lib/do_packages.js | 4 ++ 4 files changed, 139 insertions(+), 115 deletions(-) diff --git a/lib/cloudapi2.js b/lib/cloudapi2.js index 00f316d..8434906 100644 --- a/lib/cloudapi2.js +++ b/lib/cloudapi2.js @@ -29,6 +29,7 @@ var p = console.log; var assert = require('assert-plus'); var auth = require('smartdc-auth'); var format = require('util').format; +var LOMStream = require('lomstream').LOMStream; var os = require('os'); var querystring = require('querystring'); var restifyClients = require('restify-clients'); @@ -387,83 +388,60 @@ CloudAPI.prototype.getMachine = function getMachine(options, callback) { * List the user's machines. * * - * If no `offset` is given, then this will return all machines, calling - * multiple times if necessary. If `offset` is specified given, then just - * a single response will be made. - * - * @param {Object} options (optional) - * - {Number} offset (optional) An offset number of machine at which to - * return results. - * - {Number} limit (optional) Max number of machines to return. - * @param {Function} callback of the form `function (err, machines, responses)` - * where `responses` is an array of response objects in retrieving all - * the machines. ListMachines has a max number of machines, so can require - * multiple requests to list all of them. + * @param {Object} options + * See document above + * @return {LOMStream} a stream for each machine entry */ -CloudAPI.prototype.listMachines = function listMachines(options, callback) { - var self = this; - if (callback === undefined) { - callback = options; - options = {}; +CloudAPI.prototype.createListMachinesStream = +function createListMachinesStream(options) { + var self = this; + + // if the user specifies an offset we don't paginate + var once = options.limit !== undefined; + + return new LOMStream({ + fetch: fetch, + limit: 1000, + offset: true + }); + + function fetch(fetcharg, limitObj, datacb, donecb) { + options.limit = limitObj.limit; + options.offset = limitObj.offset; + var endpoint = self._path(format('/%s/machines', self.user), options); + + self._request(endpoint, function (err, req, res, body) { + var resourcecount = res.headers['x-resource-count']; + var done = once || resourcecount < options.limit; + donecb(err, {done: done, results: body}); + }); } - assert.object(options, 'options'); - assert.func(callback, 'callback'); - - var query = { - limit: options.limit - }; - - var paging = options.offset === undefined; - var offset = options.offset || 0; - var lastHeaders; - var responses = []; - var bodies = []; - async.doWhilst( - function getPage(next) { - self._getAuthHeaders(function (hErr, headers) { - if (hErr) { - next(hErr); - return; - } - query.offset = offset; - var path = sprintf('/%s/machines?%s', self.user, - querystring.stringify(query)); - var opts = { - path: path, - headers: headers - }; - self.client.get(opts, function (err, req, res, body) { - lastHeaders = res.headers; - responses.push(res); - bodies.push(body); - next(err); - }); - }); - }, - function testContinue() { - if (!paging) { - return false; - } - xQueryLimit = Number(lastHeaders['x-query-limit']); - xResourceCount = Number(lastHeaders['x-resource-count']); - assert.number(xQueryLimit, 'x-query-limit header'); - assert.number(xResourceCount, 'x-resource-count header'); - offset += Number(lastHeaders['x-resource-count']); - return xResourceCount >= xQueryLimit; - }, - function doneMachines(err) { - if (err) { - callback(err, null, responses); - } else if (bodies.length === 1) { - callback(null, bodies[0], responses); - } else { - var machines = Array.prototype.concat.apply([], bodies); - callback(null, machines, responses); - } - } - ); }; +/** + * List the user's machines. + * + * + * @param {Object} options + * See document above + * @param {Function} callback - called like `function (err, machines)` + */ +CloudAPI.prototype.listMachines = function listMachines(options, callback) { + var machines = []; + var s = this.createListMachinesStream(options); + s.on('error', function (e) { + callback(e); + }); + s.on('readable', function () { + var machine; + while ((machine = s.read()) !== null) { + machines.push(machine); + } + }); + s.on('end', function () { + callback(null, machines); + }); +}; /** * List machine audit (successful actions on the machine). diff --git a/lib/do_cloudapi.js b/lib/do_cloudapi.js index ef6e722..316f78a 100644 --- a/lib/do_cloudapi.js +++ b/lib/do_cloudapi.js @@ -10,17 +10,24 @@ function do_cloudapi (subcmd, opts, args, callback) { if (opts.help) { this.do_help('help', {}, [subcmd], callback); return; - } else if (args.length !== 2) { + } else if (args.length < 1 || args.length > 2) { callback(new Error('invalid arguments')); return; } + var method = args[0]; + var path = args[1]; + if (path === undefined) { + path = method; + method = 'GET'; + } + var reqopts = { - method: args[0].toLowerCase(), - path: args[1] + method: method.toLowerCase(), + path: path }; - this.triton.cloudapi.request(reqopts, function (err, req, res, body) { + this.triton.cloudapi._request(reqopts, function (err, req, res, body) { if (err) { callback(err); return; diff --git a/lib/do_instances.js b/lib/do_instances.js index 91db2ab..4021f39 100644 --- a/lib/do_instances.js +++ b/lib/do_instances.js @@ -1,68 +1,104 @@ /* - * Copyright (c) 2015 Joyent Inc. All rights reserved. + * Copyright 2015 Joyent Inc. * * `triton instances ...` */ -var format = require('util').format; var tabula = require('tabula'); -var errors = require('./errors'); +var common = require('./common'); +// to be passed as query string args to /my/machines +var validFilters = [ + 'name', + 'image', + 'state', + 'memory', + 'tombstone', + 'credentials' +]; + +// valid output fields to be printed +var validFields = [ + 'id', + 'name', + 'type', + 'state', + 'dataset', + 'memory', + 'disk', + 'ips', + 'metadata', + 'created', + 'updated', + 'package', + 'image' +]; function do_instances(subcmd, opts, args, callback) { - var self = this; if (opts.help) { this.do_help('help', {}, [subcmd], callback); return; } else if (args.length > 1) { - return callback(new Error('too many args: ' + args)); + callback(new Error('too many args: ' + args)); + return; } - var machines = []; - var errs = []; - var res = this.sdc.listMachines(); - res.on('data', function (dc, dcMachines) { - for (var i = 0; i < dcMachines.length; i++) { - dcMachines[i].dc = dc; - machines.push(dcMachines[i]); + var columns = opts.o.trim().split(','); + var sort = opts.s.trim().split(','); + + var listOpts; + try { + listOpts = common.kvToObj(args, validFilters); + } catch (e) { + callback(e); + return; + } + + this.triton.cloudapi.listMachines(listOpts, function (err, machines) { + if (err) { + callback(err); + return; } - }); - res.on('dcError', function (dc, dcErr) { - dcErr.dc = dc; - errs.push(dcErr); - }); - res.on('end', function () { if (opts.json) { - p(JSON.stringify(machines, null, 4)); + console.log(common.jsonStream(machines)); } else { - /* BEGIN JSSTYLED */ - // TODO: get short output down to something like - // 'us-west-1 e91897cf testforyunong2 linux running 2013-11-08' - // 'us-west-1 e91897cf testforyunong2 ubuntu/13.3.0 running 2013-11-08' - /* END JSSTYLED */ - common.tabulate(machines, { - columns: 'dc,id,name,image,state,created', - sort: 'created', - validFields: 'dc,id,name,type,state,image,package,memory,' - + 'disk,created,updated,compute_node,primaryIp' + tabula(machines, { + skipHeader: opts.H, + columns: columns, + sort: sort, + validFields: validFields }); } - var err; - if (errs.length === 1) { - err = errs[0]; - } else if (errs.length > 1) { - err = new errors.MultiError(errs); - } - callback(err); + callback(); }); -}; +} + do_instances.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,state,type,image,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', @@ -77,7 +113,6 @@ do_instances.help = ( + '\n' + '{{options}}' ); -do_instances.aliases = ['insts']; module.exports = do_instances; diff --git a/lib/do_packages.js b/lib/do_packages.js index f9fd925..e9fa217 100644 --- a/lib/do_packages.js +++ b/lib/do_packages.js @@ -32,6 +32,10 @@ function do_packages (subcmd, opts, args, callback) { } this.triton.cloudapi.listPackages(listOpts, function (err, packages) { + if (err) { + callback(err); + return; + } if (opts.json) { console.log(common.jsonStream(packages)); } else {