diff --git a/internal/cmd/delete.go b/internal/cmd/delete.go index 3aa950bf..3d134e4e 100644 --- a/internal/cmd/delete.go +++ b/internal/cmd/delete.go @@ -110,13 +110,3 @@ postgresclusters/hippo deleted`) return cmd } - -// containsString returns true if slice contains element -func containsString(slice []string, element string) bool { - for _, elem := range slice { - if elem == element { - return true - } - } - return false -} diff --git a/internal/cmd/delete_test.go b/internal/cmd/delete_test.go deleted file mode 100644 index 5b1e3657..00000000 --- a/internal/cmd/delete_test.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2021 - 2023 Crunchy Data Solutions, Inc. -// -// 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 cmd - -import ( - "testing" - - "gotest.tools/v3/assert" -) - -func TestContainsString(t *testing.T) { - testsCases := []struct { - desc string - slice []string - element string - found bool - }{ - {"found", []string{"a", "b", "c"}, "a", true}, - {"not found", []string{"a", "b", "c"}, "x", false}, - {"not found substring", []string{"ab", "bc", "cd"}, "b", false}, - } - - for _, tc := range testsCases { - t.Run(tc.desc, func(t *testing.T) { - assert.Equal(t, containsString(tc.slice, tc.element), tc.found) - }) - } -} diff --git a/internal/cmd/show.go b/internal/cmd/show.go index 5b1b1ba7..9f7730f4 100644 --- a/internal/cmd/show.go +++ b/internal/cmd/show.go @@ -27,6 +27,7 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/client-go/kubernetes/typed/core/v1" + "k8s.io/utils/strings/slices" "sigs.k8s.io/yaml" "github.com/crunchydata/postgres-operator-client/internal" @@ -353,7 +354,7 @@ func showUser(config *internal.Config, args []string, showSensitive bool) (strin for i := 0; confirmed == nil && i < 10; i++ { // retry 10 times or until a confirmation is given or denied, // whichever comes first - confirmed = confirm(os.Stdin, os.Stdout) + confirmed = util.Confirm(os.Stdin, os.Stdout) } if confirmed == nil || !*confirmed { @@ -420,7 +421,7 @@ func userData(fields []string, list *corev1.SecretList) (string, error) { if err != nil { return output, err } - if containsString(fields, k) { + if slices.Contains(fields, k) { output += fmt.Sprintf(" %s: %s\n", strings.ToUpper(k), string(d)) } } diff --git a/internal/util/util.go b/internal/util/util.go new file mode 100644 index 00000000..856c6644 --- /dev/null +++ b/internal/util/util.go @@ -0,0 +1,55 @@ +// Copyright 2021 - 2023 Crunchy Data Solutions, Inc. +// +// 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 util + +import ( + "bufio" + "fmt" + "io" + + "k8s.io/utils/strings/slices" +) + +// Confirm uses a Scanner to parse user input. A user must type in "yes" or "no" +// and then press enter. It has fuzzy matching, so "y", "Y", "yes", "YES", +// and "Yes" all count as confirmations and return 'true'. Similarly, "n", "N", +// "no", "No", "NO" all deny confirmation and return 'false'. If the input is not +// recognized, nil is returned. +func Confirm(reader io.Reader, writer io.Writer) *bool { + var response string + var boolVar bool + + scanner := bufio.NewScanner(reader) + if scanner.Scan() { + response = scanner.Text() + } + + if scanner.Err() != nil || response == "" { + fmt.Fprint(writer, "Please type yes or no and then press enter: ") + return nil + } + + yesResponses := []string{"y", "Y", "yes", "Yes", "YES"} + noResponses := []string{"n", "N", "no", "No", "NO"} + if slices.Contains(yesResponses, response) { + boolVar = true + return &boolVar + } else if slices.Contains(noResponses, response) { + return &boolVar + } else { + fmt.Fprint(writer, "Please type yes or no and then press enter: ") + return nil + } +} diff --git a/internal/util/util_test.go b/internal/util/util_test.go new file mode 100644 index 00000000..3aeb9cfb --- /dev/null +++ b/internal/util/util_test.go @@ -0,0 +1,66 @@ +// Copyright 2021 - 2023 Crunchy Data Solutions, Inc. +// +// 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 util + +import ( + "bytes" + "io" + "strings" + "testing" + + "gotest.tools/v3/assert" +) + +func TestConfirmDelete(t *testing.T) { + + testsCases := []struct { + input string + invalidResponse bool + confirmed bool + }{ + {"abc", true, false}, // invalid + {"", true, false}, // invalid + {"y", false, true}, + {"Y", false, true}, + {"yes", false, true}, + {"Yes", false, true}, + {"YES", false, true}, + {"n", false, false}, + {"N", false, false}, + {"no", false, false}, + {"No", false, false}, + {"NO", false, false}, + {"yep", true, false}, // invalid + {"nope", true, false}, // invalid + } + + for _, tc := range testsCases { + t.Run("input is "+tc.input, func(t *testing.T) { + var reader io.Reader = strings.NewReader(tc.input) + var writer bytes.Buffer + confirmed := Confirm(reader, &writer) + if tc.invalidResponse { + assert.Assert(t, confirmed == nil) + response, err := writer.ReadString(':') + assert.NilError(t, err) + assert.Equal(t, response, "Please type yes or no and then press enter:") + + } else { + assert.Assert(t, confirmed != nil) + assert.Assert(t, *confirmed == tc.confirmed) + } + }) + } +}