'triton profile -a' from stdin, JSON file or interactively
This commit is contained in:
parent
a5ee77a48e
commit
aeebcf19f0
2
TODO.txt
2
TODO.txt
@ -3,6 +3,8 @@ test suite:
|
|||||||
- TritonApi testing: test/integration/api-*.test.js
|
- TritonApi testing: test/integration/api-*.test.js
|
||||||
- more test/unit/...
|
- more test/unit/...
|
||||||
|
|
||||||
|
sub-user support (profiles, `triton account`, env, auth)
|
||||||
|
|
||||||
note in README that full UUIDs is much faster in the API
|
note in README that full UUIDs is much faster in the API
|
||||||
|
|
||||||
*type*: cloudapi changes to clarify: LX, docker, smartos, kvm instances
|
*type*: cloudapi changes to clarify: LX, docker, smartos, kvm instances
|
||||||
|
100
lib/common.js
100
lib/common.js
@ -17,11 +17,13 @@ var read = require('read');
|
|||||||
var tty = require('tty');
|
var tty = require('tty');
|
||||||
var util = require('util'),
|
var util = require('util'),
|
||||||
format = util.format;
|
format = util.format;
|
||||||
|
var wordwrap = require('wordwrap');
|
||||||
|
|
||||||
var errors = require('./errors'),
|
var errors = require('./errors'),
|
||||||
InternalError = errors.InternalError;
|
InternalError = errors.InternalError;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ---- support stuff
|
// ---- support stuff
|
||||||
|
|
||||||
function objCopy(obj, target) {
|
function objCopy(obj, target) {
|
||||||
@ -541,6 +543,65 @@ function promptEnter(prompt, cb) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Prompt the user for a value.
|
||||||
|
*
|
||||||
|
* @params field {Object}
|
||||||
|
* - field.desc {String} Optional. A description of the field to print
|
||||||
|
* before prompting.
|
||||||
|
* - field.key {String} The field name. Used as the prompt.
|
||||||
|
* - field.default Optional default value.
|
||||||
|
* - field.validate {Function} Optional. A validation/manipulation
|
||||||
|
* function of the form:
|
||||||
|
* function (value, cb)
|
||||||
|
* which should callback with
|
||||||
|
* cb([<error or null>, [<manipulated value>]])
|
||||||
|
* examples:
|
||||||
|
* cb(new Error('value is not a number'));
|
||||||
|
* cb(); // value is fine as is
|
||||||
|
* cb(null, Math.floor(Number(value))); // manip to a floored int
|
||||||
|
* @params cb {Function} `function (err, value)`
|
||||||
|
* If the user aborted, the `err` will be whatever the [read
|
||||||
|
* package](https://www.npmjs.com/package/read) returns, i.e. a
|
||||||
|
* string "cancelled".
|
||||||
|
*/
|
||||||
|
function promptField(field, cb) {
|
||||||
|
var wrap = wordwrap(Math.min(process.stdout.columns, 78));
|
||||||
|
function attempt(next) {
|
||||||
|
read({
|
||||||
|
// read/readline prompting messes up width with ANSI codes here.
|
||||||
|
prompt: field.key + ':',
|
||||||
|
default: field.default,
|
||||||
|
edit: true
|
||||||
|
}, function (err, result, isDefault) {
|
||||||
|
if (err) {
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
|
var value = result.trim();
|
||||||
|
if (!field.validate) {
|
||||||
|
return cb(null, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
field.validate(value, function (validationErr, newValue) {
|
||||||
|
if (validationErr) {
|
||||||
|
console.log(ansiStylize(
|
||||||
|
wrap(validationErr.message), 'red'));
|
||||||
|
attempt();
|
||||||
|
} else {
|
||||||
|
if (newValue !== undefined) {
|
||||||
|
value = newValue;
|
||||||
|
}
|
||||||
|
cb(null, value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(ansiStylize(wrap(field.desc), 'bold'));
|
||||||
|
attempt();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Edit the given text in $EDITOR (defaulting to `vi`) and return the edited
|
* Edit the given text in $EDITOR (defaulting to `vi`) and return the edited
|
||||||
* text.
|
* text.
|
||||||
@ -571,6 +632,42 @@ function editInEditor(opts, cb) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// http://en.wikipedia.org/wiki/ANSI_escape_code#graphics
|
||||||
|
// Suggested colors (some are unreadable in common cases):
|
||||||
|
// - Good: cyan, yellow (limited use), bold, green, magenta, red
|
||||||
|
// - Bad: blue (not visible on cmd.exe), grey (same color as background on
|
||||||
|
// Solarized Dark theme from <https://github.com/altercation/solarized>, see
|
||||||
|
// issue #160)
|
||||||
|
var colors = {
|
||||||
|
'bold' : [1, 22],
|
||||||
|
'italic' : [3, 23],
|
||||||
|
'underline' : [4, 24],
|
||||||
|
'inverse' : [7, 27],
|
||||||
|
'white' : [37, 39],
|
||||||
|
'grey' : [90, 39],
|
||||||
|
'black' : [30, 39],
|
||||||
|
'blue' : [34, 39],
|
||||||
|
'cyan' : [36, 39],
|
||||||
|
'green' : [32, 39],
|
||||||
|
'magenta' : [35, 39],
|
||||||
|
'red' : [31, 39],
|
||||||
|
'yellow' : [33, 39]
|
||||||
|
};
|
||||||
|
|
||||||
|
function ansiStylize(str, color) {
|
||||||
|
if (!str)
|
||||||
|
return '';
|
||||||
|
var codes = colors[color];
|
||||||
|
if (codes) {
|
||||||
|
return '\033[' + codes[0] + 'm' + str +
|
||||||
|
'\033[' + codes[1] + 'm';
|
||||||
|
} else {
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//---- exports
|
//---- exports
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
@ -591,6 +688,7 @@ module.exports = {
|
|||||||
getCliTableOptions: getCliTableOptions,
|
getCliTableOptions: getCliTableOptions,
|
||||||
promptYesNo: promptYesNo,
|
promptYesNo: promptYesNo,
|
||||||
promptEnter: promptEnter,
|
promptEnter: promptEnter,
|
||||||
editInEditor: editInEditor
|
editInEditor: editInEditor,
|
||||||
|
ansiStylize: ansiStylize
|
||||||
};
|
};
|
||||||
// vim: set softtabstop=4 shiftwidth=4:
|
// vim: set softtabstop=4 shiftwidth=4:
|
||||||
|
@ -64,30 +64,6 @@ var PROFILE_FIELDS = {
|
|||||||
|
|
||||||
// --- internal support stuff
|
// --- internal support stuff
|
||||||
|
|
||||||
// TODO: improve this validation: use ConfigError's instead of asserts
|
|
||||||
function _validateProfile(profile, profilePath) {
|
|
||||||
assert.object(profile, 'profile');
|
|
||||||
assert.string(profile.name, 'profile.name');
|
|
||||||
assert.string(profile.url, 'profile.url');
|
|
||||||
assert.string(profile.account, 'profile.account');
|
|
||||||
assert.string(profile.keyId, 'profile.keyId');
|
|
||||||
assert.optionalBool(profile.insecure, 'profile.insecure');
|
|
||||||
assert.optionalString(profilePath, 'profilePath');
|
|
||||||
|
|
||||||
var bogusFields = [];
|
|
||||||
Object.keys(profile).forEach(function (field) {
|
|
||||||
if (!PROFILE_FIELDS[field]) {
|
|
||||||
bogusFields.push(field);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (bogusFields.length) {
|
|
||||||
throw new errors.ConfigError(format(
|
|
||||||
'extraneous fields in "%s" profile: %s%s', profile.name,
|
|
||||||
(profilePath ? profilePath + ': ' : ''), bogusFields.join(', ')));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function configPathFromDir(configDir) {
|
function configPathFromDir(configDir) {
|
||||||
return path.resolve(configDir, 'config.json');
|
return path.resolve(configDir, 'config.json');
|
||||||
}
|
}
|
||||||
@ -204,6 +180,35 @@ function setConfigVar(opts, cb) {
|
|||||||
|
|
||||||
// --- Profiles
|
// --- Profiles
|
||||||
|
|
||||||
|
function validateProfile(profile, profilePath) {
|
||||||
|
assert.object(profile, 'profile');
|
||||||
|
assert.optionalString(profilePath, 'profilePath');
|
||||||
|
|
||||||
|
try {
|
||||||
|
assert.string(profile.name, 'profile.name');
|
||||||
|
assert.string(profile.url, 'profile.url');
|
||||||
|
assert.string(profile.account, 'profile.account');
|
||||||
|
assert.string(profile.keyId, 'profile.keyId');
|
||||||
|
assert.optionalBool(profile.insecure, 'profile.insecure');
|
||||||
|
} catch (err) {
|
||||||
|
throw new errors.ConfigError(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
var bogusFields = [];
|
||||||
|
Object.keys(profile).forEach(function (field) {
|
||||||
|
if (!PROFILE_FIELDS[field]) {
|
||||||
|
bogusFields.push(field);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (bogusFields.length) {
|
||||||
|
throw new errors.ConfigError(format(
|
||||||
|
'extraneous fields in "%s" profile: %s%s', profile.name,
|
||||||
|
(profilePath ? profilePath + ': ' : ''), bogusFields.join(', ')));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load the special 'env' profile, which handles details of getting
|
* Load the special 'env' profile, which handles details of getting
|
||||||
* values from envvars. Typically we'd piggyback on dashdash's env support
|
* values from envvars. Typically we'd piggyback on dashdash's env support
|
||||||
@ -231,7 +236,7 @@ function _loadEnvProfile() {
|
|||||||
envProfile.insecure = common.boolFromString(process.env.SDC_TESTING);
|
envProfile.insecure = common.boolFromString(process.env.SDC_TESTING);
|
||||||
}
|
}
|
||||||
|
|
||||||
_validateProfile(envProfile);
|
validateProfile(envProfile);
|
||||||
|
|
||||||
return envProfile;
|
return envProfile;
|
||||||
}
|
}
|
||||||
@ -255,11 +260,13 @@ function _profileFromPath(profilePath, name) {
|
|||||||
}
|
}
|
||||||
profile.name = name;
|
profile.name = name;
|
||||||
|
|
||||||
_validateProfile(profile, profilePath);
|
validateProfile(profile, profilePath);
|
||||||
|
|
||||||
return profile;
|
return profile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function loadProfile(opts) {
|
function loadProfile(opts) {
|
||||||
assert.string(opts.configDir, 'opts.configDir');
|
assert.string(opts.configDir, 'opts.configDir');
|
||||||
assert.string(opts.name, 'opts.name');
|
assert.string(opts.name, 'opts.name');
|
||||||
@ -322,22 +329,22 @@ function deleteProfile(opts) {
|
|||||||
|
|
||||||
function saveProfileSync(opts) {
|
function saveProfileSync(opts) {
|
||||||
assert.string(opts.configDir, 'opts.configDir');
|
assert.string(opts.configDir, 'opts.configDir');
|
||||||
assert.string(opts.name, 'opts.name');
|
|
||||||
assert.object(opts.profile, 'opts.profile');
|
assert.object(opts.profile, 'opts.profile');
|
||||||
|
|
||||||
if (opts.name === 'env') {
|
var name = opts.profile.name;
|
||||||
|
if (name === 'env') {
|
||||||
throw new Error('cannot save "env" profile');
|
throw new Error('cannot save "env" profile');
|
||||||
}
|
}
|
||||||
|
|
||||||
_validateProfile(opts.profile);
|
validateProfile(opts.profile);
|
||||||
|
|
||||||
var toSave = common.objCopy(opts.profile);
|
var toSave = common.objCopy(opts.profile);
|
||||||
delete toSave.name;
|
delete toSave.name;
|
||||||
|
|
||||||
var profilePath = path.resolve(opts.configDir, 'profiles.d',
|
var profilePath = path.resolve(opts.configDir, 'profiles.d',
|
||||||
opts.name + '.json');
|
name + '.json');
|
||||||
fs.writeFileSync(profilePath, JSON.stringify(toSave, null, 4), 'utf8');
|
fs.writeFileSync(profilePath, JSON.stringify(toSave, null, 4), 'utf8');
|
||||||
console.log('Saved profile "%s"', opts.name);
|
console.log('Saved profile "%s"', name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -346,6 +353,8 @@ function saveProfileSync(opts) {
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
loadConfig: loadConfig,
|
loadConfig: loadConfig,
|
||||||
setConfigVar: setConfigVar,
|
setConfigVar: setConfigVar,
|
||||||
|
|
||||||
|
validateProfile: validateProfile,
|
||||||
loadProfile: loadProfile,
|
loadProfile: loadProfile,
|
||||||
loadAllProfiles: loadAllProfiles,
|
loadAllProfiles: loadAllProfiles,
|
||||||
deleteProfile: deleteProfile,
|
deleteProfile: deleteProfile,
|
||||||
|
@ -6,7 +6,11 @@
|
|||||||
|
|
||||||
var assert = require('assert-plus');
|
var assert = require('assert-plus');
|
||||||
var format = require('util').format;
|
var format = require('util').format;
|
||||||
|
var fs = require('fs');
|
||||||
|
var read = require('read');
|
||||||
var strsplit = require('strsplit');
|
var strsplit = require('strsplit');
|
||||||
|
var sshpk = require('sshpk');
|
||||||
|
var tilde = require('tilde-expansion');
|
||||||
var vasync = require('vasync');
|
var vasync = require('vasync');
|
||||||
|
|
||||||
var common = require('./common');
|
var common = require('./common');
|
||||||
@ -174,7 +178,6 @@ function _editProfile(opts, cb) {
|
|||||||
|
|
||||||
mod_config.saveProfileSync({
|
mod_config.saveProfileSync({
|
||||||
configDir: cli.configDir,
|
configDir: cli.configDir,
|
||||||
name: opts.name,
|
|
||||||
profile: editedProfile
|
profile: editedProfile
|
||||||
});
|
});
|
||||||
} catch (textErr) {
|
} catch (textErr) {
|
||||||
@ -278,8 +281,199 @@ function _deleteProfile(opts, cb) {
|
|||||||
|
|
||||||
|
|
||||||
function _addProfile(opts, cb) {
|
function _addProfile(opts, cb) {
|
||||||
//XXX
|
assert.object(opts.cli, 'opts.cli');
|
||||||
cb(new errors.InternalError('_addProfile not yet implemented'));
|
assert.optionalString(opts.file, 'opts.file');
|
||||||
|
assert.func(cb, 'cb');
|
||||||
|
var cli = opts.cli;
|
||||||
|
|
||||||
|
var context;
|
||||||
|
var data;
|
||||||
|
|
||||||
|
vasync.pipeline({funcs: [
|
||||||
|
function gatherDataStdin(_, next) {
|
||||||
|
if (opts.file !== '-') {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
var stdin = '';
|
||||||
|
process.stdin.resume();
|
||||||
|
process.stdin.on('data', function (chunk) {
|
||||||
|
stdin += chunk;
|
||||||
|
});
|
||||||
|
process.stdin.on('end', function () {
|
||||||
|
try {
|
||||||
|
data = JSON.parse(stdin);
|
||||||
|
} catch (err) {
|
||||||
|
return next(new errors.TritonError(
|
||||||
|
format('invalid profile JSON on stdin: %s', err)));
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function gatherDataFile(_, next) {
|
||||||
|
if (!opts.file || opts.file === '-') {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
context = opts.file;
|
||||||
|
var input = fs.readFileSync(opts.file);
|
||||||
|
try {
|
||||||
|
data = JSON.parse(input);
|
||||||
|
} catch (err) {
|
||||||
|
return next(new errors.TritonError(format(
|
||||||
|
'invalid profile JSON in "%s": %s', opts.file, err)));
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
},
|
||||||
|
function gatherDataInteractive(_, next) {
|
||||||
|
if (opts.file) {
|
||||||
|
return next();
|
||||||
|
} else if (!process.stdin.isTTY) {
|
||||||
|
return next(new errors.UsageError('cannot interactively ' +
|
||||||
|
'create profile: stdin is not a TTY'));
|
||||||
|
} else if (!process.stdout.isTTY) {
|
||||||
|
return next(new errors.UsageError('cannot interactively ' +
|
||||||
|
'create profile: stdout is not a TTY'));
|
||||||
|
}
|
||||||
|
|
||||||
|
var profiles = mod_config.loadAllProfiles({
|
||||||
|
configDir: cli.configDir,
|
||||||
|
log: cli.log
|
||||||
|
});
|
||||||
|
|
||||||
|
var fields = [ {
|
||||||
|
desc: 'A profile name. A short string to identify a ' +
|
||||||
|
'CloudAPI endpoint to the `triton` CLI.',
|
||||||
|
key: 'name',
|
||||||
|
validate: function validateName(value, valCb) {
|
||||||
|
var regex = /^[a-z][a-z0-9_.-]*$/;
|
||||||
|
if (!regex.test(value)) {
|
||||||
|
return valCb(new Error('Must start with a lowercase ' +
|
||||||
|
'letter followed by lowercase letters, numbers ' +
|
||||||
|
'and "_", "." and "-".'));
|
||||||
|
}
|
||||||
|
for (var i = 0; i < profiles.length; i++) {
|
||||||
|
if (profiles[i].name === value) {
|
||||||
|
return valCb(new Error(format(
|
||||||
|
'Profile "%s" already exists.', value)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
valCb();
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
desc: 'The CloudAPI endpoint URL.',
|
||||||
|
default: 'https://us-sw-1.api.joyent.com',
|
||||||
|
key: 'url'
|
||||||
|
}, {
|
||||||
|
desc: 'Your account login name.',
|
||||||
|
key: 'account',
|
||||||
|
validate: function validateAccount(value, valCb) {
|
||||||
|
var regex = /^[^\\]{3,}$/;
|
||||||
|
if (value.length < 3) {
|
||||||
|
return valCb(new Error(
|
||||||
|
'Must be at least 3 characters'));
|
||||||
|
}
|
||||||
|
if (!regex.test(value)) {
|
||||||
|
return valCb(new Error('Must not container a "\\"'));
|
||||||
|
}
|
||||||
|
valCb();
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
desc: 'The fingerprint of the SSH key you have registered ' +
|
||||||
|
'for your account. You may enter a local path to a ' +
|
||||||
|
'public or private key to have the fingerprint ' +
|
||||||
|
'calculated for you.',
|
||||||
|
key: 'keyId',
|
||||||
|
validate: function validateKeyId(value, valCb) {
|
||||||
|
// First try as a fingerprint.
|
||||||
|
try {
|
||||||
|
sshpk.parseFingerprint(value);
|
||||||
|
return valCb();
|
||||||
|
} catch (fpErr) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try as a local path.
|
||||||
|
tilde(value, function (keyPath) {
|
||||||
|
fs.stat(keyPath, function (statErr, stats) {
|
||||||
|
if (statErr || !stats.isFile()) {
|
||||||
|
return valCb(new Error(
|
||||||
|
'"%s" is neither a valid fingerprint, ' +
|
||||||
|
'nor an existing file', value));
|
||||||
|
}
|
||||||
|
fs.readFile(keyPath, function (readErr, keyData) {
|
||||||
|
if (readErr) {
|
||||||
|
return valCb(readErr);
|
||||||
|
}
|
||||||
|
var keyType = (keyPath.slice(-4) === '.pub'
|
||||||
|
? 'ssh' : 'pem');
|
||||||
|
try {
|
||||||
|
var key = sshpk.parseKey(keyData, keyType);
|
||||||
|
} catch (keyErr) {
|
||||||
|
return valCb(keyErr);
|
||||||
|
}
|
||||||
|
|
||||||
|
var newVal = key.fingerprint('md5').toString();
|
||||||
|
console.log('Fingerprint: %s', newVal);
|
||||||
|
valCb(null, newVal);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} ];
|
||||||
|
|
||||||
|
data = {};
|
||||||
|
vasync.forEachPipeline({
|
||||||
|
inputs: fields,
|
||||||
|
func: function getField(field, nextField) {
|
||||||
|
if (field.key !== 'name') console.log();
|
||||||
|
common.promptField(field, function (err, value) {
|
||||||
|
data[field.key] = value;
|
||||||
|
nextField(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, function (err) {
|
||||||
|
console.log();
|
||||||
|
next(err);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function guardAlreadyExists(_, next) {
|
||||||
|
try {
|
||||||
|
var profiles = mod_config.loadAllProfiles({
|
||||||
|
configDir: cli.configDir,
|
||||||
|
log: cli.log
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
for (var i = 0; i < profiles.length; i++) {
|
||||||
|
if (data.name === profiles[i].name) {
|
||||||
|
return next(new errors.TritonError(format(
|
||||||
|
'profile "%s" already exists', data.name)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
},
|
||||||
|
function validateIt(_, next) {
|
||||||
|
// We ignore 'curr'. For now at least.
|
||||||
|
delete data.curr;
|
||||||
|
|
||||||
|
try {
|
||||||
|
mod_config.validateProfile(data, context);
|
||||||
|
} catch (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
},
|
||||||
|
function saveIt(_, next) {
|
||||||
|
try {
|
||||||
|
mod_config.saveProfileSync({
|
||||||
|
configDir: cli.configDir,
|
||||||
|
profile: data
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
]}, cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -401,7 +595,12 @@ do_profile.help = [
|
|||||||
' {{name}} profile -e|--edit [NAME] # edit a profile in $EDITOR',
|
' {{name}} profile -e|--edit [NAME] # edit a profile in $EDITOR',
|
||||||
' {{name}} profile -c|--current NAME # set NAME as current profile',
|
' {{name}} profile -c|--current NAME # set NAME as current profile',
|
||||||
' {{name}} profile -d|--delete NAME # delete a profile',
|
' {{name}} profile -d|--delete NAME # delete a profile',
|
||||||
' {{name}} profile -a|--add [FILE] # add a new profile',
|
'',
|
||||||
|
' {{name}} profile -a|--add [FILE]',
|
||||||
|
' # Add a new profile. FILE must be a file path (to JSON of',
|
||||||
|
' # the form from `triton profile -j`, "curr" field is ',
|
||||||
|
' # ignored) or "-" to pass the profile in on stdin.',
|
||||||
|
' # Or exclude FILE to interactively add.',
|
||||||
'',
|
'',
|
||||||
'{{options}}'
|
'{{options}}'
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
@ -29,7 +29,7 @@ function _listProfiles(cli, opts, args, cb) {
|
|||||||
var profiles;
|
var profiles;
|
||||||
try {
|
try {
|
||||||
profiles = mod_config.loadAllProfiles({
|
profiles = mod_config.loadAllProfiles({
|
||||||
configDir: cli.tritonapi.config._configDir,
|
configDir: cli.configDir,
|
||||||
log: cli.log
|
log: cli.log
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -69,30 +69,6 @@ function _listProfiles(cli, opts, args, cb) {
|
|||||||
cb();
|
cb();
|
||||||
}
|
}
|
||||||
|
|
||||||
function _currentProfile(cli, opts, args, cb) {
|
|
||||||
var profile = mod_config.loadProfile({
|
|
||||||
configDir: cli.configDir,
|
|
||||||
name: opts.current
|
|
||||||
});
|
|
||||||
|
|
||||||
if (cli.tritonapi.profile.name === profile.name) {
|
|
||||||
console.log('"%s" is already the current profile', profile.name);
|
|
||||||
return cb();
|
|
||||||
}
|
|
||||||
|
|
||||||
mod_config.setConfigVar({
|
|
||||||
configDir: cli.configDir,
|
|
||||||
name: 'profile',
|
|
||||||
value: profile.name
|
|
||||||
}, function (err) {
|
|
||||||
if (err) {
|
|
||||||
return cb(err);
|
|
||||||
}
|
|
||||||
console.log('Switched to "%s" profile', profile.name);
|
|
||||||
cb();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function do_profiles(subcmd, opts, args, cb) {
|
function do_profiles(subcmd, opts, args, cb) {
|
||||||
if (opts.help) {
|
if (opts.help) {
|
||||||
return this.do_help('help', {}, [subcmd], cb);
|
return this.do_help('help', {}, [subcmd], cb);
|
||||||
@ -100,11 +76,7 @@ function do_profiles(subcmd, opts, args, cb) {
|
|||||||
return cb(new errors.UsageError('too many args'));
|
return cb(new errors.UsageError('too many args'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts.current) {
|
|
||||||
_currentProfile(this, opts, args, cb);
|
|
||||||
} else {
|
|
||||||
_listProfiles(this, opts, args, cb);
|
_listProfiles(this, opts, args, cb);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
do_profiles.options = [
|
do_profiles.options = [
|
||||||
|
@ -19,11 +19,14 @@
|
|||||||
"read": "1.0.7",
|
"read": "1.0.7",
|
||||||
"restify-clients": "1.1.0",
|
"restify-clients": "1.1.0",
|
||||||
"restify-errors": "3.0.0",
|
"restify-errors": "3.0.0",
|
||||||
|
"sshpk": "1.2.1",
|
||||||
"smartdc-auth": "git+https://github.com/joyent/node-smartdc-auth.git#3be3c1e",
|
"smartdc-auth": "git+https://github.com/joyent/node-smartdc-auth.git#3be3c1e",
|
||||||
"strsplit": "1.0.0",
|
"strsplit": "1.0.0",
|
||||||
"tabula": "1.6.1",
|
"tabula": "1.6.1",
|
||||||
|
"tilde-expansion": "0.0.0",
|
||||||
"vasync": "1.6.3",
|
"vasync": "1.6.3",
|
||||||
"verror": "1.6.0"
|
"verror": "1.6.0",
|
||||||
|
"wordwrap": "1.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"tape": "4.2.0"
|
"tape": "4.2.0"
|
||||||
|
Reference in New Issue
Block a user