Skip to content

Commit a2174c3

Browse files
danil vinogradovpsergee
danil vinogradov
authored andcommitted
stop:added confirmation
Since the stop command is dangerous and can be harmful if executed accidentally, it was necessary to add a user confirmation for its execution to improve security. To use auto-confirm, you must pass the -y/--yes option, like this tt stop -y or you can use --no-prompt option to skip interactions.
1 parent 3e3e081 commit a2174c3

20 files changed

+211
-38
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
99

1010
### Added
1111

12+
- `tt stop` confirmation prompt. `-y` option is added to accept stop without prompting.
1213
- `tt cluster replicaset roles add`: command to add roles in config scope provided by flags.
1314
- `tt replicaset roles remove`: command to remove roles in the tarantool replicaset with cluster
1415
config (3.0) or cartridge orchestrator.

cli/cmd/stop.go

+37-1
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
package cmd
22

33
import (
4+
"fmt"
45
"github.com/apex/log"
56
"github.com/spf13/cobra"
67
"github.com/tarantool/tt/cli/cmd/internal"
78
"github.com/tarantool/tt/cli/cmdcontext"
89
"github.com/tarantool/tt/cli/modules"
910
"github.com/tarantool/tt/cli/running"
1011
"github.com/tarantool/tt/cli/util"
12+
"os"
1113
)
1214

1315
// NewStopCmd creates stop command.
@@ -18,9 +20,10 @@ func NewStopCmd() *cobra.Command {
1820
Run: func(cmd *cobra.Command, args []string) {
1921
cmdCtx.CommandName = cmd.Name()
2022
err := modules.RunCmd(&cmdCtx, cmd.CommandPath(), &modulesInfo,
21-
internalStopModule, args)
23+
internalStopWithConfirmationModule, args)
2224
util.HandleCmdErr(cmd, err)
2325
},
26+
Args: cobra.RangeArgs(0, 1),
2427
ValidArgsFunction: func(
2528
cmd *cobra.Command,
2629
args []string,
@@ -32,9 +35,42 @@ func NewStopCmd() *cobra.Command {
3235
},
3336
}
3437

38+
stopCmd.Flags().BoolVarP(&autoYes, "yes", "y", false,
39+
`Automatic yes to confirmation prompt`)
40+
3541
return stopCmd
3642
}
3743

44+
func internalStopWithConfirmationModule(cmdCtx *cmdcontext.CmdCtx, args []string) error {
45+
if !isConfigExist(cmdCtx) {
46+
return errNoConfig
47+
}
48+
49+
if !(autoYes || cmdCtx.Cli.NoPrompt) {
50+
instancesToConfirm := ""
51+
if len(args) == 0 {
52+
instancesToConfirm = "all instances"
53+
} else {
54+
instancesToConfirm = fmt.Sprintf("'%s'", args[0])
55+
}
56+
confirmed, err := util.AskConfirm(os.Stdin, fmt.Sprintf("Confirm stop of %s",
57+
instancesToConfirm))
58+
if err != nil {
59+
return err
60+
}
61+
if !confirmed {
62+
log.Info("Stop is cancelled.")
63+
return nil
64+
}
65+
}
66+
67+
if err := internalStopModule(cmdCtx, args); err != nil {
68+
return err
69+
}
70+
71+
return nil
72+
}
73+
3874
// internalStopModule is a default stop module.
3975
func internalStopModule(cmdCtx *cmdcontext.CmdCtx, args []string) error {
4076
if !isConfigExist(cmdCtx) {

test/cartridge_helper.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -210,13 +210,13 @@ def set_failover(self, data):
210210
assert re.search(r"Failover configured successfully", out)
211211

212212
def stop(self):
213-
cmd = [self.tt_cmd, "stop", cartridge_name]
213+
cmd = [self.tt_cmd, "stop", "-y", cartridge_name]
214214
rc, _ = run_command_and_get_output(cmd, cwd=self.workdir)
215215
assert rc == 0
216216

217217
def stop_inst(self, name):
218218
assert name in self.instances, "instance is offline"
219-
cmd = [self.tt_cmd, "stop", f"{cartridge_name}:{name}"]
219+
cmd = [self.tt_cmd, "stop", "-y", f"{cartridge_name}:{name}"]
220220
rc, _ = run_command_and_get_output(cmd, cwd=self.workdir)
221221
self.instances.remove(name)
222222
assert rc == 0

test/integration/create/test_create.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -763,7 +763,7 @@ def test_create_app_from_builtin_cartridge_template_with_dst_specified(tt_cmd, t
763763
assert status_info[key]["STATUS"] == "RUNNING"
764764

765765
# Stop the cartridge app.
766-
stop_cmd = [tt_cmd, "stop", "app1"]
766+
stop_cmd = [tt_cmd, "stop", "-y", "app1"]
767767
stop_rc, stop_out = run_command_and_get_output(stop_cmd, cwd=tmp_path)
768768
assert status_rc == 0
769769
assert re.search(r"The Instance app1:\w+ \(PID = \d+\) has been terminated.", stop_out)
@@ -907,7 +907,7 @@ def select_data_func():
907907
print(inst, f.read())
908908

909909
# Stop the vhsard_cluster app.
910-
stop_cmd = [tt_cmd, "stop", "app1"]
910+
stop_cmd = [tt_cmd, "stop", "--yes", "app1"]
911911
stop_rc, stop_out = run_command_and_get_output(stop_cmd, cwd=tmp_path)
912912
assert stop_rc == 0
913913
for inst in instances:

test/integration/daemon/test_daemon.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ def test_daemon_http_requests(tt_cmd, tmpdir_with_cfg):
218218
status_info = utils.extract_status(response.json()["res"])
219219
assert status_info["test_app"]["STATUS"] == "RUNNING"
220220

221-
body = {"command_name": "stop", "params": ["test_app"]}
221+
body = {"command_name": "stop", "params": ["-y", "test_app"]}
222222
response = requests.post(default_url, json=body)
223223
assert response.status_code == 200
224224
assert re.search(r"The Instance test_app \(PID = \d+\) has been terminated.",
@@ -291,7 +291,7 @@ def test_daemon_http_requests_with_cfg(tt_cmd, tmpdir_with_cfg):
291291
assert response.status_code == 200
292292
status_info = utils.extract_status(response.json()["res"])
293293
assert status_info["test_app"]["STATUS"] == "RUNNING"
294-
body = {"command_name": "stop", "params": ["test_app"]}
294+
body = {"command_name": "stop", "params": ["-y", "test_app"]}
295295
response = requests.post(url, json=body)
296296
assert response.status_code == 200
297297
assert re.search(r"The Instance test_app \(PID = \d+\) has been terminated.",

test/integration/replicaset/replicaset_helpers.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def start_application(tt_cmd, workdir, app_name, instances):
2121

2222

2323
def stop_application(tt_cmd, app_name, workdir, instances, force=False):
24-
stop_cmd = [tt_cmd, "stop", app_name]
24+
stop_cmd = [tt_cmd, "stop", "-y", app_name]
2525
stop_rc, stop_out = run_command_and_get_output(stop_cmd, cwd=workdir)
2626
assert stop_rc == 0
2727

test/integration/replicaset/test_replicaset_bootstrap.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def test_bootstrap_custom_app(tt_cmd, tmpdir_with_cfg, flag):
6868
expected = '⨯ bootstrap is not supported for an application by "custom" orchestrator'
6969
assert expected in out
7070
finally:
71-
stop_cmd = [tt_cmd, "stop", app_name]
71+
stop_cmd = [tt_cmd, "stop", "-y", app_name]
7272
rc, _ = run_command_and_get_output(stop_cmd, cwd=tmpdir)
7373
assert rc == 0
7474

@@ -95,7 +95,7 @@ def test_bootstrap_instance_no_replicaset_specified(tt_cmd, tmpdir_with_cfg):
9595
assert rc != 0
9696
assert "⨯ the replicaset must be specified to bootstrap an instance" in out
9797
finally:
98-
stop_cmd = [tt_cmd, "stop", app_name]
98+
stop_cmd = [tt_cmd, "stop", "-y", app_name]
9999
rc, _ = run_command_and_get_output(stop_cmd, cwd=tmpdir)
100100
assert rc == 0
101101

@@ -123,7 +123,7 @@ def test_bootstrap_app_replicaset_specified(tt_cmd, tmpdir_with_cfg):
123123
expected = "⨯ the replicaset can not be specified in the case of application bootstrapping"
124124
assert expected in out
125125
finally:
126-
stop_cmd = [tt_cmd, "stop", app_name]
126+
stop_cmd = [tt_cmd, "stop", "-y", app_name]
127127
rc, _ = run_command_and_get_output(stop_cmd, cwd=tmpdir)
128128
assert rc == 0
129129

@@ -237,7 +237,7 @@ def test_replicaset_bootstrap_cartridge_new_instance(tt_cmd, cartridge_app):
237237
assert re.search(expected, out)
238238
finally:
239239
# Get rid of the tested instance.
240-
stop_cmd = [tt_cmd, "stop", f"{cartridge_name}:new_inst"]
240+
stop_cmd = [tt_cmd, "stop", "-y", f"{cartridge_name}:new_inst"]
241241
rc, out = run_command_and_get_output(stop_cmd, cwd=cartridge_app.workdir)
242242
with open(instances_yml_path, "w") as f:
243243
f.write(old_instances_yml)

test/integration/replicaset/test_replicaset_demote.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def test_demote_cconfig_failover_off(tt_cmd, tmpdir_with_cfg, force):
2828
start_application(tt_cmd, tmpdir, app_name, instances)
2929
if force:
3030
# Stop an instance.
31-
stop_cmd = [tt_cmd, "stop", f"{app_name}:off-failover-2"]
31+
stop_cmd = [tt_cmd, "stop", "-y", f"{app_name}:off-failover-2"]
3232
rc, _ = run_command_and_get_output(stop_cmd, cwd=tmpdir)
3333
instances.remove("off-failover-2")
3434
assert rc == 0
@@ -161,7 +161,7 @@ def test_demote_cconfig_errors(
161161
box_ctl_promote(tt_cmd, app_name, "election-failover-1", tmpdir)
162162

163163
if stop_inst:
164-
stop_cmd = [tt_cmd, "stop", f"{app_name}:{stop_inst}"]
164+
stop_cmd = [tt_cmd, "stop", "-y", f"{app_name}:{stop_inst}"]
165165
rc, _ = run_command_and_get_output(stop_cmd, cwd=tmpdir)
166166
assert rc == 0
167167
instances.remove(stop_inst)

test/integration/replicaset/test_replicaset_expel.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ def test_expel_custom_app(tt_cmd, tmpdir_with_cfg, flag):
8080
⨯ expel is not supported for an application by "custom" orchestrator
8181
""", out)
8282
finally:
83-
stop_cmd = [tt_cmd, "stop", app_name]
83+
stop_cmd = [tt_cmd, "stop", "-y", app_name]
8484
rc, _ = run_command_and_get_output(stop_cmd, cwd=tmpdir)
8585
assert rc == 0
8686

@@ -211,6 +211,6 @@ def test_expel_cconfig(tt_cmd, tmpdir_with_cfg, flag):
211211
""" == out
212212

213213
finally:
214-
stop_cmd = [tt_cmd, "stop", app_name]
214+
stop_cmd = [tt_cmd, "stop", "-y", app_name]
215215
rc, _ = run_command_and_get_output(stop_cmd, cwd=tmpdir)
216216
assert rc == 0

test/integration/replicaset/test_replicaset_promote.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ def test_promote_cconfig_failovers(
150150
box_ctl_promote(tt_cmd, app_name, "election-failover-1", tmpdir)
151151

152152
if stop_inst:
153-
stop_cmd = [tt_cmd, "stop", f"{app_name}:{stop_inst}"]
153+
stop_cmd = [tt_cmd, "stop", "-y", f"{app_name}:{stop_inst}"]
154154
rc, _ = run_command_and_get_output(stop_cmd, cwd=tmpdir)
155155
assert rc == 0
156156

test/integration/replicaset/test_replicaset_rebootstrap.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ def test_rebootstrap_custom_replicaset(tt_cmd, tmp_path):
109109
assert oldSnapName != os.path.basename(snaps[0])
110110

111111
finally:
112-
run_command_and_get_output([tt_cmd, "stop"], cwd=tmp_path)
112+
run_command_and_get_output([tt_cmd, "stop", "-y"], cwd=tmp_path)
113113

114114

115115
@pytest.mark.skipif(tarantool_major_version < 3,

test/integration/replicaset/test_replicaset_roles_add.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ def test_replicaset_cconfig_roles_add(
162162
start_application(tt_cmd, tmpdir_with_cfg, app_name, instances)
163163

164164
if stop_instance:
165-
stop_cmd = [tt_cmd, "stop", f"{app_name}:{stop_instance}"]
165+
stop_cmd = [tt_cmd, "stop", "-y", f"{app_name}:{stop_instance}"]
166166
rc, _ = run_command_and_get_output(stop_cmd, cwd=tmpdir_with_cfg)
167167
assert rc == 0
168168
if is_add_role:

test/integration/replicaset/test_replicaset_vshard.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ def test_vshard_bootstrap_custom_app(tt_cmd, tmpdir_with_cfg, flag):
8181
⨯ bootstrap vshard is not supported for an application by "custom" orchestrator
8282
""", out)
8383
finally:
84-
stop_cmd = [tt_cmd, "stop", app_name]
84+
stop_cmd = [tt_cmd, "stop", "-y", app_name]
8585
rc, _ = run_command_and_get_output(stop_cmd, cwd=tmpdir)
8686
assert rc == 0
8787

test/integration/restart/test_restart.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def test_restart(tt_cmd, tmpdir_with_cfg):
4848
pid_file, []) != ""
4949

5050
finally:
51-
app_cmd(tt_cmd, tmpdir_with_cfg, ["stop", app_name], [])
51+
app_cmd(tt_cmd, tmpdir_with_cfg, ["stop", app_name], ["y\n"])
5252

5353

5454
def test_restart_with_auto_yes(tt_cmd, tmpdir_with_cfg):
@@ -75,7 +75,7 @@ def test_restart_with_auto_yes(tt_cmd, tmpdir_with_cfg):
7575
pid_file, []) != ""
7676

7777
finally:
78-
app_cmd(tt_cmd, tmpdir_with_cfg, ["stop", app_name], [])
78+
app_cmd(tt_cmd, tmpdir_with_cfg, ["stop", app_name], ["y\n"])
7979

8080

8181
def test_restart_no_args(tt_cmd, tmp_path):
@@ -93,4 +93,4 @@ def test_restart_no_args(tt_cmd, tmp_path):
9393
assert "Confirm restart of all instances [y/n]" in restart_output[0]
9494

9595
finally:
96-
app_cmd(tt_cmd, test_app_path, ["stop"], [])
96+
app_cmd(tt_cmd, test_app_path, ["stop"], ["y\n"])

test/integration/running/test_running.py

+13-13
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def test_running_base_functionality(tt_cmd, tmpdir_with_cfg):
5252
assert status_info["test_app"]["MODE"] == "RO"
5353

5454
# Stop the Instance.
55-
stop_cmd = [tt_cmd, "stop", "test_app"]
55+
stop_cmd = [tt_cmd, "stop", "-y", "test_app"]
5656
stop_rc, stop_out = run_command_and_get_output(stop_cmd, cwd=tmpdir)
5757
assert stop_rc == 0
5858
assert re.search(r"The Instance test_app \(PID = \d+\) has been terminated.", stop_out)
@@ -117,7 +117,7 @@ def test_restart(tt_cmd, tmpdir_with_cfg):
117117
assert status_out["test_app"]["STATUS"] == "RUNNING"
118118

119119
# Stop the new Instance.
120-
stop_cmd = [tt_cmd, "stop", "test_app"]
120+
stop_cmd = [tt_cmd, "stop", "-y", "test_app"]
121121
stop_rc, stop_out = run_command_and_get_output(stop_cmd, cwd=tmpdir)
122122
assert stop_rc == 0
123123
assert re.search(r"The Instance test_app \(PID = \d+\) has been terminated.", stop_out)
@@ -165,7 +165,7 @@ def test_logrotate(tt_cmd, tmpdir_with_cfg):
165165
assert "reopened" in f.read()
166166

167167
# Stop the Instance.
168-
stop_cmd = [tt_cmd, "stop", "test_env_app"]
168+
stop_cmd = [tt_cmd, "stop", "-y", "test_env_app"]
169169
stop_rc, stop_out = run_command_and_get_output(stop_cmd, cwd=tmpdir)
170170
assert stop_rc == 0
171171
assert re.search(r"The Instance test_env_app \(PID = \d+\) has been terminated.", stop_out)
@@ -222,7 +222,7 @@ def test_clean(tt_cmd, tmpdir_with_cfg):
222222
assert re.search(r"instance `test_data_app` must be stopped", clean_out)
223223

224224
# Stop the Instance.
225-
stop_cmd = [tt_cmd, "stop", "test_data_app"]
225+
stop_cmd = [tt_cmd, "stop", "-y", "test_data_app"]
226226
stop_rc, stop_out = run_command_and_get_output(stop_cmd, cwd=tmpdir)
227227
assert stop_rc == 0
228228
assert re.search(r"The Instance test_data_app \(PID = \d+\) has been terminated\.", stop_out)
@@ -282,7 +282,7 @@ def test_running_base_functionality_working_dir_app(tt_cmd):
282282
assert status_out[f"app:{instName}"]["STATUS"] == "RUNNING"
283283

284284
# Stop the application.
285-
stop_cmd = [tt_cmd, "stop", "app"]
285+
stop_cmd = [tt_cmd, "stop", "-y", "app"]
286286
stop_rc, stop_out = run_command_and_get_output(stop_cmd, cwd=test_app_path)
287287
assert stop_rc == 0
288288
assert re.search(r"The Instance app:(router|master|replica|stateboard) \(PID = \d+\) "
@@ -334,7 +334,7 @@ def test_running_base_functionality_working_dir_app_no_app_name(tt_cmd):
334334
assert status_out[f"app:{instName}"]["STATUS"] == "RUNNING"
335335

336336
# Stop the application.
337-
stop_cmd = [tt_cmd, "stop"]
337+
stop_cmd = [tt_cmd, "stop", "-y"]
338338
stop_rc, stop_out = run_command_and_get_output(stop_cmd, cwd=test_app_path)
339339
assert stop_rc == 0
340340
assert re.search(r"The Instance app:(router|master|replica|stateboard) \(PID = \d+\) "
@@ -384,7 +384,7 @@ def test_running_instance_from_multi_inst_app(tt_cmd):
384384
assert status_out[f"app:{inst}"]["STATUS"] == "NOT RUNNING"
385385

386386
# Stop the Instance.
387-
stop_cmd = [tt_cmd, "stop", "app:router"]
387+
stop_cmd = [tt_cmd, "stop", "-y", "app:router"]
388388
stop_rc, stop_out = run_command_and_get_output(stop_cmd, cwd=test_app_path)
389389
assert stop_rc == 0
390390
assert re.search(r"The Instance app:router \(PID = \d+\) has been terminated.", stop_out)
@@ -562,7 +562,7 @@ def test_no_args_usage(tt_cmd):
562562
assert re.search(r"app2: logs has been rotated. PID: \d+.", status_out)
563563

564564
# Stop all applications.
565-
stop_cmd = [tt_cmd, "stop"]
565+
stop_cmd = [tt_cmd, "stop", "-y"]
566566
stop_rc, stop_out = run_command_and_get_output(stop_cmd, cwd=test_app_path)
567567
assert stop_rc == 0
568568
assert re.search(r"The Instance app1:(router|master|replica) \(PID = \d+\) "
@@ -603,7 +603,7 @@ def test_running_env_variables(tt_cmd, tmpdir_with_cfg):
603603
assert status_out["test_env_app"]["STATUS"] == "RUNNING"
604604

605605
# Stop the Instance.
606-
stop_cmd = [tt_cmd, "stop", "test_env_app"]
606+
stop_cmd = [tt_cmd, "stop", "-y", "test_env_app"]
607607
stop_rc, stop_out = run_command_and_get_output(stop_cmd, cwd=tmpdir)
608608
assert stop_rc == 0
609609
assert re.search(r"The Instance test_env_app \(PID = \d+\) has been terminated.", stop_out)
@@ -661,7 +661,7 @@ def test_running_tarantoolctl_layout(tt_cmd, tmp_path):
661661
assert status_out["test_app"]["STATUS"] == "RUNNING"
662662

663663
# Stop the Instance.
664-
stop_cmd = [tt_cmd, "stop", "test_app"]
664+
stop_cmd = [tt_cmd, "stop", "-y", "test_app"]
665665
stop_rc, stop_out = run_command_and_get_output(stop_cmd, cwd=tmp_path)
666666
assert status_rc == 0
667667
assert re.search(r"The Instance test_app \(PID = \d+\) has been terminated.", stop_out)
@@ -710,7 +710,7 @@ def test_running_start(tt_cmd):
710710
for instName in instances:
711711
assert status_out[f'app:{instName}']["STATUS"] == "RUNNING"
712712

713-
status_cmd = [tt_cmd, "stop", "app:router"]
713+
status_cmd = [tt_cmd, "stop", "-y", "app:router"]
714714
status_rc, stop_out = run_command_and_get_output(status_cmd, cwd=test_app_path)
715715
assert status_rc == 0
716716
assert re.search(r"The Instance app:router \(PID = \d+\) "
@@ -751,7 +751,7 @@ def test_running_start(tt_cmd):
751751
assert status_out[f'app:{instName}']["STATUS"] == "RUNNING"
752752

753753
# Stop all applications.
754-
stop_cmd = [tt_cmd, "stop"]
754+
stop_cmd = [tt_cmd, "stop", "-y"]
755755
stop_rc, stop_out = run_command_and_get_output(stop_cmd, cwd=test_app_path)
756756
assert status_rc == 0
757757
assert re.search(r"The Instance app:(router|master|replica|stateboard) \(PID = \d+\) "
@@ -814,7 +814,7 @@ def rename():
814814
assert status_out[f"mi_app:{inst}"]["STATUS"] == "RUNNING"
815815

816816
# Stop the Instance.
817-
stop_cmd = [tt_cmd, "stop", "mi_app"]
817+
stop_cmd = [tt_cmd, "stop", "-y", "mi_app"]
818818
stop_rc, stop_out = run_command_and_get_output(stop_cmd, cwd=test_env_path)
819819
assert stop_rc == 0
820820
assert re.search(r"The Instance mi_app:router \(PID = \d+\) has been terminated.",

test/integration/running/test_running_cluster.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def start_application(cmd, workdir, app_name, instances):
2626

2727

2828
def stop_application(tt_cmd, app_name, workdir, instances):
29-
stop_cmd = [tt_cmd, "stop", app_name]
29+
stop_cmd = [tt_cmd, "stop", "-y", app_name]
3030
stop_rc, stop_out = run_command_and_get_output(stop_cmd, cwd=workdir)
3131
assert stop_rc == 0
3232

test/integration/stop/multi_app

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../running/multi_app

0 commit comments

Comments
 (0)