config, cache images

This commit is contained in:
Dave Eddy 2015-08-26 12:59:12 -04:00
parent d6ac9fed33
commit a5213658fa
5 changed files with 131 additions and 12 deletions

View File

@ -14,6 +14,7 @@ var cmdln = require('cmdln'),
var fs = require('fs');
var util = require('util'),
format = util.format;
var path = require('path');
var vasync = require('vasync');
var common = require('./common');
@ -99,7 +100,24 @@ CLI.prototype.init = function (opts, args, callback) {
this.__defineGetter__('triton', function () {
if (self._triton === undefined) {
self._triton = new Triton({log: log, profile: opts.profile});
var userConfigPath = require('./config').DEFAULT_USER_CONFIG_PATH;
var dir = path.dirname(userConfigPath);
var cacheDir = path.join(dir, 'cache');
[dir, cacheDir].forEach(function (d) {
try {
fs.mkdirSync(d);
} catch (e) {
log.info({err: e}, 'failed to make dir %s', d);
}
});
self._triton = new Triton({
log: log,
profile: opts.profile,
config: userConfigPath,
cachedir: cacheDir
});
}
return self._triton;
});

View File

@ -13,7 +13,7 @@ var common = require('./common');
var errors = require('./errors');
var CONFIG_PATH = path.resolve(process.env.HOME, '.triton', 'config.json');
var DEFAULT_USER_CONFIG_PATH = path.resolve(process.env.HOME, '.triton', 'config.json');
var DEFAULTS_PATH = path.resolve(__dirname, '..', 'etc', 'defaults.json');
var OVERRIDE_KEYS = []; // config object keys to do a one-level deep override
@ -24,17 +24,17 @@ var OVERRIDE_KEYS = []; // config object keys to do a one-level deep override
*
* This includes some internal data on keys with a leading underscore.
*/
function loadConfigSync() {
function loadConfigSync(configPath) {
var c = fs.readFileSync(DEFAULTS_PATH, 'utf8');
var _defaults = JSON.parse(c);
var config = JSON.parse(c);
if (fs.existsSync(CONFIG_PATH)) {
c = fs.readFileSync(CONFIG_PATH, 'utf8');
if (configPath && fs.existsSync(configPath)) {
c = fs.readFileSync(configPath, 'utf8');
var _user = JSON.parse(c);
var userConfig = JSON.parse(c);
if (typeof(userConfig) !== 'object' || Array.isArray(userConfig)) {
throw new errors.ConfigError(
sprintf('"%s" is not an object', CONFIG_PATH));
sprintf('"%s" is not an object', configPath));
}
// These special keys are merged into the key of the same name in the
// base "defaults.json".
@ -88,7 +88,7 @@ function updateUserConfigSync(config, updates) {
//---- exports
module.exports = {
CONFIG_PATH: CONFIG_PATH,
DEFAULT_USER_CONFIG_PATH: DEFAULT_USER_CONFIG_PATH,
loadConfigSync: loadConfigSync
};
// vim: set softtabstop=4 shiftwidth=4:

View File

@ -41,7 +41,7 @@ function do_images(subcmd, opts, args, callback) {
listOpts.state = 'all';
}
this.triton.cloudapi.listImages(listOpts, function onRes(err, imgs, res) {
this.triton.listImages(listOpts, function onRes(err, imgs, res) {
if (err) {
return callback(err);
}

View File

@ -4,6 +4,8 @@
* `triton instances ...`
*/
var f = require('util').format;
var tabula = require('tabula');
var common = require('./common');
@ -33,6 +35,7 @@ var validFields = [
'updated',
'package',
'image',
'img',
'ago'
];
@ -56,17 +59,46 @@ function do_instances(subcmd, opts, args, callback) {
return;
}
this.triton.cloudapi.listMachines(listOpts, function (err, machines) {
var i = 0;
i++;
var images;
this.triton.listImages({usecache: true}, function (err, _images) {
if (err) {
callback(err);
return;
}
images = _images;
done();
});
i++;
var machines;
this.triton.cloudapi.listMachines(listOpts, function (err, _machines) {
if (err) {
callback(err);
return;
}
machines = _machines;
done();
});
function done() {
if (--i > 0)
return;
// map "uuid" => "image_name"
var imgmap = {};
images.forEach(function (image) {
imgmap[image.id] = f('%s@%s', image.name, image.version);
});
// add extra fields for nice output
var now = new Date();
machines.forEach(function (machine) {
var created = new Date(machine.created);
machine.ago = common.longAgo(created, now);
machine.img = imgmap[machine.image] || machine.image;
});
if (opts.json) {
@ -80,7 +112,7 @@ function do_instances(subcmd, opts, args, callback) {
});
}
callback();
});
}
}
do_instances.options = [
@ -97,7 +129,7 @@ do_instances.options = [
{
names: ['o'],
type: 'string',
default: 'id,name,state,type,image,memory,disk,ago',
default: 'id,name,state,type,img,memory,disk,ago',
help: 'Specify fields (columns) to output.',
helpArg: 'field1,...'
},

View File

@ -36,6 +36,8 @@ function Triton(options) {
assert.object(options, 'options');
assert.object(options.log, 'options.log');
assert.optionalString(options.profile, 'options.profile');
assert.optionalString(options.config, 'options.config');
assert.optionalString(options.cachedir, 'options.cachedir');
// Make sure a given bunyan logger has reasonable client_re[qs] serializers.
// Note: This was fixed in restify, then broken again in
@ -50,11 +52,12 @@ function Triton(options) {
} else {
this.log = options.log;
}
this.config = loadConfigSync();
this.config = loadConfigSync(options.config);
this.profiles = this.config.profiles;
this.profile = this.getProfile(
options.profile || this.config.defaultProfile);
this.log.trace({profile: this.profile}, 'profile data');
this.cachedir = options.cachedir;
this.cloudapi = this._cloudapiFromProfile(this.profile);
}
@ -104,6 +107,72 @@ Triton.prototype._cloudapiFromProfile = function _cloudapiFromProfile(profile) {
return client;
};
/**
* cloudapi listImages wrapper with optional caching
*/
Triton.prototype.listImages = function listImages(opts, cb) {
var self = this;
if (cb === undefined) {
cb = opts;
opts = {};
}
assert.object(opts, 'opts');
assert.func(cb, 'cb');
var cachefile;
if (self.cachedir)
cachefile = path.join(self.cachedir, 'images.json');
if (opts.usecache && !cachefile) {
cb(new Error('opts.usecache set but no cachedir found'));
return;
}
// try to read the cache if the user wants it
// if this fails for any reason we fallback to hitting the cloudapi
if (opts.usecache) {
fs.readFile(cachefile, 'utf8', function (err, out) {
if (err) {
self.log.info({err: err}, 'failed to read cache file %s', cachefile);
fetch();
return;
}
var data;
try {
data = JSON.parse(out);
} catch (e) {
self.log.info({err: e}, 'failed to parse cache file %s', cachefile);
fetch();
return;
}
cb(null, data, {});
});
return;
}
fetch();
function fetch() {
self.cloudapi.listImages(function (err, imgs, res) {
if (!err && self.cachedir) {
// cache the results
var data = JSON.stringify(imgs);
fs.writeFile(cachefile, data, {encoding: 'utf8'}, function (err) {
if (err)
self.log.info({err: err}, 'error caching images results');
done();
});
} else {
done();
}
function done() {
cb(err, imgs, res);
}
});
}
};
/**
* Get an image by ID, exact name, or short ID, in that order.