joyent/node-triton#3 triton ssh command not aware of "ubuntu" login for ubuntu-certified images
This commit is contained in:
parent
04cc61ee1d
commit
76759e547e
@ -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.
|
||||
|
||||
|
@ -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`',
|
||||
|
@ -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;
|
||||
|
Reference in New Issue
Block a user