From de9505d5697e7e67f0430a2a2192091d22123f8b Mon Sep 17 00:00:00 2001 From: Josh Wilsdon Date: Tue, 29 Aug 2017 20:48:52 -0700 Subject: [PATCH] initial work on node-triton for PUBAPI-1420 --- lib/do_instance/do_create.js | 95 +++++++++++++++++++++++++++++++++++- 1 file changed, 93 insertions(+), 2 deletions(-) diff --git a/lib/do_instance/do_create.js b/lib/do_instance/do_create.js index cc0c636..fe53e8c 100644 --- a/lib/do_instance/do_create.js +++ b/lib/do_instance/do_create.js @@ -5,7 +5,7 @@ */ /* - * Copyright 2016 Joyent, Inc. + * Copyright 2017 Joyent, Inc. * * `triton instance create ...` */ @@ -20,6 +20,59 @@ var distractions = require('../distractions'); var errors = require('../errors'); var mat = require('../metadataandtags'); +function parseVolume(volume) { + var components; + var VALID_MODES = ['ro', 'rw']; + var VALID_VOLUME_NAME_REGEXP = /^[a-zA-Z0-9][a-zA-Z0-9_\.\-]+$/; + + if (typeof(volume) !== 'string') { + return new errors.UsageError('unparseable volume specified'); + } + + components = volume.split(':'); + if (components.length !== 2 && components.length !== 3) { + return new errors.UsageError('invalid volume specified, must be in ' + + 'the form ":[:]", got: "' + volume + + '"'); + } + + // 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(components[0])) { + 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 (components[1].length === 0 || (components[1])[0] !== '/') { + return new errors.UsageError('invalid volume mountpoint, must be ' + + 'absolute path, got: "' + volume + '"'); + } + if (components[1].indexOf('\0') !== -1) { + return new errors.UsageError('invalid volume mountpoint, contains ' + + 'invalid characters, got: "' + volume + '"'); + } + if (components[1].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(components[2]) === -1) { + return new errors.UsageError('invalid volume mode, got: "' + volume + + '"'); + } + + return { + mode: components[2] || 'rw', + mountpoint: components[1], + name: components[0] + }; + + return undefined; +} function do_create(subcmd, opts, args, cb) { if (opts.help) { @@ -132,6 +185,37 @@ function do_create(subcmd, opts, args, cb) { next(); }, + /* + * Make sure if volumes were passed, they're in the correct form. + */ + function parseVolumes(ctx, next) { + var idx; + var validationErr; + var volume; + var volumes = []; + + if (!opts.volume) { + next(); + return; + } + + for (idx = 0; idx < opts.volume.length; idx++) { + volume = parseVolume(opts.volume[idx]); + if (volume instanceof Error) { + validationErr = volume; + next(validationErr); + return; + } + volumes.push(volume); + } + + if (volumes.length > 0) { + ctx.volumes = volumes; + } + + next(); + }, + /* * Determine `ctx.locality` according to what CloudAPI supports * based on `ctx.affinities` parsed earlier. @@ -279,7 +363,8 @@ function do_create(subcmd, opts, args, cb) { image: ctx.img.id, 'package': ctx.pkg && ctx.pkg.id, networks: ctx.nets && ctx.nets.map( - function (net) { return net.id; }) + function (net) { return net.id; }), + volumes: ctx.volumes && ctx.volumes }; if (ctx.locality) { createOpts.locality = ctx.locality; @@ -495,6 +580,12 @@ do_create.options = [ names: ['json', 'j'], type: 'bool', help: 'JSON stream output.' + }, + { + names: ['volume', 'v'], + type: 'arrayOfString', + help: 'Mount a volume into the container (non-KVM only) ' + + '[:access-mode]' } ];