diff --git a/pkg/cluster/manager/scale_in.go b/pkg/cluster/manager/scale_in.go index beee220a6d..344b96f25d 100644 --- a/pkg/cluster/manager/scale_in.go +++ b/pkg/cluster/manager/scale_in.go @@ -49,11 +49,15 @@ func (m *Manager) ScaleIn( ) if !skipConfirm { if force { - if err := tui.PromptForConfirmOrAbortError( + log.Warnf(color.HiRedString(tui.ASCIIArtWarning)) + if err := tui.PromptForAnswerOrAbortError( + "Yes, I know my data might be lost.", color.HiRedString("Forcing scale in is unsafe and may result in data loss for stateful components.\n"+ - "The process is irreversible and could NOT be cancelled.\n") + - "Only use `--force` when some of the servers are already permanently offline.\n" + - "Are you sure to continue? [y/N]:", + "DO NOT use `--force` if you have any component in ")+ + color.YellowString("Pending Offline")+color.HiRedString(" status.\n")+ + color.HiRedString("The process is irreversible and could NOT be cancelled.\n")+ + "Only use `--force` when some of the servers are already permanently offline.\n"+ + "Are you sure to continue?", ); err != nil { return err } diff --git a/pkg/tui/tui.go b/pkg/tui/tui.go index a53d2ea488..b566eb96cf 100644 --- a/pkg/tui/tui.go +++ b/pkg/tui/tui.go @@ -21,6 +21,7 @@ import ( "syscall" "github.com/AstroProfundis/tabby" + "github.com/fatih/color" "github.com/pingcap/tiup/pkg/utils/mock" "golang.org/x/term" ) @@ -59,6 +60,17 @@ func addRow(t *tabby.Tabby, rawLine []string, header bool) { } } +// pre-defined ascii art strings +const ( + ASCIIArtWarning = ` + ██ ██ █████ ██████ ███ ██ ██ ███ ██ ██████ + ██ ██ ██ ██ ██ ██ ████ ██ ██ ████ ██ ██ + ██ █ ██ ███████ ██████ ██ ██ ██ ██ ██ ██ ██ ██ ███ + ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + ███ ███ ██ ██ ██ ██ ██ ████ ██ ██ ████ ██████ +` +) + // Prompt accepts input from console by user func Prompt(prompt string) string { if prompt != "" { @@ -106,6 +118,25 @@ func PromptForConfirmOrAbortError(format string, a ...interface{}) error { return nil } +// PromptForConfirmAnswer accepts string from console by user, default to empty and only return +// true if the user input is exactly the same as pre-defined answer. +func PromptForConfirmAnswer(answer string, format string, a ...interface{}) (bool, string) { + ans := Prompt(fmt.Sprintf(format, a...) + fmt.Sprintf("\n(Type \"%s\" to continue)\n:", color.CyanString(answer))) + if ans == answer { + return true, ans + } + return false, ans +} + +// PromptForAnswerOrAbortError accepts string from console by user, generates AbortError if user does +// not input the pre-defined answer. +func PromptForAnswerOrAbortError(answer string, format string, a ...interface{}) error { + if pass, ans := PromptForConfirmAnswer(answer, format, a...); !pass { + return errOperationAbort.New("Operation aborted by user (with incorrect answer '%s')", ans) + } + return nil +} + // PromptForPassword reads a password input from console func PromptForPassword(format string, a ...interface{}) string { defer fmt.Println("")