first pass at 'triton create'
This commit is contained in:
parent
15ca8ecc32
commit
0d4e93208c
25
TODO.txt
25
TODO.txt
@ -1,9 +1,6 @@
|
|||||||
|
|
||||||
# today
|
# today
|
||||||
|
|
||||||
triton instances|insts # list machines
|
|
||||||
triton instance|inst ID|NAME|UNIQUE-NAME-SUBSTRING # get machine
|
|
||||||
# -1 for unique match, a la 'vmadm lookup -1'
|
|
||||||
|
|
||||||
triton create # triton create-instance
|
triton create # triton create-instance
|
||||||
triton create -p PKG [...] IMG
|
triton create -p PKG [...] IMG
|
||||||
@ -23,38 +20,22 @@ triton image IMAGE # get image
|
|||||||
triton packages # list packages
|
triton packages # list packages
|
||||||
triton package PACKAGE
|
triton package PACKAGE
|
||||||
|
|
||||||
|
triton instances|insts # list machines
|
||||||
|
triton instance|inst ID|NAME # get machine
|
||||||
|
|
||||||
|
triton cloudapi ...
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# maybe today
|
# maybe today
|
||||||
|
|
||||||
triton config defaultPkg t4-standard-1g
|
|
||||||
|
|
||||||
triton login|ssh VM # kexec?
|
triton login|ssh VM # kexec?
|
||||||
|
|
||||||
triton delete VM|IMAGE # substring matching? too dangerous
|
triton delete VM|IMAGE # substring matching? too dangerous
|
||||||
triton delete --vm VM
|
triton delete --vm VM
|
||||||
triton delete --image IMAGE
|
triton delete --image IMAGE
|
||||||
|
|
||||||
triton raw|cloudapi ... # raw cloudapi call
|
|
||||||
Equivalent of:
|
|
||||||
function cloudapi() {
|
|
||||||
local now=`date -u "+%a, %d %h %Y %H:%M:%S GMT"` ;
|
|
||||||
local signature=`echo ${now} | tr -d '\n' | openssl dgst -sha256 -sign ~/.ssh/automation.id_rsa | openssl enc -e -a | tr -d '\n'` ;
|
|
||||||
local curl_opts=
|
|
||||||
[[ -n $SDC_TESTING ]] && curl_opts="-k $curl_opts";
|
|
||||||
[[ -n $TRACE ]] && set -x;
|
|
||||||
curl -is $curl_opts \
|
|
||||||
-H "Accept: application/json" -H "api-version: ~7.2" \
|
|
||||||
-H "Date: ${now}" \
|
|
||||||
-H "Authorization: Signature keyId=\"/$SDC_ACCOUNT/keys/$SDC_KEY_ID\",algorithm=\"rsa-sha256\" ${signature}" \
|
|
||||||
--url $SDC_URL$@ ;
|
|
||||||
[[ -n $TRACE ]] && set +x;
|
|
||||||
echo "";
|
|
||||||
}
|
|
||||||
|
|
||||||
"shortid" instead of full UUID "id" in default output, and then allow lookup
|
"shortid" instead of full UUID "id" in default output, and then allow lookup
|
||||||
by that shortid. Really nice for 80 columns.
|
by that shortid. Really nice for 80 columns.
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ function CLI() {
|
|||||||
{ group: 'Operator Commands' },
|
{ group: 'Operator Commands' },
|
||||||
'account',
|
'account',
|
||||||
{ group: 'Instances (aka VMs/Machines/Containers)' },
|
{ group: 'Instances (aka VMs/Machines/Containers)' },
|
||||||
'create',
|
'create-instance',
|
||||||
'instances',
|
'instances',
|
||||||
'instance',
|
'instance',
|
||||||
'instance-audit',
|
'instance-audit',
|
||||||
@ -117,9 +117,9 @@ CLI.prototype.do_images = require('./do_images');
|
|||||||
CLI.prototype.do_image = require('./do_image');
|
CLI.prototype.do_image = require('./do_image');
|
||||||
|
|
||||||
// Instances (aka VMs/containers/machines)
|
// Instances (aka VMs/containers/machines)
|
||||||
CLI.prototype.do_create = require('./do_create');
|
|
||||||
CLI.prototype.do_instance = require('./do_instance');
|
CLI.prototype.do_instance = require('./do_instance');
|
||||||
CLI.prototype.do_instances = require('./do_instances');
|
CLI.prototype.do_instances = require('./do_instances');
|
||||||
|
CLI.prototype.do_create_instance = require('./do_create_instance');
|
||||||
CLI.prototype.do_instance_audit = require('./do_instance_audit');
|
CLI.prototype.do_instance_audit = require('./do_instance_audit');
|
||||||
CLI.prototype.do_stop_instance = require('./do_startstop_instance')('stop');
|
CLI.prototype.do_stop_instance = require('./do_startstop_instance')('stop');
|
||||||
CLI.prototype.do_start_instance = require('./do_startstop_instance')('start');
|
CLI.prototype.do_start_instance = require('./do_startstop_instance')('start');
|
||||||
|
@ -404,19 +404,19 @@ CloudAPI.prototype._doMachine = function _doMachine(action, uuid, callback) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* wait for a specfic state for a machine
|
* Wait for a machine to go one of a set of specfic states.
|
||||||
*
|
*
|
||||||
* @param {Object} options
|
* @param {Object} options
|
||||||
* - {String} id - machine UUID
|
* - {String} id - machine UUID
|
||||||
* - {String} state - desired state
|
* - {Array of String} states - desired state
|
||||||
* - {Number} interval (optional) - time in ms to poll
|
* - {Number} interval (optional) - time in ms to poll
|
||||||
* @param {Function} callback - called when state is reached or on error
|
* @param {Function} callback - called when state is reached or on error
|
||||||
*/
|
*/
|
||||||
CloudAPI.prototype.waitForMachineState = function waitForMachineState(opts, callback) {
|
CloudAPI.prototype.waitForMachineStates = function waitForMachineStates(opts, callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
assert.object(opts, 'opts');
|
assert.object(opts, 'opts');
|
||||||
assert.string(opts.id, 'opts.id');
|
assert.string(opts.id, 'opts.id');
|
||||||
assert.string(opts.state, 'opts.state');
|
assert.arrayOfString(opts.states, 'opts.states');
|
||||||
assert.optionalNumber(opts.interval, 'opts.interval');
|
assert.optionalNumber(opts.interval, 'opts.interval');
|
||||||
assert.func(callback, 'callback');
|
assert.func(callback, 'callback');
|
||||||
var interval = (opts.interval === undefined ? 1000 : opts.interval);
|
var interval = (opts.interval === undefined ? 1000 : opts.interval);
|
||||||
@ -429,7 +429,7 @@ CloudAPI.prototype.waitForMachineState = function waitForMachineState(opts, call
|
|||||||
callback(err);
|
callback(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (machine.state === opts.state) {
|
if (opts.states.indexOf(machine.state) !== -1) {
|
||||||
callback(null, machine);
|
callback(null, machine);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -497,6 +497,26 @@ CloudAPI.prototype.listMachines = function listMachines(options, callback) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
CloudAPI.prototype.createMachine = function createMachine(options, callback) {
|
||||||
|
assert.object(options, 'options');
|
||||||
|
assert.optionalString(options.name, 'options.name');
|
||||||
|
assert.uuid(options.image, 'options.image');
|
||||||
|
assert.uuid(options.package, 'options.package');
|
||||||
|
assert.optionalArrayOfUuid(options.networks, 'options.networks');
|
||||||
|
// TODO: assert the other fields
|
||||||
|
assert.func(callback, 'callback');
|
||||||
|
|
||||||
|
// XXX how does options.networks array work here?
|
||||||
|
this._request({
|
||||||
|
method: 'POST',
|
||||||
|
path: this._path(format('/%s/machines', this.user), options)
|
||||||
|
}, function (err, req, res, body) {
|
||||||
|
callback(err, body, res);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List machine audit (successful actions on the machine).
|
* List machine audit (successful actions on the machine).
|
||||||
*
|
*
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
/**
|
/**
|
||||||
* Copyright (c) 2015 Joyent Inc. All rights reserved.
|
* Copyright (c) 2015 Joyent Inc. All rights reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
var assert = require('assert-plus');
|
var assert = require('assert-plus');
|
||||||
var sprintf = require('extsprintf').sprintf;
|
|
||||||
var util = require('util'),
|
var util = require('util'),
|
||||||
format = util.format;
|
format = util.format;
|
||||||
|
|
||||||
@ -17,6 +14,7 @@ var errors = require('./errors'),
|
|||||||
|
|
||||||
var p = console.log;
|
var p = console.log;
|
||||||
|
|
||||||
|
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
||||||
|
|
||||||
|
|
||||||
// ---- support stuff
|
// ---- support stuff
|
||||||
@ -142,9 +140,45 @@ function isUUID(s) {
|
|||||||
return /^([a-f\d]{8}(-[a-f\d]{4}){3}-[a-f\d]{12}?)$/i.test(s);
|
return /^([a-f\d]{8}(-[a-f\d]{4}){3}-[a-f\d]{12}?)$/i.test(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function humanDurationFromMs(ms) {
|
||||||
|
assert.number(ms, 'ms');
|
||||||
|
var sizes = [
|
||||||
|
['ms', 1000, 's'],
|
||||||
|
['s', 60, 'm'],
|
||||||
|
['m', 60, 'h'],
|
||||||
|
['h', 24, 'd']
|
||||||
|
];
|
||||||
|
if (ms === 0) {
|
||||||
|
return '0ms';
|
||||||
|
}
|
||||||
|
var bits = [];
|
||||||
|
var n = ms;
|
||||||
|
for (var i = 0; i < sizes.length; i++) {
|
||||||
|
var size = sizes[i];
|
||||||
|
var remainder = n % size[1];
|
||||||
|
if (remainder === 0) {
|
||||||
|
bits.unshift('');
|
||||||
|
} else {
|
||||||
|
bits.unshift(format('%d%s', remainder, size[0]));
|
||||||
|
}
|
||||||
|
n = Math.floor(n / size[1]);
|
||||||
|
if (n === 0) {
|
||||||
|
break;
|
||||||
|
} else if (size[2] === 'd') {
|
||||||
|
bits.unshift(format('%d%s', n, size[2]));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bits.slice(0, 2).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//---- exports
|
//---- exports
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
UUID_RE: UUID_RE,
|
||||||
objCopy: objCopy,
|
objCopy: objCopy,
|
||||||
deepObjCopy: deepObjCopy,
|
deepObjCopy: deepObjCopy,
|
||||||
zeroPad: zeroPad,
|
zeroPad: zeroPad,
|
||||||
@ -153,5 +187,6 @@ module.exports = {
|
|||||||
kvToObj: kvToObj,
|
kvToObj: kvToObj,
|
||||||
longAgo: longAgo,
|
longAgo: longAgo,
|
||||||
isUUID: isUUID,
|
isUUID: isUUID,
|
||||||
|
humanDurationFromMs: humanDurationFromMs
|
||||||
};
|
};
|
||||||
// vim: set softtabstop=4 shiftwidth=4:
|
// vim: set softtabstop=4 shiftwidth=4:
|
||||||
|
197
lib/do_create_instance.js
Normal file
197
lib/do_create_instance.js
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015 Joyent Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* `triton create ...`
|
||||||
|
*/
|
||||||
|
|
||||||
|
var bigspinner = require('bigspinner');
|
||||||
|
var format = require('util').format;
|
||||||
|
var tabula = require('tabula');
|
||||||
|
var vasync = require('vasync');
|
||||||
|
|
||||||
|
var common = require('./common');
|
||||||
|
var errors = require('./errors');
|
||||||
|
|
||||||
|
|
||||||
|
function do_create_instance(subcmd, opts, args, callback) {
|
||||||
|
var self = this;
|
||||||
|
if (opts.help) {
|
||||||
|
this.do_help('help', {}, [subcmd], callback);
|
||||||
|
return;
|
||||||
|
} else if (args.length < 1 || args.length > 2) {
|
||||||
|
return callback(new errors.UsageError(format(
|
||||||
|
'incorrect number of args (%d): %s', args.length, args.join(' '))));
|
||||||
|
}
|
||||||
|
|
||||||
|
var log = this.triton.log;
|
||||||
|
var cloudapi = this.triton.cloudapi;
|
||||||
|
var cOpts = {};
|
||||||
|
|
||||||
|
vasync.pipeline({arg: {}, funcs: [
|
||||||
|
function getImg(ctx, next) {
|
||||||
|
// XXX don't get the image object if it is a UUID, waste of time
|
||||||
|
self.triton.getImage(args[0], function (err, img) {
|
||||||
|
if (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
ctx.img = img;
|
||||||
|
log.trace({img: img}, 'create-instance img');
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function getPkg(ctx, next) {
|
||||||
|
if (args.length < 2) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
// XXX don't get the package object if it is a UUID, waste of time
|
||||||
|
self.triton.getPackage(args[1], function (err, pkg) {
|
||||||
|
if (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
log.trace({pkg: pkg}, 'create-instance pkg');
|
||||||
|
ctx.pkg = pkg;
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function getNets(ctx, next) {
|
||||||
|
if (!opts.networks) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
self.triton.getNetworks(opts.networks, function (err, nets) {
|
||||||
|
if (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
ctx.nets = nets;
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function createInst(ctx, next) {
|
||||||
|
var createOpts = {
|
||||||
|
name: opts.name,
|
||||||
|
image: ctx.img.id,
|
||||||
|
'package': ctx.pkg && ctx.pkg.id,
|
||||||
|
networks: ctx.nets && ctx.nets.map(
|
||||||
|
function (net) { return net.id; })
|
||||||
|
};
|
||||||
|
log.trace({createOpts: createOpts}, 'create-instance createOpts');
|
||||||
|
ctx.start = Date.now();
|
||||||
|
cloudapi.createMachine(createOpts, function (err, inst) {
|
||||||
|
if (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
ctx.inst = inst;
|
||||||
|
if (opts.json) {
|
||||||
|
console.log(JSON.stringify(inst));
|
||||||
|
} else {
|
||||||
|
console.log('Creating instance %s (%s, %s@%s, %s)',
|
||||||
|
inst.name, inst.id, ctx.img.name, ctx.img.version,
|
||||||
|
inst.package);
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function maybeWait(ctx, next) {
|
||||||
|
if (!opts.wait) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
var spinner;
|
||||||
|
if (!opts.quiet && process.stderr.isTTY) {
|
||||||
|
spinner = bigspinner.createSpinner({
|
||||||
|
delay: 250,
|
||||||
|
stream: process.stderr,
|
||||||
|
height: process.stdout.rows - 2,
|
||||||
|
width: process.stdout.columns - 1,
|
||||||
|
hideCursor: true,
|
||||||
|
fontChar: '#'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
cloudapi.waitForMachineStates({
|
||||||
|
id: ctx.inst.id,
|
||||||
|
states: ['running', 'failed']
|
||||||
|
}, function (err, inst) {
|
||||||
|
if (spinner) {
|
||||||
|
spinner.destroy();
|
||||||
|
}
|
||||||
|
if (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
if (opts.json) {
|
||||||
|
console.log(JSON.stringify(inst));
|
||||||
|
} else if (inst.state === 'running') {
|
||||||
|
var dur = Date.now() - ctx.start;
|
||||||
|
console.log('Created instance %s (%s) in %s',
|
||||||
|
inst.name, inst.id, common.humanDurationFromMs(dur));
|
||||||
|
}
|
||||||
|
if (inst.state !== 'running') {
|
||||||
|
next(new Error(format('failed to create instance %s (%s)',
|
||||||
|
inst.name, inst.id)));
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
]}, function (err) {
|
||||||
|
callback(err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
do_create_instance.options = [
|
||||||
|
{
|
||||||
|
names: ['help', 'h'],
|
||||||
|
type: 'bool',
|
||||||
|
help: 'Show this help.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
group: 'Create options'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
names: ['name', 'n'],
|
||||||
|
type: 'string',
|
||||||
|
help: 'One or more (comma-separated) networks IDs.'
|
||||||
|
},
|
||||||
|
// XXX arrayOfCommaSepString dashdash type
|
||||||
|
//{
|
||||||
|
// names: ['networks', 'nets'],
|
||||||
|
// type: 'arrayOfCommaSepString',
|
||||||
|
// help: 'One or more (comma-separated) networks IDs.'
|
||||||
|
//},
|
||||||
|
// XXX enable-firewall
|
||||||
|
// XXX locality: near, far
|
||||||
|
// XXX metadata, metadata-file
|
||||||
|
// XXX script (user-script)
|
||||||
|
// XXX tag
|
||||||
|
{
|
||||||
|
group: 'Other options'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
names: ['wait', 'w'],
|
||||||
|
type: 'bool',
|
||||||
|
help: 'Wait for the creation to complete.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
names: ['quiet', 'q'],
|
||||||
|
type: 'bool',
|
||||||
|
help: 'No progress spinner while waiting.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
names: ['json', 'j'],
|
||||||
|
type: 'bool',
|
||||||
|
help: 'JSON stream output.'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
do_create_instance.help = (
|
||||||
|
/* BEGIN JSSTYLED */
|
||||||
|
'Create a new instance.\n' +
|
||||||
|
'\n' +
|
||||||
|
'Usage:\n' +
|
||||||
|
' {{name}} create-instance [<options>] IMAGE [PACKAGE]\n' +
|
||||||
|
'\n' +
|
||||||
|
'{{options}}'
|
||||||
|
/* END JSSTYLED */
|
||||||
|
);
|
||||||
|
|
||||||
|
do_create_instance.aliases = ['create'];
|
||||||
|
|
||||||
|
module.exports = do_create_instance;
|
@ -42,8 +42,7 @@ function do_packages (subcmd, opts, args, callback) {
|
|||||||
tabula(packages, {
|
tabula(packages, {
|
||||||
skipHeader: opts.H,
|
skipHeader: opts.H,
|
||||||
columns: columns,
|
columns: columns,
|
||||||
sort: sort,
|
sort: sort
|
||||||
validFields: 'name,memory,disk,swap,vcpus,lwps,default,id,version'.split(',')
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
callback();
|
callback();
|
||||||
@ -64,7 +63,7 @@ do_packages.options = [
|
|||||||
{
|
{
|
||||||
names: ['o'],
|
names: ['o'],
|
||||||
type: 'string',
|
type: 'string',
|
||||||
default: 'id,name,version,memory,disk',
|
default: 'id,name,default,memory,disk',
|
||||||
help: 'Specify fields (columns) to output.',
|
help: 'Specify fields (columns) to output.',
|
||||||
helpArg: 'field1,...'
|
helpArg: 'field1,...'
|
||||||
},
|
},
|
||||||
|
@ -98,11 +98,10 @@ function _do_instance(action, subcmd, opts, args, callback) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var waitOpts = {
|
self.triton.cloudapi.waitForMachineStates({
|
||||||
state: state,
|
id: uuid,
|
||||||
id: uuid
|
states: [state]
|
||||||
};
|
}, function (err, machine) {
|
||||||
self.triton.cloudapi.waitForMachineState(waitOpts, function (err, machine) {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
callback(err);
|
callback(err);
|
||||||
return;
|
return;
|
||||||
|
198
lib/triton.js
198
lib/triton.js
@ -13,6 +13,7 @@ var once = require('once');
|
|||||||
var path = require('path');
|
var path = require('path');
|
||||||
var restifyClients = require('restify-clients');
|
var restifyClients = require('restify-clients');
|
||||||
var sprintf = require('util').format;
|
var sprintf = require('util').format;
|
||||||
|
var tabula = require('tabula');
|
||||||
|
|
||||||
var cloudapi = require('./cloudapi2');
|
var cloudapi = require('./cloudapi2');
|
||||||
var common = require('./common');
|
var common = require('./common');
|
||||||
@ -105,130 +106,89 @@ Triton.prototype._cloudapiFromProfile = function _cloudapiFromProfile(profile) {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find a machine in the set of DCs for the current profile.
|
* Get an image by ID or name. If there is more than one image with that name,
|
||||||
*
|
* then the latest (by published_at) is returned.
|
||||||
*
|
|
||||||
* @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) {
|
Triton.prototype.getImage = function getImage(name, cb) {
|
||||||
//XXX Eventually this can be cached for a *full* uuid. Arguably for a
|
assert.string(name, 'name');
|
||||||
// uuid prefix or machine alias match, it cannot be cached, because an
|
assert.func(cb, 'cb');
|
||||||
// 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 = [];
|
if (common.UUID_RE.test(name)) {
|
||||||
var foundMachine;
|
this.cloudapi.getImage({id: name}, function (err, img) {
|
||||||
var foundDc;
|
if (err) {
|
||||||
async.each(
|
cb(err);
|
||||||
self.dcs(),
|
} else if (img.state !== 'active') {
|
||||||
function oneDc(dc, next) {
|
cb(new Error(format('image %s is not active', name)));
|
||||||
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 {
|
} else {
|
||||||
callback(new errors.InternalError(
|
cb(null, img);
|
||||||
'unexpected error finding machine ' + options.id));
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List machines for the current profile.
|
|
||||||
*
|
|
||||||
* var res = this.jc.listMachines();
|
|
||||||
* res.on('data', function (dc, dcMachines) {
|
|
||||||
* //...
|
|
||||||
* });
|
|
||||||
* res.on('dcError', function (dc, dcErr) {
|
|
||||||
* //...
|
|
||||||
* });
|
|
||||||
* res.on('end', function () {
|
|
||||||
* //...
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* @param {Object} options Optional
|
|
||||||
*/
|
|
||||||
Triton.prototype.listMachines = function listMachines(options) {
|
|
||||||
var self = this;
|
|
||||||
if (options === undefined) {
|
|
||||||
options = {};
|
|
||||||
}
|
|
||||||
assert.object(options, 'options');
|
|
||||||
|
|
||||||
var emitter = new EventEmitter();
|
|
||||||
async.each(
|
|
||||||
self.dcs(),
|
|
||||||
function oneDc(dc, next) {
|
|
||||||
var client = self._clientFromDc(dc.name);
|
|
||||||
client.listMachines(function (err, machines) {
|
|
||||||
if (err) {
|
|
||||||
emitter.emit('dcError', dc.name, err);
|
|
||||||
} else {
|
|
||||||
emitter.emit('data', dc.name, machines);
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
function done(err) {
|
|
||||||
emitter.emit('end');
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return emitter;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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) {
|
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
});
|
} else {
|
||||||
|
this.cloudapi.listImages(function (err, imgs) {
|
||||||
|
if (err) {
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
|
var nameMatches = [];
|
||||||
|
for (var i = 0; i < imgs.length; i++) {
|
||||||
|
if (imgs[i].name === name) {
|
||||||
|
nameMatches.push(imgs[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nameMatches.length === 0) {
|
||||||
|
cb(new Error(format('no image with name=%s was found',
|
||||||
|
name)));
|
||||||
|
} else if (nameMatches.length === 1) {
|
||||||
|
cb(null, nameMatches[0]);
|
||||||
|
} else {
|
||||||
|
tabula.sortArrayOfObjects(nameMatches, 'published_at');
|
||||||
|
cb(null, nameMatches[nameMatches.length - 1]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an active package by ID or name. If there is more than one package
|
||||||
|
* with that name, then this errors out.
|
||||||
|
*/
|
||||||
|
Triton.prototype.getPackage = function getPackage(name, cb) {
|
||||||
|
assert.string(name, 'name');
|
||||||
|
assert.func(cb, 'cb');
|
||||||
|
|
||||||
|
if (common.UUID_RE.test(name)) {
|
||||||
|
this.cloudapi.getPackage({id: name}, function (err, pkg) {
|
||||||
|
if (err) {
|
||||||
|
cb(err);
|
||||||
|
} else if (!pkg.active) {
|
||||||
|
cb(new Error(format('image %s is not active', name)));
|
||||||
|
} else {
|
||||||
|
cb(null, pkg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.cloudapi.listPackages(function (err, pkgs) {
|
||||||
|
if (err) {
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
|
var nameMatches = [];
|
||||||
|
for (var i = 0; i < pkgs.length; i++) {
|
||||||
|
if (pkgs[i].name === name) {
|
||||||
|
nameMatches.push(pkgs[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nameMatches.length === 0) {
|
||||||
|
cb(new Error(format('no package with name=%s was found',
|
||||||
|
name)));
|
||||||
|
} else if (nameMatches.length === 1) {
|
||||||
|
cb(null, nameMatches[0]);
|
||||||
|
} else {
|
||||||
|
cb(new Error(format(
|
||||||
|
'package name "%s" is ambiguous: matches %d packages',
|
||||||
|
name, nameMatches.length)));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"assert-plus": "0.1.5",
|
"assert-plus": "0.1.5",
|
||||||
"backoff": "2.4.1",
|
"backoff": "2.4.1",
|
||||||
|
"bigspinner": "^3.0.0",
|
||||||
"bunyan": "1.4.0",
|
"bunyan": "1.4.0",
|
||||||
"cmdln": "3.2.3",
|
"cmdln": "3.2.3",
|
||||||
"dashdash": "1.10.0",
|
"dashdash": "1.10.0",
|
||||||
|
Reference in New Issue
Block a user