joyent/node-triton#101 Bash completion for server-side data: instances, images, etc.

This commit is contained in:
Trent Mick 2016-03-09 09:19:44 -08:00
parent 7d635fc81c
commit e3b5e6b016
42 changed files with 447 additions and 17 deletions

View File

@ -1,7 +1,14 @@
# node-triton changelog # node-triton changelog
## 4.6.1 (not yet released) ## 4.7.0 (not yet released)
- #101 Bash completion for server-side data: instances, images, etc.
Bash completion on TAB should now work for things like the following:
`triton create <TAB to complete images> <TAB to complete packages`,
`triton inst tag ls <TAB to complete instances>`. Cached (with a 5 minute
TTL) completions for the following data are supported: instances, images,
packages, networks, fwrules, account keys.
See `triton completion --help` for adding/updating Bash completion.
- #99 `triton profile set ...` alias for `set-current` - #99 `triton profile set ...` alias for `set-current`

View File

@ -1,4 +1,5 @@
# Functions for Bash completion of some 'triton' option/arg types. # Functions for Bash completion of some 'triton' option/arg types.
function complete_tritonprofile { function complete_tritonprofile {
local word="$1" local word="$1"
local candidates local candidates
@ -7,6 +8,144 @@ function complete_tritonprofile {
compgen $compgen_opts -W "$candidates" -- "$word" compgen $compgen_opts -W "$candidates" -- "$word"
} }
#
# Get completions for a given type of Triton (server-side) data.
#
# Usage:
# _complete_tritondata $type # e.g. _complete_tritondata images
#
# The easiest/slowest thing to do to complete images would be to just call:
# triton [profile-related-args] images -Ho name
# or similar. Too slow.
#
# The next easiest would be this:
# candidates=$(TRITON_COMPLETE=$type $COMP_LINE)
# where `triton` is setup to specially just handle completions if
# `TRITON_COMPLETE` is set. That special handling writes out a cache file to
# avoid hitting the server every time. This is still too slow because the
# node.js startup time for `triton` is too slow (around 1s on my laptop).
#
# The next choice is to (a) use the special `TRITON_COMPLETE` handling to
# fetch data from the server and write out a cache file, but (b) attempt to
# find and use that cache file without calling node.js code. The win is
# (at least in my usage) faster response time to a <TAB>. The cost is doing
# reproducing (imperfectly) in Bash the logic for determining the Triton profile
# info to find the cache.
#
function _complete_tritondata {
local type=$1
# First, find the Triton CLI profile.
local profile
profile=$(echo "$COMP_LINE" | grep -- '\s\+-p\s*\w\+\s\+' | sed -E 's/.* +-p *([^ ]+) +.*/\1/')
if [[ -z "$profile" ]]; then
profile=$TRITON_PROFILE
fi
if [[ -z "$profile" ]]; then
profile=$(grep '"profile":' ~/.triton/config.json | cut -d'"' -f4)
fi
if [[ -z "$profile" ]]; then
profile=env
fi
trace " profile: $profile"
# Then, determine the account and url that go into the cache dir.
# TODO: include -a/-U options that change from profile values
# TODO: subuser support
local url
local account
local profileFile
profileFile=$HOME/.triton/profiles.d/$profile.json
if [[ "$profile" == "env" ]]; then
url=$TRITON_URL
if [[ -z "$url" ]]; then
url=$SDC_URL
fi
account=$TRITON_ACCOUNT
if [[ -z "$account" ]]; then
account=$SDC_ACCOUNT
fi
elif [[ -f $profileFile ]]; then
url=$(grep '"url":' $profileFile | cut -d'"' -f4)
account=$(grep '"account":' $profileFile | cut -d'"' -f4)
fi
trace " url: $url"
trace " account: $account"
# Mimic node-triton/lib/common.js#profileSlug
local profileSlug
profileSlug="$(echo "$account" | sed -E 's/@/_/g')@$(echo "$url" | sed -E 's#^https?://##')"
profileSlug="$(echo "$profileSlug" | sed -E 's/[^a-zA-Z0-9_@-]/_/g')"
local cacheFile
cacheFile="$HOME/.triton/cache/$profileSlug/$type.completions"
trace " cacheFile: $cacheFile"
# If we have a cache file, remove it and regenerate if it is >5 minutes old.
#
# Dev Note: This 5min TTL should match what `lib/cli.js#_emitCompletions()`
# is using.
local candidates
if [[ ! -f "$cacheFile" ]]; then
candidates=$(TRITON_COMPLETE=$type $COMP_LINE)
else
local mtime
mtime=$(stat -r "$cacheFile" | awk '{print $10}')
local ttl=300 # 5 minutes in seconds
local age
age=$(echo "$(date +%s) - $mtime" | bc)
if [[ $age -gt $ttl ]]; then
# Out of date. Regenerate the cache file.
trace " cacheFile out-of-date (mtime=$mtime, age=$age, ttl=$ttl)"
rm "$cacheFile"
candidates=$(TRITON_COMPLETE=$type $COMP_LINE)
else
trace " cacheFile is in-date (mtime=$mtime, age=$age, ttl=$ttl)"
candidates=$(cat "$cacheFile")
fi
fi
echo "$candidates"
}
function complete_tritonpackage {
local word="$1"
candidates=$(_complete_tritondata packages)
compgen $compgen_opts -W "$candidates" -- "$word"
}
function complete_tritonimage {
local word="$1"
candidates=$(_complete_tritondata images)
compgen $compgen_opts -W "$candidates" -- "$word"
}
function complete_tritoninstance {
local word="$1"
candidates=$(_complete_tritondata instances)
compgen $compgen_opts -W "$candidates" -- "$word"
}
function complete_tritonnetwork {
local word="$1"
candidates=$(_complete_tritondata networks)
compgen $compgen_opts -W "$candidates" -- "$word"
}
function complete_tritonfwrule {
local word="$1"
candidates=$(_complete_tritondata fwrules)
compgen $compgen_opts -W "$candidates" -- "$word"
}
function complete_tritonkey {
local word="$1"
candidates=$(_complete_tritondata keys)
compgen $compgen_opts -W "$candidates" -- "$word"
}
function complete_tritonupdateaccountfield { function complete_tritonupdateaccountfield {
local word="$1" local word="$1"
local candidates local candidates

View File

@ -17,6 +17,7 @@ var child_process = require('child_process'),
exec = child_process.exec; exec = child_process.exec;
var cmdln = require('cmdln'), var cmdln = require('cmdln'),
Cmdln = cmdln.Cmdln; Cmdln = cmdln.Cmdln;
var fs = require('fs');
var mkdirp = require('mkdirp'); var mkdirp = require('mkdirp');
var util = require('util'), var util = require('util'),
format = util.format; format = util.format;
@ -32,7 +33,7 @@ var tritonapi = require('./tritonapi');
//---- globals //---- globals
var pkg = require('../package.json'); var packageJson = require('../package.json');
var CONFIG_DIR; var CONFIG_DIR;
if (process.platform === 'win32') { if (process.platform === 'win32') {
@ -198,7 +199,7 @@ cmdln.dashdash.addOptionType({
function CLI() { function CLI() {
Cmdln.call(this, { Cmdln.call(this, {
name: 'triton', name: 'triton',
desc: pkg.description, desc: packageJson.description,
options: OPTIONS, options: OPTIONS,
helpOpts: { helpOpts: {
includeEnv: true, includeEnv: true,
@ -260,7 +261,7 @@ CLI.prototype.init = function (opts, args, callback) {
} }
if (opts.version) { if (opts.version) {
console.log(this.name, pkg.version); console.log(this.name, packageJson.version);
callback(false); callback(false);
return; return;
} }
@ -298,8 +299,26 @@ CLI.prototype.init = function (opts, args, callback) {
return self._tritonapi; return self._tritonapi;
}); });
// Cmdln class handles `opts.help`. if (process.env.TRITON_COMPLETE) {
Cmdln.prototype.init.apply(this, arguments); /*
* If `TRITON_COMPLETE=<type>` is set (typically only in the
* Triton CLI bash completion driver, see
* "etc/triton-bash-completion-types.sh"), then Bash completions are
* fetched and printed, instead of the usual subcommand handling.
*
* Completion results are typically cached (under "~/.triton/cache")
* to avoid hitting the server for data everytime.
*
* Example usage:
* TRITON_COMPLETE=images triton -p my-profile create
*/
this._emitCompletions(process.env.TRITON_COMPLETE, function (err) {
callback(err || false);
});
} else {
// Cmdln class handles `opts.help`.
Cmdln.prototype.init.apply(this, arguments);
}
}; };
@ -313,6 +332,185 @@ CLI.prototype.fini = function fini(subcmd, err, cb) {
}; };
/*
* Fetch and display Bash completions (one completion per line) for the given
* Triton data type (e.g. 'images', 'instances', 'packages', ...).
* This caches results (per profile) with a 5 minute TTL.
*
* Dev Note: If the cache path logic changes, then the *Bash* implementation
* of the same logic in "etc/triton-bash-completion-types.sh" must be updated
* to match.
*/
CLI.prototype._emitCompletions = function _emitCompletions(type, cb) {
assert.string(type, 'type');
assert.func(cb, 'cb');
var cacheFile = path.join(this.tritonapi.cacheDir, type + '.completions');
var ttl = 5 * 60 * 1000; // timeout of cache file info (ms)
var cloudapi = this.tritonapi.cloudapi;
vasync.pipeline({arg: {}, funcs: [
function tryCacheFile(arg, next) {
fs.stat(cacheFile, function (err, stats) {
if (!err &&
stats.mtime.getTime() + ttl >= (new Date()).getTime()) {
process.stdout.write(fs.readFileSync(cacheFile));
next(true); // early abort
} else if (err && err.code !== 'ENOENT') {
next(err);
} else {
next();
}
});
},
function gather(arg, next) {
var completions;
switch (type) {
case 'packages':
cloudapi.listPackages({}, function (err, pkgs) {
if (err) {
next(err);
return;
}
completions = [];
pkgs.forEach(function (pkg) {
if (pkg.name.indexOf(' ') === -1) {
// Cannot bash complete results with spaces, so
// skip them here.
completions.push(pkg.name);
}
completions.push(pkg.id);
});
arg.completions = completions.join('\n') + '\n';
next();
});
break;
case 'images':
cloudapi.listImages({}, function (err, imgs) {
if (err) {
next(err);
return;
}
completions = [];
imgs.forEach(function (img) {
// Cannot bash complete results with spaces, so
// skip them here.
if (img.name.indexOf(' ') === -1) {
completions.push(img.name);
if (img.version.indexOf(' ') === -1) {
completions.push(img.name + '@' + img.version);
}
}
completions.push(img.id);
});
arg.completions = completions.join('\n') + '\n';
next();
});
break;
case 'instances':
cloudapi.listMachines({}, function (err, insts) {
if (err) {
next(err);
return;
}
completions = [];
insts.forEach(function (inst) {
if (inst.name.indexOf(' ') === -1) {
// Cannot bash complete results with spaces, so
// skip them here.
completions.push(inst.name);
}
completions.push(inst.id);
});
arg.completions = completions.join('\n') + '\n';
next();
});
break;
case 'networks':
cloudapi.listNetworks({}, function (err, nets) {
if (err) {
next(err);
return;
}
completions = [];
nets.forEach(function (net) {
if (net.name.indexOf(' ') === -1) {
// Cannot bash complete results with spaces, so
// skip them here.
completions.push(net.name);
}
completions.push(net.id);
});
arg.completions = completions.join('\n') + '\n';
next();
});
break;
case 'fwrules':
cloudapi.listFirewallRules({}, function (err, fwrules) {
if (err) {
next(err);
return;
}
completions = [];
fwrules.forEach(function (fwrule) {
completions.push(fwrule.id);
});
arg.completions = completions.join('\n') + '\n';
next();
});
break;
case 'keys':
cloudapi.listKeys({}, function (err, keys) {
if (err) {
next(err);
return;
}
completions = [];
keys.forEach(function (key) {
if (key.name.indexOf(' ') === -1) {
// Cannot bash complete results with spaces, so
// skip them here.
completions.push(key.name);
}
completions.push(key.fingerprint);
});
arg.completions = completions.join('\n') + '\n';
next();
});
break;
default:
process.stderr.write('warning: unknown triton completion type: '
+ type + '\n');
next();
break;
}
},
function saveCache(arg, next) {
if (!arg.completions) {
next();
return;
}
fs.writeFile(cacheFile, arg.completions, next);
},
function emit(arg, next) {
if (arg.completions) {
console.log(arg.completions);
}
next();
}
]}, function (err) {
if (err === true) { // early abort signal
err = null;
}
cb(err);
});
};
/* /*
* Apply overrides from CLI options to the given profile object *in place*. * Apply overrides from CLI options to the given profile object *in place*.
*/ */

View File

@ -10,6 +10,8 @@
* `triton create ...` bwcompat shortcut for `triton instance create ...`. * `triton create ...` bwcompat shortcut for `triton instance create ...`.
*/ */
var targ = require('./do_instance/do_create');
function do_create(subcmd, opts, args, callback) { function do_create(subcmd, opts, args, callback) {
this.handlerFromSubcmd('instance').dispatch({ this.handlerFromSubcmd('instance').dispatch({
subcmd: 'create', subcmd: 'create',
@ -19,6 +21,8 @@ function do_create(subcmd, opts, args, callback) {
} }
do_create.help = 'A shortcut for "triton instance create".'; do_create.help = 'A shortcut for "triton instance create".';
do_create.options = require('./do_instance/do_create').options; do_create.options = targ.options;
do_create.completionArgtypes = targ.completionArgtypes;
module.exports = do_create; module.exports = do_create;

View File

@ -10,6 +10,8 @@
* `triton delete ...` bwcompat shortcut for `triton instance delete ...`. * `triton delete ...` bwcompat shortcut for `triton instance delete ...`.
*/ */
var targ = require('./do_instance/do_delete');
function do_delete(subcmd, opts, args, callback) { function do_delete(subcmd, opts, args, callback) {
this.handlerFromSubcmd('instance').dispatch({ this.handlerFromSubcmd('instance').dispatch({
subcmd: 'delete', subcmd: 'delete',
@ -20,6 +22,7 @@ function do_delete(subcmd, opts, args, callback) {
do_delete.help = 'A shortcut for "triton instance delete".'; do_delete.help = 'A shortcut for "triton instance delete".';
do_delete.aliases = ['rm']; do_delete.aliases = ['rm'];
do_delete.options = require('./do_instance/do_delete').options; do_delete.options = targ.options;
do_delete.completionArgtypes = targ.completionArgtypes;
module.exports = do_delete; module.exports = do_delete;

View File

@ -107,4 +107,6 @@ do_delete.help = [
do_delete.aliases = ['rm']; do_delete.aliases = ['rm'];
do_delete.completionArgtypes = ['tritonfwrule'];
module.exports = do_delete; module.exports = do_delete;

View File

@ -65,4 +65,6 @@ do_disable.help = [
'{{options}}' '{{options}}'
].join('\n'); ].join('\n');
do_disable.completionArgtypes = ['tritonfwrule'];
module.exports = do_disable; module.exports = do_disable;

View File

@ -65,4 +65,6 @@ do_enable.help = [
'{{options}}' '{{options}}'
].join('\n'); ].join('\n');
do_enable.completionArgtypes = ['tritonfwrule'];
module.exports = do_enable; module.exports = do_enable;

View File

@ -73,4 +73,6 @@ do_get.help = [
'{{options}}' '{{options}}'
].join('\n'); ].join('\n');
do_get.completionArgtypes = ['tritonfwrule', 'none'];
module.exports = do_get; module.exports = do_get;

View File

@ -159,4 +159,6 @@ do_instances.help = [
do_instances.aliases = ['insts']; do_instances.aliases = ['insts'];
do_instances.completionArgtypes = ['tritonfwrule', 'none'];
module.exports = do_instances; module.exports = do_instances;

View File

@ -265,5 +265,6 @@ do_create.helpOpts = {
maxHelpCol: 20 maxHelpCol: 20
}; };
do_create.completionArgtypes = ['tritoninstance', 'file'];
module.exports = do_create; module.exports = do_create;

View File

@ -151,5 +151,7 @@ do_delete.options = [
} }
]; ];
do_delete.completionArgtypes = ['tritonimage'];
do_delete.aliases = ['rm']; do_delete.aliases = ['rm'];
module.exports = do_delete; module.exports = do_delete;

View File

@ -67,4 +67,6 @@ do_get.help = (
/* END JSSTYLED */ /* END JSSTYLED */
); );
do_get.completionArgtypes = ['tritonimage', 'none'];
module.exports = do_get; module.exports = do_get;

View File

@ -116,13 +116,14 @@ do_wait.help = [
'Wait for images to change to a particular state.', 'Wait for images to change to a particular state.',
'', '',
'Usage:', 'Usage:',
' {{name}} wait [-s STATES] IMAGE [IMAGE ...]', ' {{name}} wait [-s STATES] IMAGE [IMAGE ...]',
'', '',
'{{options}}', '{{options}}',
'Where "states" is a comma-separated list of target instance states,', 'Where "states" is a comma-separated list of target instance states,',
'by default "active,failed". In other words, "triton img wait foo0" will', 'by default "active,failed". In other words, "triton img wait foo0" will',
'wait for image "foo0" to complete creation.' 'wait for image "foo0" to complete creation.'
].join('\n'); ].join('\n');
do_wait.options = [ do_wait.options = [
{ {
names: ['help', 'h'], names: ['help', 'h'],
@ -139,4 +140,6 @@ do_wait.options = [
} }
]; ];
do_wait.completionArgtypes = ['tritonimage'];
module.exports = do_wait; module.exports = do_wait;

View File

@ -111,4 +111,6 @@ do_audit.help = (
+ '{{options}}' + '{{options}}'
); );
do_audit.completionArgtypes = ['tritoninstance', 'none'];
module.exports = do_audit; module.exports = do_audit;

View File

@ -5,7 +5,7 @@
*/ */
/* /*
* Copyright 2015 Joyent, Inc. * Copyright 2016 Joyent, Inc.
* *
* `triton instance create ...` * `triton instance create ...`
*/ */
@ -289,7 +289,8 @@ do_create.options = [
type: 'arrayOfCommaSepString', type: 'arrayOfCommaSepString',
helpArg: 'NETWORK', helpArg: 'NETWORK',
help: 'One or more comma-separated networks (ID, name or short id). ' + help: 'One or more comma-separated networks (ID, name or short id). ' +
'This option can be used multiple times.' 'This option can be used multiple times.',
completionType: 'tritonnetwork'
}, },
// XXX locality: near, far // XXX locality: near, far
@ -330,5 +331,6 @@ do_create.helpOpts = {
maxHelpCol: 18 maxHelpCol: 18
}; };
do_create.completionArgtypes = ['tritonimage', 'tritonpackage', 'none'];
module.exports = do_create; module.exports = do_create;

View File

@ -100,4 +100,6 @@ do_fwrules.help = [
'{{options}}' '{{options}}'
].join('\n'); ].join('\n');
do_fwrules.completionArgtypes = ['tritoninstance', 'none'];
module.exports = do_fwrules; module.exports = do_fwrules;

View File

@ -60,4 +60,6 @@ do_get.help = (
/* END JSSTYLED */ /* END JSSTYLED */
); );
do_get.completionArgtypes = ['tritoninstance', 'none'];
module.exports = do_get; module.exports = do_get;

View File

@ -133,4 +133,6 @@ do_create.help = [
'Snapshot do not work for instances of type "kvm".' 'Snapshot do not work for instances of type "kvm".'
].join('\n'); ].join('\n');
do_create.completionArgtypes = ['tritoninstance', 'none'];
module.exports = do_create; module.exports = do_create;

View File

@ -150,4 +150,8 @@ do_delete.help = [
do_delete.aliases = ['rm']; do_delete.aliases = ['rm'];
// TODO: When have 'tritonsnapshot' completion, then use this:
// do_get.completionArgtypes = ['tritoninstance', 'tritonsnapshot'];
do_delete.completionArgtypes = ['tritoninstance', 'none'];
module.exports = do_delete; module.exports = do_delete;

View File

@ -77,4 +77,8 @@ do_get.help = [
'{{options}}' '{{options}}'
].join('\n'); ].join('\n');
// TODO: When have 'tritonsnapshot' completion, then use this:
// do_get.completionArgtypes = ['tritoninstance', 'tritonsnapshot', 'none'];
do_get.completionArgtypes = ['tritoninstance', 'none'];
module.exports = do_get; module.exports = do_get;

View File

@ -93,6 +93,8 @@ do_list.help = [
'{{options}}' '{{options}}'
].join('\n'); ].join('\n');
do_list.completionArgtypes = ['tritoninstance', 'none'];
do_list.aliases = ['ls']; do_list.aliases = ['ls'];
module.exports = do_list; module.exports = do_list;

View File

@ -86,4 +86,8 @@ do_ssh.help = (
do_ssh.interspersedOptions = false; do_ssh.interspersedOptions = false;
// Use 'file' to fallback to the default bash completion... even though 'file'
// isn't quite right.
do_ssh.completionArgtypes = ['tritoninstance', 'file'];
module.exports = do_ssh; module.exports = do_ssh;

View File

@ -109,4 +109,6 @@ do_delete.help = [
do_delete.aliases = ['rm']; do_delete.aliases = ['rm'];
do_delete.completionArgtypes = ['tritoninstance', 'none'];
module.exports = do_delete; module.exports = do_delete;

View File

@ -65,4 +65,7 @@ do_get.help = [
/* END JSSTYLED */ /* END JSSTYLED */
].join('\n'); ].join('\n');
// TODO: When have 'tritoninstancetag' completion, add that in.
do_get.completionArgtypes = ['tritoninstance', 'none'];
module.exports = do_get; module.exports = do_get;

View File

@ -66,4 +66,6 @@ do_list.help = [
do_list.aliases = ['ls']; do_list.aliases = ['ls'];
do_list.completionArgtypes = ['tritoninstance', 'none'];
module.exports = do_list; module.exports = do_list;

View File

@ -129,4 +129,6 @@ do_replace_all.help = [
/* END JSSTYLED */ /* END JSSTYLED */
].join('\n'); ].join('\n');
do_replace_all.completionArgtypes = ['tritoninstance', 'file'];
module.exports = do_replace_all; module.exports = do_replace_all;

View File

@ -130,4 +130,6 @@ do_set.help = [
/* END JSSTYLED */ /* END JSSTYLED */
].join('\n'); ].join('\n');
do_set.completionArgtypes = ['tritoninstance', 'file'];
module.exports = do_set; module.exports = do_set;

View File

@ -137,4 +137,6 @@ do_wait.options = [
} }
]; ];
do_wait.completionArgtypes = ['tritoninstance'];
module.exports = do_wait; module.exports = do_wait;

View File

@ -66,6 +66,8 @@ function gen_do_ACTION(opts) {
} }
]; ];
do_ACTION.completionArgtypes = ['tritoninstance'];
if (action === 'start') { if (action === 'start') {
do_ACTION.options.push({ do_ACTION.options.push({
names: ['snapshot'], names: ['snapshot'],

View File

@ -116,4 +116,6 @@ do_delete.help = [
do_delete.aliases = ['rm']; do_delete.aliases = ['rm'];
do_delete.completionArgtypes = ['tritonkey'];
module.exports = do_delete; module.exports = do_delete;

View File

@ -77,4 +77,6 @@ do_get.help = [
'Where "KEY" is an SSH key "name" or "fingerprint".' 'Where "KEY" is an SSH key "name" or "fingerprint".'
].join('\n'); ].join('\n');
do_get.completionArgtypes = ['tritonkey', 'none'];
module.exports = do_get; module.exports = do_get;

View File

@ -60,4 +60,6 @@ do_get.help = (
+ '{{options}}' + '{{options}}'
); );
do_get.completionArgtypes = ['tritonnetwork', 'none'];
module.exports = do_get; module.exports = do_get;

View File

@ -66,4 +66,6 @@ do_get.help = (
/* END JSSTYLED */ /* END JSSTYLED */
); );
do_get.completionArgtypes = ['tritonpackage', 'none'];
module.exports = do_get; module.exports = do_get;

View File

@ -129,6 +129,7 @@ do_delete.help = [
'{{options}}' '{{options}}'
].join('\n'); ].join('\n');
do_delete.completionArgtypes = ['tritonprofile', 'none'];
do_delete.aliases = ['rm']; do_delete.aliases = ['rm'];

View File

@ -163,5 +163,6 @@ do_edit.help = [
'{{options}}' '{{options}}'
].join('\n'); ].join('\n');
do_edit.completionArgtypes = ['tritonprofile', 'none'];
module.exports = do_edit; module.exports = do_edit;

View File

@ -10,6 +10,8 @@
* `triton reboot ...` bwcompat shortcut for `triton instance reboot ...`. * `triton reboot ...` bwcompat shortcut for `triton instance reboot ...`.
*/ */
var targ = require('./do_instance/do_reboot');
function do_reboot(subcmd, opts, args, callback) { function do_reboot(subcmd, opts, args, callback) {
this.handlerFromSubcmd('instance').dispatch({ this.handlerFromSubcmd('instance').dispatch({
subcmd: 'reboot', subcmd: 'reboot',
@ -19,6 +21,7 @@ function do_reboot(subcmd, opts, args, callback) {
} }
do_reboot.help = 'A shortcut for "triton instance reboot".'; do_reboot.help = 'A shortcut for "triton instance reboot".';
do_reboot.options = require('./do_instance/do_reboot').options; do_reboot.options = targ.options;
do_reboot.completionArgtypes = targ.completionArgtypes;
module.exports = do_reboot; module.exports = do_reboot;

View File

@ -10,6 +10,8 @@
* `triton ssh ...` bwcompat shortcut for `triton instance ssh ...`. * `triton ssh ...` bwcompat shortcut for `triton instance ssh ...`.
*/ */
var targ = require('./do_instance/do_ssh');
function do_ssh(subcmd, opts, args, callback) { function do_ssh(subcmd, opts, args, callback) {
this.handlerFromSubcmd('instance').dispatch({ this.handlerFromSubcmd('instance').dispatch({
subcmd: 'ssh', subcmd: 'ssh',
@ -19,6 +21,7 @@ function do_ssh(subcmd, opts, args, callback) {
} }
do_ssh.help = 'A shortcut for "triton instance ssh".'; do_ssh.help = 'A shortcut for "triton instance ssh".';
do_ssh.options = require('./do_instance/do_ssh').options; do_ssh.options = targ.options;
do_ssh.completionArgtypes = targ.completionArgtypes;
module.exports = do_ssh; module.exports = do_ssh;

View File

@ -10,6 +10,8 @@
* `triton start ...` bwcompat shortcut for `triton instance start ...`. * `triton start ...` bwcompat shortcut for `triton instance start ...`.
*/ */
var targ = require('./do_instance/do_start');
function do_start(subcmd, opts, args, callback) { function do_start(subcmd, opts, args, callback) {
this.handlerFromSubcmd('instance').dispatch({ this.handlerFromSubcmd('instance').dispatch({
subcmd: 'start', subcmd: 'start',
@ -19,6 +21,7 @@ function do_start(subcmd, opts, args, callback) {
} }
do_start.help = 'A shortcut for "triton instance start".'; do_start.help = 'A shortcut for "triton instance start".';
do_start.options = require('./do_instance/do_start').options; do_start.options = targ.options;
do_start.completionArgtypes = targ.completionArgtypes;
module.exports = do_start; module.exports = do_start;

View File

@ -10,6 +10,8 @@
* `triton stop ...` bwcompat shortcut for `triton instance stop ...`. * `triton stop ...` bwcompat shortcut for `triton instance stop ...`.
*/ */
var targ = require('./do_instance/do_stop');
function do_stop(subcmd, opts, args, callback) { function do_stop(subcmd, opts, args, callback) {
this.handlerFromSubcmd('instance').dispatch({ this.handlerFromSubcmd('instance').dispatch({
subcmd: 'stop', subcmd: 'stop',
@ -19,6 +21,7 @@ function do_stop(subcmd, opts, args, callback) {
} }
do_stop.help = 'A shortcut for "triton instance stop".'; do_stop.help = 'A shortcut for "triton instance stop".';
do_stop.options = require('./do_instance/do_stop').options; do_stop.options = targ.options;
do_stop.completionArgtypes = targ.completionArgtypes;
module.exports = do_stop; module.exports = do_stop;

View File

@ -1271,7 +1271,7 @@ function waitForInstanceTagChanges(opts, cb) {
if (elapsedTime > timeout) { if (elapsedTime > timeout) {
cb(new errors.TimeoutError(format('timeout waiting for ' cb(new errors.TimeoutError(format('timeout waiting for '
+ 'tag changes on instance %s (elapsed %ds)', + 'tag changes on instance %s (elapsed %ds)',
opts.id, Math.round(elapsedTime * 1000)))); opts.id, Math.round(elapsedTime / 1000))));
} else { } else {
setTimeout(poll, POLL_INTERVAL); setTimeout(poll, POLL_INTERVAL);
} }

View File

@ -1,7 +1,7 @@
{ {
"name": "triton", "name": "triton",
"description": "Joyent Triton CLI and client (https://www.joyent.com/triton)", "description": "Joyent Triton CLI and client (https://www.joyent.com/triton)",
"version": "4.6.1", "version": "4.7.0",
"author": "Joyent (joyent.com)", "author": "Joyent (joyent.com)",
"dependencies": { "dependencies": {
"assert-plus": "0.2.0", "assert-plus": "0.2.0",