joyent/node-triton#28 profile opts (-i, -a, -k, -u) aren't being applied to profiles other than the env profile

Profile/CloudAPI top-level CLI options are now applied to the current
profile. Also clean up loading of the 'env' profile a bit so that
special casing of that is more hidden in "config.js".

Also add support for the TRITON_URL, TRITON_TLS_INSECURE and
TRITON_ACCOUNT envvars. (I didn't add TRITON_KEY_ID because a coming
change will do better than that.)

Fixes #28, #24.
This commit is contained in:
Trent Mick 2015-09-21 12:34:37 -07:00
parent 5c89bd32c3
commit 99d9113eae
3 changed files with 114 additions and 82 deletions

View File

@ -64,18 +64,28 @@ var OPTIONS = [
}, },
{ {
group: 'CloudApi Options' group: 'CloudAPI Options'
}, },
// XXX SDC_USER support. I don't grok the node-smartdc/README.md discussion
// of SDC_USER. /*
* Environment variable integration.
*
* While dashdash supports integrated envvar parsing with options
* we don't use that with `triton` because (a) we want to apply *option*
* usage (but not envvars) to profiles other than the default 'env'
* profile, and (b) we want to support `TRITON_*` *and* `SDC_*` envvars,
* which dashdash doesn't support.
*
* See <https://github.com/joyent/node-triton/issues/28> for some details.
*/
{ {
names: ['account', 'a'], names: ['account', 'a'],
type: 'string', type: 'string',
env: 'SDC_ACCOUNT', help: 'Account (login name). Environment: TRITON_ACCOUNT=ACCOUNT ' +
help: 'TritonApi account (login name)', 'or SDC_ACCOUNT=ACCOUNT.',
helpArg: 'ACCOUNT' helpArg: 'ACCOUNT'
}, },
// XXX // TODO: subuser/RBAC support
//{ //{
// names: ['subuser', 'user'], // names: ['subuser', 'user'],
// type: 'string', // type: 'string',
@ -93,15 +103,13 @@ var OPTIONS = [
{ {
names: ['keyId', 'k'], names: ['keyId', 'k'],
type: 'string', type: 'string',
env: 'SDC_KEY_ID', help: 'SSH key fingerprint. Environment: SDC_KEY_ID=FINGERPRINT.',
help: 'SSH key fingerprint',
helpArg: 'FINGERPRINT' helpArg: 'FINGERPRINT'
}, },
{ {
names: ['url', 'u'], names: ['url', 'u'],
type: 'string', type: 'string',
env: 'SDC_URL', help: 'CloudAPI URL. Environment: TRITON_URL=URL or SDC_URL=URL.',
help: 'CloudApi URL',
helpArg: 'URL' helpArg: 'URL'
}, },
{ {
@ -115,9 +123,10 @@ var OPTIONS = [
{ {
names: ['insecure', 'i'], names: ['insecure', 'i'],
type: 'bool', type: 'bool',
help: 'Do not validate SSL certificate', help: 'Do not validate the CloudAPI SSL certificate. Environment: ' +
'TRITON_TLS_INSECURE=1, SDC_TLS_INSECURE=1 (or the deprecated ' +
'SDC_TESTING=1).',
'default': false, 'default': false,
env: 'SDC_TLS_INSECURE' // Deprecated SDC_TESTING supported below.
} }
]; ];
@ -170,13 +179,13 @@ util.inherits(CLI, Cmdln);
CLI.prototype.init = function (opts, args, callback) { CLI.prototype.init = function (opts, args, callback) {
var self = this; var self = this;
this.opts = opts;
if (opts.version) { if (opts.version) {
console.log(this.name, pkg.version); console.log(this.name, pkg.version);
callback(false); callback(false);
return; return;
} }
this.opts = opts;
this.log = bunyan.createLogger({ this.log = bunyan.createLogger({
name: this.name, name: this.name,
@ -190,10 +199,13 @@ CLI.prototype.init = function (opts, args, callback) {
this.showErrStack = true; this.showErrStack = true;
} }
if (!opts.url && opts.J) { if (opts.url && opts.J) {
callback(new errors.UsageError(
'cannot use both "--url" and "-J" options'));
} else if (opts.J) {
opts.url = format('https://%s.api.joyent.com', opts.J); opts.url = format('https://%s.api.joyent.com', opts.J);
} }
this.envProfile = mod_config.loadEnvProfile(opts);
this.configDir = CONFIG_DIR; this.configDir = CONFIG_DIR;
this.__defineGetter__('tritonapi', function () { this.__defineGetter__('tritonapi', function () {
@ -202,16 +214,13 @@ CLI.prototype.init = function (opts, args, callback) {
configDir: self.configDir configDir: self.configDir
}); });
self.log.trace({config: config}, 'loaded config'); self.log.trace({config: config}, 'loaded config');
var profileName = opts.profile || config.profile || 'env'; var profileName = opts.profile || config.profile || 'env';
var profile; var profile = mod_config.loadProfile({
if (profileName === 'env') {
profile = self.envProfile;
} else {
profile = mod_config.loadProfile({
configDir: self.configDir, configDir: self.configDir,
name: profileName name: profileName
}); });
} self._applyProfileOverrides(profile);
self.log.trace({profile: profile}, 'loaded profile'); self.log.trace({profile: profile}, 'loaded profile');
self._tritonapi = new TritonApi({ self._tritonapi = new TritonApi({
@ -228,6 +237,19 @@ CLI.prototype.init = function (opts, args, callback) {
}; };
/*
* Apply overrides from CLI options to the given profile object *in place*.
*/
CLI.prototype._applyProfileOverrides =
function _applyProfileOverrides(profile) {
var self = this;
['account', 'url', 'keyId', 'insecure'].forEach(function (field) {
if (self.opts.hasOwnProperty(field)) {
profile[field] = self.opts[field];
}
});
};
// Meta // Meta
CLI.prototype.do_completion = require('./do_completion'); CLI.prototype.do_completion = require('./do_completion');

View File

@ -205,29 +205,30 @@ function setConfigVar(opts, cb) {
// --- Profiles // --- Profiles
/** /**
* Load the special 'env' profile, which handles some details of getting * Load the special 'env' profile, which handles details of getting
* values from envvars. *Most* of that is done already via the * values from envvars. Typically we'd piggyback on dashdash's env support
* `opts` dashdash Options object. * <https://github.com/trentm/node-dashdash#environment-variable-integration>.
* However, per the "Environment variable integration" comment in cli.js, we
* do that manually.
* *
* @returns {Object} The 'env' profile. * @returns {Object} The 'env' profile.
*/ */
function loadEnvProfile(opts) { function _loadEnvProfile() {
// XXX support keyId being a priv or pub key path, a la imgapi-cli
// XXX Add TRITON_* envvars.
var envProfile = { var envProfile = {
name: 'env', name: 'env'
account: opts.account,
url: opts.url,
keyId: opts.keyId,
insecure: opts.insecure
}; };
// If --insecure not given, look at envvar(s) for that.
var specifiedInsecureOpt = opts._order.filter( envProfile.account = process.env.TRITON_ACCOUNT || process.env.SDC_ACCOUNT;
function (opt) { return opt.key === 'insecure'; }).length > 0; envProfile.url = process.env.TRITON_URL || process.env.SDC_URL;
if (!specifiedInsecureOpt && process.env.SDC_TESTING) { envProfile.keyId = process.env.SDC_KEY_ID;
if (process.env.TRITON_TLS_INSECURE) {
envProfile.insecure = common.boolFromString( envProfile.insecure = common.boolFromString(
process.env.SDC_TESTING, process.env.TRITON_TLS_INSECURE);
false, '"SDC_TESTING" envvar'); } else if (process.env.SDC_TLS_INSECURE) {
envProfile.insecure = common.boolFromString(
process.env.SDC_TLS_INSECURE);
} else if (process.env.SDC_TESTING) { // deprecated
envProfile.insecure = common.boolFromString(process.env.SDC_TESTING);
} }
_validateProfile(envProfile); _validateProfile(envProfile);
@ -263,16 +264,22 @@ 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');
if (opts.name === 'env') {
return _loadEnvProfile();
} else {
var profilePath = path.resolve(opts.configDir, 'profiles.d', var profilePath = path.resolve(opts.configDir, 'profiles.d',
opts.name + '.json'); opts.name + '.json');
return _profileFromPath(profilePath, opts.name); return _profileFromPath(profilePath, opts.name);
}
} }
function loadAllProfiles(opts) { function loadAllProfiles(opts) {
assert.string(opts.configDir, 'opts.configDir'); assert.string(opts.configDir, 'opts.configDir');
assert.object(opts.log, 'opts.log'); assert.object(opts.log, 'opts.log');
var profiles = []; var profiles = [
_loadEnvProfile()
];
var d = path.join(opts.configDir, 'profiles.d'); var d = path.join(opts.configDir, 'profiles.d');
var files = fs.readdirSync(d); var files = fs.readdirSync(d);
@ -285,7 +292,8 @@ function loadAllProfiles(opts) {
var name = path.basename(file).slice(0, - path.extname(file).length); var name = path.basename(file).slice(0, - path.extname(file).length);
if (name.toLowerCase() === 'env') { if (name.toLowerCase() === 'env') {
// Skip the special 'env'. // Skip the special 'env'.
opts.log.debug('skip reserved name "env" profile: %s', file); opts.log.warn({profilePath: file},
'invalid "env" profile; skipping');
return; return;
} }
try { try {
@ -305,7 +313,6 @@ function loadAllProfiles(opts) {
module.exports = { module.exports = {
loadConfig: loadConfig, loadConfig: loadConfig,
setConfigVar: setConfigVar, setConfigVar: setConfigVar,
loadEnvProfile: loadEnvProfile,
loadProfile: loadProfile, loadProfile: loadProfile,
loadAllProfiles: loadAllProfiles loadAllProfiles: loadAllProfiles
}; };

View File

@ -14,7 +14,7 @@ var sortDefault = 'name';
var columnsDefault = 'name,curr,account,url'; var columnsDefault = 'name,curr,account,url';
var columnsDefaultLong = 'name,curr,account,url,insecure,keyId'; var columnsDefaultLong = 'name,curr,account,url,insecure,keyId';
function _listProfiles(_, opts, cb) { function _listProfiles(opts, args, cb) {
var columns = columnsDefault; var columns = columnsDefault;
if (opts.o) { if (opts.o) {
columns = opts.o; columns = opts.o;
@ -35,21 +35,31 @@ function _listProfiles(_, opts, cb) {
} catch (e) { } catch (e) {
return cb(e); return cb(e);
} }
profiles.push(this.envProfile);
// Current profile: Set 'curr' field. Apply CLI overrides.
for (i = 0; i < profiles.length; i++) {
var profile = profiles[i];
if (profile.name === this.tritonapi.profile.name) {
this._applyProfileOverrides(profile);
if (opts.json) {
profile.curr = true;
} else {
profile.curr = '*'; // tabular
}
} else {
if (opts.json) {
profile.curr = false;
} else {
profile.curr = ''; // tabular
}
}
}
// Display. // Display.
var i; var i;
if (opts.json) { if (opts.json) {
for (i = 0; i < profiles.length; i++) {
profiles[i].curr = (profiles[i].name ===
this.tritonapi.profile.name);
}
common.jsonStream(profiles); common.jsonStream(profiles);
} else { } else {
for (i = 0; i < profiles.length; i++) {
profiles[i].curr = (profiles[i].name === this.tritonapi.profile.name
? '*' : '');
}
tabula(profiles, { tabula(profiles, {
skipHeader: opts.H, skipHeader: opts.H,
columns: columns, columns: columns,
@ -59,7 +69,12 @@ function _listProfiles(_, opts, cb) {
cb(); cb();
} }
function _currentProfile(profile, opts, cb) { function _currentProfile(opts, args, cb) {
var profile = mod_config.loadProfile({
configDir: this.configDir,
name: opts.current
});
if (this.tritonapi.profile.name === profile.name) { if (this.tritonapi.profile.name === profile.name) {
console.log('"%s" is already the current profile', profile.name); console.log('"%s" is already the current profile', profile.name);
return cb(); return cb();
@ -95,6 +110,7 @@ function do_profiles(subcmd, opts, args, cb) {
return; return;
} }
// Which action?
var actions = []; var actions = [];
if (opts.add) { actions.push('add'); } if (opts.add) { actions.push('add'); }
if (opts.current) { actions.push('current'); } if (opts.current) { actions.push('current'); }
@ -110,20 +126,19 @@ function do_profiles(subcmd, opts, args, cb) {
action = actions[0]; action = actions[0];
} }
var name; // Arg count validation.
switch (action) { switch (action) {
case 'add': //case 'add':
if (args.length === 1) { // if (args.length === 1) {
name = args[0]; // name = args[0];
} else if (args.length > 1) { // } else if (args.length > 1) {
return cb(new errors.UsageError('too many args')); // return cb(new errors.UsageError('too many args'));
} // }
break; // break;
case 'list': case 'list':
case 'current': case 'current':
case 'edit': //case 'edit':
case 'delete': //case 'delete':
name = opts.current || opts.edit || opts['delete'];
if (args.length > 0) { if (args.length > 0) {
return cb(new errors.UsageError('too many args')); return cb(new errors.UsageError('too many args'));
} }
@ -132,18 +147,6 @@ function do_profiles(subcmd, opts, args, cb) {
throw new Error('unknown action: ' + action); throw new Error('unknown action: ' + action);
} }
var profile;
if (name) {
if (name === 'env') {
profile = this.envProfile;
} else {
profile = mod_config.loadProfile({
configDir: this.configDir,
name: name
});
}
}
var func = { var func = {
list: _listProfiles, list: _listProfiles,
current: _currentProfile current: _currentProfile
@ -152,7 +155,7 @@ function do_profiles(subcmd, opts, args, cb) {
//edit: _editProfile, //edit: _editProfile,
//'delete': _deleteProfile //'delete': _deleteProfile
}[action].bind(this); }[action].bind(this);
func(profile, opts, cb); func(opts, args, cb);
} }
do_profiles.options = [ do_profiles.options = [