diff --git a/cmd/store/delete.go b/cmd/store/delete.go index 8562c06..c467367 100644 --- a/cmd/store/delete.go +++ b/cmd/store/delete.go @@ -22,6 +22,7 @@ import ( "os" "github.com/openfga/cli/internal/cmdutils" + "github.com/openfga/cli/internal/confirmation" "github.com/openfga/cli/internal/output" "github.com/spf13/cobra" ) @@ -34,6 +35,25 @@ var deleteCmd = &cobra.Command{ Example: "fga store delete --store-id=01H0H015178Y2V4CX10C2KGHF4", RunE: func(cmd *cobra.Command, args []string) error { clientConfig := cmdutils.GetClientConfig(cmd) + // First, confirm whether this is intended + force, err := cmd.Flags().GetBool("force") + if err != nil { + return fmt.Errorf("failed to parse force flag due to %w", err) + } + if !force { + confirmation, err := confirmation.AskForConfirmation("Are you sure you want to delete the store:") + if err != nil { + return fmt.Errorf("prompt failed due to %w", err) + } + if !confirmation { + type returnMessage struct { + Message string + } + + return output.Display(returnMessage{Message: "Delete store cancelled"}) //nolint:wrapcheck + } + } + fgaClient, err := clientConfig.GetFgaClient() if err != nil { return fmt.Errorf("failed to initialize FGA Client due to %w", err) @@ -49,8 +69,10 @@ var deleteCmd = &cobra.Command{ func init() { deleteCmd.Flags().String("store-id", "", "Store ID") + deleteCmd.Flags().Bool("force", false, "Force delete without confirmation") + err := deleteCmd.MarkFlagRequired("store-id") - if err != nil { //nolint:wsl + if err != nil { fmt.Print(err) os.Exit(1) } diff --git a/internal/confirmation/confirmation.go b/internal/confirmation/confirmation.go new file mode 100644 index 0000000..9fb8d90 --- /dev/null +++ b/internal/confirmation/confirmation.go @@ -0,0 +1,54 @@ +/* +Copyright © 2023 OpenFGA + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package confirmation + +import ( + "bufio" + "fmt" + "os" + "strings" +) + +// askForConfirmation is the internal implementation that prompt user to confirm their choice. +// Default answer is no. +func askForConfirmation(ioReader *bufio.Reader, question string) (bool, error) { + for { + _, err := fmt.Fprintln(os.Stdout, question+"(y/N)") + if err != nil { + return false, fmt.Errorf("unable to ask for confirmation with error %w", err) + } + + s, err := ioReader.ReadString('\n') + if err != nil { + return false, fmt.Errorf("unable to read string with error %w", err) + } + + trimmedString := strings.ToLower(strings.TrimSpace(s)) + switch trimmedString { + case "y", "yes": + return true, nil + case "n", "no", "": + return false, nil + } + } +} + +// AskForConfirmation will prompt user to confirm their choice. +// Default answer is no. +func AskForConfirmation(question string) (bool, error) { + return askForConfirmation(bufio.NewReader(os.Stdin), question) +} diff --git a/internal/confirmation/confirmation_test.go b/internal/confirmation/confirmation_test.go new file mode 100644 index 0000000..61e093e --- /dev/null +++ b/internal/confirmation/confirmation_test.go @@ -0,0 +1,64 @@ +package confirmation + +import ( + "bufio" + "strings" + "testing" +) + +func TestConfirmation(t *testing.T) { + t.Parallel() + + type test struct { + _name string + input string + result bool + } + + tests := []test{ + { + _name: "default", + input: "\n", + result: false, + }, + { + _name: "yes", + input: " Yes\n", + result: true, + }, + + { + _name: "Y", + input: "y\n", + result: true, + }, + { + _name: "NO", + input: "NO\n", + result: false, + }, + { + _name: "n", + input: "n\n", + result: false, + }, + { + _name: "other answer then no", + input: "other\nn\n", + result: false, + }, + } + for _, test := range tests { + test := test + t.Run(test._name, func(t *testing.T) { + t.Parallel() + result, err := askForConfirmation(bufio.NewReader(strings.NewReader(test.input)), "test") + if err != nil { + t.Error(err) + } + if result != test.result { + t.Errorf("Expect result %v actual %v", test.result, result) + } + }) + } +}