joyent/node-triton#18 first cut of 'triton profiles'
This commit is contained in:
parent
a3d362423b
commit
aef539a1b0
22
TODO.txt
22
TODO.txt
@ -24,28 +24,6 @@ triton images
|
|||||||
for 'linux' ones. That might hit that stupid naming problem.
|
for 'linux' ones. That might hit that stupid naming problem.
|
||||||
|
|
||||||
|
|
||||||
# profiles
|
|
||||||
|
|
||||||
triton profile # list all profiles
|
|
||||||
triton profile NAME # show NAME profile
|
|
||||||
triton profile -a NAME # sets it as active
|
|
||||||
triton profile -n|--new # ???
|
|
||||||
|
|
||||||
For today: only the implicit 'env' profile.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# config
|
|
||||||
|
|
||||||
~/.triton/
|
|
||||||
config.json
|
|
||||||
{"currProfile": "east3b"}
|
|
||||||
east3b/ # profile
|
|
||||||
PROFILE2/
|
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# another day
|
# another day
|
||||||
|
|
||||||
triton config get|set|list # see 'npm config'
|
triton config get|set|list # see 'npm config'
|
||||||
|
@ -134,7 +134,8 @@ function CLI() {
|
|||||||
},
|
},
|
||||||
helpSubcmds: [
|
helpSubcmds: [
|
||||||
'help',
|
'help',
|
||||||
'profiles',
|
// TODO: hide until the command is fully implemented:
|
||||||
|
// 'profiles',
|
||||||
{ group: 'Other Commands' },
|
{ group: 'Other Commands' },
|
||||||
'info',
|
'info',
|
||||||
'account',
|
'account',
|
||||||
@ -192,11 +193,12 @@ CLI.prototype.init = function (opts, args, callback) {
|
|||||||
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.envProfile = mod_config.loadEnvProfile(opts);
|
||||||
|
this.configDir = CONFIG_DIR;
|
||||||
|
|
||||||
this.__defineGetter__('tritonapi', function () {
|
this.__defineGetter__('tritonapi', function () {
|
||||||
if (self._triton === undefined) {
|
if (self._triton === undefined) {
|
||||||
var config = mod_config.loadConfig({
|
var config = mod_config.loadConfig({
|
||||||
configDir: CONFIG_DIR
|
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';
|
||||||
@ -205,7 +207,7 @@ CLI.prototype.init = function (opts, args, callback) {
|
|||||||
profile = self.envProfile;
|
profile = self.envProfile;
|
||||||
} else {
|
} else {
|
||||||
profile = mod_config.loadProfile({
|
profile = mod_config.loadProfile({
|
||||||
configDir: CONFIG_DIR,
|
configDir: self.configDir,
|
||||||
name: profileName
|
name: profileName
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
101
lib/config.js
101
lib/config.js
@ -8,18 +8,51 @@
|
|||||||
* Copyright 2015 Joyent, Inc.
|
* Copyright 2015 Joyent, Inc.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This module provides functions to read and write (a) a TritonApi config
|
||||||
|
* and (b) TritonApi profiles.
|
||||||
|
*
|
||||||
|
* The config is a JSON object loaded from "etc/defaults.json" (shipped with
|
||||||
|
* node-triton) plus possibly overrides from "$configDir/config.json" --
|
||||||
|
* which is "~/.triton/config.json" for the `triton` CLI. The config has
|
||||||
|
* a strict set of allowed keys.
|
||||||
|
*
|
||||||
|
* A profile is a small object that includes the necessary info for talking
|
||||||
|
* to a CloudAPI. E.g.:
|
||||||
|
* {
|
||||||
|
* "name": "east1",
|
||||||
|
* "account": "billy.bob",
|
||||||
|
* "keyId": "de:e7:73:9a:aa:91:bb:3e:72:8d:cc:62:ca:58:a2:ec",
|
||||||
|
* "url": "https://us-east-1.api.joyent.com"
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* Profiles are stored as separate JSON files in
|
||||||
|
* "$configDir/profiles.d/$name.json". Typically `triton profiles ...` is
|
||||||
|
* used to manage them. In addition there is the special "env" profile that
|
||||||
|
* is constructed from the "SDC_*" environment variables.
|
||||||
|
*/
|
||||||
|
|
||||||
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 fs = require('fs');
|
||||||
|
var mkdirp = require('mkdirp');
|
||||||
var glob = require('glob');
|
var glob = require('glob');
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
|
var vasync = require('vasync');
|
||||||
|
|
||||||
var common = require('./common');
|
var common = require('./common');
|
||||||
var errors = require('./errors');
|
var errors = require('./errors');
|
||||||
|
|
||||||
|
|
||||||
var DEFAULTS_PATH = path.resolve(__dirname, '..', 'etc', 'defaults.json');
|
var DEFAULTS_PATH = path.resolve(__dirname, '..', 'etc', 'defaults.json');
|
||||||
var OVERRIDE_KEYS = []; // config object keys to do a one-level deep override
|
var OVERRIDE_NAMES = []; // config object keys to do a one-level deep override
|
||||||
|
|
||||||
|
// TODO: use this const to create the "Configuration" docs table.
|
||||||
|
var CONFIG_VAR_NAMES = [
|
||||||
|
'profile',
|
||||||
|
'cacheDir'
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// --- internal support stuff
|
// --- internal support stuff
|
||||||
@ -36,8 +69,12 @@ function _validateProfile(profile) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function configPathFromDir(configDir) {
|
||||||
|
return path.resolve(configDir, 'config.json');
|
||||||
|
}
|
||||||
|
|
||||||
// --- exported functions
|
|
||||||
|
// --- Config
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load the TritonApi config. This is a merge of the built-in "defaults" (at
|
* Load the TritonApi config. This is a merge of the built-in "defaults" (at
|
||||||
@ -55,7 +92,7 @@ function loadConfig(opts) {
|
|||||||
assert.object(opts, 'opts');
|
assert.object(opts, 'opts');
|
||||||
assert.string(opts.configDir, 'opts.configDir');
|
assert.string(opts.configDir, 'opts.configDir');
|
||||||
|
|
||||||
var configPath = path.resolve(opts.configDir, 'config.json');
|
var configPath = configPathFromDir(opts.configDir);
|
||||||
|
|
||||||
var c = fs.readFileSync(DEFAULTS_PATH, 'utf8');
|
var c = fs.readFileSync(DEFAULTS_PATH, 'utf8');
|
||||||
var _defaults = JSON.parse(c);
|
var _defaults = JSON.parse(c);
|
||||||
@ -71,7 +108,7 @@ function loadConfig(opts) {
|
|||||||
// These special keys are merged into the key of the same name in the
|
// These special keys are merged into the key of the same name in the
|
||||||
// base "defaults.json".
|
// base "defaults.json".
|
||||||
Object.keys(userConfig).forEach(function (key) {
|
Object.keys(userConfig).forEach(function (key) {
|
||||||
if (~OVERRIDE_KEYS.indexOf(key) && config[key] !== undefined) {
|
if (~OVERRIDE_NAMES.indexOf(key) && config[key] !== undefined) {
|
||||||
Object.keys(userConfig[key]).forEach(function (subKey) {
|
Object.keys(userConfig[key]).forEach(function (subKey) {
|
||||||
if (userConfig[key][subKey] === null) {
|
if (userConfig[key][subKey] === null) {
|
||||||
delete config[key][subKey];
|
delete config[key][subKey];
|
||||||
@ -93,6 +130,61 @@ function loadConfig(opts) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function setConfigVar(opts, cb) {
|
||||||
|
assert.object(opts, 'opts');
|
||||||
|
assert.string(opts.configDir, 'opts.configDir');
|
||||||
|
assert.string(opts.name, 'opts.name');
|
||||||
|
assert.string(opts.value, 'opts.value');
|
||||||
|
assert.ok(opts.name.indexOf('.') === -1,
|
||||||
|
'dotted config name not yet supported');
|
||||||
|
assert.ok(CONFIG_VAR_NAMES.indexOf(opts.name) !== -1,
|
||||||
|
'unknown config var name: ' + opts.name);
|
||||||
|
|
||||||
|
var configPath = configPathFromDir(opts.configDir);
|
||||||
|
|
||||||
|
var config;
|
||||||
|
vasync.pipeline({funcs: [
|
||||||
|
function loadExisting(_, next) {
|
||||||
|
fs.exists(configPath, function (exists) {
|
||||||
|
if (!exists) {
|
||||||
|
config = {};
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
fs.readFile(configPath, function (err, data) {
|
||||||
|
if (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
config = JSON.parse(data);
|
||||||
|
} catch (e) {
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
function mkConfigDir(_, next) {
|
||||||
|
fs.exists(opts.configDir, function (exists) {
|
||||||
|
if (!exists) {
|
||||||
|
mkdirp(opts.configDir, next);
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
function updateAndSave(_, next) {
|
||||||
|
config[opts.name] = opts.value;
|
||||||
|
fs.writeFile(configPath, JSON.stringify(config, null, 4), next);
|
||||||
|
}
|
||||||
|
]}, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// --- Profiles
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load the special 'env' profile, which handles some details of getting
|
* Load the special 'env' profile, which handles some details of getting
|
||||||
* values from envvars. *Most* of that is done already via the
|
* values from envvars. *Most* of that is done already via the
|
||||||
@ -183,6 +275,7 @@ function loadAllProfiles(opts) {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
loadConfig: loadConfig,
|
loadConfig: loadConfig,
|
||||||
|
setConfigVar: setConfigVar,
|
||||||
loadEnvProfile: loadEnvProfile,
|
loadEnvProfile: loadEnvProfile,
|
||||||
loadProfile: loadProfile,
|
loadProfile: loadProfile,
|
||||||
loadAllProfiles: loadAllProfiles
|
loadAllProfiles: loadAllProfiles
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2015 Joyent Inc. All rights reserved.
|
* Copyright (c) 2015 Joyent Inc. All rights reserved.
|
||||||
*
|
*
|
||||||
* `triton profile ...`
|
* `triton profiles ...`
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var common = require('./common');
|
var common = require('./common');
|
||||||
@ -14,14 +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 do_profiles(subcmd, opts, args, cb) {
|
function _listProfiles(_, opts, cb) {
|
||||||
if (opts.help) {
|
|
||||||
this.do_help('help', {}, [subcmd], cb);
|
|
||||||
return;
|
|
||||||
} else if (args.length > 1) {
|
|
||||||
return cb(new errors.UsageError('too many args'));
|
|
||||||
}
|
|
||||||
|
|
||||||
var columns = columnsDefault;
|
var columns = columnsDefault;
|
||||||
if (opts.o) {
|
if (opts.o) {
|
||||||
columns = opts.o;
|
columns = opts.o;
|
||||||
@ -48,7 +41,8 @@ function do_profiles(subcmd, opts, args, cb) {
|
|||||||
var i;
|
var i;
|
||||||
if (opts.json) {
|
if (opts.json) {
|
||||||
for (i = 0; i < profiles.length; i++) {
|
for (i = 0; i < profiles.length; i++) {
|
||||||
profiles[i].curr = (profiles[i].name === this.tritonapi.profile.name);
|
profiles[i].curr = (profiles[i].name ===
|
||||||
|
this.tritonapi.profile.name);
|
||||||
}
|
}
|
||||||
common.jsonStream(profiles);
|
common.jsonStream(profiles);
|
||||||
} else {
|
} else {
|
||||||
@ -65,12 +59,136 @@ function do_profiles(subcmd, opts, args, cb) {
|
|||||||
cb();
|
cb();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _currentProfile(profile, opts, cb) {
|
||||||
|
if (this.tritonapi.profile.name === profile.name) {
|
||||||
|
console.log('"%s" is already the current profile', profile.name);
|
||||||
|
return cb();
|
||||||
|
}
|
||||||
|
|
||||||
|
mod_config.setConfigVar({
|
||||||
|
configDir: this.configDir,
|
||||||
|
name: 'profile',
|
||||||
|
value: profile.name
|
||||||
|
}, function (err) {
|
||||||
|
if (err) {
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
|
console.log('Switched to "%s" profile', profile.name);
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: finish the implementation
|
||||||
|
//function _addProfile(profile, opts, cb) {
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//function _editProfile(profile, opts, cb) {
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//function _deleteProfile(profile, opts, cb) {
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
function do_profiles(subcmd, opts, args, cb) {
|
||||||
|
if (opts.help) {
|
||||||
|
this.do_help('help', {}, [subcmd], cb);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var actions = [];
|
||||||
|
if (opts.add) { actions.push('add'); }
|
||||||
|
if (opts.current) { actions.push('current'); }
|
||||||
|
if (opts.edit) { actions.push('edit'); }
|
||||||
|
if (opts['delete']) { actions.push('delete'); }
|
||||||
|
var action;
|
||||||
|
if (actions.length === 0) {
|
||||||
|
action = 'list';
|
||||||
|
} else if (actions.length > 1) {
|
||||||
|
return cb(new errors.UsageError(
|
||||||
|
'only one action option may be used at once'));
|
||||||
|
} else {
|
||||||
|
action = actions[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
var name;
|
||||||
|
switch (action) {
|
||||||
|
case 'add':
|
||||||
|
if (args.length === 1) {
|
||||||
|
name = args[0];
|
||||||
|
} else if (args.length > 1) {
|
||||||
|
return cb(new errors.UsageError('too many args'));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'list':
|
||||||
|
case 'current':
|
||||||
|
case 'edit':
|
||||||
|
case 'delete':
|
||||||
|
name = opts.current || opts.edit || opts['delete'];
|
||||||
|
if (args.length > 0) {
|
||||||
|
return cb(new errors.UsageError('too many args'));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
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 = {
|
||||||
|
list: _listProfiles,
|
||||||
|
current: _currentProfile
|
||||||
|
// TODO: finish the implementation
|
||||||
|
//add: _addProfile,
|
||||||
|
//edit: _editProfile,
|
||||||
|
//'delete': _deleteProfile
|
||||||
|
}[action].bind(this);
|
||||||
|
func(profile, opts, cb);
|
||||||
|
}
|
||||||
|
|
||||||
do_profiles.options = [
|
do_profiles.options = [
|
||||||
{
|
{
|
||||||
names: ['help', 'h'],
|
names: ['help', 'h'],
|
||||||
type: 'bool',
|
type: 'bool',
|
||||||
help: 'Show this help.'
|
help: 'Show this help.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
group: 'Action Options'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
names: ['current', 'c'],
|
||||||
|
type: 'string',
|
||||||
|
helpArg: 'NAME',
|
||||||
|
help: 'Switch to the given profile.'
|
||||||
}
|
}
|
||||||
|
// TODO: finish the implementation
|
||||||
|
//{
|
||||||
|
// names: ['add', 'a'],
|
||||||
|
// type: 'bool',
|
||||||
|
// help: 'Add a new profile.'
|
||||||
|
//},
|
||||||
|
//{
|
||||||
|
// names: ['edit', 'e'],
|
||||||
|
// type: 'string',
|
||||||
|
// helpArg: 'NAME',
|
||||||
|
// help: 'Edit profile NAME in your $EDITOR.'
|
||||||
|
//},
|
||||||
|
//{
|
||||||
|
// names: ['delete', 'd'],
|
||||||
|
// type: 'string',
|
||||||
|
// helpArg: 'NAME',
|
||||||
|
// help: 'Delete profile NAME.'
|
||||||
|
//}
|
||||||
|
|
||||||
].concat(common.getCliTableOptions({
|
].concat(common.getCliTableOptions({
|
||||||
includeLong: true,
|
includeLong: true,
|
||||||
sortDefault: sortDefault
|
sortDefault: sortDefault
|
||||||
@ -86,10 +204,17 @@ do_profiles.help = [
|
|||||||
'The "CURR" column indicates which profile is the current one.',
|
'The "CURR" column indicates which profile is the current one.',
|
||||||
'',
|
'',
|
||||||
'Usage:',
|
'Usage:',
|
||||||
' {{name}} profiles',
|
' {{name}} profiles # list profiles',
|
||||||
|
' {{name}} profiles -c|--current NAME # set NAME as current profile',
|
||||||
|
// TODO: finish the implementation
|
||||||
|
//' {{name}} profiles -a|--add [NAME] # add a new profile',
|
||||||
|
//' {{name}} profiles -e|--edit NAME # edit a profile in $EDITOR',
|
||||||
|
//' {{name}} profiles -d|--delete NAME # delete a profile',
|
||||||
'',
|
'',
|
||||||
'{{options}}'
|
'{{options}}'
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
|
do_profiles.hidden = true; // TODO: until -a,-e,-d are implemented
|
||||||
|
|
||||||
|
|
||||||
module.exports = do_profiles;
|
module.exports = do_profiles;
|
||||||
|
Reference in New Issue
Block a user