joyent/node-triton#173 Add support for listing and getting triton nfs volumes
joyent/node-triton#174 Add support for creating triton nfs volumes joyent/node-triton#175 Add support for deleting triton NFS volumes
This commit is contained in:
parent
d14ac041f4
commit
679157d453
@ -7,6 +7,12 @@ Known issues:
|
|||||||
|
|
||||||
## not yet released
|
## not yet released
|
||||||
|
|
||||||
|
## 5.2.0
|
||||||
|
|
||||||
|
- [joyent/mode-triton#173] Add support for listing and getting triton nfs
|
||||||
|
volumes.
|
||||||
|
- [joyent/mode-triton#174] Add support for creating triton nfs volumes.
|
||||||
|
- [joyent/mode-triton#175] Add support for deleting triton nfs volumes.
|
||||||
- [joyent/node-triton#183] `triton profile create` will no longer use ANSI
|
- [joyent/node-triton#183] `triton profile create` will no longer use ANSI
|
||||||
codes for styling if stdout isn't a TTY.
|
codes for styling if stdout isn't a TTY.
|
||||||
|
|
||||||
|
@ -205,11 +205,12 @@ function CLI() {
|
|||||||
'reboot',
|
'reboot',
|
||||||
'ssh',
|
'ssh',
|
||||||
'ip',
|
'ip',
|
||||||
{ group: 'Images, Packages, Networks, Firewall Rules' },
|
{ group: 'Images, Packages, Networks, Firewall Rules, Volumes' },
|
||||||
'image',
|
'image',
|
||||||
'package',
|
'package',
|
||||||
'network',
|
'network',
|
||||||
'fwrule',
|
'fwrule',
|
||||||
|
'volume',
|
||||||
{ group: 'Other Commands' },
|
{ group: 'Other Commands' },
|
||||||
'info',
|
'info',
|
||||||
'account',
|
'account',
|
||||||
@ -695,6 +696,9 @@ CLI.prototype.do_cloudapi = require('./do_cloudapi');
|
|||||||
CLI.prototype.do_badger = require('./do_badger');
|
CLI.prototype.do_badger = require('./do_badger');
|
||||||
CLI.prototype.do_rbac = require('./do_rbac');
|
CLI.prototype.do_rbac = require('./do_rbac');
|
||||||
|
|
||||||
|
// Volumes
|
||||||
|
CLI.prototype.do_volumes = require('./do_volumes');
|
||||||
|
CLI.prototype.do_volume = require('./do_volume');
|
||||||
|
|
||||||
|
|
||||||
//---- mainline
|
//---- mainline
|
||||||
|
111
lib/cloudapi2.js
111
lib/cloudapi2.js
@ -2275,7 +2275,118 @@ CloudApi.prototype.setRoleTags = function setRoleTags(opts, cb) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a volume by id.
|
||||||
|
*
|
||||||
|
* @param {Object} opts
|
||||||
|
* - id {UUID} Required. The volume id.
|
||||||
|
* @param {Function} cb of the form `function (err, volume, res)`
|
||||||
|
*/
|
||||||
|
CloudApi.prototype.getVolume = function getVolume(opts, cb) {
|
||||||
|
assert.object(opts, 'opts');
|
||||||
|
assert.uuid(opts.id, 'opts.id');
|
||||||
|
|
||||||
|
var endpoint = format('/%s/volumes/%s', this.account, opts.id);
|
||||||
|
this._request(endpoint, function (err, req, res, body) {
|
||||||
|
cb(err, body, res);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List the account's volumes.
|
||||||
|
*
|
||||||
|
* @param {Object} options
|
||||||
|
* @param {Function} callback - called like `function (err, volumes)`
|
||||||
|
*/
|
||||||
|
CloudApi.prototype.listVolumes = function listVolumes(options, cb) {
|
||||||
|
var endpoint = format('/%s/volumes', this.account);
|
||||||
|
this._passThrough(endpoint, options, cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a volume for the account.
|
||||||
|
*
|
||||||
|
* @param {Object} options
|
||||||
|
* - name {String} Optional: the name of the volume to be created
|
||||||
|
* - size {String} Optional: a string representing the size of the volume
|
||||||
|
* to be created
|
||||||
|
* - networks {Array} Optional: an array that contains all the networks
|
||||||
|
* that should be reachable from the newly created volume
|
||||||
|
* @param {Function} callback - called like `function (err, volume, res)`
|
||||||
|
*/
|
||||||
|
CloudApi.prototype.createVolume = function createVolume(options, callback) {
|
||||||
|
assert.object(options, 'options');
|
||||||
|
assert.optionalString(options.name, 'options.name');
|
||||||
|
assert.optionalString(options.size, 'options.size');
|
||||||
|
assert.optionalArrayOfUuid(options.networks, 'options.networks');
|
||||||
|
assert.func(callback, 'callback');
|
||||||
|
|
||||||
|
this._request({
|
||||||
|
method: 'POST',
|
||||||
|
path: format('/%s/volumes', this.account),
|
||||||
|
data: options
|
||||||
|
}, function (err, req, res, body) {
|
||||||
|
callback(err, body, res);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete an account's volume.
|
||||||
|
*
|
||||||
|
* @param {String} volumeUuid
|
||||||
|
* @param {Function} callback - called like `function (err, volume, res)`
|
||||||
|
*/
|
||||||
|
CloudApi.prototype.deleteVolume = function deleteVolume(volumeUuid, callback) {
|
||||||
|
assert.uuid(volumeUuid, 'volumeUuid');
|
||||||
|
assert.func(callback, 'callback');
|
||||||
|
|
||||||
|
this._request({
|
||||||
|
method: 'DELETE',
|
||||||
|
path: format('/%s/volumes/%s', this.account, volumeUuid)
|
||||||
|
}, function (err, req, res, body) {
|
||||||
|
callback(err, body, res);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait for a volume to go one of a set of specfic states.
|
||||||
|
*
|
||||||
|
* @param {Object} options
|
||||||
|
* - {String} id - machine UUID
|
||||||
|
* - {Array of String} states - desired state
|
||||||
|
* - {Number} interval (optional) - time in ms to poll
|
||||||
|
* @param {Function} callback - called when state is reached or on error
|
||||||
|
*/
|
||||||
|
CloudApi.prototype.waitForVolumeStates =
|
||||||
|
function waitForVolumeStates(opts, callback) {
|
||||||
|
var self = this;
|
||||||
|
assert.object(opts, 'opts');
|
||||||
|
assert.uuid(opts.id, 'opts.id');
|
||||||
|
assert.arrayOfString(opts.states, 'opts.states');
|
||||||
|
assert.optionalNumber(opts.interval, 'opts.interval');
|
||||||
|
assert.func(callback, 'callback');
|
||||||
|
var interval = (opts.interval === undefined ? 1000 : opts.interval);
|
||||||
|
assert.ok(interval > 0, 'interval must be a positive number');
|
||||||
|
|
||||||
|
poll();
|
||||||
|
|
||||||
|
function poll() {
|
||||||
|
self.getVolume({
|
||||||
|
id: opts.id
|
||||||
|
}, function (err, volume, res) {
|
||||||
|
if (err) {
|
||||||
|
callback(err, null, res);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (opts.states.indexOf(volume.state) !== -1) {
|
||||||
|
callback(null, volume, res);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setTimeout(poll, interval);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// --- Exports
|
// --- Exports
|
||||||
|
|
||||||
|
@ -156,6 +156,49 @@ function kvToObj(kvs, valid) {
|
|||||||
return o;
|
return o;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* given an array of key=value pairs, break them into a JSON predicate
|
||||||
|
*
|
||||||
|
* @param {Array} kvs - an array of key=value pairs
|
||||||
|
* @param {Array} valid (optional) - an array to validate pairs
|
||||||
|
* @param {String} compositionType - the way each key/value pair will be
|
||||||
|
* combined to form a JSON predicate. Valid values are 'or' and 'and'
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function kvToJSONPredicate(kvs, valid, compositionType) {
|
||||||
|
if (typeof ('compositionType') === 'undefined') {
|
||||||
|
compositionType = valid;
|
||||||
|
valid = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.arrayOfString(kvs, 'kvs');
|
||||||
|
assert.optionalArrayOfString(valid, 'valid');
|
||||||
|
assert.string(compositionType, 'string');
|
||||||
|
assert.ok(compositionType === 'or' || compositionType === 'and',
|
||||||
|
'compositionType');
|
||||||
|
|
||||||
|
var predicate = {};
|
||||||
|
predicate[compositionType] = [];
|
||||||
|
|
||||||
|
for (var i = 0; i < kvs.length; i++) {
|
||||||
|
var kv = kvs[i];
|
||||||
|
var idx = kv.indexOf('=');
|
||||||
|
if (idx === -1)
|
||||||
|
throw new errors.UsageError(format(
|
||||||
|
'invalid filter: "%s" (must be of the form "field=value")',
|
||||||
|
kv));
|
||||||
|
var k = kv.slice(0, idx);
|
||||||
|
var v = kv.slice(idx + 1);
|
||||||
|
if (valid && valid.indexOf(k) === -1)
|
||||||
|
throw new errors.UsageError(format(
|
||||||
|
'invalid filter name: "%s" (must be one of "%s")',
|
||||||
|
k, valid.join('", "')));
|
||||||
|
predicate[compositionType].push({eq: [k, v]});
|
||||||
|
}
|
||||||
|
|
||||||
|
return predicate;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* return how long ago something happened
|
* return how long ago something happened
|
||||||
*
|
*
|
||||||
@ -1115,6 +1158,7 @@ module.exports = {
|
|||||||
execPlus: execPlus,
|
execPlus: execPlus,
|
||||||
deepEqual: deepEqual,
|
deepEqual: deepEqual,
|
||||||
tildeSync: tildeSync,
|
tildeSync: tildeSync,
|
||||||
objFromKeyValueArgs: objFromKeyValueArgs
|
objFromKeyValueArgs: objFromKeyValueArgs,
|
||||||
|
kvToJSONPredicate: kvToJSONPredicate
|
||||||
};
|
};
|
||||||
// vim: set softtabstop=4 shiftwidth=4:
|
// vim: set softtabstop=4 shiftwidth=4:
|
||||||
|
179
lib/do_volume/do_create.js
Normal file
179
lib/do_volume/do_create.js
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
/*
|
||||||
|
* 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 2017 Joyent, Inc.
|
||||||
|
*
|
||||||
|
* `triton volume create ...`
|
||||||
|
*/
|
||||||
|
|
||||||
|
var assert = require('assert-plus');
|
||||||
|
var format = require('util').format;
|
||||||
|
var vasync = require('vasync');
|
||||||
|
|
||||||
|
var common = require('../common');
|
||||||
|
var distractions = require('../distractions');
|
||||||
|
var errors = require('../errors');
|
||||||
|
|
||||||
|
|
||||||
|
function do_create(subcmd, opts, args, callback) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (opts.help) {
|
||||||
|
this.do_help('help', {}, [subcmd], callback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var context = {};
|
||||||
|
|
||||||
|
vasync.pipeline({funcs: [
|
||||||
|
function setup(ctx, next) {
|
||||||
|
common.cliSetupTritonApi({
|
||||||
|
cli: self.top
|
||||||
|
}, function onSetup(setupErr) {
|
||||||
|
next(setupErr);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function getNetworks(ctx, next) {
|
||||||
|
if (!opts.network) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.networks = [];
|
||||||
|
|
||||||
|
vasync.forEachParallel({
|
||||||
|
inputs: opts.network,
|
||||||
|
func: function getNetwork(networkName, nextNet) {
|
||||||
|
self.top.tritonapi.getNetwork(networkName,
|
||||||
|
function onGetNetwork(getNetErr, net) {
|
||||||
|
if (net) {
|
||||||
|
ctx.networks.push(net);
|
||||||
|
}
|
||||||
|
|
||||||
|
nextNet(getNetErr);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, next);
|
||||||
|
},
|
||||||
|
function createVolume(ctx, next) {
|
||||||
|
var createVolumeParams = {
|
||||||
|
type: 'tritonnfs',
|
||||||
|
name: opts.name,
|
||||||
|
networks: ctx.networks,
|
||||||
|
size: opts.size
|
||||||
|
};
|
||||||
|
|
||||||
|
if (opts.type) {
|
||||||
|
createVolumeParams.type = opts.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.top.tritonapi.cloudapi.createVolume(createVolumeParams,
|
||||||
|
function onRes(volCreateErr, volume) {
|
||||||
|
ctx.volume = volume;
|
||||||
|
next(volCreateErr);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function maybeWait(ctx, next) {
|
||||||
|
if (!opts.wait) {
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var distraction = distractions.createDistraction(opts.wait.length);
|
||||||
|
|
||||||
|
self.top.tritonapi.cloudapi.waitForVolumeStates({
|
||||||
|
id: ctx.volume.id,
|
||||||
|
states: ['ready', 'failed']
|
||||||
|
}, function onWaitDone(waitErr, volume) {
|
||||||
|
distraction.destroy();
|
||||||
|
ctx.volume = volume;
|
||||||
|
next(waitErr);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function outputRes(ctx, next) {
|
||||||
|
assert.object(ctx.volume, 'ctx.volume');
|
||||||
|
|
||||||
|
if (opts.json) {
|
||||||
|
console.log(JSON.stringify(ctx.volume));
|
||||||
|
} else {
|
||||||
|
console.log(JSON.stringify(ctx.volume, null, 4));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
], arg: context}, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
do_create.options = [
|
||||||
|
{
|
||||||
|
names: ['help', 'h'],
|
||||||
|
type: 'bool',
|
||||||
|
help: 'Show this help.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
group: 'Create options'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
names: ['name', 'n'],
|
||||||
|
helpArg: 'NAME',
|
||||||
|
type: 'string',
|
||||||
|
help: 'Volume name. If not given, one will be generated server-side.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
names: ['type', 't'],
|
||||||
|
helpArg: 'TYPE',
|
||||||
|
type: 'string',
|
||||||
|
help: 'Volume type. Default is "tritonnfs".'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
names: ['size', 'S'],
|
||||||
|
type: 'string',
|
||||||
|
helpArg: 'SIZE',
|
||||||
|
help: '',
|
||||||
|
completionType: 'volumesize'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
names: ['network', 'N'],
|
||||||
|
type: 'arrayOfCommaSepString',
|
||||||
|
helpArg: 'NETWORK',
|
||||||
|
help: 'One or more comma-separated networks (ID, name or short id). ' +
|
||||||
|
'This option can be used multiple times.',
|
||||||
|
completionType: 'tritonnetwork'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
group: 'Other options'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
names: ['json', 'j'],
|
||||||
|
type: 'bool',
|
||||||
|
help: 'JSON stream output.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
names: ['wait', 'w'],
|
||||||
|
type: 'arrayOfBool',
|
||||||
|
help: 'Wait for the creation to complete. Use multiple times for a ' +
|
||||||
|
'spinner.'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
do_create.synopses = ['{{name}} {{cmd}} [OPTIONS] VOLUME'];
|
||||||
|
|
||||||
|
do_create.help = [
|
||||||
|
/* BEGIN JSSTYLED */
|
||||||
|
'Create a volume.',
|
||||||
|
'',
|
||||||
|
'{{usage}}',
|
||||||
|
'',
|
||||||
|
'{{options}}',
|
||||||
|
'',
|
||||||
|
'Where VOLUME is a package id (full UUID), exact name, or short id.',
|
||||||
|
'',
|
||||||
|
'Note: Currently this dumps prettified JSON by default. That might change',
|
||||||
|
'in the future. Use "-j" to explicitly get JSON output.'
|
||||||
|
/* END JSSTYLED */
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
do_create.completionArgtypes = ['tritonvolume', 'none'];
|
||||||
|
|
||||||
|
module.exports = do_create;
|
152
lib/do_volume/do_delete.js
Normal file
152
lib/do_volume/do_delete.js
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
/*
|
||||||
|
* 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 2017 Joyent, Inc.
|
||||||
|
*
|
||||||
|
* `triton volume del ...`
|
||||||
|
*/
|
||||||
|
|
||||||
|
var assert = require('assert-plus');
|
||||||
|
var format = require('util').format;
|
||||||
|
var vasync = require('vasync');
|
||||||
|
|
||||||
|
var common = require('../common');
|
||||||
|
var distractions = require('../distractions');
|
||||||
|
var errors = require('../errors');
|
||||||
|
|
||||||
|
function perror(err) {
|
||||||
|
console.error('error: %s', err.message);
|
||||||
|
}
|
||||||
|
function deleteVolume(volumeName, options, callback) {
|
||||||
|
assert.string(volumeName, 'volumeName');
|
||||||
|
assert.object(options, 'options');
|
||||||
|
assert.object(options.tritonapi, 'options.tritonapi');
|
||||||
|
assert.func(callback, 'callback');
|
||||||
|
|
||||||
|
var tritonapi = options.tritonapi;
|
||||||
|
|
||||||
|
vasync.pipeline({funcs: [
|
||||||
|
function getVolume(ctx, next) {
|
||||||
|
tritonapi.getVolume(volumeName,
|
||||||
|
function onGetVolume(getVolErr, volume) {
|
||||||
|
if (!getVolErr) {
|
||||||
|
ctx.volume = volume;
|
||||||
|
}
|
||||||
|
|
||||||
|
next(getVolErr);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function doDeleteVolume(ctx, next) {
|
||||||
|
assert.object(ctx.volume, 'ctx.volume');
|
||||||
|
|
||||||
|
tritonapi.cloudapi.deleteVolume(ctx.volume.id,
|
||||||
|
next);
|
||||||
|
},
|
||||||
|
function waitForVolumeStates(ctx, next) {
|
||||||
|
assert.object(ctx.volume, 'ctx.volume');
|
||||||
|
|
||||||
|
var distraction;
|
||||||
|
var volumeId = ctx.volume.id;
|
||||||
|
|
||||||
|
if (!options.wait) {
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
distraction = distractions.createDistraction(options.wait.length);
|
||||||
|
|
||||||
|
tritonapi.cloudapi.waitForVolumeStates({
|
||||||
|
id: volumeId,
|
||||||
|
states: ['deleted', 'failed']
|
||||||
|
}, function onWaitDone(waitErr, volume) {
|
||||||
|
distraction.destroy();
|
||||||
|
next(waitErr);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
], arg: {}}, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
function do_delete(subcmd, opts, args, callback) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (opts.help) {
|
||||||
|
self.do_help('help', {}, [subcmd], callback);
|
||||||
|
return;
|
||||||
|
} else if (args.length < 1) {
|
||||||
|
callback(new errors.UsageError('missing VOLUME arg(s)'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var context = {
|
||||||
|
volumeIds: args
|
||||||
|
};
|
||||||
|
|
||||||
|
vasync.pipeline({funcs: [
|
||||||
|
function setup(ctx, next) {
|
||||||
|
common.cliSetupTritonApi({
|
||||||
|
cli: self.top
|
||||||
|
}, function onSetup(setupErr) {
|
||||||
|
next(setupErr);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function deleteVolumes(ctx, next) {
|
||||||
|
vasync.forEachParallel({
|
||||||
|
func: function doDeleteVolume(volumeId, done) {
|
||||||
|
deleteVolume(volumeId, {
|
||||||
|
wait: opts.wait,
|
||||||
|
tritonapi: self.top.tritonapi
|
||||||
|
}, done);
|
||||||
|
},
|
||||||
|
inputs: ctx.volumeIds
|
||||||
|
}, next);
|
||||||
|
}
|
||||||
|
], arg: context}, function onDone(err) {
|
||||||
|
if (err) {
|
||||||
|
perror(err);
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('%s volume %s', common.capitalize('delete'), args);
|
||||||
|
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
do_delete.options = [
|
||||||
|
{
|
||||||
|
names: ['help', 'h'],
|
||||||
|
type: 'bool',
|
||||||
|
help: 'Show this help.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
group: 'Other options'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
names: ['wait', 'w'],
|
||||||
|
type: 'arrayOfBool',
|
||||||
|
help: 'Wait for the deletion to complete. Use multiple times for a ' +
|
||||||
|
'spinner.'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
do_delete.synopses = ['{{name}} {{cmd}} [OPTIONS] VOLUME [VOLUME ...]'];
|
||||||
|
|
||||||
|
do_delete.help = [
|
||||||
|
'Deletes a volume.',
|
||||||
|
'',
|
||||||
|
'{{usage}}',
|
||||||
|
'',
|
||||||
|
'{{options}}',
|
||||||
|
'',
|
||||||
|
'Where VOLUME is a volume id (full UUID), exact name, or short id.'
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
do_delete.completionArgtypes = ['tritonvolume', 'none'];
|
||||||
|
do_delete.aliases = ['rm'];
|
||||||
|
|
||||||
|
module.exports = do_delete;
|
79
lib/do_volume/do_get.js
Normal file
79
lib/do_volume/do_get.js
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* 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 2017 Joyent, Inc.
|
||||||
|
*
|
||||||
|
* `triton volume get ...`
|
||||||
|
*/
|
||||||
|
|
||||||
|
var format = require('util').format;
|
||||||
|
|
||||||
|
var common = require('../common');
|
||||||
|
var errors = require('../errors');
|
||||||
|
|
||||||
|
function do_get(subcmd, opts, args, callback) {
|
||||||
|
if (opts.help) {
|
||||||
|
this.do_help('help', {}, [subcmd], callback);
|
||||||
|
return;
|
||||||
|
} else if (args.length !== 1) {
|
||||||
|
return callback(new errors.UsageError(format(
|
||||||
|
'incorrect number of args (%d)', args.length)));
|
||||||
|
}
|
||||||
|
|
||||||
|
var tritonapi = this.top.tritonapi;
|
||||||
|
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
|
||||||
|
if (setupErr) {
|
||||||
|
callback(setupErr);
|
||||||
|
}
|
||||||
|
tritonapi.getVolume(args[0], function onRes(err, volume) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.json) {
|
||||||
|
console.log(JSON.stringify(volume));
|
||||||
|
} else {
|
||||||
|
console.log(JSON.stringify(volume, null, 4));
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
do_get.options = [
|
||||||
|
{
|
||||||
|
names: ['help', 'h'],
|
||||||
|
type: 'bool',
|
||||||
|
help: 'Show this help.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
names: ['json', 'j'],
|
||||||
|
type: 'bool',
|
||||||
|
help: 'JSON stream output.'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
do_get.synopses = ['{{name}} {{cmd}} [OPTIONS] VOLUME'];
|
||||||
|
|
||||||
|
do_get.help = [
|
||||||
|
/* BEGIN JSSTYLED */
|
||||||
|
'Get a volume.',
|
||||||
|
'',
|
||||||
|
'{{usage}}',
|
||||||
|
'',
|
||||||
|
'{{options}}',
|
||||||
|
'',
|
||||||
|
'Where VOLUME is a volume id (full UUID), exact name, or short id.',
|
||||||
|
'',
|
||||||
|
'Note: Currently this dumps prettified JSON by default. That might change',
|
||||||
|
'in the future. Use "-j" to explicitly get JSON output.'
|
||||||
|
/* END JSSTYLED */
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
do_get.completionArgtypes = ['tritonvolume', 'none'];
|
||||||
|
|
||||||
|
module.exports = do_get;
|
136
lib/do_volume/do_list.js
Normal file
136
lib/do_volume/do_list.js
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
/*
|
||||||
|
* 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 2016 Joyent, Inc.
|
||||||
|
*
|
||||||
|
* `triton image list ...`
|
||||||
|
*/
|
||||||
|
|
||||||
|
var format = require('util').format;
|
||||||
|
var tabula = require('tabula');
|
||||||
|
|
||||||
|
var common = require('../common');
|
||||||
|
var errors = require('../errors');
|
||||||
|
|
||||||
|
var validFilters = [
|
||||||
|
'name',
|
||||||
|
'size',
|
||||||
|
'state',
|
||||||
|
'owner',
|
||||||
|
'type'
|
||||||
|
];
|
||||||
|
|
||||||
|
// columns default without -o
|
||||||
|
var columnsDefault = 'shortid,name,size,type,state';
|
||||||
|
|
||||||
|
// columns default with -l
|
||||||
|
var columnsDefaultLong = 'id,name,size,type,state';
|
||||||
|
|
||||||
|
// sort default with -s
|
||||||
|
var sortDefault = 'create_timestamp';
|
||||||
|
|
||||||
|
function do_list(subcmd, opts, args, callback) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (opts.help) {
|
||||||
|
this.do_help('help', {}, [subcmd], callback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var columns = columnsDefault;
|
||||||
|
if (opts.o) {
|
||||||
|
columns = opts.o;
|
||||||
|
} else if (opts.long) {
|
||||||
|
columns = columnsDefaultLong;
|
||||||
|
}
|
||||||
|
columns = columns.split(',');
|
||||||
|
|
||||||
|
var sort = opts.s.split(',');
|
||||||
|
|
||||||
|
var filterPredicate;
|
||||||
|
var listOpts;
|
||||||
|
|
||||||
|
if (args) {
|
||||||
|
try {
|
||||||
|
filterPredicate = common.kvToJSONPredicate(args, validFilters,
|
||||||
|
'and');
|
||||||
|
} catch (e) {
|
||||||
|
callback(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.all === undefined) {
|
||||||
|
filterPredicate = {
|
||||||
|
and: [
|
||||||
|
{ ne: ['state', 'deleted']},
|
||||||
|
{ ne: ['state', 'failed']}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filterPredicate) {
|
||||||
|
listOpts = {
|
||||||
|
predicate: JSON.stringify(filterPredicate)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
|
||||||
|
if (setupErr) {
|
||||||
|
callback(setupErr);
|
||||||
|
}
|
||||||
|
self.top.tritonapi.cloudapi.listVolumes(listOpts,
|
||||||
|
function onRes(listVolsErr, volumes, res) {
|
||||||
|
if (listVolsErr) {
|
||||||
|
return callback(listVolsErr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.json) {
|
||||||
|
common.jsonStream(volumes);
|
||||||
|
} else {
|
||||||
|
for (var i = 0; i < volumes.length; i++) {
|
||||||
|
var volume = volumes[i];
|
||||||
|
volume.shortid = volume.id.split('-', 1)[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
tabula(volumes, {
|
||||||
|
skipHeader: opts.H,
|
||||||
|
columns: columns,
|
||||||
|
sort: sort
|
||||||
|
});
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
do_list.options = [
|
||||||
|
{
|
||||||
|
names: ['help', 'h'],
|
||||||
|
type: 'bool',
|
||||||
|
help: 'Show this help.'
|
||||||
|
}
|
||||||
|
].concat(common.getCliTableOptions({
|
||||||
|
includeLong: true,
|
||||||
|
sortDefault: sortDefault
|
||||||
|
}));
|
||||||
|
|
||||||
|
do_list.synopses = ['{{name}} {{cmd}} [OPTIONS]'];
|
||||||
|
|
||||||
|
do_list.help = [
|
||||||
|
/* BEGIN JSSTYLED */
|
||||||
|
'List volumes.',
|
||||||
|
,
|
||||||
|
'{{usage}}',
|
||||||
|
'',
|
||||||
|
'{{options}}'
|
||||||
|
/* END JSSTYLED */
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
do_list.aliases = ['ls'];
|
||||||
|
|
||||||
|
module.exports = do_list;
|
51
lib/do_volume/index.js
Normal file
51
lib/do_volume/index.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* 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 2017 Joyent, Inc.
|
||||||
|
*
|
||||||
|
* `triton volume ...`
|
||||||
|
*/
|
||||||
|
|
||||||
|
var Cmdln = require('cmdln').Cmdln;
|
||||||
|
var util = require('util');
|
||||||
|
|
||||||
|
function VolumeCLI(top) {
|
||||||
|
this.top = top;
|
||||||
|
Cmdln.call(this, {
|
||||||
|
name: top.name + ' volume',
|
||||||
|
/* BEGIN JSSTYLED */
|
||||||
|
desc: [
|
||||||
|
'List and manage Triton volumes.'
|
||||||
|
].join('\n'),
|
||||||
|
/* END JSSTYLED */
|
||||||
|
helpOpts: {
|
||||||
|
minHelpCol: 24 /* line up with option help */
|
||||||
|
},
|
||||||
|
helpSubcmds: [
|
||||||
|
'help',
|
||||||
|
'list',
|
||||||
|
'get',
|
||||||
|
'create',
|
||||||
|
'delete'
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
util.inherits(VolumeCLI, Cmdln);
|
||||||
|
|
||||||
|
VolumeCLI.prototype.init = function init(opts, args, cb) {
|
||||||
|
this.log = this.top.log;
|
||||||
|
Cmdln.prototype.init.apply(this, arguments);
|
||||||
|
};
|
||||||
|
|
||||||
|
VolumeCLI.prototype.do_list = require('./do_list');
|
||||||
|
VolumeCLI.prototype.do_get = require('./do_get');
|
||||||
|
VolumeCLI.prototype.do_create = require('./do_create');
|
||||||
|
VolumeCLI.prototype.do_delete = require('./do_delete');
|
||||||
|
|
||||||
|
VolumeCLI.aliases = ['vol'];
|
||||||
|
|
||||||
|
module.exports = VolumeCLI;
|
26
lib/do_volumes.js
Normal file
26
lib/do_volumes.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* 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 20167 Joyent, Inc.
|
||||||
|
*
|
||||||
|
* `triton volumes ...` bwcompat shortcut for `triton volumes list ...`.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function do_volumes(subcmd, opts, args, callback) {
|
||||||
|
this.handlerFromSubcmd('volume').dispatch({
|
||||||
|
subcmd: 'list',
|
||||||
|
opts: opts,
|
||||||
|
args: args
|
||||||
|
}, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
do_volumes.help = 'A shortcut for "triton volumes list".';
|
||||||
|
do_volumes.aliases = ['vols'];
|
||||||
|
do_volumes.hidden = true;
|
||||||
|
do_volumes.options = require('./do_volume/do_list').options;
|
||||||
|
|
||||||
|
module.exports = do_volumes;
|
@ -2602,6 +2602,71 @@ function _waitForInstanceUpdate(opts, cb) {
|
|||||||
setImmediate(poll);
|
setImmediate(poll);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a volume by ID, exact name, or short ID, in that order.
|
||||||
|
*
|
||||||
|
* If there is more than one volume with that name, then this errors out.
|
||||||
|
*/
|
||||||
|
TritonApi.prototype.getVolume = function getVolume(name, cb) {
|
||||||
|
assert.string(name, 'name');
|
||||||
|
assert.func(cb, 'cb');
|
||||||
|
|
||||||
|
if (common.isUUID(name)) {
|
||||||
|
this.cloudapi.getVolume({id: name}, function (err, pkg) {
|
||||||
|
if (err) {
|
||||||
|
if (err.restCode === 'ResourceNotFound') {
|
||||||
|
err = new errors.ResourceNotFoundError(err,
|
||||||
|
format('volume with id %s was not found', name));
|
||||||
|
}
|
||||||
|
cb(err);
|
||||||
|
} else {
|
||||||
|
cb(null, pkg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.cloudapi.listVolumes({
|
||||||
|
predicate: JSON.stringify({
|
||||||
|
and: [
|
||||||
|
{ ne: ['state', 'deleted']},
|
||||||
|
{ ne: ['state', 'failed']}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}, function (err, volumes) {
|
||||||
|
if (err) {
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
|
var nameMatches = [];
|
||||||
|
var shortIdMatches = [];
|
||||||
|
for (var i = 0; i < volumes.length; i++) {
|
||||||
|
var volume = volumes[i];
|
||||||
|
if (volume.name === name) {
|
||||||
|
nameMatches.push(volume);
|
||||||
|
}
|
||||||
|
if (volume.id.slice(0, 8) === name) {
|
||||||
|
shortIdMatches.push(volume);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nameMatches.length === 1) {
|
||||||
|
cb(null, nameMatches[0]);
|
||||||
|
} else if (nameMatches.length > 1) {
|
||||||
|
cb(new errors.TritonError(format(
|
||||||
|
'volume name "%s" is ambiguous: matches %d volumes',
|
||||||
|
name, nameMatches.length)));
|
||||||
|
} else if (shortIdMatches.length === 1) {
|
||||||
|
cb(null, shortIdMatches[0]);
|
||||||
|
} else if (shortIdMatches.length === 0) {
|
||||||
|
cb(new errors.ResourceNotFoundError(format(
|
||||||
|
'no volume with name or short id "%s" was found', name)));
|
||||||
|
} else {
|
||||||
|
cb(new errors.ResourceNotFoundError(
|
||||||
|
format('no volume with name "%s" was found '
|
||||||
|
+ 'and "%s" is an ambiguous short id', name)));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
//---- exports
|
//---- exports
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "triton",
|
"name": "triton",
|
||||||
"description": "Joyent Triton CLI and client (https://www.joyent.com/triton)",
|
"description": "Joyent Triton CLI and client (https://www.joyent.com/triton)",
|
||||||
"version": "5.1.0",
|
"version": "5.2.0",
|
||||||
"author": "Joyent (joyent.com)",
|
"author": "Joyent (joyent.com)",
|
||||||
"homepage": "https://github.com/joyent/node-triton",
|
"homepage": "https://github.com/joyent/node-triton",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -97,7 +97,12 @@ var subs = [
|
|||||||
['rbac image-role-tags'],
|
['rbac image-role-tags'],
|
||||||
['rbac network-role-tags'],
|
['rbac network-role-tags'],
|
||||||
['rbac package-role-tags'],
|
['rbac package-role-tags'],
|
||||||
['rbac role-tags']
|
['rbac role-tags'],
|
||||||
|
['volume', 'vol'],
|
||||||
|
['volume list', 'volume ls', 'volumes', 'vols'],
|
||||||
|
['volume delete', 'volume rm'],
|
||||||
|
['volume create'],
|
||||||
|
['volume get']
|
||||||
];
|
];
|
||||||
|
|
||||||
// --- Tests
|
// --- Tests
|
||||||
|
172
test/integration/cli-volumes.test.js
Normal file
172
test/integration/cli-volumes.test.js
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
/*
|
||||||
|
* 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 2017, Joyent, Inc.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Test volume create command.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var format = require('util').format;
|
||||||
|
var os = require('os');
|
||||||
|
var test = require('tape');
|
||||||
|
var vasync = require('vasync');
|
||||||
|
|
||||||
|
var common = require('../../lib/common');
|
||||||
|
var h = require('./helpers');
|
||||||
|
|
||||||
|
var testOpts = {
|
||||||
|
skip: !h.CONFIG.allowWriteActions
|
||||||
|
};
|
||||||
|
|
||||||
|
test('triton volume create ...', testOpts, function (tt) {
|
||||||
|
var validVolumeName =
|
||||||
|
h.makeResourceName('node-triton-test-volume-create-default');
|
||||||
|
|
||||||
|
tt.comment('Test config:');
|
||||||
|
Object.keys(h.CONFIG).forEach(function (key) {
|
||||||
|
var value = h.CONFIG[key];
|
||||||
|
tt.comment(format('- %s: %j', key, value));
|
||||||
|
});
|
||||||
|
|
||||||
|
tt.test(' cleanup leftover resources', function (t) {
|
||||||
|
h.triton(['volume', 'delete', '-w', validVolumeName].join(' '),
|
||||||
|
function onDelVolume(delVolErr, stdout, stderr) {
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
tt.test(' triton volume create with invalid name', function (t) {
|
||||||
|
var invalidVolumeName =
|
||||||
|
h.makeResourceName('node-triton-test-volume-create-invalid-name-' +
|
||||||
|
'!foo!');
|
||||||
|
var expectedErrMsg = 'triton volume create: error (InvalidArgument): ' +
|
||||||
|
'Error: Invalid volume name: ' + invalidVolumeName;
|
||||||
|
|
||||||
|
h.triton([
|
||||||
|
'volume',
|
||||||
|
'create',
|
||||||
|
'--name',
|
||||||
|
invalidVolumeName
|
||||||
|
].join(' '), function (volCreateErr, stdout, stderr) {
|
||||||
|
t.equal(stderr.indexOf(expectedErrMsg), 0,
|
||||||
|
'stderr should include error message: ' + expectedErrMsg);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
tt.test(' triton volume create with invalid size', function (t) {
|
||||||
|
var invalidSize = 'foobar';
|
||||||
|
var expectedErrMsg = 'triton volume create: error (InvalidArgument): ' +
|
||||||
|
'Error: Invalid volume size: ' + invalidSize;
|
||||||
|
var volumeName =
|
||||||
|
h.makeResourceName('node-triton-test-volume-create-invalid-size');
|
||||||
|
|
||||||
|
h.triton([
|
||||||
|
'volume',
|
||||||
|
'create',
|
||||||
|
'--name',
|
||||||
|
volumeName,
|
||||||
|
'--size',
|
||||||
|
invalidSize
|
||||||
|
].join(' '), function (volCreateErr, stdout, stderr) {
|
||||||
|
t.equal(stderr.indexOf(expectedErrMsg), 0,
|
||||||
|
'stderr should include error message: ' + expectedErrMsg);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
tt.test(' triton volume create with invalid type', function (t) {
|
||||||
|
var invalidType = 'foobar';
|
||||||
|
var volumeName =
|
||||||
|
h.makeResourceName('node-triton-test-volume-create-invalid-type');
|
||||||
|
var expectedErrMsg = 'triton volume create: error (InvalidArgument): ' +
|
||||||
|
'Error: Invalid volume type: ' + invalidType;
|
||||||
|
|
||||||
|
h.triton([
|
||||||
|
'volume',
|
||||||
|
'create',
|
||||||
|
'--name',
|
||||||
|
volumeName,
|
||||||
|
'--type',
|
||||||
|
invalidType
|
||||||
|
].join(' '), function (volCreateErr, stdout, stderr) {
|
||||||
|
t.equal(stderr.indexOf(expectedErrMsg), 0,
|
||||||
|
'stderr should include error message: ' + expectedErrMsg);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
tt.test(' triton volume create with invalid network', function (t) {
|
||||||
|
var volumeName =
|
||||||
|
h.makeResourceName('node-triton-test-volume-create-invalid-' +
|
||||||
|
'network');
|
||||||
|
var invalidNetwork = 'foobar';
|
||||||
|
var expectedErrMsg =
|
||||||
|
'triton volume create: error: first of 1 error: no network with ' +
|
||||||
|
'name or short id "' + invalidNetwork + '" was found';
|
||||||
|
|
||||||
|
h.triton([
|
||||||
|
'volume',
|
||||||
|
'create',
|
||||||
|
'--name',
|
||||||
|
volumeName,
|
||||||
|
'--network',
|
||||||
|
invalidNetwork
|
||||||
|
].join(' '), function (volCreateErr, stdout, stderr) {
|
||||||
|
t.equal(stderr.indexOf(expectedErrMsg), 0,
|
||||||
|
'stderr should include error message: ' + expectedErrMsg);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
tt.test(' triton volume create valid volume', function (t) {
|
||||||
|
h.triton([
|
||||||
|
'volume',
|
||||||
|
'create',
|
||||||
|
'--name',
|
||||||
|
validVolumeName,
|
||||||
|
'-w'
|
||||||
|
].join(' '), function (volCreateErr, stdout, stderr) {
|
||||||
|
t.equal(volCreateErr, null,
|
||||||
|
'volume creation should not error');
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
tt.test(' check volume was created', function (t) {
|
||||||
|
h.safeTriton(t, ['volume', 'get', validVolumeName],
|
||||||
|
function onGetVolume(getVolErr, stdout) {
|
||||||
|
t.equal(getVolErr, null,
|
||||||
|
'Getting volume should not error');
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
tt.test(' delete volume', function (t) {
|
||||||
|
h.triton(['volume', 'delete', '-w', validVolumeName].join(' '),
|
||||||
|
function onDelVolume(delVolErr, stdout, stderr) {
|
||||||
|
t.equal(delVolErr, null,
|
||||||
|
'Deleting volume should not error');
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
tt.test(' check volume was deleted', function (t) {
|
||||||
|
h.triton(['volume', 'get', validVolumeName].join(' '),
|
||||||
|
function onGetVolume(getVolErr, stdout, stderr) {
|
||||||
|
t.ok(getVolErr,
|
||||||
|
'Getting volume ' + validVolumeName + 'after deleting it ' +
|
||||||
|
'should errorr');
|
||||||
|
t.notEqual(stderr.indexOf('ResourceNotFound'), -1,
|
||||||
|
'Getting volume ' + validVolumeName + 'should not find it');
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@ -12,9 +12,10 @@
|
|||||||
* Test helpers for the integration tests
|
* Test helpers for the integration tests
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var error = console.error;
|
|
||||||
var assert = require('assert-plus');
|
var assert = require('assert-plus');
|
||||||
|
var error = console.error;
|
||||||
var f = require('util').format;
|
var f = require('util').format;
|
||||||
|
var os = require('os');
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var tabula = require('tabula');
|
var tabula = require('tabula');
|
||||||
|
|
||||||
@ -359,6 +360,14 @@ function printConfig(t) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns a string that represents a unique resource name for the host on which
|
||||||
|
* this function is called.
|
||||||
|
*/
|
||||||
|
function makeResourceName(prefix) {
|
||||||
|
assert.string(prefix, 'prefix');
|
||||||
|
return prefix + '-' + os.hostname();
|
||||||
|
}
|
||||||
|
|
||||||
// --- exports
|
// --- exports
|
||||||
|
|
||||||
@ -373,6 +382,7 @@ module.exports = {
|
|||||||
getTestPkg: getTestPkg,
|
getTestPkg: getTestPkg,
|
||||||
getResizeTestPkg: getResizeTestPkg,
|
getResizeTestPkg: getResizeTestPkg,
|
||||||
jsonStreamParse: jsonStreamParse,
|
jsonStreamParse: jsonStreamParse,
|
||||||
|
makeResourceName: makeResourceName,
|
||||||
printConfig: printConfig,
|
printConfig: printConfig,
|
||||||
|
|
||||||
ifErr: testcommon.ifErr
|
ifErr: testcommon.ifErr
|
||||||
|
Reference in New Issue
Block a user