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
 | 
			
		||||
 | 
			
		||||
- [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,7 +5,7 @@
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 Joyent, Inc.
 | 
			
		||||
 * Copyright 2017 Joyent, Inc.
 | 
			
		||||
 *
 | 
			
		||||
 * `triton instance create ...`
 | 
			
		||||
 */
 | 
			
		||||
@ -20,6 +20,62 @@ var distractions = require('../distractions');
 | 
			
		||||
var errors = require('../errors');
 | 
			
		||||
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) {
 | 
			
		||||
    if (opts.help) {
 | 
			
		||||
@ -132,6 +188,42 @@ function do_create(subcmd, opts, args, cb) {
 | 
			
		||||
            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
 | 
			
		||||
         * based on `ctx.affinities` parsed earlier.
 | 
			
		||||
@ -274,6 +366,8 @@ function do_create(subcmd, opts, args, cb) {
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        function createInst(ctx, next) {
 | 
			
		||||
            assert.optionalArrayOfObject(ctx.volMounts, 'ctx.volMounts');
 | 
			
		||||
 | 
			
		||||
            var createOpts = {
 | 
			
		||||
                name: opts.name,
 | 
			
		||||
                image: ctx.img.id,
 | 
			
		||||
@ -281,6 +375,10 @@ function do_create(subcmd, opts, args, cb) {
 | 
			
		||||
                networks: ctx.nets && ctx.nets.map(
 | 
			
		||||
                    function (net) { return net.id; })
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            if (ctx.volMounts) {
 | 
			
		||||
                createOpts.volumes = ctx.volMounts;
 | 
			
		||||
            }
 | 
			
		||||
            if (ctx.locality) {
 | 
			
		||||
                createOpts.locality = ctx.locality;
 | 
			
		||||
            }
 | 
			
		||||
@ -446,6 +544,17 @@ do_create.options = [
 | 
			
		||||
        help: 'Enable Cloud Firewall on this instance. See ' +
 | 
			
		||||
            '<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: ''
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user