config, cache images
This commit is contained in:
parent
d6ac9fed33
commit
a5213658fa
20
lib/cli.js
20
lib/cli.js
@ -14,6 +14,7 @@ var cmdln = require('cmdln'),
|
|||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
var util = require('util'),
|
var util = require('util'),
|
||||||
format = util.format;
|
format = util.format;
|
||||||
|
var path = require('path');
|
||||||
var vasync = require('vasync');
|
var vasync = require('vasync');
|
||||||
|
|
||||||
var common = require('./common');
|
var common = require('./common');
|
||||||
@ -99,7 +100,24 @@ CLI.prototype.init = function (opts, args, callback) {
|
|||||||
|
|
||||||
this.__defineGetter__('triton', function () {
|
this.__defineGetter__('triton', function () {
|
||||||
if (self._triton === undefined) {
|
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;
|
return self._triton;
|
||||||
});
|
});
|
||||||
|
@ -13,7 +13,7 @@ var common = require('./common');
|
|||||||
var errors = require('./errors');
|
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 DEFAULTS_PATH = path.resolve(__dirname, '..', 'etc', 'defaults.json');
|
||||||
var OVERRIDE_KEYS = []; // config object keys to do a one-level deep override
|
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.
|
* This includes some internal data on keys with a leading underscore.
|
||||||
*/
|
*/
|
||||||
function loadConfigSync() {
|
function loadConfigSync(configPath) {
|
||||||
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 (fs.existsSync(CONFIG_PATH)) {
|
if (configPath && fs.existsSync(configPath)) {
|
||||||
c = fs.readFileSync(CONFIG_PATH, '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(
|
||||||
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
|
// These special keys are merged into the key of the same name in the
|
||||||
// base "defaults.json".
|
// base "defaults.json".
|
||||||
@ -88,7 +88,7 @@ function updateUserConfigSync(config, updates) {
|
|||||||
//---- exports
|
//---- exports
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
CONFIG_PATH: CONFIG_PATH,
|
DEFAULT_USER_CONFIG_PATH: DEFAULT_USER_CONFIG_PATH,
|
||||||
loadConfigSync: loadConfigSync
|
loadConfigSync: loadConfigSync
|
||||||
};
|
};
|
||||||
// vim: set softtabstop=4 shiftwidth=4:
|
// vim: set softtabstop=4 shiftwidth=4:
|
||||||
|
@ -41,7 +41,7 @@ function do_images(subcmd, opts, args, callback) {
|
|||||||
listOpts.state = 'all';
|
listOpts.state = 'all';
|
||||||
}
|
}
|
||||||
|
|
||||||
this.triton.cloudapi.listImages(listOpts, function onRes(err, imgs, res) {
|
this.triton.listImages(listOpts, function onRes(err, imgs, res) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
* `triton instances ...`
|
* `triton instances ...`
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
var f = require('util').format;
|
||||||
|
|
||||||
var tabula = require('tabula');
|
var tabula = require('tabula');
|
||||||
|
|
||||||
var common = require('./common');
|
var common = require('./common');
|
||||||
@ -33,6 +35,7 @@ var validFields = [
|
|||||||
'updated',
|
'updated',
|
||||||
'package',
|
'package',
|
||||||
'image',
|
'image',
|
||||||
|
'img',
|
||||||
'ago'
|
'ago'
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -56,17 +59,46 @@ function do_instances(subcmd, opts, args, callback) {
|
|||||||
return;
|
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) {
|
if (err) {
|
||||||
callback(err);
|
callback(err);
|
||||||
return;
|
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
|
// add extra fields for nice output
|
||||||
var now = new Date();
|
var now = new Date();
|
||||||
machines.forEach(function (machine) {
|
machines.forEach(function (machine) {
|
||||||
var created = new Date(machine.created);
|
var created = new Date(machine.created);
|
||||||
machine.ago = common.longAgo(created, now);
|
machine.ago = common.longAgo(created, now);
|
||||||
|
machine.img = imgmap[machine.image] || machine.image;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (opts.json) {
|
if (opts.json) {
|
||||||
@ -80,7 +112,7 @@ function do_instances(subcmd, opts, args, callback) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
callback();
|
callback();
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
do_instances.options = [
|
do_instances.options = [
|
||||||
@ -97,7 +129,7 @@ do_instances.options = [
|
|||||||
{
|
{
|
||||||
names: ['o'],
|
names: ['o'],
|
||||||
type: 'string',
|
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.',
|
help: 'Specify fields (columns) to output.',
|
||||||
helpArg: 'field1,...'
|
helpArg: 'field1,...'
|
||||||
},
|
},
|
||||||
|
@ -36,6 +36,8 @@ function Triton(options) {
|
|||||||
assert.object(options, 'options');
|
assert.object(options, 'options');
|
||||||
assert.object(options.log, 'options.log');
|
assert.object(options.log, 'options.log');
|
||||||
assert.optionalString(options.profile, 'options.profile');
|
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.
|
// 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
|
||||||
@ -50,11 +52,12 @@ function Triton(options) {
|
|||||||
} else {
|
} else {
|
||||||
this.log = options.log;
|
this.log = options.log;
|
||||||
}
|
}
|
||||||
this.config = loadConfigSync();
|
this.config = loadConfigSync(options.config);
|
||||||
this.profiles = this.config.profiles;
|
this.profiles = this.config.profiles;
|
||||||
this.profile = this.getProfile(
|
this.profile = this.getProfile(
|
||||||
options.profile || this.config.defaultProfile);
|
options.profile || this.config.defaultProfile);
|
||||||
this.log.trace({profile: this.profile}, 'profile data');
|
this.log.trace({profile: this.profile}, 'profile data');
|
||||||
|
this.cachedir = options.cachedir;
|
||||||
|
|
||||||
this.cloudapi = this._cloudapiFromProfile(this.profile);
|
this.cloudapi = this._cloudapiFromProfile(this.profile);
|
||||||
}
|
}
|
||||||
@ -104,6 +107,72 @@ Triton.prototype._cloudapiFromProfile = function _cloudapiFromProfile(profile) {
|
|||||||
return client;
|
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.
|
* Get an image by ID, exact name, or short ID, in that order.
|
||||||
|
Reference in New Issue
Block a user