From e3b5e6b01618067b39027af208a76238bf9ec3ce Mon Sep 17 00:00:00 2001 From: Trent Mick Date: Wed, 9 Mar 2016 09:19:44 -0800 Subject: [PATCH] joyent/node-triton#101 Bash completion for server-side data: instances, images, etc. --- CHANGES.md | 9 +- etc/triton-bash-completion-types.sh | 139 +++++++++++++++ lib/cli.js | 208 ++++++++++++++++++++++- lib/do_create.js | 6 +- lib/do_delete.js | 5 +- lib/do_fwrule/do_delete.js | 2 + lib/do_fwrule/do_disable.js | 2 + lib/do_fwrule/do_enable.js | 2 + lib/do_fwrule/do_get.js | 2 + lib/do_fwrule/do_instances.js | 2 + lib/do_image/do_create.js | 1 + lib/do_image/do_delete.js | 2 + lib/do_image/do_get.js | 2 + lib/do_image/do_wait.js | 5 +- lib/do_instance/do_audit.js | 2 + lib/do_instance/do_create.js | 6 +- lib/do_instance/do_fwrules.js | 2 + lib/do_instance/do_get.js | 2 + lib/do_instance/do_snapshot/do_create.js | 2 + lib/do_instance/do_snapshot/do_delete.js | 4 + lib/do_instance/do_snapshot/do_get.js | 4 + lib/do_instance/do_snapshot/do_list.js | 2 + lib/do_instance/do_ssh.js | 4 + lib/do_instance/do_tag/do_delete.js | 2 + lib/do_instance/do_tag/do_get.js | 3 + lib/do_instance/do_tag/do_list.js | 2 + lib/do_instance/do_tag/do_replace_all.js | 2 + lib/do_instance/do_tag/do_set.js | 2 + lib/do_instance/do_wait.js | 2 + lib/do_instance/gen_do_ACTION.js | 2 + lib/do_key/do_delete.js | 2 + lib/do_key/do_get.js | 2 + lib/do_network/do_get.js | 2 + lib/do_package/do_get.js | 2 + lib/do_profile/do_delete.js | 1 + lib/do_profile/do_edit.js | 1 + lib/do_reboot.js | 5 +- lib/do_ssh.js | 5 +- lib/do_start.js | 5 +- lib/do_stop.js | 5 +- lib/tritonapi.js | 2 +- package.json | 2 +- 42 files changed, 447 insertions(+), 17 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 5b1c0ae..3a57a74 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,7 +1,14 @@ # 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 `. 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` diff --git a/etc/triton-bash-completion-types.sh b/etc/triton-bash-completion-types.sh index 4cc99fd..f1a57ea 100644 --- a/etc/triton-bash-completion-types.sh +++ b/etc/triton-bash-completion-types.sh @@ -1,4 +1,5 @@ # Functions for Bash completion of some 'triton' option/arg types. + function complete_tritonprofile { local word="$1" local candidates @@ -7,6 +8,144 @@ function complete_tritonprofile { 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 . 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 { local word="$1" local candidates diff --git a/lib/cli.js b/lib/cli.js index 4cc7540..3e8800c 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -17,6 +17,7 @@ var child_process = require('child_process'), exec = child_process.exec; var cmdln = require('cmdln'), Cmdln = cmdln.Cmdln; +var fs = require('fs'); var mkdirp = require('mkdirp'); var util = require('util'), format = util.format; @@ -32,7 +33,7 @@ var tritonapi = require('./tritonapi'); //---- globals -var pkg = require('../package.json'); +var packageJson = require('../package.json'); var CONFIG_DIR; if (process.platform === 'win32') { @@ -198,7 +199,7 @@ cmdln.dashdash.addOptionType({ function CLI() { Cmdln.call(this, { name: 'triton', - desc: pkg.description, + desc: packageJson.description, options: OPTIONS, helpOpts: { includeEnv: true, @@ -260,7 +261,7 @@ CLI.prototype.init = function (opts, args, callback) { } if (opts.version) { - console.log(this.name, pkg.version); + console.log(this.name, packageJson.version); callback(false); return; } @@ -298,8 +299,26 @@ CLI.prototype.init = function (opts, args, callback) { return self._tritonapi; }); - // Cmdln class handles `opts.help`. - Cmdln.prototype.init.apply(this, arguments); + if (process.env.TRITON_COMPLETE) { + /* + * If `TRITON_COMPLETE=` 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*. */ diff --git a/lib/do_create.js b/lib/do_create.js index 0a9c0a8..2b47ef1 100644 --- a/lib/do_create.js +++ b/lib/do_create.js @@ -10,6 +10,8 @@ * `triton create ...` bwcompat shortcut for `triton instance create ...`. */ +var targ = require('./do_instance/do_create'); + function do_create(subcmd, opts, args, callback) { this.handlerFromSubcmd('instance').dispatch({ subcmd: 'create', @@ -19,6 +21,8 @@ function do_create(subcmd, opts, args, callback) { } 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; diff --git a/lib/do_delete.js b/lib/do_delete.js index 3c47c5b..0e59119 100644 --- a/lib/do_delete.js +++ b/lib/do_delete.js @@ -10,6 +10,8 @@ * `triton delete ...` bwcompat shortcut for `triton instance delete ...`. */ +var targ = require('./do_instance/do_delete'); + function do_delete(subcmd, opts, args, callback) { this.handlerFromSubcmd('instance').dispatch({ subcmd: 'delete', @@ -20,6 +22,7 @@ function do_delete(subcmd, opts, args, callback) { do_delete.help = 'A shortcut for "triton instance delete".'; 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; diff --git a/lib/do_fwrule/do_delete.js b/lib/do_fwrule/do_delete.js index 29814ec..62f1a89 100644 --- a/lib/do_fwrule/do_delete.js +++ b/lib/do_fwrule/do_delete.js @@ -107,4 +107,6 @@ do_delete.help = [ do_delete.aliases = ['rm']; +do_delete.completionArgtypes = ['tritonfwrule']; + module.exports = do_delete; diff --git a/lib/do_fwrule/do_disable.js b/lib/do_fwrule/do_disable.js index 9afa8de..0be5104 100644 --- a/lib/do_fwrule/do_disable.js +++ b/lib/do_fwrule/do_disable.js @@ -65,4 +65,6 @@ do_disable.help = [ '{{options}}' ].join('\n'); +do_disable.completionArgtypes = ['tritonfwrule']; + module.exports = do_disable; diff --git a/lib/do_fwrule/do_enable.js b/lib/do_fwrule/do_enable.js index 0cd7306..32ae1ef 100644 --- a/lib/do_fwrule/do_enable.js +++ b/lib/do_fwrule/do_enable.js @@ -65,4 +65,6 @@ do_enable.help = [ '{{options}}' ].join('\n'); +do_enable.completionArgtypes = ['tritonfwrule']; + module.exports = do_enable; diff --git a/lib/do_fwrule/do_get.js b/lib/do_fwrule/do_get.js index ac0db39..88244bd 100644 --- a/lib/do_fwrule/do_get.js +++ b/lib/do_fwrule/do_get.js @@ -73,4 +73,6 @@ do_get.help = [ '{{options}}' ].join('\n'); +do_get.completionArgtypes = ['tritonfwrule', 'none']; + module.exports = do_get; diff --git a/lib/do_fwrule/do_instances.js b/lib/do_fwrule/do_instances.js index 627a481..2931a09 100644 --- a/lib/do_fwrule/do_instances.js +++ b/lib/do_fwrule/do_instances.js @@ -159,4 +159,6 @@ do_instances.help = [ do_instances.aliases = ['insts']; +do_instances.completionArgtypes = ['tritonfwrule', 'none']; + module.exports = do_instances; diff --git a/lib/do_image/do_create.js b/lib/do_image/do_create.js index 1142208..e859857 100644 --- a/lib/do_image/do_create.js +++ b/lib/do_image/do_create.js @@ -265,5 +265,6 @@ do_create.helpOpts = { maxHelpCol: 20 }; +do_create.completionArgtypes = ['tritoninstance', 'file']; module.exports = do_create; diff --git a/lib/do_image/do_delete.js b/lib/do_image/do_delete.js index a1e1a86..2a7e72b 100644 --- a/lib/do_image/do_delete.js +++ b/lib/do_image/do_delete.js @@ -151,5 +151,7 @@ do_delete.options = [ } ]; +do_delete.completionArgtypes = ['tritonimage']; + do_delete.aliases = ['rm']; module.exports = do_delete; diff --git a/lib/do_image/do_get.js b/lib/do_image/do_get.js index aaa71a9..1eca39e 100644 --- a/lib/do_image/do_get.js +++ b/lib/do_image/do_get.js @@ -67,4 +67,6 @@ do_get.help = ( /* END JSSTYLED */ ); +do_get.completionArgtypes = ['tritonimage', 'none']; + module.exports = do_get; diff --git a/lib/do_image/do_wait.js b/lib/do_image/do_wait.js index 4674c7a..f7cec9f 100644 --- a/lib/do_image/do_wait.js +++ b/lib/do_image/do_wait.js @@ -116,13 +116,14 @@ do_wait.help = [ 'Wait for images to change to a particular state.', '', 'Usage:', - ' {{name}} wait [-s STATES] IMAGE [IMAGE ...]', + ' {{name}} wait [-s STATES] IMAGE [IMAGE ...]', '', '{{options}}', 'Where "states" is a comma-separated list of target instance states,', 'by default "active,failed". In other words, "triton img wait foo0" will', 'wait for image "foo0" to complete creation.' ].join('\n'); + do_wait.options = [ { names: ['help', 'h'], @@ -139,4 +140,6 @@ do_wait.options = [ } ]; +do_wait.completionArgtypes = ['tritonimage']; + module.exports = do_wait; diff --git a/lib/do_instance/do_audit.js b/lib/do_instance/do_audit.js index 86223f0..04e8496 100644 --- a/lib/do_instance/do_audit.js +++ b/lib/do_instance/do_audit.js @@ -111,4 +111,6 @@ do_audit.help = ( + '{{options}}' ); +do_audit.completionArgtypes = ['tritoninstance', 'none']; + module.exports = do_audit; diff --git a/lib/do_instance/do_create.js b/lib/do_instance/do_create.js index 8b6b0ac..9735838 100644 --- a/lib/do_instance/do_create.js +++ b/lib/do_instance/do_create.js @@ -5,7 +5,7 @@ */ /* - * Copyright 2015 Joyent, Inc. + * Copyright 2016 Joyent, Inc. * * `triton instance create ...` */ @@ -289,7 +289,8 @@ do_create.options = [ type: 'arrayOfCommaSepString', helpArg: 'NETWORK', 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 @@ -330,5 +331,6 @@ do_create.helpOpts = { maxHelpCol: 18 }; +do_create.completionArgtypes = ['tritonimage', 'tritonpackage', 'none']; module.exports = do_create; diff --git a/lib/do_instance/do_fwrules.js b/lib/do_instance/do_fwrules.js index 327bcae..32da3c7 100644 --- a/lib/do_instance/do_fwrules.js +++ b/lib/do_instance/do_fwrules.js @@ -100,4 +100,6 @@ do_fwrules.help = [ '{{options}}' ].join('\n'); +do_fwrules.completionArgtypes = ['tritoninstance', 'none']; + module.exports = do_fwrules; diff --git a/lib/do_instance/do_get.js b/lib/do_instance/do_get.js index 9e7960d..05dd084 100644 --- a/lib/do_instance/do_get.js +++ b/lib/do_instance/do_get.js @@ -60,4 +60,6 @@ do_get.help = ( /* END JSSTYLED */ ); +do_get.completionArgtypes = ['tritoninstance', 'none']; + module.exports = do_get; diff --git a/lib/do_instance/do_snapshot/do_create.js b/lib/do_instance/do_snapshot/do_create.js index 075aa91..6c42f61 100644 --- a/lib/do_instance/do_snapshot/do_create.js +++ b/lib/do_instance/do_snapshot/do_create.js @@ -133,4 +133,6 @@ do_create.help = [ 'Snapshot do not work for instances of type "kvm".' ].join('\n'); +do_create.completionArgtypes = ['tritoninstance', 'none']; + module.exports = do_create; diff --git a/lib/do_instance/do_snapshot/do_delete.js b/lib/do_instance/do_snapshot/do_delete.js index f599240..681f8a9 100644 --- a/lib/do_instance/do_snapshot/do_delete.js +++ b/lib/do_instance/do_snapshot/do_delete.js @@ -150,4 +150,8 @@ do_delete.help = [ 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; diff --git a/lib/do_instance/do_snapshot/do_get.js b/lib/do_instance/do_snapshot/do_get.js index 102f060..79e442b 100644 --- a/lib/do_instance/do_snapshot/do_get.js +++ b/lib/do_instance/do_snapshot/do_get.js @@ -77,4 +77,8 @@ do_get.help = [ '{{options}}' ].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; diff --git a/lib/do_instance/do_snapshot/do_list.js b/lib/do_instance/do_snapshot/do_list.js index e25adc2..70686c0 100644 --- a/lib/do_instance/do_snapshot/do_list.js +++ b/lib/do_instance/do_snapshot/do_list.js @@ -93,6 +93,8 @@ do_list.help = [ '{{options}}' ].join('\n'); +do_list.completionArgtypes = ['tritoninstance', 'none']; + do_list.aliases = ['ls']; module.exports = do_list; diff --git a/lib/do_instance/do_ssh.js b/lib/do_instance/do_ssh.js index bf88b9d..291bc06 100644 --- a/lib/do_instance/do_ssh.js +++ b/lib/do_instance/do_ssh.js @@ -86,4 +86,8 @@ do_ssh.help = ( 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; diff --git a/lib/do_instance/do_tag/do_delete.js b/lib/do_instance/do_tag/do_delete.js index 5a63de4..591468e 100644 --- a/lib/do_instance/do_tag/do_delete.js +++ b/lib/do_instance/do_tag/do_delete.js @@ -109,4 +109,6 @@ do_delete.help = [ do_delete.aliases = ['rm']; +do_delete.completionArgtypes = ['tritoninstance', 'none']; + module.exports = do_delete; diff --git a/lib/do_instance/do_tag/do_get.js b/lib/do_instance/do_tag/do_get.js index b6089dc..776c656 100644 --- a/lib/do_instance/do_tag/do_get.js +++ b/lib/do_instance/do_tag/do_get.js @@ -65,4 +65,7 @@ do_get.help = [ /* END JSSTYLED */ ].join('\n'); +// TODO: When have 'tritoninstancetag' completion, add that in. +do_get.completionArgtypes = ['tritoninstance', 'none']; + module.exports = do_get; diff --git a/lib/do_instance/do_tag/do_list.js b/lib/do_instance/do_tag/do_list.js index b6d9e3e..cd2a3b8 100644 --- a/lib/do_instance/do_tag/do_list.js +++ b/lib/do_instance/do_tag/do_list.js @@ -66,4 +66,6 @@ do_list.help = [ do_list.aliases = ['ls']; +do_list.completionArgtypes = ['tritoninstance', 'none']; + module.exports = do_list; diff --git a/lib/do_instance/do_tag/do_replace_all.js b/lib/do_instance/do_tag/do_replace_all.js index 3ebd877..384dc7c 100644 --- a/lib/do_instance/do_tag/do_replace_all.js +++ b/lib/do_instance/do_tag/do_replace_all.js @@ -129,4 +129,6 @@ do_replace_all.help = [ /* END JSSTYLED */ ].join('\n'); +do_replace_all.completionArgtypes = ['tritoninstance', 'file']; + module.exports = do_replace_all; diff --git a/lib/do_instance/do_tag/do_set.js b/lib/do_instance/do_tag/do_set.js index d776331..77556aa 100644 --- a/lib/do_instance/do_tag/do_set.js +++ b/lib/do_instance/do_tag/do_set.js @@ -130,4 +130,6 @@ do_set.help = [ /* END JSSTYLED */ ].join('\n'); +do_set.completionArgtypes = ['tritoninstance', 'file']; + module.exports = do_set; diff --git a/lib/do_instance/do_wait.js b/lib/do_instance/do_wait.js index 898ce65..3e220ad 100644 --- a/lib/do_instance/do_wait.js +++ b/lib/do_instance/do_wait.js @@ -137,4 +137,6 @@ do_wait.options = [ } ]; +do_wait.completionArgtypes = ['tritoninstance']; + module.exports = do_wait; diff --git a/lib/do_instance/gen_do_ACTION.js b/lib/do_instance/gen_do_ACTION.js index b418be0..09489f5 100644 --- a/lib/do_instance/gen_do_ACTION.js +++ b/lib/do_instance/gen_do_ACTION.js @@ -66,6 +66,8 @@ function gen_do_ACTION(opts) { } ]; + do_ACTION.completionArgtypes = ['tritoninstance']; + if (action === 'start') { do_ACTION.options.push({ names: ['snapshot'], diff --git a/lib/do_key/do_delete.js b/lib/do_key/do_delete.js index db7a8af..64ca8b7 100644 --- a/lib/do_key/do_delete.js +++ b/lib/do_key/do_delete.js @@ -116,4 +116,6 @@ do_delete.help = [ do_delete.aliases = ['rm']; +do_delete.completionArgtypes = ['tritonkey']; + module.exports = do_delete; diff --git a/lib/do_key/do_get.js b/lib/do_key/do_get.js index dba3a94..c37f62d 100644 --- a/lib/do_key/do_get.js +++ b/lib/do_key/do_get.js @@ -77,4 +77,6 @@ do_get.help = [ 'Where "KEY" is an SSH key "name" or "fingerprint".' ].join('\n'); +do_get.completionArgtypes = ['tritonkey', 'none']; + module.exports = do_get; diff --git a/lib/do_network/do_get.js b/lib/do_network/do_get.js index bc537e0..7283b80 100644 --- a/lib/do_network/do_get.js +++ b/lib/do_network/do_get.js @@ -60,4 +60,6 @@ do_get.help = ( + '{{options}}' ); +do_get.completionArgtypes = ['tritonnetwork', 'none']; + module.exports = do_get; diff --git a/lib/do_package/do_get.js b/lib/do_package/do_get.js index f710a91..34e432b 100644 --- a/lib/do_package/do_get.js +++ b/lib/do_package/do_get.js @@ -66,4 +66,6 @@ do_get.help = ( /* END JSSTYLED */ ); +do_get.completionArgtypes = ['tritonpackage', 'none']; + module.exports = do_get; diff --git a/lib/do_profile/do_delete.js b/lib/do_profile/do_delete.js index 25c1418..3210209 100644 --- a/lib/do_profile/do_delete.js +++ b/lib/do_profile/do_delete.js @@ -129,6 +129,7 @@ do_delete.help = [ '{{options}}' ].join('\n'); +do_delete.completionArgtypes = ['tritonprofile', 'none']; do_delete.aliases = ['rm']; diff --git a/lib/do_profile/do_edit.js b/lib/do_profile/do_edit.js index f331e1c..8ca967b 100644 --- a/lib/do_profile/do_edit.js +++ b/lib/do_profile/do_edit.js @@ -163,5 +163,6 @@ do_edit.help = [ '{{options}}' ].join('\n'); +do_edit.completionArgtypes = ['tritonprofile', 'none']; module.exports = do_edit; diff --git a/lib/do_reboot.js b/lib/do_reboot.js index 8701147..17adb60 100644 --- a/lib/do_reboot.js +++ b/lib/do_reboot.js @@ -10,6 +10,8 @@ * `triton reboot ...` bwcompat shortcut for `triton instance reboot ...`. */ +var targ = require('./do_instance/do_reboot'); + function do_reboot(subcmd, opts, args, callback) { this.handlerFromSubcmd('instance').dispatch({ subcmd: 'reboot', @@ -19,6 +21,7 @@ function do_reboot(subcmd, opts, args, callback) { } 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; diff --git a/lib/do_ssh.js b/lib/do_ssh.js index 6695f14..14df1ca 100644 --- a/lib/do_ssh.js +++ b/lib/do_ssh.js @@ -10,6 +10,8 @@ * `triton ssh ...` bwcompat shortcut for `triton instance ssh ...`. */ +var targ = require('./do_instance/do_ssh'); + function do_ssh(subcmd, opts, args, callback) { this.handlerFromSubcmd('instance').dispatch({ subcmd: 'ssh', @@ -19,6 +21,7 @@ function do_ssh(subcmd, opts, args, callback) { } 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; diff --git a/lib/do_start.js b/lib/do_start.js index 15e491c..9d3dc9f 100644 --- a/lib/do_start.js +++ b/lib/do_start.js @@ -10,6 +10,8 @@ * `triton start ...` bwcompat shortcut for `triton instance start ...`. */ +var targ = require('./do_instance/do_start'); + function do_start(subcmd, opts, args, callback) { this.handlerFromSubcmd('instance').dispatch({ subcmd: 'start', @@ -19,6 +21,7 @@ function do_start(subcmd, opts, args, callback) { } 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; diff --git a/lib/do_stop.js b/lib/do_stop.js index 1d3985c..2c69087 100644 --- a/lib/do_stop.js +++ b/lib/do_stop.js @@ -10,6 +10,8 @@ * `triton stop ...` bwcompat shortcut for `triton instance stop ...`. */ +var targ = require('./do_instance/do_stop'); + function do_stop(subcmd, opts, args, callback) { this.handlerFromSubcmd('instance').dispatch({ subcmd: 'stop', @@ -19,6 +21,7 @@ function do_stop(subcmd, opts, args, callback) { } 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; diff --git a/lib/tritonapi.js b/lib/tritonapi.js index 161995f..0de508d 100644 --- a/lib/tritonapi.js +++ b/lib/tritonapi.js @@ -1271,7 +1271,7 @@ function waitForInstanceTagChanges(opts, cb) { if (elapsedTime > timeout) { cb(new errors.TimeoutError(format('timeout waiting for ' + 'tag changes on instance %s (elapsed %ds)', - opts.id, Math.round(elapsedTime * 1000)))); + opts.id, Math.round(elapsedTime / 1000)))); } else { setTimeout(poll, POLL_INTERVAL); } diff --git a/package.json b/package.json index 48d23a6..a698903 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "triton", "description": "Joyent Triton CLI and client (https://www.joyent.com/triton)", - "version": "4.6.1", + "version": "4.7.0", "author": "Joyent (joyent.com)", "dependencies": { "assert-plus": "0.2.0",