From e6334db9e1ab0313cb5036ba7b4eaa72d9a63fbe Mon Sep 17 00:00:00 2001 From: Marsell Kukuljevic Date: Fri, 5 Feb 2016 23:54:13 +1100 Subject: [PATCH] PUBAPI-1233/PUBAPI-1234 - add support for --snapshot= flag when starting a machine, and shortId support for fwrules. --- lib/cloudapi2.js | 5 ++- lib/common.js | 3 +- lib/do_fwrule/do_delete.js | 21 +++++++---- lib/do_fwrule/do_disable.js | 49 +++++++++++++++++-------- lib/do_fwrule/do_enable.js | 51 ++++++++++++++++++-------- lib/do_fwrule/do_get.js | 3 +- lib/do_fwrule/do_list.js | 2 +- lib/do_fwrule/do_update.js | 37 +++++++++++++++---- lib/do_instance/gen_do_ACTION.js | 18 ++++++++- lib/do_snapshot/index.js | 4 +- lib/tritonapi.js | 49 ++++++++++++++++++++++++- test/integration/cli-snapshots.test.js | 13 +++++++ 12 files changed, 197 insertions(+), 58 deletions(-) diff --git a/lib/cloudapi2.js b/lib/cloudapi2.js index e6c7f05..eea7186 100644 --- a/lib/cloudapi2.js +++ b/lib/cloudapi2.js @@ -1067,6 +1067,7 @@ function getMachineSnapshot(opts, cb) { * - {String} name (required) the snapshot's name. * @param {Function} callback of the form f(err, res). */ +CloudApi.prototype.startMachineFromSnapshot = function startMachineFromSnapshot(opts, cb) { assert.object(opts, 'opts'); assert.uuid(opts.id, 'opts.id'); @@ -1075,13 +1076,13 @@ function startMachineFromSnapshot(opts, cb) { this._request({ method: 'POST', - path: format('/%s/machine/%s/snapshots/%s', this.account, opts.id, + path: format('/%s/machines/%s/snapshots/%s', this.account, opts.id, opts.name), data: opts }, function (err, req, res, body) { cb(err, body, res); }); -} +}; /** diff --git a/lib/common.js b/lib/common.js index b5af9c7..54a53fc 100644 --- a/lib/common.js +++ b/lib/common.js @@ -314,8 +314,7 @@ function uuidToShortId(s) { * * Short IDs: * - UUID prefix - * - allow '-' to be elided (to support using containers IDs from - * docker) + * - allow '-' to be elided (to support using containers IDs from docker) * - support docker ID *longer* than a UUID? The curr implementation does. */ function normShortId(s) { diff --git a/lib/do_fwrule/do_delete.js b/lib/do_fwrule/do_delete.js index 2e612c2..e7a5aee 100644 --- a/lib/do_fwrule/do_delete.js +++ b/lib/do_fwrule/do_delete.js @@ -12,8 +12,6 @@ var assert = require('assert-plus'); var format = require('util').format; -var fs = require('fs'); -var sshpk = require('sshpk'); var vasync = require('vasync'); var common = require('../common'); @@ -60,19 +58,26 @@ function do_delete(subcmd, opts, args, cb) { }); }, function deleteThem(_, next) { - vasync.forEachPipeline({ + vasync.forEachParallel({ inputs: ruleIds, func: function deleteOne(id, nextId) { - cli.tritonapi.cloudapi.deleteFirewallRule({ - id: id - }, function (err) { + cli.tritonapi.getFirewallRule(id, function (err, fwrule) { if (err) { nextId(err); return; } - console.log('Deleted rule %s', id); - nextId(); + cli.tritonapi.cloudapi.deleteFirewallRule({ + id: fwrule.id + }, function (err2) { + if (err2) { + nextId(err2); + return; + } + + console.log('Deleted rule %s', id); + nextId(); + }); }); } }, next); diff --git a/lib/do_fwrule/do_disable.js b/lib/do_fwrule/do_disable.js index 3a132de..8a015f7 100644 --- a/lib/do_fwrule/do_disable.js +++ b/lib/do_fwrule/do_disable.js @@ -11,6 +11,7 @@ */ var assert = require('assert-plus'); +var vasync = require('vasync'); var common = require('../common'); var errors = require('../errors'); @@ -25,27 +26,45 @@ function do_disable(subcmd, opts, args, cb) { } if (args.length === 0) { - cb(new errors.UsageError('Missing FWRULE-ID argument')); - return; - } else if (args.length > 1) { - cb(new errors.UsageError('Incorrect number of arguments')); + cb(new errors.UsageError('Missing FWRULE-ID argument(s)')); return; } - var id = args[0]; var cli = this.top; - // XXX add support for shortId - cli.tritonapi.cloudapi.disableFirewallRule(id, function onRule(err) { - if (err) { - cb(err); - return; + vasync.forEachParallel({ + inputs: args, + func: function disableOne(id, nextId) { + if (common.isUUID(id)) { + enable(); + return; + } + + // we need to look up the full UUID if the given id is a short id + cli.tritonapi.getFirewallRule(id, function onRule(err, fwrule) { + if (err) { + nextId(err); + return; + } + + id = fwrule.id; + + enable(); + }); + + function enable() { + cli.tritonapi.cloudapi.disableFirewallRule(id, function (err) { + if (err) { + nextId(err); + return; + } + + console.log('Disabled firewall rule %s', id); + nextId(); + }); + } } - - console.log('Disabled firewall rule %s', id); - - cb(); - }); + }, cb); } diff --git a/lib/do_fwrule/do_enable.js b/lib/do_fwrule/do_enable.js index 383fbbb..708b588 100644 --- a/lib/do_fwrule/do_enable.js +++ b/lib/do_fwrule/do_enable.js @@ -11,6 +11,7 @@ */ var assert = require('assert-plus'); +var vasync = require('vasync'); var common = require('../common'); var errors = require('../errors'); @@ -25,27 +26,45 @@ function do_enable(subcmd, opts, args, cb) { } if (args.length === 0) { - cb(new errors.UsageError('Missing FWRULE-ID argument')); - return; - } else if (args.length > 1) { - cb(new errors.UsageError('Incorrect number of arguments')); + cb(new errors.UsageError('Missing FWRULE-ID argument(s)')); return; } - var id = args[0]; var cli = this.top; - // XXX add support for shortId - cli.tritonapi.cloudapi.enableFirewallRule(id, function onRule(err) { - if (err) { - cb(err); - return; + vasync.forEachParallel({ + inputs: args, + func: function enableOne(id, nextId) { + if (common.isUUID(id)) { + enable(); + return; + } + + // we need to look up the full UUID if the given id is a short id + cli.tritonapi.getFirewallRule(id, function onRule(err, fwrule) { + if (err) { + nextId(err); + return; + } + + id = fwrule.id; + + enable(); + }); + + function enable() { + cli.tritonapi.cloudapi.enableFirewallRule(id, function (err) { + if (err) { + nextId(err); + return; + } + + console.log('Enabled firewall rule %s', id); + nextId(); + }); + } } - - console.log('Enabled firewall rule %s', id); - - cb(); - }); + }, cb); } @@ -60,7 +79,7 @@ do_enable.help = [ 'Enable a specific firewall rule.', '', 'Usage:', - ' {{name}} enable FWRULE-ID', + ' {{name}} enable FWRULE-ID [FWRULE-ID...]', '', '{{options}}' ].join('\n'); diff --git a/lib/do_fwrule/do_get.js b/lib/do_fwrule/do_get.js index 6c0b736..a18eea5 100644 --- a/lib/do_fwrule/do_get.js +++ b/lib/do_fwrule/do_get.js @@ -36,8 +36,7 @@ function do_get(subcmd, opts, args, cb) { var id = args[0]; var cli = this.top; - // XXX add support for shortId - cli.tritonapi.cloudapi.getFirewallRule(id, function onRule(err, fwrule) { + cli.tritonapi.getFirewallRule(id, function onRule(err, fwrule) { if (err) { cb(err); return; diff --git a/lib/do_fwrule/do_list.js b/lib/do_fwrule/do_list.js index 5321793..9103e7f 100644 --- a/lib/do_fwrule/do_list.js +++ b/lib/do_fwrule/do_list.js @@ -58,7 +58,7 @@ function do_list(subcmd, opts, args, cb) { if (columns.indexOf('shortid') !== -1) { rules.forEach(function (rule) { - rule.shortid = common.normShortId(rule.id); + rule.shortid = common.uuidToShortId(rule.id); }); } diff --git a/lib/do_fwrule/do_update.js b/lib/do_fwrule/do_update.js index b81f3de..410196f 100644 --- a/lib/do_fwrule/do_update.js +++ b/lib/do_fwrule/do_update.js @@ -106,6 +106,13 @@ function do_update(subcmd, opts, args, cb) { function validateIt(ctx, next) { var keys = Object.keys(ctx.data); + + if (keys.length === 0) { + console.log('No fields given for firewall rule update'); + next(); + return; + } + for (var i = 0; i < keys.length; i++) { var key = keys[i]; var value = ctx.data[key]; @@ -129,24 +136,38 @@ function do_update(subcmd, opts, args, cb) { next(); }, - function updateAway(ctx, next) { - var keys = Object.keys(ctx.data); - if (keys.length === 0) { - console.log('No fields given for firewall rule update'); + // we need to look up the full UUID if the given id is a short id + function getFullId(ctx, next) { + if (common.isUUID(id)) { + ctx.data.id = id; next(); return; } - ctx.data.id = id; - - tritonapi.cloudapi.updateFirewallRule(ctx.data, function (err) { + tritonapi.getFirewallRule(id, function onRule(err, fwrule) { if (err) { next(err); return; } + ctx.data.id = fwrule.id; + + next(); + }); + }, + + function updateAway(ctx, next) { + var data = ctx.data; + + tritonapi.cloudapi.updateFirewallRule(data, function (err) { + if (err) { + next(err); + return; + } + + delete data.id; console.log('Updated firewall rule %s (fields: %s)', id, - keys.join(', ')); + Object.keys(data).join(', ')); next(); }); diff --git a/lib/do_instance/gen_do_ACTION.js b/lib/do_instance/gen_do_ACTION.js index f813e6d..b418be0 100644 --- a/lib/do_instance/gen_do_ACTION.js +++ b/lib/do_instance/gen_do_ACTION.js @@ -66,6 +66,14 @@ function gen_do_ACTION(opts) { } ]; + if (action === 'start') { + do_ACTION.options.push({ + names: ['snapshot'], + type: 'string', + help: 'Name of snapshot to start machine with.' + }); + } + return do_ACTION; } @@ -77,7 +85,8 @@ function _doTheAction(action, subcmd, opts, args, callback) { var command, state; switch (action) { case 'start': - command = 'startMachine'; + command = opts.snapshot ? 'startMachineFromSnapshot' : + 'startMachine'; state = 'running'; break; case 'stop': @@ -126,7 +135,12 @@ function _doTheAction(action, subcmd, opts, args, callback) { // called when "uuid" is set function done() { - self.top.tritonapi.cloudapi[command](uuid, + var cOpts = uuid; + if (command === 'startMachineFromSnapshot') { + cOpts = { id: uuid, name: opts.snapshot }; + } + + self.top.tritonapi.cloudapi[command](cOpts, function (err, body, res) { if (err) { diff --git a/lib/do_snapshot/index.js b/lib/do_snapshot/index.js index 42ac4a5..af6658c 100644 --- a/lib/do_snapshot/index.js +++ b/lib/do_snapshot/index.js @@ -30,7 +30,9 @@ function SnapshotCLI(top) { 'list', 'get', 'delete' - ] + ], + helpBody: 'Machines can be rolled back to a snapshot using\n' + + '`triton instance start --snapshot=SNAPSHOT-NAME`' }); } util.inherits(SnapshotCLI, Cmdln); diff --git a/lib/tritonapi.js b/lib/tritonapi.js index 6dbb4c3..c456d74 100644 --- a/lib/tritonapi.js +++ b/lib/tritonapi.js @@ -221,7 +221,7 @@ TritonApi.prototype._cacheGetJson = function _cacheGetJson(key, cb) { * * @param opts {Object} Optional. * - useCache {Boolean} Default false. Whether to use Triton's local cache. - * Note that the *currently* implementation will only use the cache + * Note that the *current* implementation will only use the cache * when there are no filter options. * - ... all other cloudapi ListImages options per * @@ -537,6 +537,53 @@ TritonApi.prototype.getNetwork = function getNetwork(name, cb) { }; +/** + * Get a firewall rule by ID, or short ID, in that order. + * + * If there is more than one firewall rule with that short ID, then this errors + * out. + */ +TritonApi.prototype.getFirewallRule = function getFirewallRule(id, cb) { + assert.string(id, 'id'); + assert.func(cb, 'cb'); + + if (common.isUUID(id)) { + this.cloudapi.getFirewallRule(id, function (err, fwrule) { + if (err) { + if (err.restCode === 'ResourceNotFound') { + err = new errors.ResourceNotFoundError(err, + format('firewall rule with id %s was not found', id)); + } + cb(err); + } else { + cb(null, fwrule); + } + }); + } else { + this.cloudapi.listFirewallRules(function (err, fwrules) { + if (err) { + return cb(err); + } + + var shortIdMatches = fwrules.filter(function (fwrule) { + return fwrule.id.slice(0, 8) === id; + }); + + if (shortIdMatches.length === 1) { + cb(null, shortIdMatches[0]); + } else if (shortIdMatches.length === 0) { + cb(new errors.ResourceNotFoundError(format( + 'no firewall rule with short id "%s" was found', id))); + } else { + cb(new errors.ResourceNotFoundError( + format('"%s" is an ambiguous short id, with multiple ' + + 'matching firewall rules', id))); + } + }); + } +}; + + /** * Get an instance by ID, exact name, or short ID, in that order. * diff --git a/test/integration/cli-snapshots.test.js b/test/integration/cli-snapshots.test.js index 05be1fc..577e733 100644 --- a/test/integration/cli-snapshots.test.js +++ b/test/integration/cli-snapshots.test.js @@ -86,6 +86,19 @@ test('triton snapshot', function (tt) { }); }); + tt.test(' triton instance start --snapshot', function (t) { + var cmd = 'instance start ' + INST + ' -w --snapshot=' + SNAP_NAME; + + h.triton(cmd, function (err, stdout, stderr) { + if (h.ifErr(t, err, 'triton instance start --snapshot')) + return t.end(); + + t.ok(stdout.match('Start instance ' + INST)); + + t.end(); + }); + }); + tt.test(' triton snapshot delete', function (t) { var cmd = 'snapshot delete ' + INST + ' ' + SNAP_NAME + ' -w --force'; h.triton(cmd, function (err, stdout, stderr) {