PUBAPI-1420 Add ability to mount NFS volumes with CreateMachine endpoint
Reviewed by: Trent Mick <trentm@gmail.com> Approved by: Trent Mick <trentm@gmail.com>
This commit is contained in:
parent
8438f446cc
commit
45ed8883ef
@ -8,6 +8,8 @@ Known issues:
|
|||||||
## not yet released
|
## not yet released
|
||||||
|
|
||||||
- [joyent/node-triton#226] added new `triton volume sizes` subcommand.
|
- [joyent/node-triton#226] added new `triton volume sizes` subcommand.
|
||||||
|
- [PUBAPI-1420] added support for mounting volumes in LX and SmartOS instances.
|
||||||
|
E.g., `triton instance create --volume VOLUME ...`.
|
||||||
|
|
||||||
## 5.3.1
|
## 5.3.1
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2016 Joyent, Inc.
|
* Copyright 2017 Joyent, Inc.
|
||||||
*
|
*
|
||||||
* `triton instance create ...`
|
* `triton instance create ...`
|
||||||
*/
|
*/
|
||||||
@ -20,6 +20,62 @@ var distractions = require('../distractions');
|
|||||||
var errors = require('../errors');
|
var errors = require('../errors');
|
||||||
var mat = require('../metadataandtags');
|
var mat = require('../metadataandtags');
|
||||||
|
|
||||||
|
function parseVolMount(volume) {
|
||||||
|
var components;
|
||||||
|
var volMode;
|
||||||
|
var volMountpoint;
|
||||||
|
var volName;
|
||||||
|
var VALID_MODES = ['ro', 'rw'];
|
||||||
|
var VALID_VOLUME_NAME_REGEXP = /^[a-zA-Z0-9][a-zA-Z0-9_\.\-]+$/;
|
||||||
|
|
||||||
|
assert.string(volume, 'volume');
|
||||||
|
|
||||||
|
components = volume.split(':');
|
||||||
|
if (components.length !== 2 && components.length !== 3) {
|
||||||
|
return new errors.UsageError('invalid volume specified, must be in ' +
|
||||||
|
'the form "<volume name>:<mount path>[:<mode>]", got: "' + volume +
|
||||||
|
'"');
|
||||||
|
}
|
||||||
|
|
||||||
|
volName = components[0];
|
||||||
|
volMountpoint = components[1];
|
||||||
|
volMode = components[2];
|
||||||
|
|
||||||
|
// first component should be a volume name. We only check here that it
|
||||||
|
// syntactically looks like a volume name, we'll leave the upstream to
|
||||||
|
// determine if it's not actually a volume.
|
||||||
|
if (!VALID_VOLUME_NAME_REGEXP.test(volName)) {
|
||||||
|
return new errors.UsageError('invalid volume name, got: "' + volume +
|
||||||
|
'"');
|
||||||
|
}
|
||||||
|
|
||||||
|
// second component should be an absolute path
|
||||||
|
// NOTE: if we ever move past node 0.10, we could use path.isAbsolute(path)
|
||||||
|
if (volMountpoint.length === 0 || volMountpoint[0] !== '/') {
|
||||||
|
return new errors.UsageError('invalid volume mountpoint, must be ' +
|
||||||
|
'absolute path, got: "' + volume + '"');
|
||||||
|
}
|
||||||
|
if (volMountpoint.indexOf('\0') !== -1) {
|
||||||
|
return new errors.UsageError('invalid volume mountpoint, contains ' +
|
||||||
|
'invalid characters, got: "' + volume + '"');
|
||||||
|
}
|
||||||
|
if (volMountpoint.search(/[^\/]/) === -1) {
|
||||||
|
return new errors.UsageError('invalid volume mountpoint, must contain' +
|
||||||
|
' at least one non-/ character, got: "' + volume + '"');
|
||||||
|
}
|
||||||
|
|
||||||
|
// third component is optional mode: 'ro' or 'rw'
|
||||||
|
if (components.length === 3 && VALID_MODES.indexOf(volMode) === -1) {
|
||||||
|
return new errors.UsageError('invalid volume mode, got: "' + volume +
|
||||||
|
'"');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
mode: volMode || 'rw',
|
||||||
|
mountpoint: volMountpoint,
|
||||||
|
name: volName
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function do_create(subcmd, opts, args, cb) {
|
function do_create(subcmd, opts, args, cb) {
|
||||||
if (opts.help) {
|
if (opts.help) {
|
||||||
@ -132,6 +188,42 @@ function do_create(subcmd, opts, args, cb) {
|
|||||||
next();
|
next();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Make sure if volumes were passed, they're in the correct form.
|
||||||
|
*/
|
||||||
|
function parseVolMounts(ctx, next) {
|
||||||
|
var idx;
|
||||||
|
var validationErrs = [];
|
||||||
|
var parsedObj;
|
||||||
|
var volMounts = [];
|
||||||
|
|
||||||
|
if (!opts.volume) {
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (idx = 0; idx < opts.volume.length; idx++) {
|
||||||
|
parsedObj = parseVolMount(opts.volume[idx]);
|
||||||
|
if (parsedObj instanceof Error) {
|
||||||
|
validationErrs.push(parsedObj);
|
||||||
|
} else {
|
||||||
|
// if it's not an error, it's a volume
|
||||||
|
volMounts.push(parsedObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validationErrs.length > 0) {
|
||||||
|
next(new errors.MultiError(validationErrs));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (volMounts.length > 0) {
|
||||||
|
ctx.volMounts = volMounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
},
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Determine `ctx.locality` according to what CloudAPI supports
|
* Determine `ctx.locality` according to what CloudAPI supports
|
||||||
* based on `ctx.affinities` parsed earlier.
|
* based on `ctx.affinities` parsed earlier.
|
||||||
@ -274,6 +366,8 @@ function do_create(subcmd, opts, args, cb) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
function createInst(ctx, next) {
|
function createInst(ctx, next) {
|
||||||
|
assert.optionalArrayOfObject(ctx.volMounts, 'ctx.volMounts');
|
||||||
|
|
||||||
var createOpts = {
|
var createOpts = {
|
||||||
name: opts.name,
|
name: opts.name,
|
||||||
image: ctx.img.id,
|
image: ctx.img.id,
|
||||||
@ -281,6 +375,10 @@ function do_create(subcmd, opts, args, cb) {
|
|||||||
networks: ctx.nets && ctx.nets.map(
|
networks: ctx.nets && ctx.nets.map(
|
||||||
function (net) { return net.id; })
|
function (net) { return net.id; })
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (ctx.volMounts) {
|
||||||
|
createOpts.volumes = ctx.volMounts;
|
||||||
|
}
|
||||||
if (ctx.locality) {
|
if (ctx.locality) {
|
||||||
createOpts.locality = ctx.locality;
|
createOpts.locality = ctx.locality;
|
||||||
}
|
}
|
||||||
@ -446,6 +544,17 @@ do_create.options = [
|
|||||||
help: 'Enable Cloud Firewall on this instance. See ' +
|
help: 'Enable Cloud Firewall on this instance. See ' +
|
||||||
'<https://docs.joyent.com/public-cloud/network/firewall>'
|
'<https://docs.joyent.com/public-cloud/network/firewall>'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
names: ['volume', 'v'],
|
||||||
|
type: 'arrayOfString',
|
||||||
|
help: 'Mount a volume into the instance (non-KVM only). VOLMOUNT is ' +
|
||||||
|
'"<volume-name:/mount/point>[:access-mode]" where access mode is ' +
|
||||||
|
'one of "ro" for read-only or "rw" for read-write (default). For ' +
|
||||||
|
'example: "-v myvolume:/mnt:ro" to mount "myvolume" read-only on ' +
|
||||||
|
'/mnt in this instance.',
|
||||||
|
helpArg: 'VOLMOUNT',
|
||||||
|
hidden: true
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
group: ''
|
group: ''
|
||||||
|
Reference in New Issue
Block a user