joyent/node-triton#18 first cut of 'triton profiles'
This commit is contained in:
parent
3699dd3a46
commit
d2e999916b
13
README.md
13
README.md
@ -212,6 +212,18 @@ as a single `DOCKER_HOST`. See the [Triton Docker
|
|||||||
documentation](https://apidocs.joyent.com/docker) for more information.)
|
documentation](https://apidocs.joyent.com/docker) for more information.)
|
||||||
|
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
This section defines all the vars in a TritonApi config. The baked in defaults
|
||||||
|
are in "etc/defaults.json" and can be overriden for the CLI in
|
||||||
|
"~/.triton/config.json".
|
||||||
|
|
||||||
|
| Name | Description |
|
||||||
|
| ---- | ----------- |
|
||||||
|
| profile | The name of the triton profile to use. The default with the CLI is "env", i.e. take config from `SDC_*` envvars. |
|
||||||
|
| cacheDir | The path (relative to the config dir, "~/.triton") where cache data is stored. The default is "cache", i.e. the `triton` CLI caches at "~/.triton/cache". |
|
||||||
|
|
||||||
|
|
||||||
## node-triton differences with node-smartdc
|
## node-triton differences with node-smartdc
|
||||||
|
|
||||||
- There is a single `triton` command instead of a number of `sdc-*` commands.
|
- There is a single `triton` command instead of a number of `sdc-*` commands.
|
||||||
@ -251,3 +263,4 @@ clone via:
|
|||||||
## License
|
## License
|
||||||
|
|
||||||
MPL 2.0
|
MPL 2.0
|
||||||
|
|
||||||
|
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'
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"defaultProfile": "env"
|
"cacheDir": "cache"
|
||||||
}
|
}
|
||||||
|
106
lib/cli.js
106
lib/cli.js
@ -25,6 +25,7 @@ var path = require('path');
|
|||||||
var vasync = require('vasync');
|
var vasync = require('vasync');
|
||||||
|
|
||||||
var common = require('./common');
|
var common = require('./common');
|
||||||
|
var mod_config = require('./config');
|
||||||
var errors = require('./errors');
|
var errors = require('./errors');
|
||||||
var TritonApi = require('./tritonapi');
|
var TritonApi = require('./tritonapi');
|
||||||
|
|
||||||
@ -32,16 +33,9 @@ var TritonApi = require('./tritonapi');
|
|||||||
|
|
||||||
//---- globals
|
//---- globals
|
||||||
|
|
||||||
var p = console.log;
|
|
||||||
|
|
||||||
var pkg = require('../package.json');
|
var pkg = require('../package.json');
|
||||||
var name = 'triton';
|
|
||||||
var log = bunyan.createLogger({
|
var CONFIG_DIR = path.resolve(process.env.HOME, '.triton');
|
||||||
name: name,
|
|
||||||
serializers: bunyan.stdSerializers,
|
|
||||||
stream: process.stderr,
|
|
||||||
level: 'warn'
|
|
||||||
});
|
|
||||||
|
|
||||||
var OPTIONS = [
|
var OPTIONS = [
|
||||||
{
|
{
|
||||||
@ -60,9 +54,13 @@ var OPTIONS = [
|
|||||||
help: 'Verbose/debug output.'
|
help: 'Verbose/debug output.'
|
||||||
},
|
},
|
||||||
|
|
||||||
// XXX disable profile selection for now
|
{
|
||||||
//{names: ['profile', 'p'], type: 'string', env: 'TRITON_PROFILE',
|
names: ['profile', 'p'],
|
||||||
// helpArg: 'NAME', help: 'TritonApi client profile to use.'}
|
type: 'string',
|
||||||
|
env: 'TRITON_PROFILE',
|
||||||
|
helpArg: 'NAME',
|
||||||
|
help: 'Triton client profile to use.'
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
group: 'CloudApi Options'
|
group: 'CloudApi Options'
|
||||||
@ -127,7 +125,7 @@ var OPTIONS = [
|
|||||||
|
|
||||||
function CLI() {
|
function CLI() {
|
||||||
Cmdln.call(this, {
|
Cmdln.call(this, {
|
||||||
name: pkg.name,
|
name: 'triton',
|
||||||
desc: pkg.description,
|
desc: pkg.description,
|
||||||
options: OPTIONS,
|
options: OPTIONS,
|
||||||
helpOpts: {
|
helpOpts: {
|
||||||
@ -136,6 +134,8 @@ function CLI() {
|
|||||||
},
|
},
|
||||||
helpSubcmds: [
|
helpSubcmds: [
|
||||||
'help',
|
'help',
|
||||||
|
// TODO: hide until the command is fully implemented:
|
||||||
|
// 'profiles',
|
||||||
{ group: 'Other Commands' },
|
{ group: 'Other Commands' },
|
||||||
'info',
|
'info',
|
||||||
'account',
|
'account',
|
||||||
@ -171,59 +171,52 @@ CLI.prototype.init = function (opts, args, callback) {
|
|||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
if (opts.version) {
|
if (opts.version) {
|
||||||
p(this.name, pkg.version);
|
console.log(this.name, pkg.version);
|
||||||
callback(false);
|
callback(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.opts = opts;
|
this.opts = opts;
|
||||||
|
|
||||||
|
this.log = bunyan.createLogger({
|
||||||
|
name: this.name,
|
||||||
|
serializers: bunyan.stdSerializers,
|
||||||
|
stream: process.stderr,
|
||||||
|
level: 'warn'
|
||||||
|
});
|
||||||
if (opts.verbose) {
|
if (opts.verbose) {
|
||||||
log.level('trace');
|
this.log.level('trace');
|
||||||
log.src = true;
|
this.log.src = true;
|
||||||
this.showErrStack = true;
|
this.showErrStack = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!opts.url && opts.J) {
|
||||||
|
opts.url = format('https://%s.api.joyent.com', opts.J);
|
||||||
|
}
|
||||||
|
this.envProfile = mod_config.loadEnvProfile(opts);
|
||||||
|
this.configDir = CONFIG_DIR;
|
||||||
|
|
||||||
this.__defineGetter__('tritonapi', function () {
|
this.__defineGetter__('tritonapi', function () {
|
||||||
if (self._tritonapi === undefined) {
|
if (self._triton === undefined) {
|
||||||
var userConfigPath = require('./config').DEFAULT_USER_CONFIG_PATH;
|
var config = mod_config.loadConfig({
|
||||||
var dir = path.dirname(userConfigPath);
|
configDir: self.configDir
|
||||||
var cacheDir = path.join(dir, 'cache');
|
});
|
||||||
|
self.log.trace({config: config}, 'loaded config');
|
||||||
if (!fs.existsSync(cacheDir)) {
|
var profileName = opts.profile || config.profile || 'env';
|
||||||
try {
|
var profile;
|
||||||
mkdirp.sync(cacheDir);
|
if (profileName === 'env') {
|
||||||
} catch (e) {
|
profile = self.envProfile;
|
||||||
log.info({err: e}, 'failed to make dir %s', cacheDir);
|
} else {
|
||||||
|
profile = mod_config.loadProfile({
|
||||||
|
configDir: self.configDir,
|
||||||
|
name: profileName
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
self.log.trace({profile: profile}, 'loaded profile');
|
||||||
|
|
||||||
// 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 --insecure not given, look at envvar(s) for that.
|
|
||||||
var specifiedInsecureOpt = opts._order.filter(
|
|
||||||
function (opt) { return opt.key === 'insecure'; }).length > 0;
|
|
||||||
if (!specifiedInsecureOpt && process.env.SDC_TESTING) {
|
|
||||||
envProfile.insecure = common.boolFromString(
|
|
||||||
process.env.SDC_TESTING,
|
|
||||||
false, '"SDC_TESTING" envvar');
|
|
||||||
}
|
|
||||||
if (opts.J) {
|
|
||||||
envProfile.url = format('https://%s.api.joyent.com', opts.J);
|
|
||||||
}
|
|
||||||
log.trace({envProfile: envProfile}, 'envProfile');
|
|
||||||
|
|
||||||
self._tritonapi = new TritonApi({
|
self._tritonapi = new TritonApi({
|
||||||
log: log,
|
log: self.log,
|
||||||
profileName: opts.profile,
|
profile: profile,
|
||||||
envProfile: envProfile,
|
config: config
|
||||||
configPath: userConfigPath,
|
|
||||||
cacheDir: cacheDir
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return self._tritonapi;
|
return self._tritonapi;
|
||||||
@ -237,7 +230,7 @@ CLI.prototype.init = function (opts, args, callback) {
|
|||||||
|
|
||||||
// Meta
|
// Meta
|
||||||
CLI.prototype.do_completion = require('./do_completion');
|
CLI.prototype.do_completion = require('./do_completion');
|
||||||
//CLI.prototype.do_profile = require('./do_profile');
|
CLI.prototype.do_profiles = require('./do_profiles');
|
||||||
|
|
||||||
// Other
|
// Other
|
||||||
CLI.prototype.do_account = require('./do_account');
|
CLI.prototype.do_account = require('./do_account');
|
||||||
@ -276,8 +269,6 @@ CLI.prototype.do_badger = require('./do_badger');
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//---- mainline
|
//---- mainline
|
||||||
|
|
||||||
function main(argv) {
|
function main(argv) {
|
||||||
@ -324,6 +315,7 @@ function main(argv) {
|
|||||||
//---- exports
|
//---- exports
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
CONFIG_DIR: CONFIG_DIR,
|
||||||
CLI: CLI,
|
CLI: CLI,
|
||||||
main: main
|
main: main
|
||||||
};
|
};
|
||||||
|
242
lib/config.js
242
lib/config.js
@ -8,49 +8,107 @@
|
|||||||
* 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 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 DEFAULT_USER_CONFIG_PATH = path.resolve(process.env.HOME,
|
|
||||||
'.triton', 'config.json');
|
|
||||||
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
|
||||||
|
|
||||||
|
// TODO: improve this validation
|
||||||
|
function _validateProfile(profile) {
|
||||||
|
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');
|
||||||
|
// TODO: error on extraneous params
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function configPathFromDir(configDir) {
|
||||||
|
return path.resolve(configDir, 'config.json');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// --- Config
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load the Triton client 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
|
||||||
* etc/defaults.json) and the "user" config (at ~/.triton/config.json if it
|
* etc/defaults.json) and the "user" config (at "$configDir/config.json",
|
||||||
* exists).
|
* typically "~/.triton/config.json", if it exists).
|
||||||
*
|
*
|
||||||
* This includes some internal data on keys with a leading underscore.
|
* This includes some internal data on keys with a leading underscore:
|
||||||
|
* _defaults the defaults.json object
|
||||||
|
* _user the "user" config.json object
|
||||||
|
* _configDir the user config dir
|
||||||
|
*
|
||||||
|
* @returns {Object} The loaded config.
|
||||||
*/
|
*/
|
||||||
function loadConfigSync(opts) {
|
function loadConfig(opts) {
|
||||||
assert.object(opts, 'opts');
|
assert.object(opts, 'opts');
|
||||||
assert.string(opts.configPath, 'opts.configPath');
|
assert.string(opts.configDir, 'opts.configDir');
|
||||||
assert.optionalObject(opts.envProfile, 'opts.envProfile');
|
|
||||||
|
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);
|
||||||
var config = JSON.parse(c);
|
var config = JSON.parse(c);
|
||||||
if (opts.configPath && fs.existsSync(opts.configPath)) {
|
if (fs.existsSync(configPath)) {
|
||||||
c = fs.readFileSync(opts.configPath, 'utf8');
|
c = fs.readFileSync(configPath, 'utf8');
|
||||||
var _user = JSON.parse(c);
|
var _user = JSON.parse(c);
|
||||||
var userConfig = JSON.parse(c);
|
var userConfig = JSON.parse(c);
|
||||||
if (typeof (userConfig) !== 'object' || Array.isArray(userConfig)) {
|
if (typeof (userConfig) !== 'object' || Array.isArray(userConfig)) {
|
||||||
throw new errors.ConfigError(
|
throw new errors.ConfigError(
|
||||||
format('"%s" is not an object', opts.configPath));
|
format('"%s" is not an object', configPath));
|
||||||
}
|
}
|
||||||
// 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];
|
||||||
@ -66,24 +124,160 @@ function loadConfigSync(opts) {
|
|||||||
config._user = _user;
|
config._user = _user;
|
||||||
}
|
}
|
||||||
config._defaults = _defaults;
|
config._defaults = _defaults;
|
||||||
|
config._configDir = opts.configDir;
|
||||||
// Add 'env' profile, if given.
|
|
||||||
if (opts.envProfile) {
|
|
||||||
if (!config.profiles) {
|
|
||||||
config.profiles = [];
|
|
||||||
}
|
|
||||||
config.profiles.push(opts.envProfile);
|
|
||||||
}
|
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
* values from envvars. *Most* of that is done already via the
|
||||||
|
* `opts` dashdash Options object.
|
||||||
|
*
|
||||||
|
* @returns {Object} The 'env' profile.
|
||||||
|
*/
|
||||||
|
function loadEnvProfile(opts) {
|
||||||
|
// 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 --insecure not given, look at envvar(s) for that.
|
||||||
|
var specifiedInsecureOpt = opts._order.filter(
|
||||||
|
function (opt) { return opt.key === 'insecure'; }).length > 0;
|
||||||
|
if (!specifiedInsecureOpt && process.env.SDC_TESTING) {
|
||||||
|
envProfile.insecure = common.boolFromString(
|
||||||
|
process.env.SDC_TESTING,
|
||||||
|
false, '"SDC_TESTING" envvar');
|
||||||
|
}
|
||||||
|
|
||||||
|
_validateProfile(envProfile);
|
||||||
|
|
||||||
|
return envProfile;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _profileFromPath(profilePath, name) {
|
||||||
|
if (! fs.existsSync(profilePath)) {
|
||||||
|
throw new errors.ConfigError('no such profile: ' + name);
|
||||||
|
}
|
||||||
|
var profile;
|
||||||
|
try {
|
||||||
|
profile = JSON.parse(fs.readFileSync(profilePath, 'utf8'));
|
||||||
|
} catch (e) {
|
||||||
|
throw new errors.ConfigError(e, format(
|
||||||
|
'error in "%s" profile: %s: %s', name,
|
||||||
|
profilePath, e.message));
|
||||||
|
}
|
||||||
|
profile.name = name;
|
||||||
|
|
||||||
|
_validateProfile(profile);
|
||||||
|
|
||||||
|
return profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadProfile(opts) {
|
||||||
|
assert.string(opts.configDir, 'opts.configDir');
|
||||||
|
assert.string(opts.name, 'opts.name');
|
||||||
|
|
||||||
|
var profilePath = path.resolve(opts.configDir, 'profiles.d',
|
||||||
|
opts.name + '.json');
|
||||||
|
return _profileFromPath(profilePath, opts.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadAllProfiles(opts) {
|
||||||
|
assert.string(opts.configDir, 'opts.configDir');
|
||||||
|
assert.object(opts.log, 'opts.log');
|
||||||
|
|
||||||
|
var profiles = [];
|
||||||
|
var files = glob.sync(path.resolve(opts.configDir,
|
||||||
|
'profiles.d', '*.json'));
|
||||||
|
for (var i = 0; i < files.length; i++) {
|
||||||
|
var file = files[i];
|
||||||
|
var name = path.basename(file).slice(0, - path.extname(file).length);
|
||||||
|
if (name.toLowerCase() === 'env') {
|
||||||
|
// Skip the special 'env'.
|
||||||
|
opts.log.debug('skip reserved name "env" profile: %s', file);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
profiles.push(_profileFromPath(file, name));
|
||||||
|
} catch (e) {
|
||||||
|
opts.log.warn({err: e, profilePath: file},
|
||||||
|
'error loading profile; skipping');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return profiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//---- exports
|
//---- exports
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
DEFAULT_USER_CONFIG_PATH: DEFAULT_USER_CONFIG_PATH,
|
loadConfig: loadConfig,
|
||||||
loadConfigSync: loadConfigSync
|
setConfigVar: setConfigVar,
|
||||||
|
loadEnvProfile: loadEnvProfile,
|
||||||
|
loadProfile: loadProfile,
|
||||||
|
loadAllProfiles: loadAllProfiles
|
||||||
};
|
};
|
||||||
// vim: set softtabstop=4 shiftwidth=4:
|
// vim: set softtabstop=4 shiftwidth=4:
|
||||||
|
@ -1,66 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Copyright 2015 Joyent, Inc.
|
|
||||||
*
|
|
||||||
* `triton profile ...`
|
|
||||||
*/
|
|
||||||
|
|
||||||
var common = require('./common');
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function do_profile(subcmd, opts, args, callback) {
|
|
||||||
if (opts.help) {
|
|
||||||
this.do_help('help', {}, [subcmd], callback);
|
|
||||||
return;
|
|
||||||
} else if (args.length > 1) {
|
|
||||||
return callback(new Error('too many args: ' + args));
|
|
||||||
}
|
|
||||||
|
|
||||||
var profs = common.deepObjCopy(this.sdc.profiles);
|
|
||||||
var currProfileName = this.sdc.profile.name;
|
|
||||||
for (var i = 0; i < profs.length; i++) {
|
|
||||||
profs[i].curr = (profs[i].name === currProfileName ? '*' : ' ');
|
|
||||||
profs[i].dcs = (profs[i].dcs ? profs[i].dcs : ['all'])
|
|
||||||
.join(',');
|
|
||||||
}
|
|
||||||
if (opts.json) {
|
|
||||||
common.jsonStream(profs);
|
|
||||||
} else {
|
|
||||||
common.tabulate(profs, {
|
|
||||||
columns: 'curr,name,dcs,user,keyId',
|
|
||||||
sort: 'name,user',
|
|
||||||
validFields: 'curr,name,dcs,user,keyId'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
|
|
||||||
do_profile.options = [
|
|
||||||
{
|
|
||||||
names: ['help', 'h'],
|
|
||||||
type: 'bool',
|
|
||||||
help: 'Show this help.'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
names: ['json', 'j'],
|
|
||||||
type: 'bool',
|
|
||||||
help: 'JSON output.'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
do_profile.help = (
|
|
||||||
'Create, update or inpect joyent CLI profiles.\n'
|
|
||||||
+ '\n'
|
|
||||||
+ 'Usage:\n'
|
|
||||||
+ ' {{name}} profile\n'
|
|
||||||
+ '\n'
|
|
||||||
+ '{{options}}'
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = do_profile;
|
|
220
lib/do_profiles.js
Normal file
220
lib/do_profiles.js
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015 Joyent Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* `triton profiles ...`
|
||||||
|
*/
|
||||||
|
|
||||||
|
var common = require('./common');
|
||||||
|
var errors = require('./errors');
|
||||||
|
var mod_config = require('./config');
|
||||||
|
var tabula = require('tabula');
|
||||||
|
|
||||||
|
|
||||||
|
var sortDefault = 'name';
|
||||||
|
var columnsDefault = 'name,curr,account,url';
|
||||||
|
var columnsDefaultLong = 'name,curr,account,url,insecure,keyId';
|
||||||
|
|
||||||
|
function _listProfiles(_, opts, cb) {
|
||||||
|
var columns = columnsDefault;
|
||||||
|
if (opts.o) {
|
||||||
|
columns = opts.o;
|
||||||
|
} else if (opts.long) {
|
||||||
|
columns = columnsDefaultLong;
|
||||||
|
}
|
||||||
|
columns = columns.split(',');
|
||||||
|
|
||||||
|
var sort = opts.s.split(',');
|
||||||
|
|
||||||
|
// Load all the profiles. "env" is a special one managed by the CLI.
|
||||||
|
var profiles;
|
||||||
|
try {
|
||||||
|
profiles = mod_config.loadAllProfiles({
|
||||||
|
configDir: this.tritonapi.config._configDir,
|
||||||
|
log: this.log
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
return cb(e);
|
||||||
|
}
|
||||||
|
profiles.push(this.envProfile);
|
||||||
|
|
||||||
|
// Display.
|
||||||
|
var i;
|
||||||
|
if (opts.json) {
|
||||||
|
for (i = 0; i < profiles.length; i++) {
|
||||||
|
profiles[i].curr = (profiles[i].name ===
|
||||||
|
this.tritonapi.profile.name);
|
||||||
|
}
|
||||||
|
common.jsonStream(profiles);
|
||||||
|
} else {
|
||||||
|
for (i = 0; i < profiles.length; i++) {
|
||||||
|
profiles[i].curr = (profiles[i].name === this.tritonapi.profile.name
|
||||||
|
? '*' : '');
|
||||||
|
}
|
||||||
|
tabula(profiles, {
|
||||||
|
skipHeader: opts.H,
|
||||||
|
columns: columns,
|
||||||
|
sort: sort
|
||||||
|
});
|
||||||
|
}
|
||||||
|
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 = [
|
||||||
|
{
|
||||||
|
names: ['help', 'h'],
|
||||||
|
type: 'bool',
|
||||||
|
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({
|
||||||
|
includeLong: true,
|
||||||
|
sortDefault: sortDefault
|
||||||
|
}));
|
||||||
|
do_profiles.help = [
|
||||||
|
'List and update `triton` CLI profiles.',
|
||||||
|
'',
|
||||||
|
'A profile is a configured Triton CloudAPI endpoint. I.e. the',
|
||||||
|
'url, account, key, etc. information required to call a CloudAPI.',
|
||||||
|
'You can then switch between profiles with `triton -p PROFILE`',
|
||||||
|
'or the TRITON_PROFILE environment variable.',
|
||||||
|
'',
|
||||||
|
'The "CURR" column indicates which profile is the current one.',
|
||||||
|
'',
|
||||||
|
'Usage:',
|
||||||
|
' {{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}}'
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
do_profiles.hidden = true; // TODO: until -a,-e,-d are implemented
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = do_profiles;
|
@ -14,7 +14,6 @@ var util = require('util'),
|
|||||||
format = util.format;
|
format = util.format;
|
||||||
var assert = require('assert-plus');
|
var assert = require('assert-plus');
|
||||||
var verror = require('verror'),
|
var verror = require('verror'),
|
||||||
WError = verror.WError,
|
|
||||||
VError = verror.VError;
|
VError = verror.VError;
|
||||||
|
|
||||||
|
|
||||||
@ -36,7 +35,7 @@ function TritonError(options) {
|
|||||||
var args = [];
|
var args = [];
|
||||||
if (options.cause) args.push(options.cause);
|
if (options.cause) args.push(options.cause);
|
||||||
args.push(options.message);
|
args.push(options.message);
|
||||||
WError.apply(this, args);
|
VError.apply(this, args);
|
||||||
|
|
||||||
var extra = Object.keys(options).filter(
|
var extra = Object.keys(options).filter(
|
||||||
function (k) { return ['cause', 'message'].indexOf(k) === -1; });
|
function (k) { return ['cause', 'message'].indexOf(k) === -1; });
|
||||||
|
201
lib/tritonapi.js
201
lib/tritonapi.js
@ -38,50 +38,45 @@ var loadConfigSync = require('./config').loadConfigSync;
|
|||||||
/**
|
/**
|
||||||
* Create a TritonApi client.
|
* Create a TritonApi client.
|
||||||
*
|
*
|
||||||
* @param options {Object}
|
* @param opts {Object}
|
||||||
* - log {Bunyan Logger}
|
* - log {Bunyan Logger}
|
||||||
* - 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 TritonApi(options) {
|
function TritonApi(opts) {
|
||||||
assert.object(options, 'options');
|
assert.object(opts, 'opts');
|
||||||
assert.object(options.log, 'options.log');
|
assert.object(opts.log, 'opts.log');
|
||||||
assert.optionalString(options.profileName, 'options.profileName');
|
assert.object(opts.profile, 'opts.profile');
|
||||||
assert.optionalString(options.configPath, 'options.configPath');
|
assert.object(opts.config, 'opts.config');
|
||||||
assert.optionalString(options.cacheDir, 'options.cacheDir');
|
|
||||||
assert.optionalObject(options.envProfile, 'options.envProfile');
|
this.profile = opts.profile;
|
||||||
|
this.config = opts.config;
|
||||||
|
|
||||||
// Make sure a given bunyan logger has reasonable client_re[qs] serializers.
|
// Make sure a given bunyan logger has reasonable client_re[qs] serializers.
|
||||||
// Note: This was fixed in restify, then broken again in
|
// Note: This was fixed in restify, then broken again in
|
||||||
// https://github.com/mcavage/node-restify/pull/501
|
// https://github.com/mcavage/node-restify/pull/501
|
||||||
if (options.log.serializers &&
|
if (opts.log.serializers &&
|
||||||
(!options.log.serializers.client_req ||
|
(!opts.log.serializers.client_req ||
|
||||||
!options.log.serializers.client_req)) {
|
!opts.log.serializers.client_req)) {
|
||||||
this.log = options.log.child({
|
this.log = opts.log.child({
|
||||||
serializers: restifyBunyanSerializers
|
serializers: restifyBunyanSerializers
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.log = options.log;
|
this.log = opts.log;
|
||||||
}
|
}
|
||||||
this.config = loadConfigSync({
|
|
||||||
configPath: options.configPath,
|
|
||||||
envProfile: options.envProfile
|
|
||||||
});
|
|
||||||
this.profiles = this.config.profiles;
|
|
||||||
this.profile = this.getProfile(
|
|
||||||
options.profileName || this.config.defaultProfile);
|
|
||||||
this.log.trace({profile: this.profile}, 'profile data');
|
|
||||||
|
|
||||||
if (options.cacheDir !== undefined) {
|
if (this.config.cacheDir) {
|
||||||
var slug = common.slug(this.profile);
|
this.cacheDir = path.resolve(this.config._configDir,
|
||||||
this.cacheDir = path.join(options.cacheDir, slug);
|
this.config.cacheDir,
|
||||||
|
common.slug(this.profile));
|
||||||
this.log.trace({cacheDir: this.cacheDir}, 'cache dir');
|
this.log.trace({cacheDir: this.cacheDir}, 'cache dir');
|
||||||
|
// TODO perhaps move this to an async .init()
|
||||||
|
if (!fs.existsSync(this.cacheDir)) {
|
||||||
try {
|
try {
|
||||||
mkdirp.sync(this.cacheDir);
|
mkdirp.sync(this.cacheDir);
|
||||||
} catch (e) {}
|
} catch (e) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.cloudapi = this._cloudapiFromProfile(this.profile);
|
this.cloudapi = this._cloudapiFromProfile(this.profile);
|
||||||
@ -89,17 +84,9 @@ function TritonApi(options) {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
TritonApi.prototype.getProfile = function getProfile(name) {
|
|
||||||
for (var i = 0; i < this.profiles.length; i++) {
|
|
||||||
if (this.profiles[i].name === name) {
|
|
||||||
return this.profiles[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
TritonApi.prototype._cloudapiFromProfile =
|
TritonApi.prototype._cloudapiFromProfile =
|
||||||
function _cloudapiFromProfile(profile) {
|
function _cloudapiFromProfile(profile)
|
||||||
|
{
|
||||||
assert.object(profile, 'profile');
|
assert.object(profile, 'profile');
|
||||||
assert.string(profile.account, 'profile.account');
|
assert.string(profile.account, 'profile.account');
|
||||||
assert.string(profile.keyId, 'profile.keyId');
|
assert.string(profile.keyId, 'profile.keyId');
|
||||||
@ -133,6 +120,56 @@ TritonApi.prototype._cloudapiFromProfile =
|
|||||||
return client;
|
return client;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
TritonApi.prototype._cachePutJson = function _cachePutJson(key, obj, cb) {
|
||||||
|
var self = this;
|
||||||
|
assert.string(this.cacheDir, 'this.cacheDir');
|
||||||
|
assert.string(key, 'key');
|
||||||
|
assert.object(obj, 'obj');
|
||||||
|
assert.func(cb, 'cb');
|
||||||
|
|
||||||
|
var keyPath = path.resolve(this.cacheDir, key);
|
||||||
|
var data = JSON.stringify(obj);
|
||||||
|
fs.writeFile(keyPath, data, {encoding: 'utf8'}, function (err) {
|
||||||
|
if (err) {
|
||||||
|
self.log.info({err: err, keyPath: keyPath}, 'error caching');
|
||||||
|
}
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
TritonApi.prototype._cacheGetJson = function _cacheGetJson(key, cb) {
|
||||||
|
var self = this;
|
||||||
|
assert.string(this.cacheDir, 'this.cacheDir');
|
||||||
|
assert.string(key, 'key');
|
||||||
|
assert.func(cb, 'cb');
|
||||||
|
|
||||||
|
var keyPath = path.resolve(this.cacheDir, key);
|
||||||
|
fs.exists(keyPath, function (exists) {
|
||||||
|
if (!exists) {
|
||||||
|
self.log.trace({keyPath: keyPath}, 'cache file does not exist');
|
||||||
|
return cb();
|
||||||
|
}
|
||||||
|
fs.readFile(keyPath, 'utf8', function (err, data) {
|
||||||
|
if (err) {
|
||||||
|
self.log.warn({err: err, keyPath: keyPath},
|
||||||
|
'error reading cache file');
|
||||||
|
return cb();
|
||||||
|
}
|
||||||
|
var obj;
|
||||||
|
try {
|
||||||
|
obj = JSON.parse(data);
|
||||||
|
} catch (dataErr) {
|
||||||
|
self.log.warn({err: dataErr, keyPath: keyPath},
|
||||||
|
'error parsing JSON cache file');
|
||||||
|
return cb();
|
||||||
|
}
|
||||||
|
cb(null, obj);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* cloudapi listImages wrapper with optional caching
|
* cloudapi listImages wrapper with optional caching
|
||||||
*/
|
*/
|
||||||
@ -146,69 +183,57 @@ TritonApi.prototype.listImages = function listImages(opts, cb) {
|
|||||||
assert.optionalBool(opts.useCache, 'opts.useCache');
|
assert.optionalBool(opts.useCache, 'opts.useCache');
|
||||||
assert.func(cb, 'cb');
|
assert.func(cb, 'cb');
|
||||||
|
|
||||||
var cacheFile;
|
var cacheKey = 'images.json';
|
||||||
if (self.cacheDir)
|
var cached;
|
||||||
cacheFile = path.join(self.cacheDir, 'images.json');
|
var fetched;
|
||||||
|
var res;
|
||||||
|
|
||||||
if (opts.useCache && !cacheFile) {
|
vasync.pipeline({funcs: [
|
||||||
cb(new Error('opts.useCache set but no cacheDir found'));
|
function tryCache(_, next) {
|
||||||
return;
|
if (!opts.useCache) {
|
||||||
|
return next();
|
||||||
}
|
}
|
||||||
|
self._cacheGetJson(cacheKey, function (err, cached_) {
|
||||||
// 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) {
|
|
||||||
self.log.debug({file: cacheFile}, 'reading images cache file');
|
|
||||||
fs.readFile(cacheFile, 'utf8', function (err, out) {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
self.log.warn({err: err, cacheFile: cacheFile},
|
return next(err);
|
||||||
'failed to read cache file');
|
|
||||||
fetch();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
var data;
|
cached = cached_;
|
||||||
try {
|
next();
|
||||||
data = JSON.parse(out);
|
});
|
||||||
} catch (e) {
|
},
|
||||||
self.log.warn({err: e, cacheFile: cacheFile},
|
|
||||||
'failed to parse cache file');
|
function listImgs(_, next) {
|
||||||
fetch();
|
if (cached) {
|
||||||
return;
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.log.info('calling back with images cache data');
|
self.cloudapi.listImages(opts, function (err, imgs, res_) {
|
||||||
cb(null, data, {});
|
if (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
fetched = imgs;
|
||||||
|
res = res_;
|
||||||
|
next();
|
||||||
});
|
});
|
||||||
return;
|
},
|
||||||
|
|
||||||
|
function cacheFetched(_, next) {
|
||||||
|
if (!fetched) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
self._cachePutJson(cacheKey, fetched, next);
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch();
|
]}, function (err) {
|
||||||
function fetch() {
|
if (err) {
|
||||||
self.cloudapi.listImages(opts, function (err, imgs, res) {
|
cb(err, null, res);
|
||||||
if (!err && self.cacheDir) {
|
|
||||||
// cache the results
|
|
||||||
var data = JSON.stringify(imgs);
|
|
||||||
fs.writeFile(cacheFile, data, {encoding: 'utf8'},
|
|
||||||
function (err2) {
|
|
||||||
if (err2) {
|
|
||||||
self.log.info({err: err2},
|
|
||||||
'error caching images results');
|
|
||||||
}
|
|
||||||
self.log.trace({file: cacheFile}, 'images cache updated');
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
done();
|
cb(null, fetched || cached, res);
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function done() {
|
|
||||||
cb(err, imgs, res);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get an image by ID, exact name, or short ID, in that order.
|
* Get an image by ID, exact name, or short ID, in that order.
|
||||||
*
|
*
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
"cmdln": "3.3.0",
|
"cmdln": "3.3.0",
|
||||||
"dashdash": "1.10.0",
|
"dashdash": "1.10.0",
|
||||||
"extsprintf": "1.0.2",
|
"extsprintf": "1.0.2",
|
||||||
|
"glob": "5.0.14",
|
||||||
"lomstream": "1.1.0",
|
"lomstream": "1.1.0",
|
||||||
"mkdirp": "0.5.1",
|
"mkdirp": "0.5.1",
|
||||||
"node-uuid": "1.4.3",
|
"node-uuid": "1.4.3",
|
||||||
|
Reference in New Issue
Block a user