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