shortid support for instances; --url,--account et al top-level options
This commit is contained in:
parent
5b60fffc04
commit
9e3df02a5e
1
TODO.txt
1
TODO.txt
@ -1,5 +1,6 @@
|
||||
TritonApi
|
||||
|
||||
|
||||
"shortid" instead of full UUID "id" in default output, and then allow lookup
|
||||
by that shortid. Really nice for 80 columns.
|
||||
- insts
|
||||
|
@ -1,8 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
/*
|
||||
* Copyright (c) 2015 Joyent Inc. All rights reserved.
|
||||
*
|
||||
* triton command
|
||||
*/
|
||||
|
||||
var p = console.log;
|
||||
|
113
lib/cli.js
113
lib/cli.js
@ -36,6 +36,84 @@ var log = bunyan.createLogger({
|
||||
level: 'warn'
|
||||
});
|
||||
|
||||
var OPTIONS = [
|
||||
{
|
||||
names: ['help', 'h'],
|
||||
type: 'bool',
|
||||
help: 'Print this help and exit.'
|
||||
},
|
||||
{
|
||||
name: 'version',
|
||||
type: 'bool',
|
||||
help: 'Print version and exit.'
|
||||
},
|
||||
{
|
||||
names: ['verbose', 'v'],
|
||||
type: 'bool',
|
||||
help: 'Verbose/debug output.'
|
||||
},
|
||||
|
||||
// XXX disable profile selection for now
|
||||
//{names: ['profile', 'p'], type: 'string', env: 'TRITON_PROFILE',
|
||||
// helpArg: 'NAME', help: 'Triton client profile to use.'}
|
||||
|
||||
{
|
||||
group: 'CloudAPI Options'
|
||||
},
|
||||
// XXX SDC_USER support. I don't grok the node-smartdc/README.md discussion
|
||||
// of SDC_USER.
|
||||
{
|
||||
names: ['account', 'a'],
|
||||
type: 'string',
|
||||
env: 'SDC_ACCOUNT',
|
||||
help: 'Triton account (login name)',
|
||||
helpArg: 'ACCOUNT'
|
||||
},
|
||||
// XXX
|
||||
//{
|
||||
// names: ['subuser', 'user'],
|
||||
// type: 'string',
|
||||
// env: 'MANTA_SUBUSER',
|
||||
// help: 'Manta User (login name)',
|
||||
// helpArg: 'USER'
|
||||
//},
|
||||
//{
|
||||
// names: ['role'],
|
||||
// type: 'arrayOfString',
|
||||
// env: 'MANTA_ROLE',
|
||||
// help: 'Assume a role. Use multiple times or once with a list',
|
||||
// helpArg: 'ROLE,ROLE,...'
|
||||
//},
|
||||
{
|
||||
names: ['keyId', 'k'],
|
||||
type: 'string',
|
||||
env: 'SDC_KEY_ID',
|
||||
help: 'SSH key fingerprint',
|
||||
helpArg: 'FINGERPRINT'
|
||||
},
|
||||
{
|
||||
names: ['url', 'u'],
|
||||
type: 'string',
|
||||
env: 'SDC_URL',
|
||||
help: 'CloudAPI URL',
|
||||
helpArg: 'URL'
|
||||
},
|
||||
{
|
||||
names: ['J'],
|
||||
type: 'string',
|
||||
hidden: true,
|
||||
help: 'Joyent Public Cloud (JPC) datacenter name. This is ' +
|
||||
'a shortcut to the "https://$dc.api.joyent.com" ' +
|
||||
'cloudapi URL.'
|
||||
},
|
||||
{
|
||||
names: ['insecure', 'i'],
|
||||
type: 'bool',
|
||||
help: 'Do not validate SSL certificate',
|
||||
'default': false,
|
||||
env: 'SDC_TLS_INSECURE' // Deprecated SDC_TESTING supported below.
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
//---- CLI class
|
||||
@ -44,15 +122,7 @@ function CLI() {
|
||||
Cmdln.call(this, {
|
||||
name: pkg.name,
|
||||
desc: pkg.description,
|
||||
options: [
|
||||
{names: ['help', 'h'], type: 'bool', help: 'Print help and exit.'},
|
||||
{name: 'version', type: 'bool', help: 'Print version and exit.'},
|
||||
{names: ['verbose', 'v'], type: 'bool',
|
||||
help: 'Verbose/debug output.'},
|
||||
// XXX disable profile selection for now
|
||||
//{names: ['profile', 'p'], type: 'string', env: 'TRITON_PROFILE',
|
||||
// helpArg: 'NAME', help: 'Triton client profile to use.'}
|
||||
],
|
||||
options: OPTIONS,
|
||||
helpOpts: {
|
||||
includeEnv: true,
|
||||
minHelpCol: 30
|
||||
@ -102,6 +172,7 @@ CLI.prototype.init = function (opts, args, callback) {
|
||||
if (opts.verbose) {
|
||||
log.level('trace');
|
||||
log.src = true;
|
||||
this.showErrStack = true;
|
||||
}
|
||||
|
||||
this.__defineGetter__('triton', function () {
|
||||
@ -118,11 +189,29 @@ CLI.prototype.init = function (opts, args, callback) {
|
||||
}
|
||||
});
|
||||
|
||||
// XXX support keyId being a priv or pub key path, a la imgapi-cli
|
||||
// XXX Add TRITON_* envvars.
|
||||
var envProfile = {
|
||||
name: 'env',
|
||||
account: opts.account,
|
||||
url: opts.url,
|
||||
keyId: opts.keyId,
|
||||
insecure: opts.insecure
|
||||
};
|
||||
if (opts.insecure === undefined && process.env.SDC_TESTING) {
|
||||
opts.insecure = common.boolFromString(process.env.SDC_TESTING);
|
||||
}
|
||||
if (opts.J) {
|
||||
envProfile.url = format('https://%s.api.joyent.com', opts.J);
|
||||
}
|
||||
log.trace({envProfile: envProfile}, 'envProfile');
|
||||
|
||||
self._triton = new Triton({
|
||||
log: log,
|
||||
profile: opts.profile,
|
||||
config: userConfigPath,
|
||||
cachedir: cacheDir
|
||||
profileName: opts.profile,
|
||||
envProfile: envProfile,
|
||||
configPath: userConfigPath,
|
||||
cacheDir: cacheDir
|
||||
});
|
||||
}
|
||||
return self._triton;
|
||||
|
@ -197,6 +197,54 @@ function capitalize(s) {
|
||||
return s[0].toUpperCase() + s.substr(1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Normalize a short ID. Returns undefined if the given string isn't a valid
|
||||
* short id.
|
||||
*
|
||||
* Short IDs:
|
||||
* - UUID prefix
|
||||
* - allow '-' to be elided (to support using containers IDs from
|
||||
* docker)
|
||||
* - support docker ID *longer* than a UUID? The curr implementation does.
|
||||
*/
|
||||
function normShortId(s) {
|
||||
var shortIdCharsRe = /^[a-f0-9]+$/;
|
||||
var shortId;
|
||||
if (s.indexOf('-') === -1) {
|
||||
if (!shortIdCharsRe.test(s)) {
|
||||
return;
|
||||
}
|
||||
shortId = s.substr(0, 8) + '-'
|
||||
+ s.substr(8, 4) + '-'
|
||||
+ s.substr(12, 4) + '-'
|
||||
+ s.substr(16, 4) + '-'
|
||||
+ s.substr(20, 12);
|
||||
shortId = shortId.replace(/-+$/, '');
|
||||
} else {
|
||||
// UUID prefix.
|
||||
var shortId = '';
|
||||
var chunk;
|
||||
var remaining = s;
|
||||
var spans = [8, 4, 4, 4, 12];
|
||||
for (var i = 0; i < spans.length; i++) {
|
||||
var span = spans[i];
|
||||
head = remaining.slice(0, span);
|
||||
remaining = remaining.slice(span + 1);
|
||||
if (!shortIdCharsRe.test(head)) {
|
||||
return;
|
||||
}
|
||||
shortId += head;
|
||||
if (remaining) {
|
||||
shortId += '-';
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return shortId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//---- exports
|
||||
|
||||
@ -212,6 +260,7 @@ module.exports = {
|
||||
isUUID: isUUID,
|
||||
humanDurationFromMs: humanDurationFromMs,
|
||||
humanSizeFromBytes: humanSizeFromBytes,
|
||||
capitalize: capitalize
|
||||
capitalize: capitalize,
|
||||
normShortId: normShortId
|
||||
};
|
||||
// vim: set softtabstop=4 shiftwidth=4:
|
||||
|
@ -1,13 +1,12 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Copyright (c) 2014 Joyent Inc. All rights reserved.
|
||||
* Copyright (c) 2015 Joyent Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
var p = console.log;
|
||||
var assert = require('assert-plus');
|
||||
var format = require('util').format;
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var sprintf = require('extsprintf').sprintf;
|
||||
|
||||
var common = require('./common');
|
||||
var errors = require('./errors');
|
||||
@ -17,6 +16,8 @@ var DEFAULT_USER_CONFIG_PATH = path.resolve(process.env.HOME, '.triton', 'config
|
||||
var DEFAULTS_PATH = path.resolve(__dirname, '..', 'etc', 'defaults.json');
|
||||
var OVERRIDE_KEYS = []; // config object keys to do a one-level deep override
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Load the Triton client config. This is a merge of the built-in "defaults" (at
|
||||
* etc/defaults.json) and the "user" config (at ~/.triton/config.json if it
|
||||
@ -24,17 +25,21 @@ var OVERRIDE_KEYS = []; // config object keys to do a one-level deep override
|
||||
*
|
||||
* This includes some internal data on keys with a leading underscore.
|
||||
*/
|
||||
function loadConfigSync(configPath) {
|
||||
function loadConfigSync(opts) {
|
||||
assert.object(opts, 'opts');
|
||||
assert.string(opts.configPath, 'opts.configPath');
|
||||
assert.optionalObject(opts.envProfile, 'opts.envProfile');
|
||||
|
||||
var c = fs.readFileSync(DEFAULTS_PATH, 'utf8');
|
||||
var _defaults = JSON.parse(c);
|
||||
var config = JSON.parse(c);
|
||||
if (configPath && fs.existsSync(configPath)) {
|
||||
c = fs.readFileSync(configPath, 'utf8');
|
||||
if (opts.configPath && fs.existsSync(opts.configPath)) {
|
||||
c = fs.readFileSync(opts.configPath, 'utf8');
|
||||
var _user = JSON.parse(c);
|
||||
var userConfig = JSON.parse(c);
|
||||
if (typeof(userConfig) !== 'object' || Array.isArray(userConfig)) {
|
||||
throw new errors.ConfigError(
|
||||
sprintf('"%s" is not an object', configPath));
|
||||
format('"%s" is not an object', opts.configPath));
|
||||
}
|
||||
// These special keys are merged into the key of the same name in the
|
||||
// base "defaults.json".
|
||||
@ -56,18 +61,13 @@ function loadConfigSync(configPath) {
|
||||
}
|
||||
config._defaults = _defaults;
|
||||
|
||||
// Add 'env' profile.
|
||||
// Add 'env' profile, if given.
|
||||
if (opts.envProfile) {
|
||||
if (!config.profiles) {
|
||||
config.profiles = [];
|
||||
}
|
||||
//XXX Add TRITON_* envvars.
|
||||
config.profiles.push({
|
||||
name: 'env',
|
||||
account: process.env.SDC_USER || process.env.SDC_ACCOUNT,
|
||||
url: process.env.SDC_URL,
|
||||
keyId: process.env.SDC_KEY_ID,
|
||||
insecure: common.boolFromString(process.env.SDC_TESTING)
|
||||
});
|
||||
config.profiles.push(opts.envProfile);
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
@ -6,36 +6,25 @@
|
||||
|
||||
var common = require('./common');
|
||||
|
||||
function do_instance(subcmd, opts, args, callback) {
|
||||
function do_instance(subcmd, opts, args, cb) {
|
||||
if (opts.help) {
|
||||
this.do_help('help', {}, [subcmd], callback);
|
||||
return;
|
||||
return this.do_help('help', {}, [subcmd], cb);
|
||||
} else if (args.length !== 1) {
|
||||
callback(new Error('invalid args: ' + args));
|
||||
return;
|
||||
return cb(new Error('invalid args: ' + args));
|
||||
}
|
||||
|
||||
var id = args[0];
|
||||
|
||||
if (common.isUUID(id)) {
|
||||
this.triton.cloudapi.getMachine(id, cb);
|
||||
} else {
|
||||
this.triton.getMachineByAlias(id, cb);
|
||||
}
|
||||
|
||||
function cb(err, machine) {
|
||||
this.triton.getInstance(args[0], function (err, inst) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
if (opts.json) {
|
||||
console.log(JSON.stringify(machine));
|
||||
console.log(JSON.stringify(inst));
|
||||
} else {
|
||||
console.log(JSON.stringify(machine, null, 4));
|
||||
}
|
||||
callback();
|
||||
console.log(JSON.stringify(inst, null, 4));
|
||||
}
|
||||
cb();
|
||||
});
|
||||
}
|
||||
|
||||
do_instance.options = [
|
||||
|
@ -36,7 +36,8 @@ var validFields = [
|
||||
'package',
|
||||
'image',
|
||||
'img',
|
||||
'ago'
|
||||
'ago',
|
||||
'shortid'
|
||||
];
|
||||
|
||||
function do_instances(subcmd, opts, args, callback) {
|
||||
@ -45,8 +46,15 @@ function do_instances(subcmd, opts, args, callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
var columns = opts.o.trim().split(',');
|
||||
var sort = opts.s.trim().split(',');
|
||||
var columns = 'shortid,name,state,type,img,memory,disk,ago'.split(',');
|
||||
if (opts.o) {
|
||||
/* JSSTYLED */
|
||||
columns = opts.o.trim().split(/\s*,\s*/g);
|
||||
} else if (opts.long) {
|
||||
columns[0] = 'id';
|
||||
}
|
||||
/* JSSTYLED */
|
||||
var sort = opts.s.trim().split(/\s*,\s*/g);
|
||||
|
||||
var listOpts;
|
||||
try {
|
||||
@ -60,7 +68,7 @@ function do_instances(subcmd, opts, args, callback) {
|
||||
|
||||
i++;
|
||||
var images;
|
||||
this.triton.listImages({usecache: true}, function (err, _images) {
|
||||
this.triton.listImages({useCache: true}, function (err, _images) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
@ -90,12 +98,15 @@ function do_instances(subcmd, opts, args, callback) {
|
||||
imgmap[image.id] = f('%s@%s', image.name, image.version);
|
||||
});
|
||||
|
||||
// add extra fields for nice output
|
||||
// Add extra fields for nice output.
|
||||
// XXX FWIW, the "extra fields" for images and packages are not added
|
||||
// for `opts.json`. Thoughts? We should be consistent there. --TM
|
||||
var now = new Date();
|
||||
machines.forEach(function (machine) {
|
||||
var created = new Date(machine.created);
|
||||
machine.ago = common.longAgo(created, now);
|
||||
machine.img = imgmap[machine.image] || machine.image;
|
||||
machine.shortid = machine.id.split('-', 1)[0];
|
||||
});
|
||||
|
||||
if (opts.json) {
|
||||
@ -118,6 +129,9 @@ do_instances.options = [
|
||||
type: 'bool',
|
||||
help: 'Show this help.'
|
||||
},
|
||||
{
|
||||
group: 'Output options'
|
||||
},
|
||||
{
|
||||
names: ['H'],
|
||||
type: 'bool',
|
||||
@ -126,10 +140,14 @@ do_instances.options = [
|
||||
{
|
||||
names: ['o'],
|
||||
type: 'string',
|
||||
default: 'id,name,state,type,img,memory,disk,ago',
|
||||
help: 'Specify fields (columns) to output.',
|
||||
helpArg: 'field1,...'
|
||||
},
|
||||
{
|
||||
names: ['long', 'l'],
|
||||
type: 'bool',
|
||||
help: 'Long/wider output. Ignored if "-o ..." is used.'
|
||||
},
|
||||
{
|
||||
names: ['s'],
|
||||
type: 'string',
|
||||
|
@ -19,22 +19,15 @@ function do_ssh(subcmd, opts, args, callback) {
|
||||
}
|
||||
|
||||
var id = args.shift();
|
||||
|
||||
if (common.isUUID(id)) {
|
||||
this.triton.cloudapi.getMachine(id, cb);
|
||||
} else {
|
||||
this.triton.getMachineByAlias(id, cb);
|
||||
}
|
||||
|
||||
function cb(err, machine) {
|
||||
this.triton.getInstance(id, function (err, inst) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
var ip = machine.primaryIp;
|
||||
var ip = inst.primaryIp;
|
||||
if (!ip) {
|
||||
callback(new Error('primaryIp not found for machine'));
|
||||
callback(new Error('primaryIp not found for instance'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -45,7 +38,7 @@ function do_ssh(subcmd, opts, args, callback) {
|
||||
child.on('close', function (code) {
|
||||
process.exit(code);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
do_ssh.options = [
|
||||
|
@ -83,7 +83,7 @@ function _do_instance(action, subcmd, opts, args, callback) {
|
||||
uuid = arg;
|
||||
go1();
|
||||
} else {
|
||||
self.triton.getMachineByAlias(arg, function (err, machine) {
|
||||
self.triton.getInstance(arg, function (err, machine) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
|
@ -36,7 +36,7 @@ function do_wait_instance(subcmd, opts, args, cb) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.triton.getMachineByAlias(id, function (err, machine) {
|
||||
self.triton.getInstance(id, function (err, machine) {
|
||||
if (err) {
|
||||
cb(err);
|
||||
return;
|
||||
|
150
lib/triton.js
150
lib/triton.js
@ -14,6 +14,7 @@ var once = require('once');
|
||||
var path = require('path');
|
||||
var restifyClients = require('restify-clients');
|
||||
var tabula = require('tabula');
|
||||
var vasync = require('vasync');
|
||||
|
||||
var cloudapi = require('./cloudapi2');
|
||||
var common = require('./common');
|
||||
@ -29,15 +30,19 @@ var loadConfigSync = require('./config').loadConfigSync;
|
||||
*
|
||||
* @param options {Object}
|
||||
* - log {Bunyan Logger}
|
||||
* - profile {String} Optional. Name of profile to use. Defaults to
|
||||
* - profileName {String} Optional. Name of profile to use. Defaults to
|
||||
* 'defaultProfile' in the config.
|
||||
* - envProfile {Object} Optional. A starter 'env' profile object. Missing
|
||||
* fields will be filled in from standard SDC_* envvars.
|
||||
* ...
|
||||
*/
|
||||
function Triton(options) {
|
||||
assert.object(options, 'options');
|
||||
assert.object(options.log, 'options.log');
|
||||
assert.optionalString(options.profile, 'options.profile');
|
||||
assert.optionalString(options.config, 'options.config');
|
||||
assert.optionalString(options.cachedir, 'options.cachedir');
|
||||
assert.optionalString(options.profileName, 'options.profileName');
|
||||
assert.optionalString(options.configPath, 'options.configPath');
|
||||
assert.optionalString(options.cacheDir, 'options.cacheDir');
|
||||
assert.optionalObject(options.envProfile, 'options.envProfile');
|
||||
|
||||
// Make sure a given bunyan logger has reasonable client_re[qs] serializers.
|
||||
// Note: This was fixed in restify, then broken again in
|
||||
@ -52,12 +57,15 @@ function Triton(options) {
|
||||
} else {
|
||||
this.log = options.log;
|
||||
}
|
||||
this.config = loadConfigSync(options.config);
|
||||
this.config = loadConfigSync({
|
||||
configPath: options.configPath,
|
||||
envProfile: options.envProfile
|
||||
});
|
||||
this.profiles = this.config.profiles;
|
||||
this.profile = this.getProfile(
|
||||
options.profile || this.config.defaultProfile);
|
||||
options.profileName || this.config.defaultProfile);
|
||||
this.log.trace({profile: this.profile}, 'profile data');
|
||||
this.cachedir = options.cachedir;
|
||||
this.cacheDir = options.cacheDir;
|
||||
|
||||
this.cloudapi = this._cloudapiFromProfile(this.profile);
|
||||
}
|
||||
@ -117,23 +125,24 @@ Triton.prototype.listImages = function listImages(opts, cb) {
|
||||
opts = {};
|
||||
}
|
||||
assert.object(opts, 'opts');
|
||||
assert.optionalBool(opts.useCache, 'opts.useCache');
|
||||
assert.func(cb, 'cb');
|
||||
|
||||
var cachefile;
|
||||
if (self.cachedir)
|
||||
cachefile = path.join(self.cachedir, 'images.json');
|
||||
var cacheFile;
|
||||
if (self.cacheDir)
|
||||
cacheFile = path.join(self.cacheDir, 'images.json');
|
||||
|
||||
if (opts.usecache && !cachefile) {
|
||||
cb(new Error('opts.usecache set but no cachedir found'));
|
||||
if (opts.useCache && !cacheFile) {
|
||||
cb(new Error('opts.useCache set but no cacheDir found'));
|
||||
return;
|
||||
}
|
||||
|
||||
// try to read the cache if the user wants it
|
||||
// if this fails for any reason we fallback to hitting the cloudapi
|
||||
if (opts.usecache) {
|
||||
fs.readFile(cachefile, 'utf8', function (err, out) {
|
||||
if (opts.useCache) {
|
||||
fs.readFile(cacheFile, 'utf8', function (err, out) {
|
||||
if (err) {
|
||||
self.log.info({err: err}, 'failed to read cache file %s', cachefile);
|
||||
self.log.info({err: err}, 'failed to read cache file %s', cacheFile);
|
||||
fetch();
|
||||
return;
|
||||
}
|
||||
@ -141,7 +150,7 @@ Triton.prototype.listImages = function listImages(opts, cb) {
|
||||
try {
|
||||
data = JSON.parse(out);
|
||||
} catch (e) {
|
||||
self.log.info({err: e}, 'failed to parse cache file %s', cachefile);
|
||||
self.log.info({err: e}, 'failed to parse cache file %s', cacheFile);
|
||||
fetch();
|
||||
return;
|
||||
}
|
||||
@ -154,10 +163,10 @@ Triton.prototype.listImages = function listImages(opts, cb) {
|
||||
fetch();
|
||||
function fetch() {
|
||||
self.cloudapi.listImages(opts, function (err, imgs, res) {
|
||||
if (!err && self.cachedir) {
|
||||
if (!err && self.cacheDir) {
|
||||
// cache the results
|
||||
var data = JSON.stringify(imgs);
|
||||
fs.writeFile(cachefile, data, {encoding: 'utf8'}, function (err) {
|
||||
fs.writeFile(cacheFile, data, {encoding: 'utf8'}, function (err) {
|
||||
if (err)
|
||||
self.log.info({err: err}, 'error caching images results');
|
||||
done();
|
||||
@ -289,27 +298,100 @@ Triton.prototype.getPackage = function getPackage(name, cb) {
|
||||
|
||||
|
||||
/**
|
||||
* getMachine for an alias
|
||||
* Get an instance by ID, exact name, or short ID, in that order.
|
||||
*
|
||||
* @param {String} alias - the machine alias
|
||||
* @param {Function} callback `function (err, machine)`
|
||||
* @param {String} name
|
||||
* @param {Function} callback `function (err, inst)`
|
||||
*/
|
||||
Triton.prototype.getMachineByAlias = function getMachineByAlias(alias, callback) {
|
||||
this.cloudapi.listMachines({name: alias}, function (err, machines) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
Triton.prototype.getInstance = function getInstance(name, cb) {
|
||||
var self = this;
|
||||
assert.string(name, 'name');
|
||||
assert.func(cb, 'cb');
|
||||
|
||||
var shortId;
|
||||
var inst;
|
||||
|
||||
vasync.pipeline({funcs: [
|
||||
function tryUuid(_, next) {
|
||||
var uuid;
|
||||
if (common.isUUID(name)) {
|
||||
uuid = name;
|
||||
} else {
|
||||
shortId = common.normShortId(name);
|
||||
if (shortId && common.isUUID(shortId)) {
|
||||
// E.g. a >32-char docker container ID normalized to a UUID.
|
||||
uuid = shortId;
|
||||
} else {
|
||||
return next();
|
||||
}
|
||||
}
|
||||
this.cloudapi.getMachine(uuid, function (err, inst) {
|
||||
inst = inst;
|
||||
next(err);
|
||||
});
|
||||
},
|
||||
|
||||
function tryName(_, next) {
|
||||
if (inst) {
|
||||
return next();
|
||||
}
|
||||
|
||||
self.cloudapi.listMachines({name: name}, function (err, insts) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
for (var i = 0; i < insts.length; i++) {
|
||||
if (insts[i].name === name) {
|
||||
inst = insts[i];
|
||||
// Relying on rule that instance name is unique
|
||||
// for a user and DC.
|
||||
return next();
|
||||
}
|
||||
}
|
||||
next();
|
||||
});
|
||||
},
|
||||
|
||||
function tryShortId(_, next) {
|
||||
if (inst || !shortId) {
|
||||
return next();
|
||||
}
|
||||
var nextOnce = once(next);
|
||||
|
||||
var match;
|
||||
var s = self.cloudapi.createListMachinesStream();
|
||||
s.on('error', function (err) {
|
||||
nextOnce(err);
|
||||
});
|
||||
s.on('readable', function () {
|
||||
var inst;
|
||||
while ((inst = s.read()) !== null) {
|
||||
if (inst.id.slice(0, shortId.length) === shortId) {
|
||||
if (match) {
|
||||
return nextOnce(new Error(
|
||||
'instance short id "%s" is ambiguous',
|
||||
shortId));
|
||||
} else {
|
||||
match = inst;
|
||||
}
|
||||
}
|
||||
var found = false;
|
||||
machines.forEach(function (machine) {
|
||||
if (!found && machine.name === alias) {
|
||||
callback(null, machine);
|
||||
found = true;
|
||||
}
|
||||
});
|
||||
if (!found) {
|
||||
callback(new Error('machine ' + alias + ' not found'));
|
||||
return;
|
||||
s.on('end', function () {
|
||||
if (match) {
|
||||
inst = match;
|
||||
}
|
||||
nextOnce();
|
||||
});
|
||||
}
|
||||
]}, function (err) {
|
||||
if (err) {
|
||||
cb(err);
|
||||
} else if (inst) {
|
||||
cb(null, inst);
|
||||
} else {
|
||||
cb(new Error(format(
|
||||
'no instance with name or shortId "%s" was found', name)));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
Reference in New Issue
Block a user