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