This repository has been archived on 2020-01-20. You can view files and clone it, but cannot push or open issues or pull requests.
node-spearhead/lib/triton.js

265 lines
7.7 KiB
JavaScript
Raw Normal View History

2014-02-07 23:21:24 +02:00
/*
* Copyright (c) 2015, Joyent, Inc. All rights reserved.
2014-02-07 23:21:24 +02:00
*
* Core Triton client driver class.
2014-02-07 23:21:24 +02:00
*/
var p = console.log;
var assert = require('assert-plus');
var auth = require('smartdc-auth');
2014-02-08 10:15:26 +02:00
var EventEmitter = require('events').EventEmitter;
2014-02-07 23:21:24 +02:00
var fs = require('fs');
2015-07-26 08:45:20 +03:00
var once = require('once');
2014-02-07 23:21:24 +02:00
var path = require('path');
var restifyClients = require('restify-clients');
2015-07-26 08:45:20 +03:00
var sprintf = require('util').format;
2014-02-07 23:21:24 +02:00
var cloudapi = require('./cloudapi2');
2014-02-07 23:21:24 +02:00
var common = require('./common');
2015-07-26 08:45:20 +03:00
var errors = require('./errors');
2014-02-07 23:21:24 +02:00
var loadConfigSync = require('./config').loadConfigSync;
//---- Triton class
2014-02-07 23:21:24 +02:00
/**
* Create a Triton client.
2014-02-07 23:21:24 +02:00
*
* @param options {Object}
* - log {Bunyan Logger}
* - profile {String} Optional. Name of profile to use. Defaults to
* 'defaultProfile' in the config.
*/
function Triton(options) {
2014-02-07 23:21:24 +02:00
assert.object(options, 'options');
assert.object(options.log, 'options.log');
assert.optionalString(options.profile, 'options.profile');
// Make sure a given bunyan logger has reasonable client_re[qs] serializers.
// Note: This was fixed in restify, then broken again in
// https://github.com/mcavage/node-restify/pull/501
if (options.log.serializers &&
(!options.log.serializers.client_req ||
!options.log.serializers.client_req)) {
this.log = options.log.child({
// XXX cheating. restify-clients should export its 'bunyan'.
serializers: require('restify-clients/lib/helpers/bunyan').serializers
});
} else {
this.log = options.log;
}
2014-02-07 23:21:24 +02:00
this.config = loadConfigSync();
this.profiles = this.config.profiles;
this.profile = this.getProfile(
options.profile || this.config.defaultProfile);
this.log.trace({profile: this.profile}, 'profile data');
this.cloudapi = this._cloudapiFromProfile(this.profile);
2014-02-07 23:21:24 +02:00
}
Triton.prototype.getProfile = function getProfile(name) {
2014-02-07 23:21:24 +02:00
for (var i = 0; i < this.profiles.length; i++) {
if (this.profiles[i].name === name) {
return this.profiles[i];
}
}
};
Triton.prototype._cloudapiFromProfile = function _cloudapiFromProfile(profile) {
assert.object(profile, 'profile');
assert.string(profile.account, 'profile.account');
assert.string(profile.keyId, 'profile.keyId');
assert.string(profile.url, 'profile.url');
assert.optionalString(profile.privKey, 'profile.privKey');
assert.optionalBool(profile.insecure, 'profile.insecure');
var rejectUnauthorized = (profile.insecure === undefined
? true : !profile.insecure);
var sign;
if (profile.privKey) {
sign = auth.privateKeySigner({
user: profile.account,
keyId: profile.keyId,
key: profile.privKey
2014-02-07 23:21:24 +02:00
});
} else {
sign = auth.cliSigner({
keyId: profile.keyId,
user: profile.account
2015-07-26 08:45:20 +03:00
});
}
var client = cloudapi.createClient({
url: profile.url,
user: profile.account,
version: '*',
rejectUnauthorized: rejectUnauthorized,
sign: sign,
log: this.log
2015-07-26 08:45:20 +03:00
});
return client;
2015-07-26 08:45:20 +03:00
};
/**
* Find a machine in the set of DCs for the current profile.
*
*
* @param {Object} options
* - {String} machine (required) The machine id.
* XXX support name matching, prefix, etc.
* @param {Function} callback `function (err, machine, dc)`
* Returns the machine object (as from cloudapi GetMachine) and the `dc`,
* e.g. "us-west-1".
*/
Triton.prototype.findMachine = function findMachine(options, callback) {
2015-07-26 08:45:20 +03:00
//XXX Eventually this can be cached for a *full* uuid. Arguably for a
// uuid prefix or machine alias match, it cannot be cached, because an
// ambiguous machine could have been added.
var self = this;
assert.object(options, 'options');
assert.string(options.machine, 'options.machine');
assert.func(callback, 'callback');
var callback = once(callback);
var errs = [];
var foundMachine;
var foundDc;
async.each(
self.dcs(),
function oneDc(dc, next) {
var client = self._clientFromDc(dc.name);
client.getMachine({id: options.machine}, function (err, machine) {
if (err) {
errs.push(err);
} else if (machine) {
foundMachine = machine;
foundDc = dc.name;
// Return early on an unambiguous match.
// XXX When other than full 'id' is supported, this isn't unambiguous.
callback(null, foundMachine, foundDc);
}
next();
});
},
function done(surpriseErr) {
if (surpriseErr) {
callback(surpriseErr);
} else if (foundMachine) {
callback(null, foundMachine, foundDc)
} else if (errs.length) {
callback(errs.length === 1 ?
errs[0] : new errors.MultiError(errs));
} else {
callback(new errors.InternalError(
'unexpected error finding machine ' + options.id));
}
}
);
};
2014-02-07 23:21:24 +02:00
/**
* List machines for the current profile.
*
2014-02-08 10:15:26 +02:00
* var res = this.jc.listMachines();
* res.on('data', function (dc, dcMachines) {
* //...
* });
* res.on('dcError', function (dc, dcErr) {
* //...
* });
* res.on('end', function () {
* //...
* });
*
2014-02-07 23:21:24 +02:00
* @param {Object} options Optional
*/
Triton.prototype.listMachines = function listMachines(options) {
2014-02-07 23:21:24 +02:00
var self = this;
2014-02-08 10:15:26 +02:00
if (options === undefined) {
2014-02-08 02:49:07 +02:00
options = {};
2014-02-07 23:21:24 +02:00
}
assert.object(options, 'options');
2014-02-08 10:15:26 +02:00
var emitter = new EventEmitter();
2014-02-07 23:21:24 +02:00
async.each(
2015-07-26 08:45:20 +03:00
self.dcs(),
2014-02-07 23:21:24 +02:00
function oneDc(dc, next) {
2015-07-26 08:45:20 +03:00
var client = self._clientFromDc(dc.name);
2014-02-07 23:21:24 +02:00
client.listMachines(function (err, machines) {
if (err) {
2015-07-26 08:45:20 +03:00
emitter.emit('dcError', dc.name, err);
2014-02-07 23:21:24 +02:00
} else {
2015-07-26 08:45:20 +03:00
emitter.emit('data', dc.name, machines);
2014-02-07 23:21:24 +02:00
}
next();
});
},
function done(err) {
2014-02-08 10:15:26 +02:00
emitter.emit('end');
2014-02-07 23:21:24 +02:00
}
);
2014-02-08 10:15:26 +02:00
return emitter;
2014-02-07 23:21:24 +02:00
};
2015-07-26 08:45:20 +03:00
/**
* Return the audit for the given machine.
*
* @param {Object} options
* - {String} machine (required) The machine id.
* XXX support `machine` being more than just the UUID.
* @param {Function} callback of the form `function (err, audit, dc)`
*/
Triton.prototype.machineAudit = function machineAudit(options, callback) {
2015-07-26 08:45:20 +03:00
var self = this;
assert.object(options, 'options');
assert.string(options.machine, 'options.machine');
self.findMachine({machine: options.machine}, function (err, machine, dc) {
if (err) {
return callback(err);
}
var client = self._clientFromDc(dc);
client.machineAudit({id: machine.id}, function (err, audit) {
callback(err, audit, dc);
});
});
};
2015-08-26 03:27:46 +03:00
/**
* getMachine for an alias
*
* @param {String} alias - the machine alias
* @param {Function} callback `function (err, machine)`
*/
Triton.prototype.getMachineByAlias = function getMachineByAlias(alias, callback) {
this.cloudapi.listMachines({name: alias}, function (err, machines) {
if (err) {
callback(err);
return;
}
var found = false;
machines.forEach(function (machine) {
if (!found && machine.name === alias) {
callback(null, machine);
found = true;
}
});
if (!found) {
callback(new Error('machine ' + alias + ' not found'));
return;
}
});
};
2014-02-07 23:21:24 +02:00
//---- exports
module.exports = Triton;