diff --git a/releasenotes/notes/add-user-confirmation-to-node-delete-ca8c240bfd71c0ba.yaml b/releasenotes/notes/add-user-confirmation-to-node-delete-ca8c240bfd71c0ba.yaml new file mode 100644 index 000000000..09c5fe459 --- /dev/null +++ b/releasenotes/notes/add-user-confirmation-to-node-delete-ca8c240bfd71c0ba.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Added a y/n prompt to the node delete command to prevent a user from + accidently executing a node delete action since it's a destructive + action. diff --git a/tripleoclient/tests/v1/overcloud_node/test_overcloud_node.py b/tripleoclient/tests/v1/overcloud_node/test_overcloud_node.py index c2fc52a23..5f7512909 100644 --- a/tripleoclient/tests/v1/overcloud_node/test_overcloud_node.py +++ b/tripleoclient/tests/v1/overcloud_node/test_overcloud_node.py @@ -20,6 +20,7 @@ import mock import os import tempfile +from osc_lib import exceptions as oscexc from osc_lib.tests import utils as test_utils import yaml @@ -56,7 +57,7 @@ class TestDeleteNode(fakes.TestDeleteNode): # probably be fixed so that it can pass with that. def test_node_delete(self): argslist = ['instance1', 'instance2', '--templates', - '--stack', 'overcast', '--timeout', '90'] + '--stack', 'overcast', '--timeout', '90', '--yes'] verifylist = [ ('stack', 'overcast'), ('nodes', ['instance1', 'instance2']) @@ -82,9 +83,24 @@ class TestDeleteNode(fakes.TestDeleteNode): 'timeout': 90 }) + @mock.patch('tripleoclient.utils.prompt_user_for_confirmation', + return_value=False) + def test_node_delete_no_confirm(self, confirm_mock): + argslist = ['instance1', 'instance2', '--templates', + '--stack', 'overcast', '--timeout', '90'] + verifylist = [ + ('stack', 'overcast'), + ('nodes', ['instance1', 'instance2']) + ] + parsed_args = self.check_parser(self.cmd, argslist, verifylist) + + self.assertRaises(oscexc.CommandError, + self.cmd.take_action, + parsed_args) + def test_node_wrong_stack(self): argslist = ['instance1', '--templates', - '--stack', 'overcast'] + '--stack', 'overcast', '--yes'] verifylist = [ ('stack', 'overcast'), ('nodes', ['instance1', ]) @@ -102,7 +118,7 @@ class TestDeleteNode(fakes.TestDeleteNode): def test_node_delete_without_stack(self): - arglist = ['instance1', ] + arglist = ['instance1', '--yes'] verifylist = [ ('stack', 'overcloud'), @@ -130,7 +146,7 @@ class TestDeleteNode(fakes.TestDeleteNode): def test_node_delete_wrong_instance(self): argslist = ['wrong_instance', '--templates', - '--stack', 'overcloud'] + '--stack', 'overcloud', '--yes'] verifylist = [ ('stack', 'overcloud'), ('nodes', ['wrong_instance']), diff --git a/tripleoclient/v1/overcloud_node.py b/tripleoclient/v1/overcloud_node.py index 457661071..ea12a371c 100644 --- a/tripleoclient/v1/overcloud_node.py +++ b/tripleoclient/v1/overcloud_node.py @@ -17,6 +17,7 @@ import argparse import logging import os +from osc_lib import exceptions as oscexc from osc_lib.i18n import _ from osc_lib import utils import yaml @@ -69,11 +70,24 @@ class DeleteNode(command.Command): "Keep in mind that due to keystone session duration " "that timeout has an upper bound of 4 hours ") ) + parser.add_argument('-y', '--yes', + help=_('Skip yes/no prompt (assume yes).'), + default=False, + action="store_true") return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)" % parsed_args) clients = self.app.client_manager + + if not parsed_args.yes: + confirm = oooutils.prompt_user_for_confirmation( + message=_("Are you sure you want to delete these overcloud " + "nodes [y/N]? "), + logger=self.log) + if not confirm: + raise oscexc.CommandError("Action not confirmed, exiting.") + orchestration_client = clients.orchestration stack = oooutils.get_stack(orchestration_client, parsed_args.stack)