PUBAPI-1266: Add instance disable-firewall/enable-firewall to node-triton (CR changes)

- Don't need confirmation/--force for undoable commands
- Drop duration from output message after wait. I don't feel the time
  for this should ever be long enough that the number is interesting
  enough to get top billing.
- If "shouldn't get here", then it should be an assert.
- Use '_' instead of '__' for unused options.
- Drop the res.instId hack. Instead use a partial faux instance
  in place where other endpoints return a machine object.
- Add 'tritoninstance' bash completion.
- Group together with 'fwrules' in 'triton inst' help output.
This commit is contained in:
Trent Mick 2016-03-11 11:24:44 -08:00
parent c227e15ae3
commit 3824623404
6 changed files with 91 additions and 166 deletions

View File

@ -941,16 +941,16 @@ CloudApi.prototype.machineAudit = function machineAudit(id, cb) {
/**
* Wait for a machine's firewall to enable or disable.
* Wait for a machine's `firewall_enabled` field to go true/false.
*
* @param {Object} options
* - {String} id {required} machine id
* - {Boolean} state {require} - desired state
* - {Number} interval (optional) - time in ms to poll
* - {String} id: Required. The machine UUID.
* - {Boolean} state: Required. The desired `firewall_enabled` state.
* - {Number} interval: Optional. Time (in ms) to poll.
* @param {Function} callback of the form f(err, machine, res).
*/
CloudApi.prototype.waitForMachineFirewallState =
function waitForMachineFirewallState(opts, cb) {
CloudApi.prototype.waitForMachineFirewallEnabled =
function waitForMachineFirewallEnabled(opts, cb) {
var self = this;
assert.object(opts, 'opts');

View File

@ -14,7 +14,6 @@ var assert = require('assert-plus');
var format = require('util').format;
var vasync = require('vasync');
var common = require('../common');
var errors = require('../errors');
@ -32,88 +31,46 @@ function do_disable_firewall(subcmd, opts, args, cb) {
}
var cli = this.top;
var insts = args;
function wait(instId, startTime, next) {
var cloudapi = cli.tritonapi.cloudapi;
var waiter = cloudapi.waitForMachineFirewallState.bind(cloudapi);
waiter({
id: instId,
function wait(name, id, next) {
cli.tritonapi.cloudapi.waitForMachineFirewallEnabled({
id: id,
state: false
}, function (err, inst) {
if (err) {
return next(err);
next(err);
return;
}
assert.ok(!inst.firewall_enabled, format(
'inst %s firewall_enabled not in expected state after '
+ 'waitForMachineFirewallEnabled', id));
if (inst.firewall_enabled === false) {
var duration = Date.now() - startTime;
var durStr = common.humanDurationFromMs(duration);
console.log('Disabled firewall for instance "%s" in %s', instId,
durStr);
next();
} else {
// shouldn't get here, but...
var msg = 'Failed to disable firewall for instance "%s"';
next(new Error(format(msg, instId)));
}
console.log('Disabled firewall for instance "%s"', name);
next();
});
}
vasync.pipeline({funcs: [
function confirm(_, next) {
if (opts.force) {
return next();
}
vasync.forEachParallel({
inputs: args,
func: function disableOne(name, nextInst) {
cli.tritonapi.disableInstanceFirewall({
id: name
}, function (err, fauxInst) {
if (err) {
nextInst(err);
return;
}
var msg;
if (insts.length === 1) {
msg = 'Disable firewall for instance "' + insts[0] +
'"? [y/n] ';
} else {
msg = format('Disable firewalls for %d instances (%s)? [y/n] ',
insts.length, insts.join(', '));
}
console.log('Disabling firewall for instance "%s"', name);
common.promptYesNo({msg: msg}, function (answer) {
if (answer !== 'y') {
console.error('Aborting');
next(true); // early abort signal
if (opts.wait) {
wait(name, fauxInst.id, nextInst);
} else {
next();
nextInst();
}
});
},
function disableThem(_, next) {
var startTime = Date.now();
vasync.forEachParallel({
inputs: insts,
func: function disableOne(instId, nextId) {
cli.tritonapi.disableInstanceFirewall({
id: instId
}, function (err, __, res) {
if (err) {
nextId(err);
return;
}
var msg = 'Disabling firewall for instance "%s"';
console.log(msg, res.instId);
if (opts.wait) {
wait(res.instId, startTime, nextId);
} else {
nextId();
}
});
}
}, next);
}
]}, function (err) {
if (err === true) {
err = null;
}
}, function (err) {
cb(err);
});
}
@ -125,11 +82,6 @@ do_disable_firewall.options = [
type: 'bool',
help: 'Show this help.'
},
{
names: ['force', 'f'],
type: 'bool',
help: 'Skip confirmation to enable.'
},
{
names: ['wait', 'w'],
type: 'bool',
@ -145,4 +97,6 @@ do_disable_firewall.help = [
'{{options}}'
].join('\n');
do_disable_firewall.completionArgtypes = ['tritoninstance'];
module.exports = do_disable_firewall;

View File

@ -14,7 +14,6 @@ var assert = require('assert-plus');
var format = require('util').format;
var vasync = require('vasync');
var common = require('../common');
var errors = require('../errors');
@ -32,87 +31,46 @@ function do_enable_firewall(subcmd, opts, args, cb) {
}
var cli = this.top;
var insts = args;
function wait(instId, startTime, next) {
var cloudapi = cli.tritonapi.cloudapi;
var waiter = cloudapi.waitForMachineFirewallState.bind(cloudapi);
waiter({
id: instId,
function wait(name, id, next) {
cli.tritonapi.cloudapi.waitForMachineFirewallEnabled({
id: id,
state: true
}, function (err, inst) {
if (err) {
return next(err);
next(err);
return;
}
assert.ok(inst.firewall_enabled, format(
'inst %s firewall_enabled not in expected state after '
+ 'waitForMachineFirewallEnabled', id));
if (inst.firewall_enabled === true) {
var duration = Date.now() - startTime;
var durStr = common.humanDurationFromMs(duration);
console.log('Enabled firewall for instance "%s" in %s', instId,
durStr);
next();
} else {
// shouldn't get here, but...
var msg = 'Failed to enable firewall for instance "%s"';
next(new Error(format(msg, instId)));
}
console.log('Enabled firewall for instance "%s"', name);
next();
});
}
vasync.pipeline({funcs: [
function confirm(_, next) {
if (opts.force) {
return next();
}
vasync.forEachParallel({
inputs: args,
func: function enableOne(name, nextInst) {
cli.tritonapi.enableInstanceFirewall({
id: name
}, function (err, fauxInst) {
if (err) {
nextInst(err);
return;
}
var msg;
if (insts.length === 1) {
msg = 'Enable firewall for instance "' + insts[0] + '"? [y/n] ';
} else {
msg = format('Enable firewalls for %d instances (%s)? [y/n] ',
insts.length, insts.join(', '));
}
console.log('Enabling firewall for instance "%s"', name);
common.promptYesNo({msg: msg}, function (answer) {
if (answer !== 'y') {
console.error('Aborting');
next(true); // early abort signal
if (opts.wait) {
wait(name, fauxInst.id, nextInst);
} else {
next();
nextInst();
}
});
},
function enableThem(_, next) {
var startTime = Date.now();
vasync.forEachParallel({
inputs: insts,
func: function enableOne(instId, nextId) {
cli.tritonapi.enableInstanceFirewall({
id: instId
}, function (err, __, res) {
if (err) {
nextId(err);
return;
}
var msg = 'Enabling firewall for instance "%s"';
console.log(msg, res.instId);
if (opts.wait) {
wait(res.instId, startTime, nextId);
} else {
nextId();
}
});
}
}, next);
}
]}, function (err) {
if (err === true) {
err = null;
}
}, function (err) {
cb(err);
});
}
@ -124,11 +82,6 @@ do_enable_firewall.options = [
type: 'bool',
help: 'Show this help.'
},
{
names: ['force', 'f'],
type: 'bool',
help: 'Skip confirmation to enable.'
},
{
names: ['wait', 'w'],
type: 'bool',
@ -144,4 +97,6 @@ do_enable_firewall.help = [
'{{options}}'
].join('\n');
do_enable_firewall.completionArgtypes = ['tritoninstance'];
module.exports = do_enable_firewall;

View File

@ -39,13 +39,13 @@ function InstanceCLI(top) {
'stop',
'reboot',
{ group: '' },
'fwrules',
'enable-firewall',
'disable-firewall',
{ group: '' },
'ssh',
'wait',
'audit',
'fwrules',
'snapshot',
'tag'
]
@ -67,13 +67,13 @@ InstanceCLI.prototype.do_start = require('./do_start');
InstanceCLI.prototype.do_stop = require('./do_stop');
InstanceCLI.prototype.do_reboot = require('./do_reboot');
InstanceCLI.prototype.do_fwrules = require('./do_fwrules');
InstanceCLI.prototype.do_enable_firewall = require('./do_enable_firewall');
InstanceCLI.prototype.do_disable_firewall = require('./do_disable_firewall');
InstanceCLI.prototype.do_ssh = require('./do_ssh');
InstanceCLI.prototype.do_wait = require('./do_wait');
InstanceCLI.prototype.do_audit = require('./do_audit');
InstanceCLI.prototype.do_fwrules = require('./do_fwrules');
InstanceCLI.prototype.do_snapshot = require('./do_snapshot');
InstanceCLI.prototype.do_snapshots = require('./do_snapshots');
InstanceCLI.prototype.do_tag = require('./do_tag');

View File

@ -769,14 +769,20 @@ TritonApi.prototype.getInstance = function getInstance(opts, cb) {
};
// ---- instance firewall
// ---- instance enable/disable firewall
/**
* Enable the firewall on an instance.
*
* @param {Object} opts
* - {String} id: The instance ID, or short ID. Required.
* @param {Function} callback `function (err, null, res)`
* - {String} id: Required. The instance ID, name, or short ID.
* @param {Function} callback `function (err, fauxInst, res)`
* On failure `err` is an error instance, else it is null.
* On success: `fauxInst` is an object with just the instance id,
* `{id: <instance UUID>}` and `res` is the CloudAPI
* `EnableMachineFirewall` response.
* The API call does not return the instance/machine object, hence we
* are limited to just the id for `fauxInst`.
*/
TritonApi.prototype.enableInstanceFirewall =
function enableInstanceFirewall(opts, cb) {
@ -785,20 +791,22 @@ function enableInstanceFirewall(opts, cb) {
var self = this;
var res;
var fauxInst;
vasync.pipeline({arg: {client: self, id: opts.id}, funcs: [
_stepInstId,
function enableFirewall(arg, next) {
fauxInst = {id: arg.instId};
self.cloudapi.enableMachineFirewall(arg.instId,
function (err, _, _res) {
function (err, _, _res) {
res = _res;
res.instId = arg.instId; // gross hack, in case caller needs it
next(err);
});
}
]}, function (err) {
cb(err, null, res);
cb(err, fauxInst, res);
});
};
@ -807,8 +815,14 @@ function enableInstanceFirewall(opts, cb) {
* Disable the firewall on an instance.
*
* @param {Object} opts
* - {String} id: The instance ID, or short ID. Required.
* @param {Function} callback `function (err, null, res)`
* - {String} id: Required. The instance ID, name, or short ID.
* @param {Function} callback `function (err, fauxInst, res)`
* On failure `err` is an error instance, else it is null.
* On success: `fauxInst` is an object with just the instance id,
* `{id: <instance UUID>}` and `res` is the CloudAPI
* `EnableMachineFirewall` response.
* The API call does not return the instance/machine object, hence we
* are limited to just the id for `fauxInst`.
*/
TritonApi.prototype.disableInstanceFirewall =
function disableInstanceFirewall(opts, cb) {
@ -817,20 +831,22 @@ function disableInstanceFirewall(opts, cb) {
var self = this;
var res;
var fauxInst;
vasync.pipeline({arg: {client: self, id: opts.id}, funcs: [
_stepInstId,
function disableFirewall(arg, next) {
fauxInst = {id: arg.instId};
self.cloudapi.disableMachineFirewall(arg.instId,
function (err, _, _res) {
function (err, _, _res) {
res = _res;
res.instId = arg.instId; // gross hack, in case caller needs it
next(err);
});
}
]}, function (err) {
cb(err, null, res);
cb(err, fauxInst, res);
});
};

View File

@ -249,7 +249,7 @@ test('triton fwrule', OPTS, function (tt) {
});
tt.test(' triton instance enable-firewall', function (t) {
var cmd = 'instance enable-firewall ' + INST + ' -fw';
var cmd = 'instance enable-firewall ' + INST + ' -w';
h.triton(cmd, function (err, stdout, stderr) {
if (h.ifErr(t, err, 'triton instance enable-firewall'))
return t.end();
@ -270,7 +270,7 @@ test('triton fwrule', OPTS, function (tt) {
});
tt.test(' triton instance disable-firewall', function (t) {
var cmd = 'instance disable-firewall ' + INST + ' -fw';
var cmd = 'instance disable-firewall ' + INST + ' -w';
h.triton(cmd, function (err, stdout, stderr) {
if (h.ifErr(t, err, 'triton instance disable-firewall'))
return t.end();