TRITON-19 Triton equivalent to AWS' termination protection
Reviewed by: Trent Mick <trentm@gmail.com> Approved by: Trent Mick <trentm@gmail.com>
This commit is contained in:
parent
002171ea06
commit
8e6cf27121
@ -6,6 +6,14 @@ Known issues:
|
|||||||
|
|
||||||
## not yet released
|
## not yet released
|
||||||
|
|
||||||
|
- [TRITON-19] add support for deletion protection on instances. An instance with
|
||||||
|
the deletion protection flag set true cannot be destroyed until the flag is
|
||||||
|
set false. It is exposed through
|
||||||
|
`triton instance create --deletion-protection ...`,
|
||||||
|
`triton instance enable-deletion-protection ...`, and
|
||||||
|
`triton instance disable-deletion-protection ...`. This flag is only supported
|
||||||
|
on cloudapi versions 8.7.0 or above.
|
||||||
|
|
||||||
## 5.9.0
|
## 5.9.0
|
||||||
|
|
||||||
- [TRITON-190] remove support for `triton instance create --brand=bhyve ...`.
|
- [TRITON-190] remove support for `triton instance create --brand=bhyve ...`.
|
||||||
|
@ -1012,7 +1012,6 @@ function enableMachineFirewall(uuid, callback) {
|
|||||||
return this._doMachine('enable_firewall', uuid, callback);
|
return this._doMachine('enable_firewall', uuid, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disables machine firewall.
|
* Disables machine firewall.
|
||||||
*
|
*
|
||||||
@ -1024,6 +1023,28 @@ function disableMachineFirewall(uuid, callback) {
|
|||||||
return this._doMachine('disable_firewall', uuid, callback);
|
return this._doMachine('disable_firewall', uuid, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables machine deletion protection.
|
||||||
|
*
|
||||||
|
* @param {String} id (required) The machine id.
|
||||||
|
* @param {Function} callback of the form `function (err, null, res)`
|
||||||
|
*/
|
||||||
|
CloudApi.prototype.enableMachineDeletionProtection =
|
||||||
|
function enableMachineDeletionProtection(uuid, callback) {
|
||||||
|
return this._doMachine('enable_deletion_protection', uuid, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disables machine deletion protection.
|
||||||
|
*
|
||||||
|
* @param {String} id (required) The machine id.
|
||||||
|
* @param {Function} callback of the form `function (err, null, res)`
|
||||||
|
*/
|
||||||
|
CloudApi.prototype.disableMachineDeletionProtection =
|
||||||
|
function disableMachineDeletionProtection(uuid, callback) {
|
||||||
|
return this._doMachine('disable_deletion_protection', uuid, callback);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* internal function for start/stop/reboot/enable_firewall/disable_firewall
|
* internal function for start/stop/reboot/enable_firewall/disable_firewall
|
||||||
*/
|
*/
|
||||||
@ -1234,6 +1255,53 @@ function waitForMachineFirewallEnabled(opts, cb) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait for a machine's `deletion_protection` field to go true or
|
||||||
|
* false/undefined.
|
||||||
|
*
|
||||||
|
* @param {Object} options
|
||||||
|
* - {String} id: Required. The machine UUID.
|
||||||
|
* - {Boolean} state: Required. The desired `deletion_protection` state.
|
||||||
|
* - {Number} interval: Optional. Time (in ms) to poll.
|
||||||
|
* @param {Function} callback of the form f(err, machine, res).
|
||||||
|
*/
|
||||||
|
CloudApi.prototype.waitForDeletionProtectionEnabled =
|
||||||
|
function waitForDeletionProtectionEnabled(opts, cb) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
assert.object(opts, 'opts');
|
||||||
|
assert.uuid(opts.id, 'opts.id');
|
||||||
|
assert.bool(opts.state, 'opts.state');
|
||||||
|
assert.optionalNumber(opts.interval, 'opts.interval');
|
||||||
|
assert.func(cb, 'cb');
|
||||||
|
|
||||||
|
var interval = opts.interval || 1000;
|
||||||
|
assert.ok(interval > 0, 'interval must be a positive number');
|
||||||
|
|
||||||
|
poll();
|
||||||
|
|
||||||
|
function poll() {
|
||||||
|
self.getMachine({
|
||||||
|
id: opts.id
|
||||||
|
}, function getMachineCb(err, machine, res) {
|
||||||
|
if (err) {
|
||||||
|
cb(err, null, res);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// !! converts an undefined to a false
|
||||||
|
if (opts.state === !!machine.deletion_protection) {
|
||||||
|
cb(null, machine, res);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(poll, interval);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
// --- machine tags
|
// --- machine tags
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -115,6 +115,7 @@ function do_instances(subcmd, opts, args, cb) {
|
|||||||
if (inst.docker) flags.push('D');
|
if (inst.docker) flags.push('D');
|
||||||
if (inst.firewall_enabled) flags.push('F');
|
if (inst.firewall_enabled) flags.push('F');
|
||||||
if (inst.brand === 'kvm') flags.push('K');
|
if (inst.brand === 'kvm') flags.push('K');
|
||||||
|
if (inst.deletion_protection) flags.push('P');
|
||||||
inst.flags = flags.length ? flags.join('') : undefined;
|
inst.flags = flags.length ? flags.join('') : undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -164,6 +165,7 @@ do_instances.help = [
|
|||||||
' "D" docker instance',
|
' "D" docker instance',
|
||||||
' "F" firewall is enabled',
|
' "F" firewall is enabled',
|
||||||
' "K" the brand is "kvm"',
|
' "K" the brand is "kvm"',
|
||||||
|
' "P" deletion protected',
|
||||||
' age* Approximate time since created, e.g. 1y, 2w.',
|
' age* Approximate time since created, e.g. 1y, 2w.',
|
||||||
' img* The image "name@version", if available, else its',
|
' img* The image "name@version", if available, else its',
|
||||||
' "shortid".'
|
' "shortid".'
|
||||||
|
@ -397,6 +397,8 @@ function do_create(subcmd, opts, args, cb) {
|
|||||||
var opt = opts._order[i];
|
var opt = opts._order[i];
|
||||||
if (opt.key === 'firewall') {
|
if (opt.key === 'firewall') {
|
||||||
createOpts.firewall_enabled = opt.value;
|
createOpts.firewall_enabled = opt.value;
|
||||||
|
} else if (opt.key === 'deletion_protection') {
|
||||||
|
createOpts.deletion_protection = opt.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -544,6 +546,13 @@ 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: ['deletion-protection'],
|
||||||
|
type: 'bool',
|
||||||
|
help: 'Enable Deletion Protection on this instance. Such an instance ' +
|
||||||
|
'cannot be deleted until the protection is disabled. See ' +
|
||||||
|
'<https://apidocs.joyent.com/cloudapi/#deletion-protection>'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
names: ['volume', 'v'],
|
names: ['volume', 'v'],
|
||||||
type: 'arrayOfString',
|
type: 'arrayOfString',
|
||||||
|
125
lib/do_instance/do_disable_deletion_protection.js
Normal file
125
lib/do_instance/do_disable_deletion_protection.js
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2018 Joyent, Inc.
|
||||||
|
*
|
||||||
|
* `triton instance disable-deletion-protection ...`
|
||||||
|
*/
|
||||||
|
|
||||||
|
var assert = require('assert-plus');
|
||||||
|
var vasync = require('vasync');
|
||||||
|
|
||||||
|
var common = require('../common');
|
||||||
|
var errors = require('../errors');
|
||||||
|
|
||||||
|
|
||||||
|
function do_disable_deletion_protection(subcmd, opts, args, cb) {
|
||||||
|
assert.object(opts, 'opts');
|
||||||
|
assert.arrayOfString(args, 'args');
|
||||||
|
assert.func(cb, 'cb');
|
||||||
|
|
||||||
|
if (opts.help) {
|
||||||
|
this.do_help('help', {}, [subcmd], cb);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.length === 0) {
|
||||||
|
cb(new errors.UsageError('missing INST argument(s)'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cli = this.top;
|
||||||
|
|
||||||
|
function wait(name, id, next) {
|
||||||
|
assert.string(name, 'name');
|
||||||
|
assert.uuid(id, 'id');
|
||||||
|
assert.func(next, 'next');
|
||||||
|
|
||||||
|
cli.tritonapi.cloudapi.waitForDeletionProtectionEnabled({
|
||||||
|
id: id,
|
||||||
|
state: false
|
||||||
|
}, function (err, inst) {
|
||||||
|
if (err) {
|
||||||
|
next(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.ok(!inst.deletion_protection, 'inst ' + id
|
||||||
|
+ ' deletion_protection not in expected state after '
|
||||||
|
+ 'waitForDeletionProtectionEnabled');
|
||||||
|
|
||||||
|
console.log('Disabled deletion protection for instance "%s"', name);
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function disableOne(name, next) {
|
||||||
|
assert.string(name, 'name');
|
||||||
|
assert.func(next, 'next');
|
||||||
|
|
||||||
|
cli.tritonapi.disableInstanceDeletionProtection({
|
||||||
|
id: name
|
||||||
|
}, function disableProtectionCb(err, fauxInst) {
|
||||||
|
if (err) {
|
||||||
|
next(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Disabling deletion protection for instance "%s"',
|
||||||
|
name);
|
||||||
|
|
||||||
|
if (opts.wait) {
|
||||||
|
wait(name, fauxInst.id, next);
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
common.cliSetupTritonApi({cli: cli}, function onSetup(setupErr) {
|
||||||
|
if (setupErr) {
|
||||||
|
cb(setupErr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
vasync.forEachParallel({
|
||||||
|
inputs: args,
|
||||||
|
func: disableOne
|
||||||
|
}, function vasyncCb(err) {
|
||||||
|
cb(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
do_disable_deletion_protection.options = [
|
||||||
|
{
|
||||||
|
names: ['help', 'h'],
|
||||||
|
type: 'bool',
|
||||||
|
help: 'Show this help.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
names: ['wait', 'w'],
|
||||||
|
type: 'bool',
|
||||||
|
help: 'Wait for deletion protection to be removed.'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
do_disable_deletion_protection.synopses = [
|
||||||
|
'{{name}} disable-deletion-protection [OPTIONS] INST [INST ...]'
|
||||||
|
];
|
||||||
|
do_disable_deletion_protection.help = [
|
||||||
|
'Disable deletion protection on one or more instances.',
|
||||||
|
'',
|
||||||
|
'{{usage}}',
|
||||||
|
'',
|
||||||
|
'{{options}}',
|
||||||
|
'Where "INST" is an instance name, id, or short id.'
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
do_disable_deletion_protection.completionArgtypes = ['tritoninstance'];
|
||||||
|
|
||||||
|
module.exports = do_disable_deletion_protection;
|
125
lib/do_instance/do_enable_deletion_protection.js
Normal file
125
lib/do_instance/do_enable_deletion_protection.js
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2018 Joyent, Inc.
|
||||||
|
*
|
||||||
|
* `triton instance enable-deletion-protection ...`
|
||||||
|
*/
|
||||||
|
|
||||||
|
var assert = require('assert-plus');
|
||||||
|
var vasync = require('vasync');
|
||||||
|
|
||||||
|
var common = require('../common');
|
||||||
|
var errors = require('../errors');
|
||||||
|
|
||||||
|
|
||||||
|
function do_enable_deletion_protection(subcmd, opts, args, cb) {
|
||||||
|
assert.object(opts, 'opts');
|
||||||
|
assert.arrayOfString(args, 'args');
|
||||||
|
assert.func(cb, 'cb');
|
||||||
|
|
||||||
|
if (opts.help) {
|
||||||
|
this.do_help('help', {}, [subcmd], cb);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.length === 0) {
|
||||||
|
cb(new errors.UsageError('missing INST argument(s)'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cli = this.top;
|
||||||
|
|
||||||
|
function wait(name, id, next) {
|
||||||
|
assert.string(name, 'name');
|
||||||
|
assert.uuid(id, 'id');
|
||||||
|
assert.func(next, 'next');
|
||||||
|
|
||||||
|
cli.tritonapi.cloudapi.waitForDeletionProtectionEnabled({
|
||||||
|
id: id,
|
||||||
|
state: true
|
||||||
|
}, function (err, inst) {
|
||||||
|
if (err) {
|
||||||
|
next(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.ok(inst.deletion_protection, 'inst ' + id
|
||||||
|
+ ' deletion_protection not in expected state after '
|
||||||
|
+ 'waitForDeletionProtectionEnabled');
|
||||||
|
|
||||||
|
console.log('Enabled deletion protection for instance "%s"', name);
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function enableOne(name, next) {
|
||||||
|
assert.string(name, 'name');
|
||||||
|
assert.func(next, 'next');
|
||||||
|
|
||||||
|
cli.tritonapi.enableInstanceDeletionProtection({
|
||||||
|
id: name
|
||||||
|
}, function enableProtectionCb(err, fauxInst) {
|
||||||
|
if (err) {
|
||||||
|
next(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Enabling deletion protection for instance "%s"',
|
||||||
|
name);
|
||||||
|
|
||||||
|
if (opts.wait) {
|
||||||
|
wait(name, fauxInst.id, next);
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
common.cliSetupTritonApi({cli: cli}, function onSetup(setupErr) {
|
||||||
|
if (setupErr) {
|
||||||
|
cb(setupErr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
vasync.forEachParallel({
|
||||||
|
inputs: args,
|
||||||
|
func: enableOne
|
||||||
|
}, function vasyncCb(err) {
|
||||||
|
cb(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
do_enable_deletion_protection.options = [
|
||||||
|
{
|
||||||
|
names: ['help', 'h'],
|
||||||
|
type: 'bool',
|
||||||
|
help: 'Show this help.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
names: ['wait', 'w'],
|
||||||
|
type: 'bool',
|
||||||
|
help: 'Wait for deletion protection to be enabled.'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
do_enable_deletion_protection.synopses = [
|
||||||
|
'{{name}} enable-deletion-protection [OPTIONS] INST [INST ...]'
|
||||||
|
];
|
||||||
|
do_enable_deletion_protection.help = [
|
||||||
|
'Enable deletion protection for one or more instances.',
|
||||||
|
'',
|
||||||
|
'{{usage}}',
|
||||||
|
'',
|
||||||
|
'{{options}}',
|
||||||
|
'Where "INST" is an instance name, id, or short id.'
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
do_enable_deletion_protection.completionArgtypes = ['tritoninstance'];
|
||||||
|
|
||||||
|
module.exports = do_enable_deletion_protection;
|
@ -154,6 +154,7 @@ function do_list(subcmd, opts, args, callback) {
|
|||||||
if (inst.docker) flags.push('D');
|
if (inst.docker) flags.push('D');
|
||||||
if (inst.firewall_enabled) flags.push('F');
|
if (inst.firewall_enabled) flags.push('F');
|
||||||
if (inst.brand === 'kvm') flags.push('K');
|
if (inst.brand === 'kvm') flags.push('K');
|
||||||
|
if (inst.deletion_protection) flags.push('P');
|
||||||
inst.flags = flags.length ? flags.join('') : undefined;
|
inst.flags = flags.length ? flags.join('') : undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -213,6 +214,7 @@ do_list.help = [
|
|||||||
' "D" docker instance',
|
' "D" docker instance',
|
||||||
' "F" firewall is enabled',
|
' "F" firewall is enabled',
|
||||||
' "K" the brand is "kvm"',
|
' "K" the brand is "kvm"',
|
||||||
|
' "P" deletion protected',
|
||||||
' age* Approximate time since created, e.g. 1y, 2w.',
|
' age* Approximate time since created, e.g. 1y, 2w.',
|
||||||
' img* The image "name@version", if available, else its',
|
' img* The image "name@version", if available, else its',
|
||||||
' "shortid".',
|
' "shortid".',
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2015 Joyent, Inc.
|
* Copyright 2018 Joyent, Inc.
|
||||||
*
|
*
|
||||||
* `triton instance ...`
|
* `triton instance ...`
|
||||||
*/
|
*/
|
||||||
@ -45,6 +45,9 @@ function InstanceCLI(top) {
|
|||||||
'enable-firewall',
|
'enable-firewall',
|
||||||
'disable-firewall',
|
'disable-firewall',
|
||||||
{ group: '' },
|
{ group: '' },
|
||||||
|
'enable-deletion-protection',
|
||||||
|
'disable-deletion-protection',
|
||||||
|
{ group: '' },
|
||||||
'ssh',
|
'ssh',
|
||||||
'ip',
|
'ip',
|
||||||
'wait',
|
'wait',
|
||||||
@ -78,6 +81,11 @@ InstanceCLI.prototype.do_fwrules = require('./do_fwrules');
|
|||||||
InstanceCLI.prototype.do_enable_firewall = require('./do_enable_firewall');
|
InstanceCLI.prototype.do_enable_firewall = require('./do_enable_firewall');
|
||||||
InstanceCLI.prototype.do_disable_firewall = require('./do_disable_firewall');
|
InstanceCLI.prototype.do_disable_firewall = require('./do_disable_firewall');
|
||||||
|
|
||||||
|
InstanceCLI.prototype.do_enable_deletion_protection =
|
||||||
|
require('./do_enable_deletion_protection');
|
||||||
|
InstanceCLI.prototype.do_disable_deletion_protection =
|
||||||
|
require('./do_disable_deletion_protection');
|
||||||
|
|
||||||
InstanceCLI.prototype.do_ssh = require('./do_ssh');
|
InstanceCLI.prototype.do_ssh = require('./do_ssh');
|
||||||
InstanceCLI.prototype.do_ip = require('./do_ip');
|
InstanceCLI.prototype.do_ip = require('./do_ip');
|
||||||
InstanceCLI.prototype.do_wait = require('./do_wait');
|
InstanceCLI.prototype.do_wait = require('./do_wait');
|
||||||
|
@ -1364,6 +1364,92 @@ function disableInstanceFirewall(opts, cb) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// ---- instance enable/disable deletion protection
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable deletion protection on an instance.
|
||||||
|
*
|
||||||
|
* @param {Object} opts
|
||||||
|
* - {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
|
||||||
|
* `EnableMachineDeletionProtection` response.
|
||||||
|
* The API call does not return the instance/machine object, hence we
|
||||||
|
* are limited to just the id for `fauxInst`.
|
||||||
|
*/
|
||||||
|
TritonApi.prototype.enableInstanceDeletionProtection =
|
||||||
|
function enableInstanceDeletionProtection(opts, cb) {
|
||||||
|
assert.object(opts, 'opts');
|
||||||
|
assert.string(opts.id, 'opts.id');
|
||||||
|
assert.func(cb, 'cb');
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
var res;
|
||||||
|
var fauxInst;
|
||||||
|
|
||||||
|
function enableDeletionProtection(arg, next) {
|
||||||
|
fauxInst = {id: arg.instId};
|
||||||
|
|
||||||
|
self.cloudapi.enableMachineDeletionProtection(arg.instId,
|
||||||
|
function enableCb(err, _, _res) {
|
||||||
|
res = _res;
|
||||||
|
next(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
vasync.pipeline({arg: {client: self, id: opts.id}, funcs: [
|
||||||
|
_stepInstId,
|
||||||
|
enableDeletionProtection
|
||||||
|
]}, function vasyncCb(err) {
|
||||||
|
cb(err, fauxInst, res);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable deletion protection on an instance.
|
||||||
|
*
|
||||||
|
* @param {Object} opts
|
||||||
|
* - {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
|
||||||
|
* `DisableMachineDeletionProtectiomn` response.
|
||||||
|
* The API call does not return the instance/machine object, hence we
|
||||||
|
* are limited to just the id for `fauxInst`.
|
||||||
|
*/
|
||||||
|
TritonApi.prototype.disableInstanceDeletionProtection =
|
||||||
|
function disableInstanceDeletionProtection(opts, cb) {
|
||||||
|
assert.object(opts, 'opts');
|
||||||
|
assert.string(opts.id, 'opts.id');
|
||||||
|
assert.func(cb, 'cb');
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
var res;
|
||||||
|
var fauxInst;
|
||||||
|
|
||||||
|
function disableDeletionProtection(arg, next) {
|
||||||
|
fauxInst = {id: arg.instId};
|
||||||
|
|
||||||
|
self.cloudapi.disableMachineDeletionProtection(arg.instId,
|
||||||
|
function (err, _, _res) {
|
||||||
|
res = _res;
|
||||||
|
next(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
vasync.pipeline({arg: {client: self, id: opts.id}, funcs: [
|
||||||
|
_stepInstId,
|
||||||
|
disableDeletionProtection
|
||||||
|
]}, function vasyncCb(err) {
|
||||||
|
cb(err, fauxInst, res);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
// ---- instance snapshots
|
// ---- instance snapshots
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
191
test/integration/cli-deletion-protection.test.js
Normal file
191
test/integration/cli-deletion-protection.test.js
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Joyent, Inc.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Integration tests for `triton instance enable-deletion-protection ...` and
|
||||||
|
* `triton instance disable-deletion-protection ...`
|
||||||
|
*/
|
||||||
|
|
||||||
|
var h = require('./helpers');
|
||||||
|
var f = require('util').format;
|
||||||
|
var os = require('os');
|
||||||
|
var test = require('tape');
|
||||||
|
|
||||||
|
// --- Globals
|
||||||
|
|
||||||
|
var INST_ALIAS = f('nodetritontest-deletion-protection-%s', os.hostname());
|
||||||
|
var INST;
|
||||||
|
var OPTS = {
|
||||||
|
skip: !h.CONFIG.allowWriteActions
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Helpers
|
||||||
|
|
||||||
|
function cleanup(t) {
|
||||||
|
var cmd = 'instance disable-deletion-protection ' + INST_ALIAS + ' -w';
|
||||||
|
|
||||||
|
h.triton(cmd, function (err, stdout, stderr) {
|
||||||
|
if (err)
|
||||||
|
return t.end();
|
||||||
|
|
||||||
|
h.deleteTestInst(t, INST_ALIAS, function (err2) {
|
||||||
|
t.ifErr(err2, 'delete inst err');
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Tests
|
||||||
|
|
||||||
|
if (OPTS.skip) {
|
||||||
|
console.error('** skipping %s tests', __filename);
|
||||||
|
console.error('** set "allowWriteActions" in test config to enable');
|
||||||
|
}
|
||||||
|
|
||||||
|
test('triton instance', OPTS, function (tt) {
|
||||||
|
h.printConfig(tt);
|
||||||
|
|
||||||
|
tt.test(' cleanup existing inst with alias ' + INST_ALIAS, cleanup);
|
||||||
|
|
||||||
|
|
||||||
|
tt.test(' triton create --deletion-protection', function (t) {
|
||||||
|
h.createTestInst(t, INST_ALIAS, {
|
||||||
|
extraFlags: ['--deletion-protection']
|
||||||
|
}, function onInst(err2, instId) {
|
||||||
|
if (h.ifErr(t, err2, 'triton instance create'))
|
||||||
|
return t.end();
|
||||||
|
|
||||||
|
INST = instId;
|
||||||
|
|
||||||
|
h.triton('instance get -j ' + INST, function (err3, stdout) {
|
||||||
|
if (h.ifErr(t, err3, 'triton instance get'))
|
||||||
|
return t.end();
|
||||||
|
|
||||||
|
var inst = JSON.parse(stdout);
|
||||||
|
t.ok(inst.deletion_protection, 'deletion_protection');
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
tt.test(' attempt to delete deletion-protected instance', function (t) {
|
||||||
|
var cmd = 'instance rm ' + INST + ' -w';
|
||||||
|
|
||||||
|
h.triton(cmd, function (err, stdout, stderr) {
|
||||||
|
t.ok(err, 'err expected');
|
||||||
|
/* JSSTYLED */
|
||||||
|
t.ok(stderr.match(/Instance has "deletion_protection" enabled/));
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
tt.test(' triton instance disable-deletion-protection', function (t) {
|
||||||
|
var cmd = 'instance disable-deletion-protection ' + INST + ' -w';
|
||||||
|
|
||||||
|
h.triton(cmd, function (err, stdout, stderr) {
|
||||||
|
if (h.ifErr(t, err, 'triton instance disable-deletion-protection'))
|
||||||
|
return t.end();
|
||||||
|
|
||||||
|
t.ok(stdout.match('Disabled deletion protection for instance "' +
|
||||||
|
INST + '"'), 'deletion protection disabled');
|
||||||
|
|
||||||
|
h.triton('instance get -j ' + INST, function (err2, stdout2) {
|
||||||
|
if (h.ifErr(t, err2, 'triton instance get'))
|
||||||
|
return t.end();
|
||||||
|
|
||||||
|
var inst = JSON.parse(stdout2);
|
||||||
|
t.ok(!inst.deletion_protection, 'deletion_protection');
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
tt.test(' triton instance disable-deletion-protection (already enabled)',
|
||||||
|
function (t) {
|
||||||
|
var cmd = 'instance disable-deletion-protection ' + INST + ' -w';
|
||||||
|
|
||||||
|
h.triton(cmd, function (err, stdout, stderr) {
|
||||||
|
if (h.ifErr(t, err, 'triton instance disable-deletion-protection'))
|
||||||
|
return t.end();
|
||||||
|
|
||||||
|
t.ok(stdout.match('Disabled deletion protection for instance "' +
|
||||||
|
INST + '"'), 'deletion protection disabled');
|
||||||
|
|
||||||
|
h.triton('instance get -j ' + INST, function (err2, stdout2) {
|
||||||
|
if (h.ifErr(t, err2, 'triton instance get'))
|
||||||
|
return t.end();
|
||||||
|
|
||||||
|
var inst = JSON.parse(stdout2);
|
||||||
|
t.ok(!inst.deletion_protection, 'deletion_protection');
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
tt.test(' triton instance enable-deletion-protection', function (t) {
|
||||||
|
var cmd = 'instance enable-deletion-protection ' + INST + ' -w';
|
||||||
|
|
||||||
|
h.triton(cmd, function (err, stdout, stderr) {
|
||||||
|
if (h.ifErr(t, err, 'triton instance enable-deletion-protection'))
|
||||||
|
return t.end();
|
||||||
|
|
||||||
|
t.ok(stdout.match('Enabled deletion protection for instance "' +
|
||||||
|
INST + '"'), 'deletion protection enabled');
|
||||||
|
|
||||||
|
h.triton('instance get -j ' + INST, function (err2, stdout2) {
|
||||||
|
if (h.ifErr(t, err2, 'triton instance get'))
|
||||||
|
return t.end();
|
||||||
|
|
||||||
|
var inst = JSON.parse(stdout2);
|
||||||
|
t.ok(inst.deletion_protection, 'deletion_protection');
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
tt.test(' triton instance enable-deletion-protection (already enabled)',
|
||||||
|
function (t) {
|
||||||
|
var cmd = 'instance enable-deletion-protection ' + INST + ' -w';
|
||||||
|
|
||||||
|
h.triton(cmd, function (err, stdout, stderr) {
|
||||||
|
if (h.ifErr(t, err, 'triton instance enable-deletion-protection'))
|
||||||
|
return t.end();
|
||||||
|
|
||||||
|
t.ok(stdout.match('Enabled deletion protection for instance "' +
|
||||||
|
INST + '"'), 'deletion protection enabled');
|
||||||
|
|
||||||
|
h.triton('instance get -j ' + INST, function (err2, stdout2) {
|
||||||
|
if (h.ifErr(t, err2, 'triton instance get'))
|
||||||
|
return t.end();
|
||||||
|
|
||||||
|
var inst = JSON.parse(stdout2);
|
||||||
|
t.ok(inst.deletion_protection, 'deletion_protection');
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Use a timeout, because '-w' on delete doesn't have a way to know if the
|
||||||
|
* attempt failed or if it is just taking a really long time.
|
||||||
|
*/
|
||||||
|
tt.test(' cleanup: triton rm INST', {timeout: 10 * 60 * 1000}, cleanup);
|
||||||
|
});
|
@ -47,7 +47,7 @@ test('triton fwrule', OPTS, function (tt) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
tt.test(' setup: triton create', function (t) {
|
tt.test(' setup: triton create', function (t) {
|
||||||
h.createTestInst(t, INST_ALIAS, function onInst(err2, instId) {
|
h.createTestInst(t, INST_ALIAS, {}, function onInst(err2, instId) {
|
||||||
if (h.ifErr(t, err2, 'triton instance create'))
|
if (h.ifErr(t, err2, 'triton instance create'))
|
||||||
return t.end();
|
return t.end();
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ test('triton instance nics', OPTS, function (tt) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
tt.test(' setup: triton instance create', function (t) {
|
tt.test(' setup: triton instance create', function (t) {
|
||||||
h.createTestInst(t, INST_ALIAS, function onInst(err, instId) {
|
h.createTestInst(t, INST_ALIAS, {}, function onInst(err, instId) {
|
||||||
if (h.ifErr(t, err, 'triton instance create'))
|
if (h.ifErr(t, err, 'triton instance create'))
|
||||||
return t.end();
|
return t.end();
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ test('triton instance snapshot', OPTS, function (tt) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
tt.test(' setup: triton instance create', function (t) {
|
tt.test(' setup: triton instance create', function (t) {
|
||||||
h.createTestInst(t, INST_ALIAS, function onInst(err2, instId) {
|
h.createTestInst(t, INST_ALIAS, {}, function onInst(err2, instId) {
|
||||||
if (h.ifErr(t, err2, 'triton instance create'))
|
if (h.ifErr(t, err2, 'triton instance create'))
|
||||||
return t.end();
|
return t.end();
|
||||||
|
|
||||||
|
@ -45,6 +45,8 @@ var subs = [
|
|||||||
['instance delete', 'instance rm', 'delete', 'rm'],
|
['instance delete', 'instance rm', 'delete', 'rm'],
|
||||||
['instance enable-firewall'],
|
['instance enable-firewall'],
|
||||||
['instance disable-firewall'],
|
['instance disable-firewall'],
|
||||||
|
['instance enable-deletion-protection'],
|
||||||
|
['instance disable-deletion-protection'],
|
||||||
['instance rename'],
|
['instance rename'],
|
||||||
['instance ssh'],
|
['instance ssh'],
|
||||||
['instance ip'],
|
['instance ip'],
|
||||||
|
@ -369,7 +369,13 @@ function createClient(cb) {
|
|||||||
/*
|
/*
|
||||||
* Create a small test instance.
|
* Create a small test instance.
|
||||||
*/
|
*/
|
||||||
function createTestInst(t, name, cb) {
|
function createTestInst(t, name, opts, cb) {
|
||||||
|
assert.object(t, 't');
|
||||||
|
assert.string(name, 'name');
|
||||||
|
assert.object(opts, 'opts');
|
||||||
|
assert.optionalArrayOfString(opts.extraFlags, 'opts.extraFlags');
|
||||||
|
assert.func(cb, 'cb');
|
||||||
|
|
||||||
getTestPkg(t, function (err, pkgId) {
|
getTestPkg(t, function (err, pkgId) {
|
||||||
t.ifErr(err);
|
t.ifErr(err);
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -385,6 +391,10 @@ function createTestInst(t, name, cb) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var cmd = f('instance create -w -n %s %s %s', name, imgId, pkgId);
|
var cmd = f('instance create -w -n %s %s %s', name, imgId, pkgId);
|
||||||
|
if (opts.extraFlags) {
|
||||||
|
cmd += ' ' + opts.extraFlags.join(' ');
|
||||||
|
}
|
||||||
|
|
||||||
triton(cmd, function (err3, stdout) {
|
triton(cmd, function (err3, stdout) {
|
||||||
t.ifErr(err3, 'create test instance');
|
t.ifErr(err3, 'create test instance');
|
||||||
if (err3) {
|
if (err3) {
|
||||||
|
Reference in New Issue
Block a user