github.com/gravitational/teleport/api@v0.0.0-20240507183017-3110591cbafc/utils/prompt/confirmation.go (about) 1 /* 2 Copyright 2021 Gravitational, Inc. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Package prompt implements CLI prompts to the user. 18 package prompt 19 20 import ( 21 "context" 22 "fmt" 23 "io" 24 "strings" 25 26 "github.com/gravitational/trace" 27 ) 28 29 // Reader is the interface for prompt readers. 30 type Reader interface { 31 // ReadContext reads from the underlying buffer, respecting context 32 // cancellation. 33 ReadContext(ctx context.Context) ([]byte, error) 34 } 35 36 // SecureReader is the interface for password readers. 37 type SecureReader interface { 38 // ReadPassword reads from the underlying buffer, respecting context 39 // cancellation. 40 ReadPassword(ctx context.Context) ([]byte, error) 41 } 42 43 // Confirmation prompts the user for a yes/no confirmation for question. 44 // The prompt is written to out and the answer is read from in. 45 // 46 // question should be a plain sentence without "[yes/no]"-type hints at the end. 47 // 48 // ctx can be canceled to abort the prompt. 49 func Confirmation(ctx context.Context, out io.Writer, in Reader, question string) (bool, error) { 50 fmt.Fprintf(out, "%s [y/N]: ", question) 51 answer, err := in.ReadContext(ctx) 52 if err != nil { 53 return false, trace.WrapWithMessage(err, "failed reading prompt response") 54 } 55 switch strings.ToLower(strings.TrimSpace(string(answer))) { 56 case "y", "yes": 57 return true, nil 58 default: 59 return false, nil 60 } 61 } 62 63 // PickOne prompts the user to pick one of the provided string options. 64 // The prompt is written to out and the answer is read from in. 65 // 66 // question should be a plain sentence without the list of provided options. 67 // 68 // ctx can be canceled to abort the prompt. 69 func PickOne(ctx context.Context, out io.Writer, in Reader, question string, options []string) (string, error) { 70 fmt.Fprintf(out, "%s [%s]: ", question, strings.Join(options, ", ")) 71 answerOrig, err := in.ReadContext(ctx) 72 if err != nil { 73 return "", trace.Wrap(err, "failed reading prompt response") 74 } 75 answer := strings.ToLower(strings.TrimSpace(string(answerOrig))) 76 for _, opt := range options { 77 if strings.ToLower(opt) == answer { 78 return opt, nil 79 } 80 } 81 return "", trace.BadParameter( 82 "%q is not a valid option, please specify one of [%s]", strings.TrimSpace(string(answerOrig)), strings.Join(options, ", ")) 83 } 84 85 // Input prompts the user for freeform text input. 86 // The prompt is written to out and the answer is read from in. 87 // 88 // ctx can be canceled to abort the prompt. 89 func Input(ctx context.Context, out io.Writer, in Reader, question string) (string, error) { 90 fmt.Fprintf(out, "%s: ", question) 91 answer, err := in.ReadContext(ctx) 92 if err != nil { 93 return "", trace.Wrap(err, "failed reading prompt response") 94 } 95 return strings.TrimSpace(string(answer)), nil 96 } 97 98 // Password prompts the user for a password. The prompt is written to out and 99 // the answer is read from in. 100 // The in reader has to be a terminal. 101 func Password(ctx context.Context, out io.Writer, in SecureReader, question string) (string, error) { 102 if question != "" { 103 fmt.Fprintf(out, "%s:\n", question) 104 } 105 answer, err := in.ReadPassword(ctx) 106 if err != nil { 107 return "", trace.Wrap(err, "failed reading prompt response") 108 } 109 return string(answer), nil // passwords not trimmed 110 }