triton instances

This commit is contained in:
Dave Eddy 2015-08-25 19:46:14 -04:00
parent 23fc9d4b3f
commit eaf93e619b
4 changed files with 139 additions and 115 deletions

View File

@ -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.
* <http://apidocs.joyent.com/cloudapi/#ListMachines>
*
* 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) {
CloudAPI.prototype.createListMachinesStream =
function createListMachinesStream(options) {
var self = this;
if (callback === undefined) {
callback = options;
options = {};
}
assert.object(options, 'options');
assert.func(callback, 'callback');
var query = {
limit: options.limit
};
// if the user specifies an offset we don't paginate
var once = options.limit !== undefined;
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);
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});
});
},
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.
* <http://apidocs.joyent.com/cloudapi/#ListMachines>
*
* @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).

View File

@ -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;

View File

@ -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]);
}
});
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));
} 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'
});
}
var err;
if (errs.length === 1) {
err = errs[0];
} else if (errs.length > 1) {
err = new errors.MultiError(errs);
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;
}
if (opts.json) {
console.log(common.jsonStream(machines));
} else {
tabula(machines, {
skipHeader: opts.H,
columns: columns,
sort: sort,
validFields: validFields
});
};
}
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;

View File

@ -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 {