joyent/node-triton#3 triton ssh command not aware of "ubuntu" login for ubuntu-certified images

This commit is contained in:
Dillon Amburgey 2016-12-22 16:27:13 -05:00
parent 04cc61ee1d
commit 76759e547e
3 changed files with 71 additions and 31 deletions

View File

@ -7,6 +7,8 @@ Known issues:
## not yet released ## 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 - [joyent/node-triton#137] Improve the handling for the getting started case
when a user may not have envvars or a profile setup. when a user may not have envvars or a profile setup.

View File

@ -12,14 +12,13 @@
var path = require('path'); var path = require('path');
var spawn = require('child_process').spawn; var spawn = require('child_process').spawn;
var vasync = require('vasync');
var common = require('../common'); var common = require('../common');
var errors = require('../errors'); var errors = require('../errors');
function do_ssh(subcmd, opts, args, callback) { function do_ssh(subcmd, opts, args, callback) {
var self = this;
if (opts.help) { if (opts.help) {
this.do_help('help', {}, [subcmd], callback); this.do_help('help', {}, [subcmd], callback);
return; return;
@ -28,39 +27,72 @@ function do_ssh(subcmd, opts, args, callback) {
return; return;
} }
var cli = this.top;
var id = args.shift(); var id = args.shift();
var user = 'root'; var user;
var i = id.indexOf('@'); var i = id.indexOf('@');
if (i >= 0) { if (i >= 0) {
user = id.substr(0, i); user = id.substr(0, i);
id = id.substr(i + 1); id = id.substr(i + 1);
} }
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) { vasync.pipeline({arg: {cli: this.top}, funcs: [
if (setupErr) { common.cliSetupTritonApi,
callback(setupErr);
} function getInstanceIp(ctx, next) {
cli.tritonapi.getInstance(id, function (err, inst) { ctx.cli.tritonapi.getInstance(id, function (err, inst) {
if (err) { if (err) {
callback(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; return;
} }
var ip = inst.primaryIp; ctx.cli.tritonapi.getImage({
if (!ip) { name: ctx.inst.image,
callback(new Error('primaryIp not found for instance')); useCache: true
return; }, 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 * By default we disable ControlMaster (aka mux, aka SSH
* multiplexing) because of * connection multiplexing) because of
* https://github.com/joyent/node-triton/issues/52 * https://github.com/joyent/node-triton/issues/52
*
*/ */
if (!opts.no_disable_mux) { if (!opts.no_disable_mux) {
/* /*
@ -73,7 +105,7 @@ function do_ssh(subcmd, opts, args, callback) {
* and also because Windows. * and also because Windows.
*/ */
var nullSshControlPath = path.resolve( var nullSshControlPath = path.resolve(
cli.tritonapi.config._configDir, 'tmp', ctx.cli.tritonapi.config._configDir, 'tmp',
'nullSshControlPath'); 'nullSshControlPath');
args = [ args = [
'-o', 'ControlMaster=no', '-o', 'ControlMaster=no',
@ -81,7 +113,7 @@ function do_ssh(subcmd, opts, args, callback) {
].concat(args); ].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'}); var child = spawn('ssh', args, {stdio: 'inherit'});
child.on('close', function (code) { child.on('close', function (code) {
/* /*
@ -90,9 +122,10 @@ function do_ssh(subcmd, opts, args, callback) {
* callback(); * callback();
*/ */
process.exit(code); process.exit(code);
/* process.exit does not return so no need to call next(). */
}); });
}); }
}); ]}, callback);
} }
do_ssh.options = [ do_ssh.options = [
@ -102,7 +135,7 @@ do_ssh.options = [
help: 'Show this help.' 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 = [ do_ssh.help = [
/* BEGIN JSSTYLED */ /* BEGIN JSSTYLED */
'SSH to the primary IP of an instance', 'SSH to the primary IP of an instance',
@ -112,6 +145,10 @@ do_ssh.help = [
'{{options}}', '{{options}}',
'Where INST is the name, id, or short id of an instance. Note that', '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.', '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. ', 'There is a known issue with SSH connection multiplexing (a.k.a. ',
'ControlMaster, mux) where stdout/stderr is lost. As a workaround, `ssh`', 'ControlMaster, mux) where stdout/stderr is lost. As a workaround, `ssh`',

View File

@ -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`. * Lookup the given key in the cache and return a hit or `undefined`.
* *
* @param {String} key: The cache key, e.g. 'images.json'. * @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)`. * @param {Function} cb: `function (err, hit)`.
* `err` is an Error if there was an unexpected error loading from the * `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 * cache. `hit` is undefined if there was no cache hit. On a hit, the
* type of `hit` depends on the key. * 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; var self = this;
assert.string(this.cacheDir, 'this.cacheDir'); assert.string(this.cacheDir, 'this.cacheDir');
assert.string(key, 'key'); assert.string(key, 'key');
assert.number(ttl, 'ttl');
assert.func(cb, 'cb'); assert.func(cb, 'cb');
var ttl = 5 * 60 * 1000; // timeout of cache file info (ms)
var keyPath = path.resolve(this.cacheDir, key); var keyPath = path.resolve(this.cacheDir, key);
fs.stat(keyPath, function (statErr, stats) { fs.stat(keyPath, function (statErr, stats) {
if (!statErr && 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) { fs.readFile(keyPath, 'utf8', function (err, data) {
if (err && err.code === 'ENOENT') { if (err && err.code === 'ENOENT') {
self.log.trace({keyPath: keyPath}, self.log.trace({keyPath: keyPath},
@ -459,7 +460,7 @@ TritonApi.prototype.listImages = function listImages(opts, cb) {
if (!useCache) { if (!useCache) {
return next(); return next();
} }
self._cacheGetJson(cacheKey, function (err, cached_) { self._cacheGetJson(cacheKey, 5*60, function (err, cached_) {
if (err) { if (err) {
return next(err); return next(err);
} }
@ -525,7 +526,7 @@ TritonApi.prototype.getImage = function getImage(opts, cb) {
return; return;
} }
var cacheKey = 'images.json'; var cacheKey = 'images.json';
self._cacheGetJson(cacheKey, function (err, images) { self._cacheGetJson(cacheKey, 60*60, function (err, images) {
if (err) { if (err) {
next(err); next(err);
return; return;