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
|
## 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.
|
||||||
|
|
||||||
|
@ -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`',
|
||||||
|
@ -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;
|
||||||
|
Reference in New Issue
Block a user