update cmdln, move 'profile' command out to separate file

This commit is contained in:
Trent Mick 2015-08-25 12:14:16 -07:00
parent 7d706a0358
commit 1f123975ae
7 changed files with 121 additions and 405 deletions

2
.gitignore vendored
View File

@ -1,5 +1,3 @@
/node_modules /node_modules
/tmp /tmp
/docs/*.json
/docs/*.html
/build /build

135
TODO.md
View File

@ -1,135 +0,0 @@
# first
- Adding/removing DCs. Want this to work reasonably mainly to support dogfooding
with internal DCs. Also to allow this to be a general tool for *SDC*,
with default values for JPC, but not restricted to. Also allow the right thing
to happen if JPC adds new DCs.
- Don't use "all" catch all DC. Use "joyent" alias for the default set.
- Add DC aliases (starting a generic aliasing).
- Show the aliases in `sdc dcs`
- support aliases in the command lookups. Method to get the DCs for the current
profile
XXX START HERE
- changing dcs:
sdc dcs add us-beta-4 https://beta4-cloudapi.joyent.us
sdc dcs set-url us-beta-4 https://beta4-cloudapi.joyent.us
sdc dcs rm us-beta-4
Note: If having config.dcs override this means that any DC change means
that user doesn't "see" DC changes by new node-sdc versions.
- Impl 'sdc config' to edit these easily on the CLI.
sdc config alias.dc.<alias> <dc-name-1> <dc-name-2> ...
sdc config alias.image.<alias> <image-uuid> ...
- machines:
- short default output
- 'cdate' short created, just the date
- 'img' is 'name/version'
- 'sid' is the short id prefix
- long '-l' output, -H, -o, -s
- get image defaults and fill those in
- few more commands? provision (create-machine?)
- uuid caching
- UUID prefix support
- profile command (adding profile, edit, etc.)
- `sdc config` command similar to git config
# account vs user vs subuser vs role
See MANTA-2401 and scrum discussion from 14 Aug 2014..
Suggestion: use "account" and "user" since "since those are the documented
tools for the abstractions and that's what smartdc uses."
Envvars: SDC_ACCOUNT and SDC_USER.
# later (in no particular order)
- adding a dc:
sdc dcs -a us-beta-4 https://beta4-cloudapi.joyent.us
or
- signing: should sigstr include more than just the date? How about the request
path??? Not according to the cloudapi docs.
- restify-client and bunyan-light without dtrace-provider
- Get node-smartdc-auth to take a log option. Perhaps borrow from imgapi.js'
cliSigner et al.
- node-smartdc-auth: Support a path to a priv key for "keyId" arg. Or a separate
alternative arg. Copy this from imgapi.cliSigner.
sign: cloudapi.cliSigner({
keyId: <KEY-ID>,
user: <USER>,
log: <BUNYAN-LOGGER>,
}),
- the error reporting for a signing error sucks:
getAccount: err { message: 'error signing request',
code: 'Signing',
exitStatus: 1 }
e.g. when the KEY_ID is nonsense. Does imgapi's auth have better error
reporting?
- how to add/exclude DCs?
- cmdln.js support for bash tab completion
- node-smartdc installs joyentcloud and warns about deprecation on stderr.
- bunyan logging setup:
- one output stream to a file at trace level:
/var/log/joyentcloud/$timestamp.log
- periodically keep the number of those files down. This is hard. Do it
at startup? Yah should be fine.
- another "raw" stream to stderr at WARN at above (maybe INFO?)
where we console.error just the minimal fields that we want to show
joyentcloud: warn: $msg
Not sure about other fields.
- plugin support, e.g. allow 3rd-party node-joyentcloud-foo npm modules that would
add a "joyentcloud foo" subcmd. Reasonable?
- windows testing
# ideas
- `sdc whatsnew` grabs current images and packages and compares to last time
it was called to short new images/packages. Perhaps for other resources too.
# notes on `sdc provision` (in progress)
- Lame: I <# that our packages are separate for kvm vs smartos usage. Do they
have conflicting data?
- Q: "package" or "instance-type"? Probably package for now.
Need: dc (if profile has multiple, have a settable preferred dc for provisions),
image (uuid, name to get latest, have a settable preferred?), package (settable
preferred, settable preferred ram).
What about using "same as last time" or a way to say that?
Want interactive asking for missing params if TTY? -f to avoid.
$ sdc provision ...
Datacenter [us-west-1]: <prompt>
...
Name: AWS equiv is 'aws-cli ec2 run-instances'
http://docs.aws.amazon.com/cli/latest/reference/ec2/run-instances.html
E.g.:
aws ec2 run-instances --image-id ami-c3b8d6aa --count 1 --instance-type t1.micro --key-name MyKeyPair --security-groups MySecurityGroup
sdc create-machine ...
sdc provision ...
sdc provision -i IMAGE -p PACKAGE
shortcut?
sdc provision IMAGE:PKG ?
sdc provision IMAGE PKG ?
sdc provision image=IMAGE package=PKG ? no
sdc provision -i IMAGE -p PKG -c 3 --name 'test%d' # printf codes for the count
sdc provision -d east -i base -p g3-standard-1 -n shirley # -d|--dc
Clarify what IMAGE can be. "Name" matching is first against one's own private
images, then against public ones. UUID. UUID prefix. "Name/version" matching.
Image alias (`sdc config alias.bob $uuid`, though for git that alias is for
*commands*. Perhaps `sdc alias image.bob $uuid`. Dunno. Later.).
Similar matching for PKG.

66
TODO.txt Normal file
View File

@ -0,0 +1,66 @@
# today
triton
triton -v # bunyan trace logging
triton vms|containers|machines # list machines
triton vm|container|machine ID|ALIAS # get machine
# -1 for unique match
triton images # list
triton packages # list
triton image IMAGE
triton package PACKAGE
triton provision # triton create-machine ??
triton create -p PKG [...] IMG
triton create -i IMG -p PKG [-n NAME] [...]
# example: triton create base64 -p t4-standard-1g
# maybe today
triton config defaultPkg t4-standard-1g
triton login|ssh VM # kexec?
triton delete VM|IMAGE # substring matching? too dangerous
triton delete --vm VM
triton delete --image IMAGE
# profiles
triton profile # list all profiles
triton profile NAME # show NAME profile
triton profile -a NAME # sets it as active
triton profile -n|--new # ???
For today: only the implicit 'env' profile.
# config
~/.triton/
config.json
{"currProfile": "east3b"}
east3b/ # profile
PROFILE2/
...
# another day
triton config get|set|list # see 'npm config'
triton --shell # or whatever, repl

View File

@ -10,5 +10,10 @@ var cmdln = require('cmdln');
var CLI = require('../lib/cli'); var CLI = require('../lib/cli');
if (require.main === module) { if (require.main === module) {
cmdln.main(CLI, process.argv, {showCode: true}); var cli = new CLI();
cmdln.main(cli, {
argv: process.argv,
showCode: true,
showNoCommandErr: false
});
} }

View File

@ -1,34 +1,34 @@
/* /*
* Copyright (c) 2014 Joyent Inc. All rights reserved. * Copyright (c) 2015 Joyent Inc. All rights reserved.
* *
* The 'sdc' CLI class. * The `triton` CLI class.
*/ */
var p = console.log; var assert = require('assert-plus');
var e = console.error; var bunyan = require('bunyan');
var util = require('util'),
format = util.format;
var child_process = require('child_process'), var child_process = require('child_process'),
spawn = child_process.spawn, spawn = child_process.spawn,
exec = child_process.exec; exec = child_process.exec;
var fs = require('fs');
var assert = require('assert-plus');
var async = require('async');
var bunyan = require('bunyan');
var cmdln = require('cmdln'), var cmdln = require('cmdln'),
Cmdln = cmdln.Cmdln; Cmdln = cmdln.Cmdln;
var fs = require('fs');
var util = require('util'),
format = util.format;
var vasync = require('vasync');
var common = require('./common'); var common = require('./common');
var errors = require('./errors'); var errors = require('./errors');
var SDC = require('./sdc'); //XXX
//var SDC = require('./sdc');
//---- globals //---- globals
var p = console.log;
var pkg = require('../package.json'); var pkg = require('../package.json');
var name = 'sdc'; var name = 'triton';
var log = bunyan.createLogger({ var log = bunyan.createLogger({
name: name, name: name,
serializers: bunyan.stdSerializers, serializers: bunyan.stdSerializers,
@ -70,250 +70,24 @@ CLI.prototype.init = function (opts, args, callback) {
} }
this.opts = opts; this.opts = opts;
if (opts.verbose) { if (opts.verbose) {
process.env.DEBUG = 1; //TODO This is a lame req of cmdln.main().
log.level('trace'); log.level('trace');
log.src = true; log.src = true;
} }
this.__defineGetter__('sdc', function () { //XXX
if (self._sdc === undefined) { //this.__defineGetter__('sdc', function () {
self._sdc = new SDC({log: log, profile: opts.profile}); // if (self._sdc === undefined) {
} // self._sdc = new SDC({log: log, profile: opts.profile});
return self._sdc; // }
}); // return self._sdc;
//});
// Cmdln class handles `opts.help`. // Cmdln class handles `opts.help`.
Cmdln.prototype.init.apply(this, arguments); Cmdln.prototype.init.apply(this, arguments);
}; };
CLI.prototype.do_config = function (subcmd, opts, args, callback) { CLI.prototype.do_profile = require('./do_profile');
if (opts.help) {
this.do_help('help', {}, [subcmd], callback);
return;
}
var action;
var actions = [];
if (opts.add) actions.push('add');
if (opts['delete']) actions.push('delete');
if (opts.edit) actions.push('edit');
if (actions.length === 0) {
action = 'show';
} else if (actions.length > 1) {
return callback(new errors.UsageError(
'cannot specify more than one action: ' + actions.join(', ')));
} else {
action = actions[0];
}
var numArgs = {
}
if (action === 'show') {
var c = common.objCopy(this.sdc.config);
delete c._defaults;
delete c._user;
if (args.length > 1) {
return callback(new errors.UsageError('too many args'));
} else if (args.length === 1) {
var lookups = args[0].split(/\./g);
for (var i = 0; i < lookups.length; i++) {
c = c[lookups[i]];
if (c === undefined) {
return callback(new errors.UsageError(
'no such config var: ' + args[0]));
}
}
}
if (typeof(c) === 'string') {
console.log(c)
} else {
console.log(JSON.stringify(c, null, 4));
}
} else if (action === 'add') {
if (args.length !== 2)
return callback(new errors.UsageError('incorrect number of args'));
XXX
} else if (action === 'delete') {
if (args.length !== 1)
return callback(new errors.UsageError('incorrect number of args'));
XXX
} else if (action === 'edit') {
if (args.length !== 0)
return callback(new errors.UsageError('incorrect number of args'));
XXX
}
callback();
};
CLI.prototype.do_config.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['add', 'a'],
type: 'bool',
help: 'Add a config var.'
},
{
names: ['delete', 'd'],
type: 'bool',
help: 'Delete a config var.'
},
{
names: ['edit', 'e'],
type: 'bool',
help: 'Edit config in $EDITOR.'
}
];
CLI.prototype.do_config.help = (
'Show and edit the `sdc` CLI config.\n'
+ '\n'
+ 'Usage:\n'
+ ' {{name}} config # show config\n'
+ ' {{name}} config <name> # show particular config var\n'
+ ' {{name}} config -a <name> <value> # add/set a config var\n'
+ ' {{name}} config -d <name> # delete a config var\n'
+ ' {{name}} config -e # edit config in $EDITOR\n'
+ '\n'
+ '{{options}}'
);
CLI.prototype.do_profile = function (subcmd, opts, args, callback) {
if (opts.help) {
this.do_help('help', {}, [subcmd], callback);
return;
} else if (args.length > 1) {
return callback(new Error('too many args: ' + args));
}
var profs = common.deepObjCopy(this.sdc.profiles);
var currProfileName = this.sdc.profile.name;
for (var i = 0; i < profs.length; i++) {
profs[i].curr = (profs[i].name === currProfileName ? '*' : ' ');
profs[i].dcs = (profs[i].dcs ? profs[i].dcs : ['all'])
.join(',');
}
if (opts.json) {
p(JSON.stringify(profs, null, 4));
} else {
common.tabulate(profs, {
columns: 'curr,name,dcs,user,keyId',
sort: 'name,user',
validFields: 'curr,name,dcs,user,keyId'
});
}
callback();
};
CLI.prototype.do_profile.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['json', 'j'],
type: 'bool',
help: 'JSON output.'
}
];
CLI.prototype.do_profile.help = (
'Create, update or inpect joyent CLI profiles.\n'
+ '\n'
+ 'Usage:\n'
+ ' {{name}} profile\n'
+ '\n'
+ '{{options}}'
);
CLI.prototype.do_dcs = function (subcmd, opts, args, callback) {
var self = this;
if (opts.help) {
this.do_help('help', {}, [subcmd], callback);
return;
}
var action = args[0] || 'list';
var name;
var url;
switch (action) {
case 'list':
if (args.length !== 0) {
return callback(new errors.UsageError('too many args: ' + args));
}
var dcs = self.sdc.config.dc;
var dcsArray = Object.keys(dcs).map(
function (n) { return {name: n, url: dcs[n]}; });
if (self.sdc.config.dcAlias) {
Object.keys(self.sdc.config.dcAlias).forEach(function (alias) {
dcsArray.push(
{alias: alias, names: self.sdc.config.dcAlias[alias]});
});
}
if (opts.json) {
p(JSON.stringify(dcsArray, null, 4));
} else {
for (var i = 0; i < dcsArray.length; i++) {
var d = dcsArray[i];
d.name = (d.name ? d.name : d.alias + '*');
d.url = d.url || d.names.join(', ');
}
common.tabulate(dcsArray, {
columns: 'name,url',
sort: 'alias,name',
validFields: 'name,url,alias,names'
});
}
callback();
break;
case 'rm':
if (args.length !== 2) {
return callback(new errors.UsageError(
'incorrect number of args: ' + args));
}
name = args[1];
XXX
break;
case 'add':
if (args.length !== 3) {
return callback(new errors.UsageError(
'incorrect number of args: ' + args));
}
name = args[1];
url = args[2];
XXX
break;
default:
return callback(new errors.UsageError('unknown dcs command: ' + args))
}
};
CLI.prototype.do_dcs.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['json', 'j'],
type: 'bool',
help: 'JSON output.'
}
];
CLI.prototype.do_dcs.help = (
'List, add or remove datacenters.\n'
+ '\n'
+ 'Usage:\n'
+ ' {{name}} dcs # list DCs (and DC aliases marked with "*")\n'
+ ' {{name}} dcs add <name> <url> # add an SDC cloudapi endpoint\n'
+ ' {{name}} dcs rm <name> # remove a DC\n'
+ '\n'
+ '{{options}}'
);
CLI.prototype.do_provision = function (subcmd, opts, args, callback) { CLI.prototype.do_provision = function (subcmd, opts, args, callback) {
@ -406,7 +180,6 @@ CLI.prototype.do_provision.help = (
+ '\n' + '\n'
+ '{{options}}' + '{{options}}'
); );
CLI.prototype.do_provision.aliases = ['create-machine'];
CLI.prototype.do_machines = function (subcmd, opts, args, callback) { CLI.prototype.do_machines = function (subcmd, opts, args, callback) {
@ -536,6 +309,12 @@ CLI.prototype.do_machine_audit.help = (
); );
//---- mainline
if (require.main === module) {
var cli = new CLI();
cmdln.main(cli, {showNoCommandErr: false});
}
//---- exports //---- exports

View File

@ -1,23 +1,26 @@
#!/usr/bin/env node #!/usr/bin/env node
/** /**
* Copyright (c) 2014 Joyent Inc. All rights reserved. * Copyright (c) 2015 Joyent Inc. All rights reserved.
*/ */
var p = console.log;
var assert = require('assert-plus'); var assert = require('assert-plus');
var async = require('async');
var backoff = require('backoff');
var fs = require('fs');
var once = require('once');
var sprintf = require('extsprintf').sprintf; var sprintf = require('extsprintf').sprintf;
var util = require('util'), var util = require('util'),
format = util.format; format = util.format;
var verror = require('verror');
var errors = require('./errors'), var errors = require('./errors'),
InternalError = errors.InternalError; InternalError = errors.InternalError;
// ---- globals
var p = console.log;
// ---- support stuff
function objCopy(obj, target) { function objCopy(obj, target) {
if (target === undefined) { if (target === undefined) {
target = {}; target = {};

View File

@ -1,23 +1,23 @@
{ {
"name": "triton", "name": "triton",
"description": "Joyent Triton tool and client (http://www.joyent.com/products/compute-service)", "description": "Joyent Triton tool and client (https://www.joyent.com/triton)",
"version": "1.0.0", "version": "1.0.0",
"author": "Joyent (joyent.com)", "author": "Joyent (joyent.com)",
"private": true, "private": true,
"dependencies": { "dependencies": {
"async": "0.2.9", "assert-plus": "0.1.5",
"assert-plus": "0.1.4", "backoff": "2.4.1",
"backoff": "2.3.0", "bunyan": "1.4.0",
"bunyan": "0.22.0", "cmdln": "3.2.1",
"cmdln": "1.3.1", "dashdash": "1.10.0",
"dashdash": "1.3.2",
"extsprintf": "1.0.2", "extsprintf": "1.0.2",
"mkdirp": "0.3.5", "mkdirp": "0.5.1",
"node-uuid": "1.4.1", "node-uuid": "1.4.3",
"once": "1.3.0", "once": "1.3.2",
"restify": "git+ssh://git@github.com:mcavage/node-restify.git#9bab8b7f", "restify-clients": "1.0.0",
"smartdc-auth": "git+ssh://git@github.com:joyent/node-smartdc-auth.git#9f21966", "smartdc-auth": "git+ssh://git@github.com:joyent/node-smartdc-auth.git#9f21966",
"verror": "1.3.7" "vasync": "*",
"verror": "1.6.0"
}, },
"engines": { "engines": {
"node": ">=0.10" "node": ">=0.10"