From 76759e547e67d0c45970bb881f90ae9a82912c77 Mon Sep 17 00:00:00 2001 From: Dillon Amburgey Date: Thu, 22 Dec 2016 16:27:13 -0500 Subject: [PATCH] joyent/node-triton#3 triton ssh command not aware of "ubuntu" login for ubuntu-certified images --- CHANGES.md | 2 + lib/do_instance/do_ssh.js | 87 ++++++++++++++++++++++++++++----------- lib/tritonapi.js | 13 +++--- 3 files changed, 71 insertions(+), 31 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index a0410af..86b1221 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,8 @@ Known issues: ## not yet released +- [joyent/node-triton#3] triton ssh command not aware of "ubuntu" login for ubuntu-certified images + - [joyent/node-triton#137] Improve the handling for the getting started case when a user may not have envvars or a profile setup. diff --git a/lib/do_instance/do_ssh.js b/lib/do_instance/do_ssh.js index be2da84..8b45571 100644 --- a/lib/do_instance/do_ssh.js +++ b/lib/do_instance/do_ssh.js @@ -12,14 +12,13 @@ var path = require('path'); var spawn = require('child_process').spawn; +var vasync = require('vasync'); var common = require('../common'); var errors = require('../errors'); function do_ssh(subcmd, opts, args, callback) { - var self = this; - if (opts.help) { this.do_help('help', {}, [subcmd], callback); return; @@ -28,39 +27,72 @@ function do_ssh(subcmd, opts, args, callback) { return; } - var cli = this.top; var id = args.shift(); - var user = 'root'; + var user; var i = id.indexOf('@'); if (i >= 0) { user = id.substr(0, i); id = id.substr(i + 1); } - common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) { - if (setupErr) { - callback(setupErr); - } - cli.tritonapi.getInstance(id, function (err, inst) { - if (err) { - callback(err); + vasync.pipeline({arg: {cli: this.top}, funcs: [ + common.cliSetupTritonApi, + + function getInstanceIp(ctx, next) { + ctx.cli.tritonapi.getInstance(id, function (err, inst) { + if (err) { + next(err); + return; + } + + ctx.inst = inst; + + ctx.ip = inst.primaryIp; + if (!ctx.ip) { + next(new Error('primaryIp not found for instance')); + return; + } + next(); + }); + }, + + function getUser(ctx, next) { + if (user) { + next(); return; } - var ip = inst.primaryIp; - if (!ip) { - callback(new Error('primaryIp not found for instance')); - return; - } + ctx.cli.tritonapi.getImage({ + name: ctx.inst.image, + useCache: true + }, function (getImageErr, image) { + if (getImageErr) { + next(getImageErr); + return; + } - args = ['-l', user, ip].concat(args); + /* + * This is a convention as seen on Joyent's + * "ubuntu-certified" KVM images. + */ + if (image.tags.default_user) { + user = image.tags.default_user; + } else { + user = 'root'; + } + + next(); + }); + }, + + function doSsh(ctx, next) { + args = ['-l', user, ctx.ip].concat(args); /* - * By default we disable ControlMaster (aka mux, aka SSH connection - * multiplexing) because of + * By default we disable ControlMaster (aka mux, aka SSH + * connection multiplexing) because of * https://github.com/joyent/node-triton/issues/52 - * */ if (!opts.no_disable_mux) { /* @@ -73,7 +105,7 @@ function do_ssh(subcmd, opts, args, callback) { * and also because Windows. */ var nullSshControlPath = path.resolve( - cli.tritonapi.config._configDir, 'tmp', + ctx.cli.tritonapi.config._configDir, 'tmp', 'nullSshControlPath'); args = [ '-o', 'ControlMaster=no', @@ -81,7 +113,7 @@ function do_ssh(subcmd, opts, args, callback) { ].concat(args); } - self.top.log.info({args: args}, 'forking ssh'); + ctx.cli.log.info({args: args}, 'forking ssh'); var child = spawn('ssh', args, {stdio: 'inherit'}); child.on('close', function (code) { /* @@ -90,9 +122,10 @@ function do_ssh(subcmd, opts, args, callback) { * callback(); */ process.exit(code); + /* process.exit does not return so no need to call next(). */ }); - }); - }); + } + ]}, callback); } do_ssh.options = [ @@ -102,7 +135,7 @@ do_ssh.options = [ help: 'Show this help.' } ]; -do_ssh.synopses = ['{{name}} ssh [-h] INST [SSH-ARGUMENTS]']; +do_ssh.synopses = ['{{name}} ssh [-h] [USER@]INST [SSH-ARGUMENTS]']; do_ssh.help = [ /* BEGIN JSSTYLED */ 'SSH to the primary IP of an instance', @@ -112,6 +145,10 @@ do_ssh.help = [ '{{options}}', 'Where INST is the name, id, or short id of an instance. Note that', 'the INST argument must come before any `ssh` options or arguments.', + 'Where USER is the username to use on the instance. If not specified,', + 'the instance\'s image is inspected for the default_user tag.', + 'If USER is not specified and the default_user tag is not set, the user', + 'is assumed to be \"root\".', '', 'There is a known issue with SSH connection multiplexing (a.k.a. ', 'ControlMaster, mux) where stdout/stderr is lost. As a workaround, `ssh`', diff --git a/lib/tritonapi.js b/lib/tritonapi.js index 648a900..b016a98 100644 --- a/lib/tritonapi.js +++ b/lib/tritonapi.js @@ -364,23 +364,24 @@ TritonApi.prototype._cachePutJson = function _cachePutJson(key, obj, cb) { * Lookup the given key in the cache and return a hit or `undefined`. * * @param {String} key: The cache key, e.g. 'images.json'. + * @param {Number} ttl: The number of seconds the cached data is valid. * @param {Function} cb: `function (err, hit)`. * `err` is an Error if there was an unexpected error loading from the * cache. `hit` is undefined if there was no cache hit. On a hit, the * type of `hit` depends on the key. */ -TritonApi.prototype._cacheGetJson = function _cacheGetJson(key, cb) { +TritonApi.prototype._cacheGetJson = function _cacheGetJson(key, ttl, cb) { var self = this; assert.string(this.cacheDir, 'this.cacheDir'); assert.string(key, 'key'); + assert.number(ttl, 'ttl'); assert.func(cb, 'cb'); - var ttl = 5 * 60 * 1000; // timeout of cache file info (ms) - var keyPath = path.resolve(this.cacheDir, key); fs.stat(keyPath, function (statErr, stats) { if (!statErr && - stats.mtime.getTime() + ttl >= (new Date()).getTime()) { + // TTL is in seconds so we need to multiply by 1000. + stats.mtime.getTime() + (ttl * 1000) >= (new Date()).getTime()) { fs.readFile(keyPath, 'utf8', function (err, data) { if (err && err.code === 'ENOENT') { self.log.trace({keyPath: keyPath}, @@ -459,7 +460,7 @@ TritonApi.prototype.listImages = function listImages(opts, cb) { if (!useCache) { return next(); } - self._cacheGetJson(cacheKey, function (err, cached_) { + self._cacheGetJson(cacheKey, 5*60, function (err, cached_) { if (err) { return next(err); } @@ -525,7 +526,7 @@ TritonApi.prototype.getImage = function getImage(opts, cb) { return; } var cacheKey = 'images.json'; - self._cacheGetJson(cacheKey, function (err, images) { + self._cacheGetJson(cacheKey, 60*60, function (err, images) { if (err) { next(err); return;